Skip to content

Commit

Permalink
upgrade to ExoPlayer 2.16.0
Browse files Browse the repository at this point in the history
  • Loading branch information
marcbaechinger authored and dturner committed Jan 17, 2022
1 parent 3eb5396 commit 329a21b
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import androidx.core.content.edit
import com.example.android.uamp.media.MusicService
import com.google.android.exoplayer2.ControlDispatcher
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector.CommandReceiver
import kotlinx.coroutines.ExperimentalCoroutinesApi

/** UAMP specific command for logging into the service. */
Expand Down Expand Up @@ -131,11 +129,10 @@ class AutomotiveMusicService : MusicService() {
*
* Suppress the warning because the original name, `cb` is not as clear as to its purpose.
*/
private inner class AutomotiveCommandReceiver : CommandReceiver {
private inner class AutomotiveCommandReceiver : MediaSessionConnector.CommandReceiver {
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override fun onCommand(
player: Player,
controlDispatcher: ControlDispatcher,
command: String,
extras: Bundle?,
callback: ResultReceiver?
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
buildscript {
ext {
// App SDK versions.
compileSdkVersion = 28
compileSdkVersion = 30
minSdkVersion = 19
targetSdkVersion = 30

Expand All @@ -33,7 +33,7 @@ buildscript {
arch_lifecycle_version = '2.2.0'
constraint_layout_version = '2.0.1'
espresso_version = '3.3.0'
exoplayer_version = '2.11.5'
exoplayer_version = '2.16.0'
fragment_version = '1.2.5'
glide_version = '4.11.0'
gms_strict_version_matcher_version = '1.0.3'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.example.android.uamp.media

import android.net.Uri
import android.support.v4.media.MediaMetadataCompat
import com.example.android.uamp.media.library.JsonSource
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.ext.cast.DefaultMediaItemConverter
import com.google.android.exoplayer2.ext.cast.MediaItemConverter
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaQueueItem
import com.google.android.gms.common.images.WebImage

class CastMediaItemConverter : MediaItemConverter {

private val defaultMediaItemConverter = DefaultMediaItemConverter()

override fun toMediaQueueItem(mediaItem: MediaItem): MediaQueueItem {
val castMediaMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK)
mediaItem.mediaMetadata.title?.let {
castMediaMetadata.putString(MediaMetadata.KEY_TITLE, it.toString() )
}
mediaItem.mediaMetadata.subtitle?.let {
castMediaMetadata.putString(MediaMetadata.KEY_SUBTITLE, it.toString())
}
mediaItem.mediaMetadata.artist?.let {
castMediaMetadata.putString(MediaMetadata.KEY_ARTIST, it.toString())
}
mediaItem.mediaMetadata.albumTitle?.let {
castMediaMetadata.putString(MediaMetadata.KEY_ALBUM_TITLE, it.toString())
}
mediaItem.mediaMetadata.albumArtist?.let {
castMediaMetadata.putString(MediaMetadata.KEY_ALBUM_ARTIST, it.toString())
}
mediaItem.mediaMetadata.composer?.let {
castMediaMetadata.putString(MediaMetadata.KEY_COMPOSER, it.toString())
}
mediaItem.mediaMetadata.trackNumber?.let{
castMediaMetadata.putInt(MediaMetadata.KEY_TRACK_NUMBER, it)
}
mediaItem.mediaMetadata.discNumber?.let {
castMediaMetadata.putInt(MediaMetadata.KEY_DISC_NUMBER, it)
}
val mediaInfo = MediaInfo.Builder(mediaItem.localConfiguration!!.uri.toString())
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType(MimeTypes.AUDIO_MPEG)
mediaItem.localConfiguration?.let {
mediaInfo.setContentUrl(it.uri.toString())
}
mediaItem.mediaMetadata.extras?.let { bundle ->
// Use the original artwork URI for Cast.
bundle.getString(JsonSource.ORIGINAL_ARTWORK_URI_KEY)?.let {
castMediaMetadata.addImage(WebImage(Uri.parse(it)))
}
mediaInfo.setStreamDuration(bundle.getLong(MediaMetadataCompat.METADATA_KEY_DURATION,0))
}
mediaInfo.setMetadata(castMediaMetadata)
return MediaQueueItem.Builder(mediaInfo.build()).build()
}

override fun toMediaItem(mediaQueueItem: MediaQueueItem): MediaItem {
return defaultMediaItemConverter.toMediaItem(mediaQueueItem)
}
}
116 changes: 52 additions & 64 deletions common/src/main/java/com/example/android/uamp/media/MusicService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ package com.example.android.uamp.media

import android.app.Notification
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.ResultReceiver
Expand All @@ -36,8 +38,7 @@ import androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT
import com.example.android.uamp.media.extensions.album
import com.example.android.uamp.media.extensions.flag
import com.example.android.uamp.media.extensions.id
import com.example.android.uamp.media.extensions.toMediaQueueItem
import com.example.android.uamp.media.extensions.toMediaSource
import com.example.android.uamp.media.extensions.toMediaItem
import com.example.android.uamp.media.extensions.trackNumber
import com.example.android.uamp.media.library.AbstractMusicSource
import com.example.android.uamp.media.library.BrowseTree
Expand All @@ -48,26 +49,30 @@ import com.example.android.uamp.media.library.UAMP_BROWSABLE_ROOT
import com.example.android.uamp.media.library.UAMP_EMPTY_ROOT
import com.example.android.uamp.media.library.UAMP_RECENT_ROOT
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ControlDispatcher
import com.google.android.exoplayer2.ExoPlaybackException
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.Player.EVENT_MEDIA_ITEM_TRANSITION
import com.google.android.exoplayer2.Player.EVENT_PLAY_WHEN_READY_CHANGED
import com.google.android.exoplayer2.Player.EVENT_POSITION_DISCONTINUITY
import com.google.android.exoplayer2.Player.EVENT_TIMELINE_CHANGED
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ext.cast.CastPlayer
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import com.google.android.gms.cast.MediaQueueItem
import com.google.android.exoplayer2.util.Util.constrainValue
import com.google.android.gms.cast.framework.CastContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlin.math.max
import kotlin.math.min

/**
* This class is the entry point for browsing and playback commands from the APP's UI
Expand Down Expand Up @@ -101,6 +106,7 @@ open class MusicService : MediaBrowserServiceCompat() {
protected lateinit var mediaSession: MediaSessionCompat
protected lateinit var mediaSessionConnector: MediaSessionConnector
private var currentPlaylistItems: List<MediaMetadataCompat> = emptyList()
private var currentMediaItemIndex: Int = 0

private lateinit var storage: PersistentStorage

Expand All @@ -113,14 +119,6 @@ open class MusicService : MediaBrowserServiceCompat() {
BrowseTree(applicationContext, mediaSource)
}

private val dataSourceFactory: DefaultDataSourceFactory by lazy {
DefaultDataSourceFactory(
/* context= */ this,
Util.getUserAgent(/* context= */ this, UAMP_USER_AGENT), /* listener= */
null
)
}

private var isForegroundService = false

private val remoteJsonSource: Uri =
Expand Down Expand Up @@ -151,7 +149,7 @@ open class MusicService : MediaBrowserServiceCompat() {
private val castPlayer: CastPlayer? by lazy {
try {
val castContext = CastContext.getSharedInstance(this)
CastPlayer(castContext).apply {
CastPlayer(castContext, CastMediaItemConverter()).apply {
setSessionAvailabilityListener(UampCastSessionAvailabilityListener())
addListener(playerListener)
}
Expand Down Expand Up @@ -182,7 +180,6 @@ open class MusicService : MediaBrowserServiceCompat() {
setSessionActivity(sessionActivityPendingIntent)
isActive = true
}

/**
* In order for [MediaBrowserCompat.ConnectionCallback.onConnected] to be called,
* a [MediaSessionCompat.Token] needs to be set on the [MediaBrowserServiceCompat].
Expand Down Expand Up @@ -391,22 +388,11 @@ open class MusicService : MediaBrowserServiceCompat() {
currentPlaylistItems = metadataList

currentPlayer.playWhenReady = playWhenReady
currentPlayer.stop(/* reset= */ true)
if (currentPlayer == exoPlayer) {
val mediaSource = metadataList.toMediaSource(dataSourceFactory)
exoPlayer.prepare(mediaSource)
exoPlayer.seekTo(initialWindowIndex, playbackStartPositionMs)
} else /* currentPlayer == castPlayer */ {
val items: Array<MediaQueueItem> = metadataList.map {
it.toMediaQueueItem()
}.toTypedArray()
castPlayer!!.loadItems(
items,
initialWindowIndex,
playbackStartPositionMs,
Player.REPEAT_MODE_OFF
)
}
currentPlayer.stop()
// Set playlist and prepare.
currentPlayer.setMediaItems(
metadataList.map { it.toMediaItem() }, initialWindowIndex, playbackStartPositionMs)
currentPlayer.prepare()
}

private fun switchToPlayer(previousPlayer: Player?, newPlayer: Player) {
Expand All @@ -419,11 +405,12 @@ open class MusicService : MediaBrowserServiceCompat() {
if (currentPlaylistItems.isEmpty()) {
// We are joining a playback session. Loading the session from the new player is
// not supported, so we stop playback.
currentPlayer.stop(/* reset= */true)
currentPlayer.clearMediaItems()
currentPlayer.stop()
} else if (playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED) {
preparePlaylist(
metadataList = currentPlaylistItems,
itemToPlay = currentPlaylistItems[previousPlayer.currentWindowIndex],
itemToPlay = currentPlaylistItems[currentMediaItemIndex],
playWhenReady = previousPlayer.playWhenReady,
playbackStartPositionMs = previousPlayer.currentPosition
)
Expand All @@ -437,7 +424,10 @@ open class MusicService : MediaBrowserServiceCompat() {

// Obtain the current song details *before* saving them on a separate thread, otherwise
// the current player may have been unloaded by the time the save routine runs.
val description = currentPlaylistItems[currentPlayer.currentWindowIndex].description
if (currentPlaylistItems.isEmpty()) {
return
}
val description = currentPlaylistItems[currentMediaItemIndex].description
val position = currentPlayer.currentPosition

serviceScope.launch {
Expand Down Expand Up @@ -469,8 +459,12 @@ open class MusicService : MediaBrowserServiceCompat() {
private inner class UampQueueNavigator(
mediaSession: MediaSessionCompat
) : TimelineQueueNavigator(mediaSession) {
override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat =
currentPlaylistItems[windowIndex].description
override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat {
if (windowIndex < currentPlaylistItems.size) {
return currentPlaylistItems[windowIndex].description
}
return MediaDescriptionCompat.Builder().build()
}
}

private inner class UampPlaybackPreparer : MediaSessionConnector.PlaybackPreparer {
Expand Down Expand Up @@ -550,7 +544,6 @@ open class MusicService : MediaBrowserServiceCompat() {

override fun onCommand(
player: Player,
controlDispatcher: ControlDispatcher,
command: String,
extras: Bundle?,
cb: ResultReceiver?
Expand Down Expand Up @@ -599,7 +592,8 @@ open class MusicService : MediaBrowserServiceCompat() {
/**
* Listen for events from ExoPlayer.
*/
private inner class PlayerEventListener : Player.EventListener {
private inner class PlayerEventListener : Player.Listener {

override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
when (playbackState) {
Player.STATE_BUFFERING,
Expand Down Expand Up @@ -628,32 +622,26 @@ open class MusicService : MediaBrowserServiceCompat() {
}
}

override fun onPlayerError(error: ExoPlaybackException) {
override fun onEvents(player: Player, events: Player.Events) {
if (events.contains(EVENT_POSITION_DISCONTINUITY)
|| events.contains(EVENT_MEDIA_ITEM_TRANSITION)
|| events.contains(EVENT_PLAY_WHEN_READY_CHANGED)) {
currentMediaItemIndex = if (currentPlaylistItems.isNotEmpty()) {
constrainValue(
player.currentMediaItemIndex,
/* min= */ 0,
/* max= */ currentPlaylistItems.size - 1
)
} else 0
}
}

override fun onPlayerError(error: PlaybackException) {
var message = R.string.generic_error;
when (error.type) {
// If the data from MediaSource object could not be loaded the Exoplayer raises
// a type_source error.
// An error message is printed to UI via Toast message to inform the user.
ExoPlaybackException.TYPE_SOURCE -> {
message = R.string.error_media_not_found;
Log.e(TAG, "TYPE_SOURCE: " + error.sourceException.message)
}
// If the error occurs in a render component, Exoplayer raises a type_remote error.
ExoPlaybackException.TYPE_RENDERER -> {
Log.e(TAG, "TYPE_RENDERER: " + error.rendererException.message)
}
// If occurs an unexpected RuntimeException Exoplayer raises a type_unexpected error.
ExoPlaybackException.TYPE_UNEXPECTED -> {
Log.e(TAG, "TYPE_UNEXPECTED: " + error.unexpectedException.message)
}
// Occurs when there is a OutOfMemory error.
ExoPlaybackException.TYPE_OUT_OF_MEMORY -> {
Log.e(TAG, "TYPE_OUT_OF_MEMORY: " + error.outOfMemoryError.message)
}
// If the error occurs in a remote component, Exoplayer raises a type_remote error.
ExoPlaybackException.TYPE_REMOTE -> {
Log.e(TAG, "TYPE_REMOTE: " + error.message)
}
Log.e(TAG, "Player error: " + error.errorCodeName + " (" + error.errorCode + ")");
if (error.errorCode == PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS
|| error.errorCode == PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND) {
message = R.string.error_media_not_found;
}
Toast.makeText(
applicationContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import java.security.NoSuchAlgorithmException
*
* For more information, see res/xml/allowed_media_browser_callers.xml.
*/
class PackageValidator(context: Context, @XmlRes xmlResId: Int) {
internal class PackageValidator(context: Context, @XmlRes xmlResId: Int) {
private val context: Context
private val packageManager: PackageManager

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import com.example.android.uamp.media.extensions.asAlbumArtContentUri
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class PersistentStorage private constructor(val context: Context) {
internal class PersistentStorage private constructor(val context: Context) {

/**
* Store any data which must persist between restarts, such as the most recently played song.
Expand Down
Loading

0 comments on commit 329a21b

Please sign in to comment.