Playback state tracking improvements

This commit is contained in:
Aleksandr Ivanov 2024-02-12 19:17:13 +03:00
parent d637b9263c
commit 83dba19c60
2 changed files with 147 additions and 33 deletions

View File

@ -0,0 +1,124 @@
/* Copyright (C) 2024 Aleksandr Ivanov
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.PlaybackState;
import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
public class MediaStateReceiver extends MediaController.Callback {
private long lastPosition = 0;
private long lastUpdateTime = 0;
private MediaController mMediaController;
public MediaStateReceiver(MediaController mediaController) {
mMediaController = mediaController;
}
public boolean isPlaybackActive() {
// https://developer.android.com/reference/android/media/session/PlaybackState#isActive()
switch (mMediaController.getPlaybackState().getState()) {
case PlaybackState.STATE_BUFFERING:
case PlaybackState.STATE_CONNECTING:
case PlaybackState.STATE_FAST_FORWARDING:
case PlaybackState.STATE_PLAYING:
case PlaybackState.STATE_REWINDING:
case PlaybackState.STATE_SKIPPING_TO_NEXT:
case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
return true;
default:
return false;
}
}
public void startReceiving() {
setMetadata(mMediaController.getMetadata());
setPlaybackState(mMediaController.getPlaybackState());
mMediaController.registerCallback(this);
}
public void stopReceiving() {
mMediaController.unregisterCallback(this);
}
@Override
public void onMetadataChanged(@Nullable MediaMetadata metadata) {
super.onMetadataChanged(metadata);
setMetadata(metadata);
}
@Override
public void onPlaybackStateChanged(@Nullable PlaybackState state) {
super.onPlaybackStateChanged(state);
setPlaybackState(state);
}
private void setMetadata(@Nullable MediaMetadata metadata) {
final MusicSpec musicSpec = MediaManager.extractMusicSpec(metadata);
if (musicSpec != null) {
GBApplication.deviceService().onSetMusicInfo(musicSpec);
}
}
private void setPlaybackState(@Nullable PlaybackState state) {
if (state.getState() == PlaybackState.STATE_PLAYING && !doStateUpdate(state)) {
return;
}
final MusicStateSpec stateSpec = MediaManager.extractMusicStateSpec(state);
if (stateSpec != null) {
GBApplication.deviceService().onSetMusicState(stateSpec);
}
}
private boolean doStateUpdate(PlaybackState state)
{
// To prevent spamming device with state updates
float speed = state.getPlaybackSpeed();
long currentTime = System.currentTimeMillis();
long currentPosition = state.getPosition();
long timeDiff = (long) (speed * (currentTime - lastUpdateTime));
long positionDiff = currentPosition - lastPosition;
long epsilon = (long) Math.abs(speed * 50);
if (Math.abs(timeDiff - positionDiff) > epsilon)
{
lastUpdateTime = currentTime;
lastPosition = currentPosition;
return true;
}
return false;
}
}

View File

@ -36,7 +36,6 @@ import android.graphics.drawable.Drawable;
import android.media.session.MediaController; import android.media.session.MediaController;
import android.media.session.MediaSession; import android.media.session.MediaSession;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.Process; import android.os.Process;
import android.os.UserHandle; import android.os.UserHandle;
@ -75,14 +74,11 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.notifications.GoogleM
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.AppNotificationType; import nodomain.freeyourgadget.gadgetbridge.model.AppNotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil; import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils; import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@ -126,9 +122,7 @@ public class NotificationListener extends NotificationListenerService {
private long activeCallPostTime; private long activeCallPostTime;
private int mLastCallCommand = CallSpec.CALL_UNDEFINED; private int mLastCallCommand = CallSpec.CALL_UNDEFINED;
private final Handler mHandler = new Handler(); private MediaStateReceiver mMediaStateReceiver = null;
private Runnable mSetMusicInfoRunnable = null;
private Runnable mSetMusicStateRunnable = null;
private GoogleMapsNotificationHandler googleMapsNotificationHandler = new GoogleMapsNotificationHandler(); private GoogleMapsNotificationHandler googleMapsNotificationHandler = new GoogleMapsNotificationHandler();
@ -245,6 +239,11 @@ public class NotificationListener extends NotificationListenerService {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
notificationStack.clear(); notificationStack.clear();
notificationsActive.clear(); notificationsActive.clear();
if (mMediaStateReceiver != null) {
mMediaStateReceiver.stopReceiving();
}
super.onDestroy(); super.onDestroy();
} }
@ -669,7 +668,7 @@ public class NotificationListener extends NotificationListenerService {
private boolean handleMediaSessionNotification(final StatusBarNotification sbn) { private boolean handleMediaSessionNotification(final StatusBarNotification sbn) {
final MediaSession.Token token = sbn.getNotification().extras.getParcelable(Notification.EXTRA_MEDIA_SESSION); final MediaSession.Token token = sbn.getNotification().extras.getParcelable(Notification.EXTRA_MEDIA_SESSION);
return token != null && handleMediaSessionNotification(token); return handleMediaSessionNotification(token);
} }
/** /**
@ -680,38 +679,29 @@ public class NotificationListener extends NotificationListenerService {
*/ */
public boolean handleMediaSessionNotification(MediaSession.Token mediaSession) { public boolean handleMediaSessionNotification(MediaSession.Token mediaSession) {
try { try {
final MediaController c = new MediaController(getApplicationContext(), mediaSession); if (mediaSession == null)
if (c.getMetadata() == null) { {
if (mMediaStateReceiver != null) {
mMediaStateReceiver.stopReceiving();
mMediaStateReceiver = null;
}
return false; return false;
} }
final MusicStateSpec stateSpec = MediaManager.extractMusicStateSpec(c.getPlaybackState()); MediaController mediaController = new MediaController(getApplicationContext(), mediaSession);
final MusicSpec musicSpec = MediaManager.extractMusicSpec(c.getMetadata()); MediaStateReceiver mediaStateReceiver = new MediaStateReceiver(mediaController);
// finally, tell the device about it if (mMediaStateReceiver != null && mediaStateReceiver.isPlaybackActive())
if (mSetMusicInfoRunnable != null) { {
mHandler.removeCallbacks(mSetMusicInfoRunnable); mMediaStateReceiver.stopReceiving();
mMediaStateReceiver = null;
} }
mSetMusicInfoRunnable = new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSetMusicInfo(musicSpec);
}
};
mHandler.postDelayed(mSetMusicInfoRunnable, 100);
if (stateSpec != null) { if (mMediaStateReceiver == null) {
if (mSetMusicStateRunnable != null) { mMediaStateReceiver = mediaStateReceiver;
mHandler.removeCallbacks(mSetMusicStateRunnable); mMediaStateReceiver.startReceiving();
}
mSetMusicStateRunnable = new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSetMusicState(stateSpec);
}
};
} }
mHandler.postDelayed(mSetMusicStateRunnable, 100);
return true; return true;
} catch (final NullPointerException | SecurityException e) { } catch (final NullPointerException | SecurityException e) {