Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update dependencies #46

Merged
merged 1 commit into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ android {
minSdkVersion 26
//noinspection OldTargetApi
targetSdkVersion 33
versionCode 194
versionName "2.3.0"
versionCode 195
versionName "2.3.1"
}
buildTypes {
release {
Expand Down Expand Up @@ -106,7 +106,7 @@ dependencies {
implementation "com.github.skydoves:landscapist-glide:$landscapist_glide_version"
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0'
implementation "com.airbnb.android:lottie-compose:$lottie_version"
implementation "com.patrykandpatrick.vico:compose-m3:1.6.5"
implementation "com.patrykandpatrick.vico:compose-m3:$vico_version"

//test
testImplementation "junit:junit:$junit_version"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,10 @@ class AndroidEqualizerManager(
}

override fun onCastingStatusChanged(isCasting: Boolean) {
if (isCasting) {
equalizerState.value = if (isCasting) {
EqualizerState.NotAvailableWhileCasting
} else {
equalizer.toState()
}
equalizerState.value = if (isCasting) {
EqualizerState.NotAvailableWhileCasting
} else {
equalizer.toState()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,22 @@ class ReplaceableForwardingPlayer(private var player: Player) : Player {
playlist.addAll(min(newIndex, playlist.size), removedItems)
}

override fun replaceMediaItem(index: Int, mediaItem: MediaItem) {
player.replaceMediaItem(index, mediaItem)
playlist.set(index, mediaItem)
}

override fun replaceMediaItems(
fromIndex: Int,
toIndex: Int,
mediaItems: MutableList<MediaItem>
) {
player.replaceMediaItems(fromIndex, toIndex, mediaItems)
mediaItems.forEachIndexed { index, mediaItem ->
playlist.set(fromIndex + index, mediaItem)
}
}

override fun removeMediaItem(index: Int) {
player.removeMediaItem(index)
playlist.removeAt(index)
Expand Down Expand Up @@ -255,13 +271,6 @@ class ReplaceableForwardingPlayer(private var player: Player) : Player {

override fun stop() = player.stop()

override fun stop(reset: Boolean) {
player.stop(reset)
if (reset) {
playlist.clear()
}
}

override fun release() {
player.release()
playlist.clear()
Expand Down Expand Up @@ -401,14 +410,30 @@ class ReplaceableForwardingPlayer(private var player: Player) : Player {
player.deviceVolume = volume
}

override fun setDeviceVolume(volume: Int, flags: Int) {
player.setDeviceVolume(volume, flags)
}

override fun increaseDeviceVolume() = player.increaseDeviceVolume()

override fun increaseDeviceVolume(flags: Int) {
player.increaseDeviceVolume(flags)
}

override fun decreaseDeviceVolume() = player.decreaseDeviceVolume()

override fun decreaseDeviceVolume(flags: Int) {
player.decreaseDeviceVolume(flags)
}

override fun setDeviceMuted(muted: Boolean) {
player.isDeviceMuted = muted
}

override fun setDeviceMuted(muted: Boolean, flags: Int) {
player.setDeviceMuted(muted, flags)
}

private inner class PlayerListener : Listener {
override fun onEvents(player: Player, events: Events) {
if (events.contains(EVENT_POSITION_DISCONTINUITY) ||
Expand Down
142 changes: 68 additions & 74 deletions app/src/main/java/com/nielsmasdorp/nederadio/playback/StreamService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.*
import androidx.media3.session.MediaConstants.*
import androidx.media3.session.MediaLibraryService.MediaLibrarySession
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
import androidx.media3.session.SessionCommand.*
import androidx.media3.session.SessionResult.RESULT_SUCCESS
import com.google.android.gms.cast.framework.CastContext
Expand All @@ -29,11 +30,10 @@ import com.nielsmasdorp.nederadio.domain.equalizer.EqualizerManager
import com.nielsmasdorp.nederadio.domain.settings.GetLastPlayedId
import com.nielsmasdorp.nederadio.domain.stream.SetActiveStream
import com.nielsmasdorp.nederadio.playback.library.StreamLibrary
import com.nielsmasdorp.nederadio.playback.library.Tree
import com.nielsmasdorp.nederadio.ui.NederadioActivity
import com.nielsmasdorp.nederadio.util.connectedDeviceName
import com.nielsmasdorp.nederadio.util.moveToFront
import com.nielsmasdorp.nederadio.util.sendCommandToController
import com.nielsmasdorp.nederadio.util.toMediaItem
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.guava.future
Expand All @@ -58,6 +58,10 @@ class StreamService :
SessionAvailabilityListener {

private val streamLibrary: StreamLibrary by inject()

private val contentTree: Flow<Tree> = streamLibrary.browsableContent
private suspend fun Flow<Tree>.await(): Tree = first()

private val setActiveStream: SetActiveStream by inject()
private val getLastPlayedId: GetLastPlayedId by inject()
private val equalizerManager: EqualizerManager by inject()
Expand Down Expand Up @@ -102,13 +106,13 @@ class StreamService :
): ListenableFuture<LibraryResult<MediaItem>> {
return serviceScope.future {
val isRecentRequest = params?.isRecent ?: false
val tree = streamLibrary.browsableContent.first()
val contentTree = contentTree.await()
val rootItem = if (!isRecentRequest) {
tree.rootNode.mediaItem
contentTree.rootNode.mediaItem
} else {
// Playback resumption
tree.recentRootNode.also {
tree.getLastPlayed(lastPlayedId = getLastPlayedId()!!).let { item ->
contentTree.recentRootNode.also {
contentTree.getLastPlayed(lastPlayedId = getLastPlayedId()!!).let { item ->
player.setMediaItem(item)
player.prepare()
}
Expand Down Expand Up @@ -138,11 +142,11 @@ class StreamService :
params: LibraryParams?
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
return serviceScope.future {
val tree = streamLibrary.browsableContent.first()
val children = if (parentId == tree.recentRootNode.mediaId) {
listOf(tree.getLastPlayed(lastPlayedId = getLastPlayedId()!!))
val contentTree = contentTree.await()
val children = if (parentId == contentTree.recentRootNode.mediaId) {
listOf(contentTree.getLastPlayed(lastPlayedId = getLastPlayedId()!!))
} else {
tree.getChildren(nodeId = parentId).toMutableList()
contentTree.getChildren(nodeId = parentId).toMutableList()
}
LibraryResult.ofItemList(children, params)
}
Expand All @@ -154,8 +158,7 @@ class StreamService :
mediaId: String
): ListenableFuture<LibraryResult<MediaItem>> {
return serviceScope.future {
val item = streamLibrary.browsableContent.first().getItem(itemId = mediaId)
LibraryResult.ofItem(item, null)
LibraryResult.ofItem(contentTree.await().getItem(itemId = mediaId), null)
}
}

Expand All @@ -166,9 +169,12 @@ class StreamService :
params: LibraryParams?
): ListenableFuture<LibraryResult<Void>> {
return serviceScope.future {
// Ignore extras since we have only top level streams without genres etc.
val results = streamLibrary.browsableContent.first().search(query = query)
mediaSession.notifySearchResultChanged(browser, query, results.size, params)
mediaSession.notifySearchResultChanged(
/* browser */ browser,
/* query */ query,
/* itemCount */ contentTree.await().search(query = query).size,
/* params */ params
)
LibraryResult.ofVoid()
}
}
Expand All @@ -182,24 +188,26 @@ class StreamService :
params: LibraryParams?
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
return serviceScope.future {
// Ignore extras since we have only top level streams without genres etc.
val results = streamLibrary.browsableContent.first().search(query = query)
val results = contentTree.await().search(query = query)
val fromIndex = max((page - 1) * pageSize, results.size - 1)
val toIndex = max(fromIndex + pageSize, results.size)
LibraryResult.ofItemList(results.subList(fromIndex, toIndex), params)
LibraryResult.ofItemList(
/* items */ results.subList(fromIndex, toIndex),
/* params */ params
)
}
}

/**
* Casting has started, switch to [CastPlayer]
* Casting has started, switch to [CastPlayer] and notify equalizer
*/
override fun onCastSessionAvailable() {
player.setPlayer(newPlayer = castPlayer)
equalizerManager.onCastingStatusChanged(isCasting = true)
}

/**
* Casting has been stopped, switch to [ExoPlayer]
* Casting has been stopped, switch to [ExoPlayer] and notify equalizer
*/
override fun onCastSessionUnavailable() {
player.setPlayer(newPlayer = localPlayer)
Expand Down Expand Up @@ -254,26 +262,7 @@ class StreamService :
session: MediaSession,
controller: MediaSession.ControllerInfo
): MediaSession.ConnectionResult {
if (controller.packageName == "com.google.android.projection.gearhead" &&
player.playbackState == STATE_IDLE
) {
// If there is a last played stream, preload the item in Android Auto
serviceScope.launch {
val lastPlayedId = getLastPlayedId()
if (lastPlayedId != null) {
val content = streamLibrary.streams.first()
content.find { it.id == lastPlayedId }?.toMediaItem()?.run {
withContext(Dispatchers.Main) {
player.setMediaItem(this@run)
player.prepare()
}
}
}
}
}

val result = super.onConnect(session, controller)

val sessionCommands = result.availableSessionCommands
.buildUpon()
.add(SessionCommand(START_TIMER_COMMAND, Bundle()))
Expand Down Expand Up @@ -315,55 +304,60 @@ class StreamService :
}
}

override fun onAddMediaItems(
override fun onSetMediaItems(
mediaSession: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItems: MutableList<MediaItem>
): ListenableFuture<MutableList<MediaItem>> {
// There are a couple of issues with media3:
// Firstly when the [MediaController] in [AndroidStreamManager] adds media items this
// hook gets called. As a security measure content URI's get removed before the [MediaItem]s
// reach the [MediaSession]. We have to fill these URI's again.
//
// Also when the [MediaController] used by Android Auto selects a new track
// this method is called with only one item (the selected one), the rest of the queue
// is dropped for some reason. his is a known problem,
// See: https:/androidx/media/issues/156 and https:/androidx/media/issues/236
// To solve both these problems we check the [MediaItems]s here and replace them with the
// Known items in our repository. In the case of Android Auto we use the whole list instead
// of having a queue with a single item.
mediaItems: MutableList<MediaItem>,
startIndex: Int,
startPositionMs: Long
): ListenableFuture<MediaItemsWithStartPosition> {
return serviceScope.future {
val streams = streamLibrary
.browsableContent
.first()
val contentTree = contentTree.await()
if (mediaItems.size == 1) {
val singleItem = mediaItems[0]
if (singleItem.mediaId.isBlank() &&
singleItem.requestMetadata.searchQuery != null
) {
// User has preformed a voice search in Android Auto
streams.search(query = singleItem.requestMetadata.searchQuery)
val itemId = singleItem.mediaId
if (itemId.isBlank() && singleItem.requestMetadata.searchQuery != null) {
// Voice search -> return search results
val streams = contentTree
.search(query = singleItem.requestMetadata.searchQuery)
.toMutableList()
MediaItemsWithStartPosition(streams, startIndex, startPositionMs)
} else {
// User has selected an item in Android Auto
// It is expected behavior, but might get solved in the future
// That the rest of the queue is removed by Android Auto
// So we have to recreate the queue with the selected item in front
streams
.getAllPlayableItems()
.toMutableList()
.apply { moveToFront { it.mediaId == singleItem.mediaId } }
// Selected item in Android Auto, whole list gets dropped (bug)
// so -> recreate items with correct starting index of selected item
// see: https:/androidx/media/issues/156 and 236
val streams = contentTree.getAllPlayableItems().toMutableList()
val index = streams.indexOfFirst { it.mediaId == itemId }
MediaItemsWithStartPosition(streams, index, startPositionMs)
}
} else {
// Just use the [MediaItem] from the content library since the URI exists there
val allContent = streams.getAllPlayableItems()
mediaItems.map { mediaItem ->
allContent.find { it.mediaId == mediaItem.mediaId }!!
// normal cases -> map to content tree for usable URIs
// because they get stripped for IPC safety
val streams = contentTree.getAllPlayableItems()
val mappedStreams = mediaItems.map { mediaItem ->
streams.find { it.mediaId == mediaItem.mediaId }!!
}.toMutableList()
MediaItemsWithStartPosition(mappedStreams, startIndex, startPositionMs)
}
}
}

override fun onPlaybackResumption(
mediaSession: MediaSession,
controller: MediaSession.ControllerInfo
): ListenableFuture<MediaItemsWithStartPosition> {
return serviceScope.future {
val content = contentTree.await()
val streams = content.getAllPlayableItems()
val lastPlayedStream = content.getLastPlayed(lastPlayedId = getLastPlayedId()!!)
MediaItemsWithStartPosition(
/* mediaItems */ streams.toMutableList(),
/* startIndex */streams.indexOf(lastPlayedStream),
/* startPositionMs */ 0L
)
}
}

@SuppressLint("ObsoleteSdkInt")
private fun initialize() {
localPlayer = ExoPlayer.Builder(this)
Expand Down
17 changes: 9 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {

//google
ext.google_cast_version = '21.3.0'
ext.material_version = '1.8.0'
ext.material_version = '1.9.0'

//kotlin
ext.kotlin_version = '1.7.10'
Expand All @@ -14,13 +14,13 @@ buildscript {
ext.kotlinx_guava_version = '1.6.4'

//androidx
ext.androidx_media_router_version = '1.3.1'
ext.androidx_media_router_version = '1.4.0'
ext.androidx_app_compat_version = '1.6.1'
ext.androidx_activity_version = '1.6.1'
ext.androidx_core_version = '1.9.0'
ext.androidx_lifecycle_version = '2.5.1'
ext.androidx_navigation_version = '2.5.3'
ext.androidx_media3_version = '1.0.1'
ext.androidx_activity_version = '1.7.2'
ext.androidx_core_version = '1.10.1'
ext.androidx_lifecycle_version = '2.6.1'
ext.androidx_navigation_version = '2.6.0'
ext.androidx_media3_version = '1.1.0-rc01'
ext.androidx_compose_version = '1.3.1'
ext.androidx_compose_material3_version = '1.0.1'

Expand All @@ -37,6 +37,7 @@ buildscript {
//misc
ext.landscapist_glide_version = '1.4.5'
ext.lottie_version = '4.2.2'
ext.vico_version = '1.6.5'

//test
ext.junit_version = "4.13.2"
Expand All @@ -56,7 +57,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
Expand Down