android: Implement basic media session handling for next and previous track
This starts a Media3 MediaSession when the service starts. A custom player class gets passed into that session to receive commands from other apps and the android os. Currently we pad out some dummy items to make SimpleBasePlayer think we can do next and previous tracks. MPD handles the threading for the native calls so we can just directly call the bridge from the player class.
This commit is contained in:
parent
e086f09d48
commit
3711bd0d24
|
@ -64,6 +64,8 @@ dependencies {
|
||||||
implementation("com.github.alorma:compose-settings-storage-preferences:1.0.3")
|
implementation("com.github.alorma:compose-settings-storage-preferences:1.0.3")
|
||||||
implementation("com.google.accompanist:accompanist-permissions:0.33.2-alpha")
|
implementation("com.google.accompanist:accompanist-permissions:0.33.2-alpha")
|
||||||
|
|
||||||
|
implementation("androidx.media3:media3-session:1.2.0")
|
||||||
|
|
||||||
// Android Studio Preview support
|
// Android Studio Preview support
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package org.musicpd;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.media3.common.Player;
|
||||||
|
import androidx.media3.common.SimpleBasePlayer;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
public class MPDPlayer extends SimpleBasePlayer {
|
||||||
|
|
||||||
|
List<MediaItemData> placeholderItems;
|
||||||
|
public MPDPlayer(Looper looper) {
|
||||||
|
super(looper);
|
||||||
|
|
||||||
|
// Dummy items to let us receive next and previous commands
|
||||||
|
MediaItemData item0 = new MediaItemData.Builder(0)
|
||||||
|
.build();
|
||||||
|
MediaItemData item1 = new MediaItemData.Builder(1)
|
||||||
|
.build();
|
||||||
|
MediaItemData item2 = new MediaItemData.Builder(2)
|
||||||
|
.build();
|
||||||
|
MediaItemData[] items = new MediaItemData[] { item0, item1, item2 };
|
||||||
|
|
||||||
|
placeholderItems = Arrays.asList(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected State getState() {
|
||||||
|
Commands commands = new Commands.Builder().addAll(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build();
|
||||||
|
|
||||||
|
return new State.Builder()
|
||||||
|
.setAvailableCommands(commands)
|
||||||
|
.setPlaybackState(Player.STATE_READY)
|
||||||
|
.setPlaylist(placeholderItems)
|
||||||
|
.setCurrentMediaItemIndex(1)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@SuppressLint("SwitchIntDef")
|
||||||
|
@Override
|
||||||
|
protected ListenableFuture<?> handleSeek(int mediaItemIndex, long positionMs, int seekCommand) {
|
||||||
|
switch (seekCommand) {
|
||||||
|
case COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM:
|
||||||
|
case COMMAND_SEEK_TO_PREVIOUS:
|
||||||
|
Bridge.playPrevious();
|
||||||
|
break;
|
||||||
|
case COMMAND_SEEK_TO_NEXT_MEDIA_ITEM:
|
||||||
|
case COMMAND_SEEK_TO_NEXT:
|
||||||
|
Bridge.playNext();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return Futures.immediateVoidFuture();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
package org.musicpd;
|
package org.musicpd;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
@ -17,13 +16,15 @@ import android.content.ServiceConnection;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.RemoteCallbackList;
|
import android.os.RemoteCallbackList;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.RemoteViews;
|
|
||||||
|
|
||||||
import androidx.core.app.ServiceCompat;
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.session.MediaSession;
|
||||||
|
|
||||||
import org.musicpd.ui.SettingsActivity;
|
import org.musicpd.ui.SettingsActivity;
|
||||||
|
|
||||||
|
@ -50,6 +51,8 @@ public class Main extends Service implements Runnable {
|
||||||
private boolean mPauseOnHeadphonesDisconnect = false;
|
private boolean mPauseOnHeadphonesDisconnect = false;
|
||||||
private PowerManager.WakeLock mWakelock = null;
|
private PowerManager.WakeLock mWakelock = null;
|
||||||
|
|
||||||
|
private MediaSession mMediaSession = null;
|
||||||
|
|
||||||
static class MainStub extends IMain.Stub {
|
static class MainStub extends IMain.Stub {
|
||||||
private Main mService;
|
private Main mService;
|
||||||
MainStub(Main service) {
|
MainStub(Main service) {
|
||||||
|
@ -183,6 +186,7 @@ public class Main extends Service implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
private void start() {
|
private void start() {
|
||||||
if (mThread != null)
|
if (mThread != null)
|
||||||
return;
|
return;
|
||||||
|
@ -224,11 +228,16 @@ public class Main extends Service implements Runnable {
|
||||||
mThread = new Thread(this);
|
mThread = new Thread(this);
|
||||||
mThread.start();
|
mThread.start();
|
||||||
|
|
||||||
|
MPDPlayer player = new MPDPlayer(Looper.getMainLooper());
|
||||||
|
mMediaSession = new MediaSession.Builder(this, player).build();
|
||||||
|
|
||||||
startForeground(R.string.notification_title_mpd_running, notification);
|
startForeground(R.string.notification_title_mpd_running, notification);
|
||||||
startService(new Intent(this, Main.class));
|
startService(new Intent(this, Main.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stop() {
|
private void stop() {
|
||||||
|
mMediaSession.release();
|
||||||
|
mMediaSession = null;
|
||||||
if (mThread != null) {
|
if (mThread != null) {
|
||||||
if (mThread.isAlive()) {
|
if (mThread.isAlive()) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
|
Loading…
Reference in New Issue