From 3fac021ff287b9eccb195520370cd6aa37e7c9b1 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Mon, 6 Aug 2018 23:11:40 +0200 Subject: [PATCH] Amazfit Cor: implement basic music controls Play/pause and skip to previous/next song work. The currently playing song name is shown on the Cor. The track length and progress are now shown as we don't know how to send these yet. --- .../gadgetbridge/model/MusicSpec.java | 12 ++ .../gadgetbridge/model/MusicStateSpec.java | 11 ++ .../service/devices/huami/HuamiSupport.java | 109 +++++++++++++++++- 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java index c0a7d3285..305aae64f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java @@ -75,4 +75,16 @@ public class MusicSpec { result = 31 * result + trackNr; return result; } + + @Override + public String toString() { + return "MusicSpec{" + + "artist='" + artist + '\'' + + ", album='" + album + '\'' + + ", track='" + track + '\'' + + ", duration=" + duration + + ", trackCount=" + trackCount + + ", trackNr=" + trackNr + + '}'; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java index 286fdf4cb..b7f4da047 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java @@ -71,4 +71,15 @@ public class MusicStateSpec { result = 31 * result + (int) repeat; return result; } + + @Override + public String toString() { + return "MusicStateSpec{" + + "state=" + state + + ", position=" + position + + ", playRate=" + playRate + + ", shuffle=" + shuffle + + ", repeat=" + repeat + + '}'; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java index 0a7aa530b..81790470a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java @@ -34,6 +34,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -171,6 +173,10 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport { private RealtimeSamplesSupport realtimeSamplesSupport; private boolean alarmClockRinging; + private boolean isMusicAppStarted = false; + private MusicSpec bufferMusicSpec = null; + private MusicStateSpec bufferMusicStateSpec = null; + public HuamiSupport() { this(LOG); } @@ -726,12 +732,102 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport { @Override public void onSetMusicState(MusicStateSpec stateSpec) { - // not supported + if (bufferMusicStateSpec != stateSpec) + bufferMusicStateSpec = stateSpec; + sendMusicStateToDevice(); + } @Override public void onSetMusicInfo(MusicSpec musicSpec) { - // not supported + if (bufferMusicSpec != musicSpec) + bufferMusicSpec = musicSpec; + + if (!isMusicAppStarted) { + return; + } + sendMusicStateToDevice(); + } + + + private void sendMusicStateToDevice() { + + + if (characteristicChunked == null) { + return; + } + if (bufferMusicSpec == null && bufferMusicStateSpec == null) { + try { + TransactionBuilder builder = performInitialized("send dummy playback info to enable music controls"); + writeToChunked(builder, 3, new byte[]{1, 0, 1, 0, 0, 0, 1, 0}); + builder.queue(getQueue()); + } catch (IOException e) { + LOG.error("Unable to send dummy music controls"); + } + return; + } + + byte flags = 0x00; + flags |= 0x01; + int length = 8; + if (bufferMusicSpec.track != null && bufferMusicSpec.track.getBytes().length > 0) { + length += bufferMusicSpec.track.getBytes().length + 1; + flags |= 0x02; + } + if (bufferMusicSpec.album != null && bufferMusicSpec.album.getBytes().length > 0) { + length += bufferMusicSpec.album.getBytes().length + 1; + flags |= 0x04; + } + if (bufferMusicSpec.artist != null && bufferMusicSpec.artist.getBytes().length > 0) { + length += bufferMusicSpec.artist.getBytes().length + 1; + flags |= 0x08; + } + + +// LOG.info("Music flags are: " + (flags & 0xff)); + try { + ByteBuffer buf = ByteBuffer.allocate(length); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.put(flags); + byte state; + switch (bufferMusicStateSpec.state) { + case MusicStateSpec.STATE_PLAYING: + state = 1; + break; + default: + state = 0; + } + + buf.put(state); + buf.put(new byte[]{0x1, 0x0, 0x0, 0x0}); //unknown + buf.put(new byte[]{0x1,0x0}); //show track +// buf.put(new byte[]{0x1,0x1}); //show album + + + if (bufferMusicSpec.track != null && bufferMusicSpec.track.getBytes().length > 0) { + buf.put(bufferMusicSpec.track.getBytes()); + buf.put((byte) 0); + } + if (bufferMusicSpec.album != null && bufferMusicSpec.album.getBytes().length > 0) { + buf.put(bufferMusicSpec.album.getBytes()); + buf.put((byte) 0); + } + if (bufferMusicSpec.artist != null && bufferMusicSpec.artist.getBytes().length > 0) { + buf.put(bufferMusicSpec.artist.getBytes()); + buf.put((byte) 0); + } + + + TransactionBuilder builder = performInitialized("send playback info"); + writeToChunked(builder, 3, buf.array()); + + builder.queue(getQueue()); + } catch (IOException e) { + LOG.error("Unable to send playback state"); + } + +// LOG.info("Sent music: " + bufferMusicSpec.toString() + " " + bufferMusicStateSpec.toString()); + } @Override @@ -1012,6 +1108,15 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport { case 6: deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN; break; + case (byte) 224: + LOG.info("Music app started"); + isMusicAppStarted = true; + sendMusicStateToDevice(); + break; + case (byte) 225: + LOG.info("Music app terminated"); + isMusicAppStarted = false; + break; default: LOG.info("unhandled music control event " + value[1]); return;