mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
316 lines
12 KiB
Kotlin
316 lines
12 KiB
Kotlin
|
|
package com.cappielloantonio.tempo.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.common.util.UnstableApi
|
||
|
|
import androidx.media3.exoplayer.ExoPlayer
|
||
|
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||
|
|
import androidx.media3.session.*
|
||
|
|
import androidx.media3.session.MediaSession.ControllerInfo
|
||
|
|
import com.cappielloantonio.tempo.R
|
||
|
|
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||
|
|
import com.cappielloantonio.tempo.util.Constants
|
||
|
|
import com.cappielloantonio.tempo.util.DownloadUtil
|
||
|
|
import com.cappielloantonio.tempo.util.UIUtil
|
||
|
|
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
|
||
|
|
|
||
|
|
|
||
|
|
@UnstableApi
|
||
|
|
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 (this::castPlayer.isInitialized && 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 ->
|
||
|
|
// TODO: Aggiungere i comandi personalizzati
|
||
|
|
// 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: 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)
|
||
|
|
.setRenderersFactory(getRenderersFactory())
|
||
|
|
.setMediaSourceFactory(getMediaSourceFactory())
|
||
|
|
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||
|
|
.setHandleAudioBecomingNoisy(true)
|
||
|
|
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||
|
|
.build()
|
||
|
|
}
|
||
|
|
|
||
|
|
private fun initializeCastPlayer() {
|
||
|
|
if (UIUtil.isCastApiAvailable(this)) {
|
||
|
|
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
|
||
|
|
|
||
|
|
if(reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
||
|
|
MediaManager.setLastPlayedTimestamp(mediaItem)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||
|
|
if (!isPlaying) {
|
||
|
|
MediaManager.setPlayingPausedTimestamp(
|
||
|
|
player.currentMediaItem,
|
||
|
|
player.currentPosition
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun onPositionDiscontinuity(
|
||
|
|
oldPosition: Player.PositionInfo,
|
||
|
|
newPosition: Player.PositionInfo,
|
||
|
|
reason: Int
|
||
|
|
) {
|
||
|
|
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||
|
|
|
||
|
|
if(reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||
|
|
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||
|
|
MediaManager.scrobble(oldPosition.mediaItem)
|
||
|
|
MediaManager.saveChronology(oldPosition.mediaItem)
|
||
|
|
}
|
||
|
|
|
||
|
|
if (newPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||
|
|
MediaManager.setLastPlayedTimestamp(newPosition.mediaItem)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
private fun setPlayer(oldPlayer: Player?, newPlayer: Player) {
|
||
|
|
if (oldPlayer === newPlayer) return
|
||
|
|
oldPlayer?.stop()
|
||
|
|
mediaLibrarySession.player = newPlayer
|
||
|
|
}
|
||
|
|
|
||
|
|
private fun releasePlayer() {
|
||
|
|
if (this::castPlayer.isInitialized) castPlayer.setSessionAvailabilityListener(null)
|
||
|
|
if (this::castPlayer.isInitialized) 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. */
|
||
|
|
}
|
||
|
|
|
||
|
|
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||
|
|
|
||
|
|
private fun getMediaSourceFactory() =
|
||
|
|
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
|
||
|
|
|
||
|
|
override fun onCastSessionAvailable() {
|
||
|
|
setPlayer(player, castPlayer)
|
||
|
|
}
|
||
|
|
|
||
|
|
override fun onCastSessionUnavailable() {
|
||
|
|
setPlayer(castPlayer, player)
|
||
|
|
}
|
||
|
|
}
|