mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
The service that manages the music player has been rewritten in kotlin
This commit is contained in:
parent
6b92fc5b18
commit
fc9f4054dd
3 changed files with 281 additions and 144 deletions
|
|
@ -1,144 +0,0 @@
|
|||
package com.cappielloantonio.play.service;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.cast.CastPlayer;
|
||||
import androidx.media3.cast.SessionAvailabilityListener;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.session.MediaLibraryService;
|
||||
import androidx.media3.session.MediaSession;
|
||||
|
||||
import com.cappielloantonio.play.model.Media;
|
||||
import com.cappielloantonio.play.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.play.util.DownloadUtil;
|
||||
import com.google.android.gms.cast.framework.CastContext;
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
public class MediaService extends MediaLibraryService implements SessionAvailabilityListener {
|
||||
private static final String TAG = "MediaService";
|
||||
|
||||
public static final int REQUEST_CODE = 432;
|
||||
|
||||
private ExoPlayer player;
|
||||
private CastPlayer castPlayer;
|
||||
private DefaultMediaSourceFactory defaultMediaSourceFactory;
|
||||
private MediaLibrarySession mediaLibrarySession;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
initializeMediaSource();
|
||||
initializePlayer();
|
||||
initializeCastPlayer();
|
||||
initializeMediaLibrarySession();
|
||||
initializePlayerListener();
|
||||
|
||||
setPlayer(null, castPlayer.isCastSessionAvailable() ? castPlayer : player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
releasePlayer();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MediaLibrarySession onGetSession(MediaSession.ControllerInfo controllerInfo) {
|
||||
return mediaLibrarySession;
|
||||
}
|
||||
|
||||
private void initializeMediaSource() {
|
||||
DataSource.Factory dataSourceFactory = DownloadUtil.getDataSourceFactory(this);
|
||||
defaultMediaSourceFactory = new DefaultMediaSourceFactory(dataSourceFactory);
|
||||
}
|
||||
|
||||
private void initializePlayer() {
|
||||
player = new ExoPlayer.Builder(this)
|
||||
.setMediaSourceFactory(defaultMediaSourceFactory)
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void initializeCastPlayer() {
|
||||
castPlayer = new CastPlayer(CastContext.getSharedInstance(this));
|
||||
castPlayer.setSessionAvailabilityListener(this);
|
||||
}
|
||||
|
||||
private void initializeMediaLibrarySession() {
|
||||
mediaLibrarySession = new MediaLibrarySession.Builder(this, player, new LibrarySessionCallback())
|
||||
.setMediaItemFiller(new CustomMediaItemFiller())
|
||||
.setSessionActivity(PendingIntent.getActivity(getApplicationContext(), REQUEST_CODE, new Intent(getApplicationContext(), MainActivity.class), PendingIntent.FLAG_IMMUTABLE))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void setPlayer(Player oldPlayer, Player newPlayer) {
|
||||
if (oldPlayer == newPlayer) return;
|
||||
if (oldPlayer != null) oldPlayer.stop();
|
||||
|
||||
mediaLibrarySession.setPlayer(newPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCastSessionAvailable() {
|
||||
setPlayer(player, castPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCastSessionUnavailable() {
|
||||
setPlayer(castPlayer, player);
|
||||
}
|
||||
|
||||
private void releasePlayer() {
|
||||
castPlayer.setSessionAvailabilityListener(null);
|
||||
castPlayer.release();
|
||||
player.release();
|
||||
mediaLibrarySession.release();
|
||||
}
|
||||
|
||||
private class LibrarySessionCallback implements MediaLibrarySession.MediaLibrarySessionCallback {
|
||||
}
|
||||
|
||||
private class CustomMediaItemFiller implements MediaSession.MediaItemFiller {
|
||||
@Override
|
||||
public MediaItem fillInLocalConfiguration(MediaSession session, MediaSession.ControllerInfo controller, MediaItem mediaItem) {
|
||||
return mediaItem.buildUpon()
|
||||
.setUri(mediaItem.mediaMetadata.mediaUri)
|
||||
.setMediaMetadata(mediaItem.mediaMetadata)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private void initializePlayerListener() {
|
||||
player.addListener(new Player.Listener() {
|
||||
@Override
|
||||
public void onMediaItemTransition(@Nullable MediaItem mediaItem, int reason) {
|
||||
if (mediaItem == null) return;
|
||||
|
||||
MediaManager.setLastPlayedTimestamp(mediaItem);
|
||||
if (mediaItem.mediaMetadata.extras.getString("mediaType").equals(Media.MEDIA_TYPE_MUSIC))
|
||||
MediaManager.scrobble(mediaItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIsPlayingChanged(boolean isPlaying) {
|
||||
if (isPlaying) {
|
||||
MediaManager.setPlayingPausedTimestamp(player.getCurrentMediaItem(), player.getCurrentPosition());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
package com.cappielloantonio.play.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.media3.cast.CastPlayer
|
||||
import androidx.media3.cast.SessionAvailabilityListener
|
||||
import androidx.media3.common.*
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.session.*
|
||||
import androidx.media3.session.MediaSession.ControllerInfo
|
||||
import com.cappielloantonio.play.App
|
||||
import com.cappielloantonio.play.R
|
||||
import com.cappielloantonio.play.model.Media
|
||||
import com.cappielloantonio.play.repository.QueueRepository
|
||||
import com.cappielloantonio.play.ui.activity.MainActivity
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
|
||||
|
||||
class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
|
||||
|
||||
private lateinit var player: ExoPlayer
|
||||
private lateinit var castPlayer: CastPlayer
|
||||
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||
private lateinit var customCommands: List<CommandButton>
|
||||
|
||||
private var customLayout = ImmutableList.of<CommandButton>()
|
||||
|
||||
companion object {
|
||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
|
||||
"android.media3.session.demo.SHUFFLE_ON"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
|
||||
"android.media3.session.demo.SHUFFLE_OFF"
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
initializeCustomCommands()
|
||||
initializePlayer()
|
||||
initializeCastPlayer()
|
||||
initializeMediaLibrarySession()
|
||||
initializePlayerListener()
|
||||
|
||||
setPlayer(null, if (castPlayer.isCastSessionAvailable) castPlayer else player)
|
||||
}
|
||||
|
||||
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
|
||||
return mediaLibrarySession
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
releasePlayer()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private inner class CustomMediaLibrarySessionCallback : MediaLibrarySession.Callback {
|
||||
|
||||
override fun onConnect(
|
||||
session: MediaSession,
|
||||
controller: ControllerInfo
|
||||
): MediaSession.ConnectionResult {
|
||||
val connectionResult = super.onConnect(session, controller)
|
||||
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
|
||||
|
||||
customCommands.forEach { commandButton ->
|
||||
commandButton.sessionCommand?.let { availableSessionCommands.add(it) }
|
||||
}
|
||||
|
||||
return MediaSession.ConnectionResult.accept(
|
||||
availableSessionCommands.build(),
|
||||
connectionResult.availablePlayerCommands
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPostConnect(session: MediaSession, controller: ControllerInfo) {
|
||||
if (!customLayout.isEmpty() && controller.controllerVersion != 0) {
|
||||
ignoreFuture(mediaLibrarySession.setCustomLayout(controller, customLayout))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCustomCommand(
|
||||
session: MediaSession,
|
||||
controller: ControllerInfo,
|
||||
customCommand: SessionCommand,
|
||||
args: Bundle
|
||||
): ListenableFuture<SessionResult> {
|
||||
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
|
||||
player.shuffleModeEnabled = true
|
||||
customLayout = ImmutableList.of(customCommands[1])
|
||||
session.setCustomLayout(customLayout)
|
||||
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
|
||||
player.shuffleModeEnabled = false
|
||||
customLayout = ImmutableList.of(customCommands[0])
|
||||
session.setCustomLayout(customLayout)
|
||||
}
|
||||
|
||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
|
||||
/* override fun onGetLibraryRoot(
|
||||
session: MediaLibrarySession,
|
||||
browser: ControllerInfo,
|
||||
params: LibraryParams?
|
||||
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
|
||||
}
|
||||
|
||||
override fun onGetItem(
|
||||
session: MediaLibrarySession,
|
||||
browser: ControllerInfo,
|
||||
mediaId: String
|
||||
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||
val item =
|
||||
MediaItemTree.getItem(mediaId)
|
||||
?: return Futures.immediateFuture(
|
||||
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
|
||||
)
|
||||
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
|
||||
}
|
||||
|
||||
override fun onSubscribe(
|
||||
session: MediaLibrarySession,
|
||||
browser: ControllerInfo,
|
||||
parentId: String,
|
||||
params: LibraryParams?
|
||||
): ListenableFuture<LibraryResult<Void>> {
|
||||
val children =
|
||||
MediaItemTree.getChildren(parentId)
|
||||
?: return Futures.immediateFuture(
|
||||
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
|
||||
)
|
||||
session.notifyChildrenChanged(browser, parentId, children.size, params)
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid())
|
||||
}
|
||||
|
||||
override fun onGetChildren(
|
||||
session: MediaLibrarySession,
|
||||
browser: ControllerInfo,
|
||||
parentId: String,
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
params: LibraryParams?
|
||||
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||
val children =
|
||||
MediaItemTree.getChildren(parentId)
|
||||
?: return Futures.immediateFuture(
|
||||
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
|
||||
)
|
||||
|
||||
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
|
||||
}*/
|
||||
|
||||
override fun onAddMediaItems(
|
||||
mediaSession: MediaSession,
|
||||
controller: MediaSession.ControllerInfo,
|
||||
mediaItems: List<MediaItem>
|
||||
): ListenableFuture<List<MediaItem>> {
|
||||
val updatedMediaItems = mediaItems.map {
|
||||
it.buildUpon()
|
||||
.setUri(it.requestMetadata.mediaUri)
|
||||
.setMediaMetadata(it.mediaMetadata)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.build()
|
||||
}
|
||||
return Futures.immediateFuture(updatedMediaItems)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeCustomCommands() {
|
||||
customCommands =
|
||||
listOf(
|
||||
getShuffleCommandButton(
|
||||
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)
|
||||
),
|
||||
getShuffleCommandButton(
|
||||
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)
|
||||
)
|
||||
)
|
||||
|
||||
customLayout = ImmutableList.of(customCommands[0])
|
||||
}
|
||||
|
||||
private fun initializePlayer() {
|
||||
player = ExoPlayer.Builder(this)
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun initializeCastPlayer() {
|
||||
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
|
||||
castPlayer.setSessionAvailabilityListener(this)
|
||||
}
|
||||
|
||||
private fun initializeMediaLibrarySession() {
|
||||
val sessionActivityPendingIntent =
|
||||
TaskStackBuilder.create(this).run {
|
||||
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
|
||||
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
mediaLibrarySession =
|
||||
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||
.setSessionActivity(sessionActivityPendingIntent)
|
||||
.build()
|
||||
|
||||
if (!customLayout.isEmpty()) {
|
||||
mediaLibrarySession.setCustomLayout(customLayout)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializePlayerListener() {
|
||||
player.addListener(object : Player.Listener {
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
if (mediaItem == null) return
|
||||
MediaManager.setLastPlayedTimestamp(mediaItem)
|
||||
if (mediaItem.mediaMetadata.extras!!.getString("mediaType") == Media.MEDIA_TYPE_MUSIC)
|
||||
MediaManager.scrobble(mediaItem)
|
||||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
if (isPlaying) {
|
||||
MediaManager.setPlayingPausedTimestamp(
|
||||
player.currentMediaItem,
|
||||
player.currentPosition
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setPlayer(oldPlayer: Player?, newPlayer: Player) {
|
||||
if (oldPlayer === newPlayer) return
|
||||
oldPlayer?.stop()
|
||||
mediaLibrarySession.player = newPlayer
|
||||
}
|
||||
|
||||
private fun releasePlayer() {
|
||||
castPlayer.setSessionAvailabilityListener(null)
|
||||
castPlayer.release()
|
||||
player.release()
|
||||
mediaLibrarySession.release()
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton {
|
||||
val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
|
||||
return CommandButton.Builder()
|
||||
.setDisplayName(
|
||||
getString(
|
||||
if (isOn) R.string.exo_controls_shuffle_on_description
|
||||
else R.string.exo_controls_shuffle_off_description
|
||||
)
|
||||
)
|
||||
.setSessionCommand(sessionCommand)
|
||||
.setIconResId(if (isOn) R.drawable.exo_icon_shuffle_off else R.drawable.exo_icon_shuffle_on)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun ignoreFuture(customLayout: ListenableFuture<SessionResult>) {
|
||||
/* Do nothing. */
|
||||
}
|
||||
|
||||
override fun onCastSessionAvailable() {
|
||||
setPlayer(player, castPlayer)
|
||||
}
|
||||
|
||||
override fun onCastSessionUnavailable() {
|
||||
setPlayer(castPlayer, player)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue