diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index baa19891..2a73e54b 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -131,5 +131,5 @@ jobs: with: distribution: 'adopt' java-version: '11' - - name: Build Sample App - run: ./gradlew :kotlin-audio-sample:assembleDebug + - name: Build Example App + run: ./gradlew :kotlin-audio-example:assembleDebug diff --git a/kotlin-audio-example/build.gradle.kts b/kotlin-audio-example/build.gradle.kts index a1870e27..32c950c9 100644 --- a/kotlin-audio-example/build.gradle.kts +++ b/kotlin-audio-example/build.gradle.kts @@ -60,14 +60,14 @@ dependencies { } implementation(project(":kotlin-audio")) - implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.core:core-ktx:1.10.1") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") - implementation("androidx.activity:activity-compose:1.7.0") + implementation("androidx.activity:activity-compose:1.7.2") implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.material3:material3") + implementation("androidx.compose.material3:material3:1.1.1") implementation("androidx.compose.material:material-icons-extended") implementation("io.coil-kt:coil-compose:2.4.0") testImplementation("junit:junit:4.13.2") diff --git a/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/MainActivity.kt b/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/MainActivity.kt index 85569783..e0e459fb 100644 --- a/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/MainActivity.kt +++ b/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/MainActivity.kt @@ -9,7 +9,11 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -34,6 +38,7 @@ import com.doublesymmetry.kotlinaudio.models.NotificationConfig import com.doublesymmetry.kotlinaudio.models.RepeatMode import com.doublesymmetry.kotlinaudio.models.PlayerConfig import com.doublesymmetry.kotlinaudio.players.QueuedAudioPlayer +import com.example.kotlin_audio_example.ui.component.ActionBottomSheet import com.example.kotlin_audio_example.ui.component.PlayerControls import com.example.kotlin_audio_example.ui.component.TrackDisplay import com.example.kotlin_audio_example.ui.theme.KotlinAudioTheme @@ -47,7 +52,9 @@ import kotlin.time.Duration.Companion.seconds class MainActivity : ComponentActivity() { private lateinit var player: QueuedAudioPlayer + @OptIn(ExperimentalMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { + Timber.plant(Timber.DebugTree()) super.onCreate(savedInstanceState) player = QueuedAudioPlayer( @@ -72,27 +79,46 @@ class MainActivity : ComponentActivity() { var duration by remember { mutableStateOf(0L) } var isLive by remember { mutableStateOf(false) } - Inner( - title = title, - artist = artist, - artwork = artwork, - position = position, - duration = duration, - isLive = isLive, - onPrevious = { player.previous() }, - onNext = { player.next() }, - isPaused = state.value != AudioPlayerState.PLAYING, - onPlayPause = { - if (player.playerState == AudioPlayerState.PLAYING) { - player.pause() - } else { - player.play() + var showSheet by remember { mutableStateOf(false) } + + if (showSheet) { + ActionBottomSheet( + onDismiss = { showSheet = false }, + onRandomMetadata = { + val currentIndex = player.currentIndex + val track = tracks[currentIndex].copy( + title = "Random Title - ${System.currentTimeMillis()}", + artwork = "https://random.imagecdn.app/800/800" + ) + player.replaceItem(currentIndex, track) } - }, - onSeek = { player.seek(it, TimeUnit.MILLISECONDS) } - ) + ) + } - LaunchedEffect(key1 = player, key2 = player.event.audioItemTransition) { + KotlinAudioTheme { + MainScreen( + title = title, + artist = artist, + artwork = artwork, + position = position, + duration = duration, + isLive = isLive, + onPrevious = { player.previous() }, + onNext = { player.next() }, + isPaused = state.value != AudioPlayerState.PLAYING, + onTopBarAction = { showSheet = true }, + onPlayPause = { + if (player.playerState == AudioPlayerState.PLAYING) { + player.pause() + } else { + player.play() + } + }, + onSeek = { player.seek(it, TimeUnit.MILLISECONDS) } + ) + } + + LaunchedEffect(key1 = player, key2 = player.event.audioItemTransition, key3 = player.event.onPlayerActionTriggeredExternally) { player.event.audioItemTransition .onEach { title = player.currentItem?.title ?: "" @@ -121,15 +147,13 @@ class MainActivity : ComponentActivity() { .launchIn(this) } - if (player.playerState == AudioPlayerState.PLAYING) { - LaunchedEffect(Unit) { - while(true) { - position = player.position - duration = player.duration - isLive = player.isCurrentMediaItemLive + LaunchedEffect(Unit) { + while(true) { + position = player.position + duration = player.duration + isLive = player.isCurrentMediaItemLive - delay(1.seconds / 30) - } + delay(1.seconds / 30) } } } @@ -200,7 +224,7 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3Api::class) @Composable -fun Inner( +fun MainScreen( title: String, artist: String, artwork: String, @@ -210,47 +234,53 @@ fun Inner( onPrevious: () -> Unit = {}, onNext: () -> Unit = {}, isPaused: Boolean, + onTopBarAction: () -> Unit = {}, onPlayPause: () -> Unit = {}, onSeek: (Long) -> Unit = {}, ) { - KotlinAudioTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - Column( - modifier = Modifier - .fillMaxSize() - ) { - TopAppBar( - title = { - Text( - text = "Kotlin Audio Example", - color = MaterialTheme.colorScheme.onPrimary + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Column(modifier = Modifier.fillMaxSize()) { + TopAppBar( + title = { + Text( + text = "Kotlin Audio Example", + color = MaterialTheme.colorScheme.onPrimary + ) + }, + actions = { + IconButton(onClick = onTopBarAction) { + Icon( + Icons.Default.MoreVert, + contentDescription = "Settings", + tint = MaterialTheme.colorScheme.onPrimary ) - }, - colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = MaterialTheme.colorScheme.primary) - ) - TrackDisplay( - title = title, - artist = artist, - artwork = artwork, - position = position, - duration = duration, - isLive = isLive, - onSeek = onSeek, - ) - Spacer(modifier = Modifier.weight(1f)) - PlayerControls( - onPrevious = onPrevious, - onNext = onNext, - isPaused = isPaused, - onPlayPause = onPlayPause, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 60.dp)) - } + } + }, + colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = MaterialTheme.colorScheme.primary) + ) + TrackDisplay( + title = title, + artist = artist, + artwork = artwork, + position = position, + duration = duration, + isLive = isLive, + onSeek = onSeek, + modifier = Modifier.padding(top = 46.dp) + ) + Spacer(modifier = Modifier.weight(1f)) + PlayerControls( + onPrevious = onPrevious, + onNext = onNext, + isPaused = isPaused, + onPlayPause = onPlayPause, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 60.dp) + ) } } } @@ -258,7 +288,7 @@ fun Inner( @Composable fun ContentPreview() { KotlinAudioTheme { - Inner( + MainScreen( title = "Title", artist = "Artist", artwork = "", diff --git a/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/ui/component/ActionBottomSheet.kt b/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/ui/component/ActionBottomSheet.kt new file mode 100644 index 00000000..bf65205f --- /dev/null +++ b/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/ui/component/ActionBottomSheet.kt @@ -0,0 +1,51 @@ +package com.example.kotlin_audio_example.ui.component + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.BottomSheetDefaults +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Composable +@ExperimentalMaterial3Api +fun ActionBottomSheet( + onDismiss: () -> Unit, + onRandomMetadata: () -> Unit, +) { + val modalBottomSheetState = rememberModalBottomSheetState() + + ModalBottomSheet( + onDismissRequest = { onDismiss() }, + sheetState = modalBottomSheetState, + dragHandle = { BottomSheetDefaults.DragHandle() }, + ) { + InnerSheet(onRandomMetadata = onRandomMetadata) + } +} + +@Composable +fun InnerSheet(onRandomMetadata: () -> Unit = {}) { + // Add a button to perform an action when clicked + Button( + onClick = onRandomMetadata, + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + ) { + Text("Metadata: Update Title Randomly") + } +} + +@Preview +@ExperimentalMaterial3Api +@Composable +fun ActionBottomSheetPreview() { + InnerSheet() +} \ No newline at end of file diff --git a/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/ui/component/TrackDisplay.kt b/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/ui/component/TrackDisplay.kt index d8d95b70..d0bdea44 100644 --- a/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/ui/component/TrackDisplay.kt +++ b/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/ui/component/TrackDisplay.kt @@ -29,17 +29,17 @@ fun TrackDisplay( position: Long, duration: Long, isLive: Boolean, + modifier: Modifier = Modifier, onSeek: (Long) -> Unit = {}, ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { + Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier) { if (artwork.isEmpty()) Image( painter = painterResource(id = R.drawable.ic_launcher_background), contentDescription = "Album Cover", modifier = Modifier .fillMaxWidth() - .height(240.dp) - .padding(top = 48.dp) + .height(200.dp) ) else AsyncImage( @@ -63,7 +63,7 @@ fun TrackDisplay( Text( text = "Live", style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(top = 16.dp, bottom = 16.dp) + modifier = Modifier.padding(top = 16.dp) ) else Column { @@ -83,7 +83,7 @@ fun TrackDisplay( Row( modifier = Modifier .fillMaxWidth() - .padding(top = 0.dp, start = 16.dp, end = 16.dp, bottom = 16.dp), + .padding(start = 16.dp, end = 16.dp), ) { Text( text = position.millisecondsToString(), diff --git a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/AudioItem.kt b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/AudioItem.kt index 510cf616..e29d861e 100644 --- a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/AudioItem.kt +++ b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/AudioItem.kt @@ -52,7 +52,7 @@ data class DefaultAudioItem( override var artist: String? = null, override var title: String? = null, override var albumTitle: String? = null, - override val artwork: String? = null, + override var artwork: String? = null, override val duration: Long = -1, override val options: AudioItemOptions? = null, ) : AudioItem diff --git a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/NotificationMetadata.kt b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/NotificationMetadata.kt deleted file mode 100644 index 76c91a5e..00000000 --- a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/NotificationMetadata.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.doublesymmetry.kotlinaudio.models - -data class NotificationMetadata( - val title: String?= null, - val artist: String? = null, - val artworkUrl: String? = null -) diff --git a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/notification/NotificationManager.kt b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/notification/NotificationManager.kt index 29c3b5a6..0b4666d6 100644 --- a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/notification/NotificationManager.kt +++ b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/notification/NotificationManager.kt @@ -18,15 +18,16 @@ import android.support.v4.media.session.PlaybackStateCompat import androidx.annotation.DrawableRes import androidx.core.app.NotificationCompat import coil.imageLoader +import coil.request.CachePolicy import coil.request.Disposable import coil.request.ImageRequest import com.doublesymmetry.kotlinaudio.R import com.doublesymmetry.kotlinaudio.event.NotificationEventHolder import com.doublesymmetry.kotlinaudio.event.PlayerEventHolder +import com.doublesymmetry.kotlinaudio.models.AudioItem import com.doublesymmetry.kotlinaudio.models.MediaSessionCallback import com.doublesymmetry.kotlinaudio.models.NotificationButton import com.doublesymmetry.kotlinaudio.models.NotificationConfig -import com.doublesymmetry.kotlinaudio.models.NotificationMetadata import com.doublesymmetry.kotlinaudio.models.NotificationState import com.doublesymmetry.kotlinaudio.players.components.getAudioItemHolder import com.google.android.exoplayer2.Player @@ -68,7 +69,7 @@ class NotificationManager internal constructor( player: Player, callback: PlayerNotificationManager.BitmapCallback, ): Bitmap? { - val bitmap = getArtworkBitmap() + val bitmap = getCachedArtworkBitmap() if (bitmap != null) { return bitmap } @@ -78,6 +79,8 @@ class NotificationManager internal constructor( context.imageLoader.enqueue( ImageRequest.Builder(context) .data(artwork) + .memoryCachePolicy(CachePolicy.DISABLED) + .diskCachePolicy(CachePolicy.WRITE_ONLY) .target { result -> val resultBitmap = (result as BitmapDrawable).bitmap holder?.artworkBitmap = resultBitmap @@ -94,127 +97,109 @@ class NotificationManager internal constructor( private val scope = MainScope() private val buttons = mutableSetOf() private var invalidateThrottleCount = 0 + private var iconPlaceholder = Bitmap.createBitmap(64, 64, Bitmap.Config.ARGB_8888) + private var notificationMetadataBitmap: Bitmap? = null private var notificationMetadataArtworkDisposable: Disposable? = null - private var iconPlaceholder = Bitmap.createBitmap(64, 64, Bitmap.Config.ARGB_8888) - var notificationMetadata: NotificationMetadata? = null + + /** + * The item that should be used for the notification + * This is used when the user manually sets the notification item + */ + internal var overrideAudioItem: AudioItem? = null set(value) { - if (value == null) { - val changed = field != null - if (changed) { - field = null - notificationMetadataBitmap = null - invalidate() - } - return - } - val holder = player.currentMediaItem?.getAudioItemHolder() - val artworkChanged = field?.artworkUrl != value.artworkUrl - && holder?.audioItem?.artwork != value.artworkUrl - val titleChanged = holder?.audioItem?.title != value.title - val artistChanged = holder?.audioItem?.artist != value.artist - - if (artworkChanged) { - notificationMetadataBitmap = null - // Cancel loading previous artwork: - notificationMetadataArtworkDisposable?.dispose() - if (value.artworkUrl != null) { + notificationMetadataBitmap = null + + if (field != value) { + if (value?.artwork != null) { + notificationMetadataArtworkDisposable?.dispose() notificationMetadataArtworkDisposable = context.imageLoader.enqueue( ImageRequest.Builder(context) - .data(value.artworkUrl) + .data(value.artwork) + .memoryCachePolicy(CachePolicy.DISABLED) + .diskCachePolicy(CachePolicy.WRITE_ONLY) .target { result -> notificationMetadataBitmap = (result as BitmapDrawable).bitmap invalidate() } .build() ) - } else { - notificationMetadataArtworkDisposable = null } } - if (artworkChanged || titleChanged || artistChanged) { - field = value - invalidate() - } + + field = value + invalidate() } private fun getTitle(index: Int? = null): String? { - val mediaItem = if (index == null) player.getCurrentMediaItem() - else player.getMediaItemAt(index) - val isCurrent = index == null || index == player.currentMediaItemIndex - return ((if (isCurrent) notificationMetadata else null)?.title - ?: mediaItem?.mediaMetadata?.title - ?: mediaItem?.getAudioItemHolder()?.audioItem?.title)?.toString() + val mediaItem = if (index == null) player.currentMediaItem else player.getMediaItemAt(index) + + val audioItem = mediaItem?.getAudioItemHolder()?.audioItem + return overrideAudioItem?.title + ?:mediaItem?.mediaMetadata?.title?.toString() + ?: audioItem?.title } private fun getArtist(index: Int? = null): String? { - val mediaItem = if (index == null) player.getCurrentMediaItem() - else player.getMediaItemAt(index) - val isCurrent = index == null || index == player.currentMediaItemIndex - return ( - (if (isCurrent) notificationMetadata else null)?.artist - ?: mediaItem?.mediaMetadata?.artist - ?: mediaItem?.mediaMetadata?.albumArtist - ?: mediaItem?.getAudioItemHolder()?.audioItem?.artist - )?.toString() + val mediaItem = if (index == null) player.currentMediaItem else player.getMediaItemAt(index) + val audioItem = mediaItem?.getAudioItemHolder()?.audioItem + + return overrideAudioItem?.artist + ?: mediaItem?.mediaMetadata?.artist?.toString() + ?: mediaItem?.mediaMetadata?.albumArtist?.toString() + ?: audioItem?.artist } private fun getGenre(index: Int? = null): String? { - val mediaItem = if (index == null) player.getCurrentMediaItem() - else player.getMediaItemAt(index) + val mediaItem = if (index == null) player.currentMediaItem else player.getMediaItemAt(index) return mediaItem?.mediaMetadata?.genre?.toString() } private fun getAlbumTitle(index: Int? = null): String? { - val mediaItem = if (index == null) player.getCurrentMediaItem() - else player.getMediaItemAt(index) - return (mediaItem?.mediaMetadata?.albumTitle - ?: mediaItem?.getAudioItemHolder()?.audioItem?.albumTitle)?.toString() + val mediaItem = if (index == null) player.currentMediaItem else player.getMediaItemAt(index) + return mediaItem?.mediaMetadata?.albumTitle?.toString() + ?: mediaItem?.getAudioItemHolder()?.audioItem?.albumTitle } private fun getArtworkUrl(index: Int? = null): String? { - val isCurrent = index == null || index == player.currentMediaItemIndex - return ( - (if (isCurrent) notificationMetadata else null)?.artworkUrl - ?: getMediaItemArtworkUrl(index) - )?.toString() + return getMediaItemArtworkUrl(index) } private fun getMediaItemArtworkUrl(index: Int? = null): String? { - val mediaItem = if (index == null) player.getCurrentMediaItem() - else player.getMediaItemAt(index) - return ( - mediaItem?.mediaMetadata?.artworkUri + val mediaItem = if (index == null) player.currentMediaItem else player.getMediaItemAt(index) + + return overrideAudioItem?.artwork + ?: mediaItem?.mediaMetadata?.artworkUri?.toString() ?: mediaItem?.getAudioItemHolder()?.audioItem?.artwork - )?.toString() } - private fun getArtworkBitmap(index: Int? = null): Bitmap? { - val mediaItem = if (index == null) player.getCurrentMediaItem() - else player.getMediaItemAt(index) + /** + * Returns the cached artwork bitmap for the current media item. + * Bitmap might be cached if the media item has extracted one from the media file + * or if a user is setting custom data for the notification. + */ + private fun getCachedArtworkBitmap(index: Int? = null): Bitmap? { + val mediaItem = if (index == null) player.currentMediaItem else player.getMediaItemAt(index) val isCurrent = index == null || index == player.currentMediaItemIndex val artworkData = player.mediaMetadata.artworkData - return ( - if (isCurrent && notificationMetadata?.artworkUrl != null) - notificationMetadataBitmap - else - null - ) ?: ( - if (isCurrent && artworkData != null) - BitmapFactory.decodeByteArray(artworkData, 0, artworkData.size) - else - null - ) ?: mediaItem?.getAudioItemHolder()?.artworkBitmap + + return if (isCurrent && overrideAudioItem != null) { + notificationMetadataBitmap + } else if (isCurrent && artworkData != null) { + BitmapFactory.decodeByteArray(artworkData, 0, artworkData.size) + } else { + mediaItem?.getAudioItemHolder()?.artworkBitmap + } } private fun getDuration(index: Int? = null): Long? { - val mediaItem = if (index == null) player.getCurrentMediaItem() + val mediaItem = if (index == null) player.currentMediaItem else player.getMediaItemAt(index) return mediaItem?.getAudioItemHolder()?.audioItem?.duration ?: -1 } private fun getUserRating(index: Int? = null): RatingCompat? { - val mediaItem = if (index == null) player.getCurrentMediaItem() + val mediaItem = if (index == null) player.currentMediaItem else player.getMediaItemAt(index) return RatingCompat.fromRating(mediaItem?.mediaMetadata?.userRating) } @@ -378,7 +363,7 @@ class NotificationManager internal constructor( getArtworkUrl()?.let { putString(MediaMetadataCompat.METADATA_KEY_ART_URI, it) } - getArtworkBitmap()?.let { + getCachedArtworkBitmap()?.let { putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, it); putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, it); } diff --git a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/BaseAudioPlayer.kt b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/BaseAudioPlayer.kt index d81ccfdc..a10eebd7 100644 --- a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/BaseAudioPlayer.kt +++ b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/BaseAudioPlayer.kt @@ -8,7 +8,6 @@ import android.os.Bundle import android.os.ResultReceiver import android.support.v4.media.RatingCompat import android.support.v4.media.session.MediaSessionCompat -import android.support.v4.media.MediaMetadataCompat import androidx.annotation.CallSuper import androidx.core.content.ContextCompat import androidx.media.AudioAttributesCompat @@ -30,7 +29,6 @@ import com.doublesymmetry.kotlinaudio.models.CacheConfig import com.doublesymmetry.kotlinaudio.models.DefaultPlayerOptions import com.doublesymmetry.kotlinaudio.models.MediaSessionCallback import com.doublesymmetry.kotlinaudio.models.MediaType -import com.doublesymmetry.kotlinaudio.models.NotificationMetadata import com.doublesymmetry.kotlinaudio.models.PlayWhenReadyChangeData import com.doublesymmetry.kotlinaudio.models.PlaybackError import com.doublesymmetry.kotlinaudio.models.PlaybackMetadata @@ -55,7 +53,6 @@ import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.PlaybackException import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player.Listener -import com.google.android.exoplayer2.Timeline import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory @@ -213,8 +210,8 @@ abstract class BaseAudioPlayer internal constructor( private var hasAudioFocus = false private var wasDucking = false - protected val mediaSession = MediaSessionCompat(context, "KotlinAudioPlayer") - protected val mediaSessionConnector = MediaSessionConnector(mediaSession) + private val mediaSession = MediaSessionCompat(context, "KotlinAudioPlayer") + private val mediaSessionConnector = MediaSessionConnector(mediaSession) init { if (cacheConfig != null) { @@ -321,9 +318,9 @@ abstract class BaseAudioPlayer internal constructor( } } - internal fun resetNotificationMetadataIfAutomatic() { + internal fun updateNotificationIfNecessary(overrideAudioItem: AudioItem? = null) { if (automaticallyUpdateNotificationMetadata) { - notificationManager.notificationMetadata = null + notificationManager.overrideAudioItem = overrideAudioItem } } @@ -403,12 +400,14 @@ abstract class BaseAudioPlayer internal constructor( * state to transition to AudioPlayerState.IDLE and the player will release the loaded media and * resources required for playback. */ + @CallSuper open fun stop() { playerState = AudioPlayerState.STOPPED exoPlayer.playWhenReady = false exoPlayer.stop() } + @CallSuper open fun clear() { exoPlayer.clearMediaItems() } @@ -417,7 +416,7 @@ abstract class BaseAudioPlayer internal constructor( * Pause playback whenever an item plays to its end. */ fun setPauseAtEndOfItem(pause: Boolean) { - exoPlayer.setPauseAtEndOfMediaItems(pause) + exoPlayer.pauseAtEndOfMediaItems = pause } /** @@ -570,8 +569,8 @@ abstract class BaseAudioPlayer internal constructor( override fun onAudioFocusChange(focusChange: Int) { Timber.d("Audio focus changed") - var isPermanent = focusChange == AUDIOFOCUS_LOSS - var isPaused = when (focusChange) { + val isPermanent = focusChange == AUDIOFOCUS_LOSS + val isPaused = when (focusChange) { AUDIOFOCUS_LOSS, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> true AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> playerOptions.alwaysPauseOnInterruption else -> false @@ -579,7 +578,7 @@ abstract class BaseAudioPlayer internal constructor( if (!playerConfig.handleAudioFocus) { if (isPermanent) abandonAudioFocusIfHeld() - var isDucking = focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK + val isDucking = focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK && !playerOptions.alwaysPauseOnInterruption if (isDucking) { volumeMultiplier = 0.5f @@ -678,7 +677,7 @@ abstract class BaseAudioPlayer internal constructor( ) } - resetNotificationMetadataIfAutomatic() + updateNotificationIfNecessary() } /** @@ -700,7 +699,7 @@ abstract class BaseAudioPlayer internal constructor( for (i in 0 until events.size()) { when (events[i]) { Player.EVENT_PLAYBACK_STATE_CHANGED -> { - var state = when (player.playbackState) { + val state = when (player.playbackState) { Player.STATE_BUFFERING -> AudioPlayerState.BUFFERING Player.STATE_READY -> AudioPlayerState.READY Player.STATE_IDLE -> @@ -746,7 +745,7 @@ abstract class BaseAudioPlayer internal constructor( } override fun onPlayerError(error: PlaybackException) { - var _playbackError = PlaybackError( + val _playbackError = PlaybackError( error.errorCodeName .replace("ERROR_CODE_", "") .lowercase(Locale.getDefault()) diff --git a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/QueuedAudioPlayer.kt b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/QueuedAudioPlayer.kt index 5143365d..b8550c93 100644 --- a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/QueuedAudioPlayer.kt +++ b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/QueuedAudioPlayer.kt @@ -148,7 +148,7 @@ class QueuedAudioPlayer( * @param indexes The indexes of the items to remove. */ fun remove(indexes: List) { - var sorted = indexes.toList() + val sorted = indexes.toList() // Sort the indexes in descending order so we can safely remove them one by one // without having the next index possibly newly pointing to another item than intended: Collections.sort(sorted, Collections.reverseOrder()); @@ -182,7 +182,7 @@ class QueuedAudioPlayer( */ fun move(fromIndex: Int, toIndex: Int) { exoPlayer.moveMediaItem(fromIndex, toIndex) - var item = queue[fromIndex] + val item = queue[fromIndex] queue.removeAt(fromIndex) queue.add(max(0, min(items.size, if (toIndex > fromIndex) toIndex else toIndex - 1)), item) } @@ -218,7 +218,7 @@ class QueuedAudioPlayer( val mediaSource = getMediaSourceFromAudioItem(item) queue[index] = mediaSource if (index == currentIndex) { - resetNotificationMetadataIfAutomatic() + updateNotificationIfNecessary(overrideAudioItem = item) } }