The service that manages the music player has been rewritten in kotlin

This commit is contained in:
antonio 2022-08-26 18:14:29 +02:00
parent 6b92fc5b18
commit fc9f4054dd
3 changed files with 281 additions and 144 deletions

View file

@ -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());
}
}
});
}
}

View file

@ -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)
}
}