Skip to content

Commit 9415056

Browse files
StaehliJMGaetan89
andauthored
Improve media controller service (#485)
Co-authored-by: Gaëtan Muller <[email protected]>
1 parent e3b6906 commit 9415056

File tree

37 files changed

+2379
-707
lines changed

37 files changed

+2379
-707
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ mockk-android = { group = "io.mockk", name = "mockk-android", version.ref = "moc
9393
mockk-dsl = { group = "io.mockk", name = "mockk-dsl-jvm", version.ref = "mockk" }
9494
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
9595
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
96+
kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinx-coroutines" }
9697
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
9798
androidx-media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "androidx-media3" }
9899
androidx-media3-datasource = { group = "androidx.media3", name = "media3-datasource", version.ref = "androidx-media3" }

pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/DefaultPillarbox.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,36 @@ import ch.srgssr.pillarbox.core.business.integrationlayer.service.HttpMediaCompo
1313
import ch.srgssr.pillarbox.core.business.integrationlayer.service.MediaCompositionService
1414
import ch.srgssr.pillarbox.core.business.source.SRGAssetLoader
1515
import ch.srgssr.pillarbox.core.business.tracker.DefaultMediaItemTrackerRepository
16+
import ch.srgssr.pillarbox.player.PillarboxExoPlayer
1617
import ch.srgssr.pillarbox.player.PillarboxLoadControl
17-
import ch.srgssr.pillarbox.player.PillarboxPlayer
1818
import ch.srgssr.pillarbox.player.SeekIncrement
1919
import ch.srgssr.pillarbox.player.source.PillarboxMediaSourceFactory
2020
import ch.srgssr.pillarbox.player.tracker.MediaItemTrackerProvider
2121
import kotlin.time.Duration.Companion.seconds
2222

2323
/**
24-
* DefaultPillarbox convenient class to create [PillarboxPlayer] that suit Default SRG needs.
24+
* [DefaultPillarbox] is a convenient class to create a [PillarboxExoPlayer] that suits the default SRG needs.
2525
*/
2626
object DefaultPillarbox {
2727
private val defaultSeekIncrement = SeekIncrement(backward = 10.seconds, forward = 30.seconds)
2828

2929
/**
30-
* Invoke create an instance of [PillarboxPlayer]
30+
* Invoke create an instance of [PillarboxExoPlayer]
3131
*
3232
* @param context The context.
3333
* @param seekIncrement The seek increment.
3434
* @param mediaItemTrackerRepository The provider of MediaItemTracker, by default [DefaultMediaItemTrackerRepository].
3535
* @param mediaCompositionService The [MediaCompositionService] to use, by default [HttpMediaCompositionService].
3636
* @param loadControl The load control, by default [PillarboxLoadControl].
37-
* @return [PillarboxPlayer] suited for SRG.
37+
* @return [PillarboxExoPlayer] suited for SRG.
3838
*/
3939
operator fun invoke(
4040
context: Context,
4141
seekIncrement: SeekIncrement = defaultSeekIncrement,
4242
mediaItemTrackerRepository: MediaItemTrackerProvider = DefaultMediaItemTrackerRepository(),
4343
mediaCompositionService: MediaCompositionService = HttpMediaCompositionService(),
4444
loadControl: LoadControl = PillarboxLoadControl(),
45-
): PillarboxPlayer {
45+
): PillarboxExoPlayer {
4646
return DefaultPillarbox(
4747
context = context,
4848
seekIncrement = seekIncrement,
@@ -54,15 +54,15 @@ object DefaultPillarbox {
5454
}
5555

5656
/**
57-
* Invoke create an instance of [PillarboxPlayer]
57+
* Invoke create an instance of [PillarboxExoPlayer]
5858
*
5959
* @param context The context.
6060
* @param seekIncrement The seek increment.
6161
* @param mediaItemTrackerRepository The provider of MediaItemTracker, by default [DefaultMediaItemTrackerRepository].
6262
* @param loadControl The load control, by default [DefaultLoadControl].
6363
* @param mediaCompositionService The [MediaCompositionService] to use, by default [HttpMediaCompositionService].
6464
* @param clock The internal clock used by the player.
65-
* @return [PillarboxPlayer] suited for SRG.
65+
* @return [PillarboxExoPlayer] suited for SRG.
6666
*/
6767
@VisibleForTesting
6868
operator fun invoke(
@@ -72,8 +72,8 @@ object DefaultPillarbox {
7272
loadControl: LoadControl = DefaultLoadControl(),
7373
mediaCompositionService: MediaCompositionService = HttpMediaCompositionService(),
7474
clock: Clock,
75-
): PillarboxPlayer {
76-
return PillarboxPlayer(
75+
): PillarboxExoPlayer {
76+
return PillarboxExoPlayer(
7777
context = context,
7878
seekIncrement = seekIncrement,
7979
mediaSourceFactory = PillarboxMediaSourceFactory(context).apply {

pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/DemoBrowser.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import androidx.media3.common.MediaMetadata
2020
*/
2121
class DemoBrowser {
2222

23+
/**
24+
* Every Android Auto navigable [MediaItem] accessed by id.
25+
*/
2326
private val mapMediaIdMediaItem = mutableMapOf<String, MediaItem>()
2427
private val mapMediaIdToChildren = mutableMapOf<String, MutableList<MediaItem>>()
2528

@@ -41,6 +44,7 @@ class DemoBrowser {
4144

4245
init {
4346
val rootList = mapMediaIdToChildren[DEMO_BROWSABLE_ROOT] ?: mutableListOf()
47+
mapMediaIdMediaItem[DEMO_BROWSABLE_ROOT] = rootMediaItem
4448
val listPlaylist = listOf(
4549
Playlist.StreamUrls,
4650
Playlist.StreamUrns,
@@ -52,7 +56,9 @@ class DemoBrowser {
5256
Playlist.BitmovinSamples,
5357
)
5458
for (playlist in listPlaylist) {
55-
rootList += playlist.toMediaItem()
59+
val playlistRootItem = playlist.toMediaItem()
60+
rootList += playlistRootItem
61+
mapMediaIdMediaItem[playlistRootItem.mediaId] = playlistRootItem
5662
for (playlistItem in playlist.items) {
5763
val item = playlistItem.toMediaItem()
5864
mapMediaIdMediaItem[item.mediaId] = item

pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/di/PlayerModule.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import ch.srgssr.pillarbox.core.business.source.SRGAssetLoader
1313
import ch.srgssr.pillarbox.core.business.tracker.DefaultMediaItemTrackerRepository
1414
import ch.srgssr.pillarbox.demo.shared.source.CustomAssetLoader
1515
import ch.srgssr.pillarbox.demo.shared.ui.integrationLayer.data.ILRepository
16-
import ch.srgssr.pillarbox.player.PillarboxPlayer
16+
import ch.srgssr.pillarbox.player.PillarboxExoPlayer
1717
import ch.srgssr.pillarbox.player.source.PillarboxMediaSourceFactory
1818
import java.net.URL
1919

@@ -25,8 +25,8 @@ object PlayerModule {
2525
/**
2626
* Provide default player that allow to play urls and urns content from the SRG
2727
*/
28-
fun provideDefaultPlayer(context: Context): PillarboxPlayer {
29-
return PillarboxPlayer(
28+
fun provideDefaultPlayer(context: Context): PillarboxExoPlayer {
29+
return PillarboxExoPlayer(
3030
context = context,
3131
mediaSourceFactory = PillarboxMediaSourceFactory(context).apply {
3232
addAssetLoader(SRGAssetLoader(context))

pillarbox-demo-tv/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ dependencies {
3636
implementation(libs.androidx.lifecycle.viewmodel.compose)
3737
implementation(libs.androidx.media3.common)
3838
implementation(libs.androidx.media3.exoplayer)
39-
implementation(libs.androidx.media3.session)
4039
implementation(libs.androidx.media3.ui.leanback)
4140
implementation(libs.androidx.navigation.common)
4241
implementation(libs.androidx.navigation.compose)

pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/PlayerActivity.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,26 @@ import androidx.activity.ComponentActivity
1212
import androidx.activity.compose.setContent
1313
import androidx.compose.foundation.layout.fillMaxSize
1414
import androidx.compose.ui.Modifier
15-
import androidx.media3.session.MediaSession
1615
import ch.srgssr.pillarbox.demo.shared.data.DemoItem
1716
import ch.srgssr.pillarbox.demo.shared.di.PlayerModule
1817
import ch.srgssr.pillarbox.demo.tv.ui.player.compose.PlayerView
1918
import ch.srgssr.pillarbox.demo.tv.ui.theme.PillarboxTheme
20-
import ch.srgssr.pillarbox.player.PillarboxPlayer
19+
import ch.srgssr.pillarbox.player.PillarboxExoPlayer
20+
import ch.srgssr.pillarbox.player.session.PillarboxMediaSession
2121

2222
/**
2323
* Player activity
2424
*
2525
* @constructor Create empty Player activity
2626
*/
2727
class PlayerActivity : ComponentActivity() {
28-
private lateinit var player: PillarboxPlayer
29-
private lateinit var mediaSession: MediaSession
28+
private lateinit var player: PillarboxExoPlayer
29+
private lateinit var mediaSession: PillarboxMediaSession
3030

3131
override fun onCreate(savedInstanceState: Bundle?) {
3232
super.onCreate(savedInstanceState)
3333
player = PlayerModule.provideDefaultPlayer(this)
34-
mediaSession = MediaSession.Builder(this, player)
34+
mediaSession = PillarboxMediaSession.Builder(this, player)
3535
.build()
3636
val demoItem = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
3737
intent.getSerializableExtra(ARG_ITEM, DemoItem::class.java)

pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/leanback/LeanbackPlayerFragment.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ import androidx.media3.ui.leanback.LeanbackPlayerAdapter
1919
import ch.srgssr.pillarbox.core.business.SRGErrorMessageProvider
2020
import ch.srgssr.pillarbox.demo.shared.data.DemoItem
2121
import ch.srgssr.pillarbox.demo.shared.di.PlayerModule
22-
import ch.srgssr.pillarbox.player.PillarboxPlayer
22+
import ch.srgssr.pillarbox.player.PillarboxExoPlayer
2323
import ch.srgssr.pillarbox.player.currentMediaMetadataAsFlow
24+
import ch.srgssr.pillarbox.player.extension.setHandleAudioFocus
2425
import kotlinx.coroutines.flow.collectLatest
2526
import kotlinx.coroutines.launch
2627

@@ -33,10 +34,10 @@ private const val UpdateInterval = 1_000
3334
* Lot of work is still needed to have a good player experience.
3435
*/
3536
class LeanbackPlayerFragment : VideoSupportFragment() {
36-
private lateinit var player: PillarboxPlayer
37+
private lateinit var player: PillarboxExoPlayer
3738

3839
/**
39-
* Set demo item to [PillarboxPlayer]
40+
* Set demo item to [PillarboxExoPlayer]
4041
*
4142
* @param demoItem
4243
*/

pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/service/DemoMediaLibraryService.kt

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,29 @@ import androidx.media3.session.MediaSession
1414
import ch.srgssr.pillarbox.demo.shared.data.DemoBrowser
1515
import ch.srgssr.pillarbox.demo.shared.di.PlayerModule
1616
import ch.srgssr.pillarbox.demo.ui.showcases.integrations.MediaControllerActivity
17-
import ch.srgssr.pillarbox.player.service.PillarboxMediaLibraryService
17+
import ch.srgssr.pillarbox.player.session.PillarboxMediaLibraryService
18+
import ch.srgssr.pillarbox.player.session.PillarboxMediaLibrarySession
19+
import ch.srgssr.pillarbox.player.session.PillarboxMediaSession
1820
import ch.srgssr.pillarbox.player.utils.PendingIntentUtils
1921
import com.google.common.collect.ImmutableList
2022
import com.google.common.util.concurrent.Futures
2123
import com.google.common.util.concurrent.ListenableFuture
2224
import okhttp3.internal.toImmutableList
2325

2426
/**
25-
* Demo media session service to handle background playback has Media3 would us to use.
26-
* Can be still useful when using with MediaLibrary for android auto.
27-
*
28-
* Limitations :
29-
* - No custom data access from MediaController so no MediaComposition or other custom attributes integrator wants.
27+
* The only way to handle an Android Auto application.
3028
*
3129
* Hints for testing : https://developer.android.com/training/cars/testing
32-
*
33-
* @constructor Create empty Demo media session service
3430
*/
3531
class DemoMediaLibraryService : PillarboxMediaLibraryService() {
3632

3733
private lateinit var demoBrowser: DemoBrowser
3834

3935
override fun onCreate() {
4036
super.onCreate()
41-
Log.d(TAG, "onCreate")
42-
val player = PlayerModule.provideDefaultPlayer(this)
43-
setPlayer(player, DemoCallback())
44-
4537
demoBrowser = DemoBrowser()
38+
val player = PlayerModule.provideDefaultPlayer(this)
39+
setPlayer(player = player, callback = DemoCallback(), sessionId = "AndroidAutoSession")
4640
}
4741

4842
override fun sessionActivity(): PendingIntent {
@@ -56,78 +50,62 @@ class DemoMediaLibraryService : PillarboxMediaLibraryService() {
5650
)
5751
}
5852

59-
override fun onDestroy() {
60-
Log.d(TAG, "onDestroy")
61-
super.onDestroy()
62-
}
63-
64-
private inner class DemoCallback : MediaLibrarySession.Callback {
53+
/**
54+
* Demo callback is used by Android Auto to create the navigation.
55+
*/
56+
private inner class DemoCallback : PillarboxMediaLibrarySession.Callback {
6557
override fun onGetLibraryRoot(
66-
session: MediaLibrarySession,
58+
session: PillarboxMediaLibrarySession,
6759
browser: MediaSession.ControllerInfo,
6860
params: LibraryParams?
6961
): ListenableFuture<LibraryResult<MediaItem>> {
70-
Log.d(TAG, "onGetLibraryRoot")
7162
val rootExtras = Bundle().apply {
7263
putBoolean(MEDIA_SEARCH_SUPPORTED, false)
7364
putBoolean(CONTENT_STYLE_SUPPORTED, true)
7465
putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID)
7566
putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST)
7667
}
68+
Log.d(TAG, "onGetLibraryRoot isSuggested = ${params?.isSuggested} isRecent = ${params?.isRecent}")
7769
val libraryParams = LibraryParams.Builder().setExtras(rootExtras).build()
7870
return Futures.immediateFuture(LibraryResult.ofItem(demoBrowser.rootMediaItem, libraryParams))
7971
}
8072

8173
override fun onGetChildren(
82-
session: MediaLibrarySession,
74+
session: PillarboxMediaLibrarySession,
8375
browser: MediaSession.ControllerInfo,
8476
parentId: String,
8577
page: Int,
8678
pageSize: Int,
8779
params: LibraryParams?
8880
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
89-
Log.d(TAG, "onGetChildren($parentId)")
9081
return demoBrowser.getChildren(parentId)?.let {
9182
Futures.immediateFuture(LibraryResult.ofItemList(it.toImmutableList(), LibraryParams.Builder().build()))
9283
} ?: super.onGetChildren(session, browser, parentId, page, pageSize, params)
9384
}
9485

9586
override fun onGetItem(
96-
session: MediaLibrarySession,
87+
session: PillarboxMediaLibrarySession,
9788
browser: MediaSession.ControllerInfo,
9889
mediaId: String
9990
): ListenableFuture<LibraryResult<MediaItem>> {
100-
Log.d(TAG, "onGetItem $mediaId")
91+
val mediaItem = demoBrowser.getMediaItemFromId(mediaId) ?: MediaItem.EMPTY
10192
return Futures.immediateFuture(
102-
LibraryResult.ofItem(
103-
demoBrowser.getMediaItemFromId(mediaId) ?: MediaItem.EMPTY, LibraryParams.Builder().build()
104-
)
93+
LibraryResult.ofItem(mediaItem, LibraryParams.Builder().build())
10594
)
10695
}
10796

10897
override fun onAddMediaItems(
109-
mediaSession: MediaSession,
98+
mediaSession: PillarboxMediaSession,
11099
controller: MediaSession.ControllerInfo,
111100
mediaItems: MutableList<MediaItem>
112101
): ListenableFuture<MutableList<MediaItem>> {
113-
Log.d(TAG, "onAddMediaItems")
114102
/*
115103
* MediaItem from Browser are directly the one we want to play.
116104
* For MediaItem with only id, like urn, it is fine. But one with uri not, as the localConfiguration is null here.
117-
* We have to get the orignal mediaItem with uri set.
105+
* We have to get the original mediaItem with uri set.
118106
*/
119107
return Futures.immediateFuture(mediaItems.map { demoBrowser.getMediaItemFromId(it.mediaId) ?: it }.toMutableList())
120108
}
121-
122-
override fun onSearch(
123-
session: MediaLibrarySession,
124-
browser: MediaSession.ControllerInfo,
125-
query: String,
126-
params: LibraryParams?
127-
): ListenableFuture<LibraryResult<Void>> {
128-
Log.d(TAG, "onSearch: $query")
129-
return super.onSearch(session, browser, query, params)
130-
}
131109
}
132110

133111
companion object {

pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/service/DemoMediaSessionService.kt

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ import android.app.PendingIntent
88
import android.content.Intent
99
import android.util.Log
1010
import androidx.media3.common.C
11-
import androidx.media3.common.MediaItem
12-
import ch.srgssr.pillarbox.demo.shared.data.DemoItem
1311
import ch.srgssr.pillarbox.demo.shared.di.PlayerModule
1412
import ch.srgssr.pillarbox.demo.ui.showcases.integrations.MediaControllerActivity
15-
import ch.srgssr.pillarbox.player.service.PillarboxMediaSessionService
13+
import ch.srgssr.pillarbox.player.extension.setHandleAudioFocus
14+
import ch.srgssr.pillarbox.player.session.PillarboxMediaSessionService
1615
import ch.srgssr.pillarbox.player.utils.PendingIntentUtils
1716

1817
/**
@@ -31,21 +30,12 @@ class DemoMediaSessionService : PillarboxMediaSessionService() {
3130
super.onCreate()
3231
Log.d(TAG, "onCreate")
3332
val player = PlayerModule.provideDefaultPlayer(this)
34-
// TODO add item elsewhere
35-
player.setMediaItems(
36-
listOf(
37-
MediaItem.Builder().setMediaId("urn:rts:video:6820736").build(),
38-
MediaItem.Builder().setMediaId("urn:rts:video:8393241").build(),
39-
DemoItem(title = "Swiss cheese fondue", uri = "https://swi-vod.akamaized.net/videoJson/47603186/master.m3u8").toMediaItem(),
40-
MediaItem.Builder().setMediaId("urn:rts:video:3608506").build(),
41-
)
42-
)
4333
player.setWakeMode(C.WAKE_MODE_NETWORK)
4434
player.setHandleAudioFocus(true)
4535
player.prepare()
4636
player.play()
4737

48-
setPlayer(player)
38+
setPlayer(player = player, sessionId = "DemoMediaSession")
4939
}
5040

5141
override fun sessionActivity(): PendingIntent {

pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/SimplePlayerViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import androidx.media3.common.Timeline
1919
import androidx.media3.common.VideoSize
2020
import ch.srgssr.pillarbox.demo.shared.data.DemoItem
2121
import ch.srgssr.pillarbox.demo.shared.di.PlayerModule
22+
import ch.srgssr.pillarbox.player.extension.setHandleAudioFocus
2223
import ch.srgssr.pillarbox.player.extension.toRational
2324
import kotlinx.coroutines.flow.MutableStateFlow
2425
import java.net.URL

0 commit comments

Comments
 (0)