diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index cb0bb9771..402bed883 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -64,6 +64,8 @@ dependencies { implementation("com.github.alorma:compose-settings-storage-preferences:1.0.3") implementation("com.google.accompanist:accompanist-permissions:0.33.2-alpha") + implementation("androidx.media3:media3-session:1.2.0") + // Android Studio Preview support implementation("androidx.compose.ui:ui-tooling-preview") debugImplementation("androidx.compose.ui:ui-tooling") diff --git a/android/app/src/main/java/org/musicpd/MPDPlayer.java b/android/app/src/main/java/org/musicpd/MPDPlayer.java new file mode 100644 index 000000000..3a1d8c087 --- /dev/null +++ b/android/app/src/main/java/org/musicpd/MPDPlayer.java @@ -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 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(); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/org/musicpd/Main.java b/android/app/src/main/java/org/musicpd/Main.java index 7196e0c5f..532039b48 100644 --- a/android/app/src/main/java/org/musicpd/Main.java +++ b/android/app/src/main/java/org/musicpd/Main.java @@ -3,7 +3,6 @@ package org.musicpd; -import android.annotation.TargetApi; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -17,13 +16,15 @@ import android.content.ServiceConnection; import android.media.AudioManager; import android.os.Build; import android.os.IBinder; +import android.os.Looper; import android.os.PowerManager; import android.os.RemoteCallbackList; import android.os.RemoteException; 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; @@ -50,6 +51,8 @@ public class Main extends Service implements Runnable { private boolean mPauseOnHeadphonesDisconnect = false; private PowerManager.WakeLock mWakelock = null; + private MediaSession mMediaSession = null; + static class MainStub extends IMain.Stub { private Main mService; MainStub(Main service) { @@ -183,6 +186,7 @@ public class Main extends Service implements Runnable { } } + @OptIn(markerClass = UnstableApi.class) private void start() { if (mThread != null) return; @@ -224,11 +228,16 @@ public class Main extends Service implements Runnable { mThread = new Thread(this); mThread.start(); + MPDPlayer player = new MPDPlayer(Looper.getMainLooper()); + mMediaSession = new MediaSession.Builder(this, player).build(); + startForeground(R.string.notification_title_mpd_running, notification); startService(new Intent(this, Main.class)); } private void stop() { + mMediaSession.release(); + mMediaSession = null; if (mThread != null) { if (mThread.isAlive()) { synchronized (this) {