pillarbox-player
Provides PillarboxPlayer, an AndroidX Media3 Player implementation for media playback on Android.
Integration
To use this module, add the following dependency to your module's build.gradle
/build.gradle.kts
file:
implementation("ch.srgssr.pillarbox:pillarbox-player:<pillarbox_version>")
Getting started
Create the player
val player = PillarboxExoPlayer(context, Default)
// Make the player ready to play content
player.prepare()
// Will start playback when a MediaItem is ready to play
player.play()
Playback monitoring
By default, PillarboxExoPlayer does not record any monitoring data. You can configure this behaviour when creating the player:
val player = PillarboxExoPlayer(context, Default) {
// Disable monitoring recording (default behavior)
disableMonitoring()
// Output each monitoring event to Logcat
monitoring(Logcat)
// Send each monitoring event to a remote server
monitoring(Remote) {
config(endpointUrl = "https://example.com/monitoring")
}
}
Create a MediaItem
val mediaUri = "https://example.com/media.mp4"
val mediaItem = MediaItem.fromUri(mediaUri)
player.setMediaItem(mediaItem)
More information about MediaItem creation can be found in the MediaItem
documentation.
Display a Player
PillarboxPlayer can be used with the Views provided by AndroidX Media3 without any modifications.
To quickly get started, add the following to your module's build.gradle
/build.gradle.kts
file:
implementation("androidx.media3:media3-ui:<androidx_media3_version>")
Then link your player to a PlayerView:
@Override
fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
val player = PillarboxExoPlayer(context, Default)
val playerView: PlayerView = findViewById(R.id.player_view)
// A player can only be attached to one View!
playerView.player = player
}
For more detailed information, you can check AndroidX Media3 UI.
Tip: for integration with Compose, you can use pillarbox-ui.
Release a Player
When the player is not needed anymore, you have to release it. This will free resources allocated by the player.
player.release()
Warning: the player can't be used anymore after that.
Custom AssetLoader
AssetLoader is used to load content that doesn't directly have a playable URL, for example, a resource id or a URI. Its responsibility is to provide a MediaSource that:
Is playable by the player;
Contains tracking data;
Provides optional media metadata.
class CustomAssetLoader(context: Context) : AssetLoader(DefaultMediaSourceFactory(context)) {
override fun canLoadAsset(mediaItem: MediaItem): Boolean {
return mediaItem.localConfigruation?.uri?.scheme == "custom"
}
override suspend fun loadAsset(mediaItem: MediaItem): Asset {
val data = service.fetchData(mediaItem.localConfigruation!!.uri)
val trackerData = MutableMediaItemTrackerData()
trackerData[KEY] = FactoryData(CustomMediaItemTracker.Factory(), CustomTrackerData("CustomData"))
val mediaMetadata = MediaMetadata.Builder()
.setTitle(data.title)
.setArtworkUri(data.imageUri)
.setChapters(data.chapters)
.setCredits(data.credits)
.build()
val mediaSource = mediaSourceFactory.createMediaSource(MediaItem.fromUri(data.url))
return Asset(
mediaSource = mediaSource,
trackersData = trackerData.toMediaItemTrackerData(),
mediaMetadata = mediaMetadata,
blockedTimeRanges = emptyList(),
)
}
}
Now pass your CustomAssetLoader
to your player, so it can understand and play your custom data:
val player = PillarboxExoPlayer(context, Default) {
+CustomAssetLoader(context)
}
player.prepare()
player.setMediaItem(MediaItem.fromUri("custom://video:1234"))
player.play()
Chapters
Chapters represent the temporal segmentation of the playing media.
A Chapter can be created like that:
val chapter = Chapter(
id = "1",
start = 0L,
end = 12_000L,
mediaMetadata = MediaMetadata.Builder().setTitle("Chapter 1").build(),
)
PillarboxPlayer provides methods to observe and access chapters:
val player = PillarboxExoPlayer(context, Default)
player.addListener(object : Listener {
override fun onChapterChanged(chapter: Chapter?) {
if (chapter == null) {
// Hide chapter information
} else {
// Display chapter information
}
}
})
val chapters = player.getCurrentChapters()
val currentChapter = player.getChapterAtPosition()
val chapterAtPosition = player.getChapterAtPosition(10_000L)
Chapters can be added to a MediaItem via its metadata:
val mediaMetadata = MediaMetadata.Builder()
.setChapters(listOf(chapter))
.build()
val mediaItem = MediaItem.Builder()
.setMediaMetadata(mediaMetadata)
.build()
Credits
Credits represent a point in the player timeline where opening credits or closing credits should be displayed.
A Credit can be created like that:
val openingCredits = Credit.Opening(start = 5_000L, end = 10_000L)
val closingCredits = Credit.Closing(start = 20_000L, end = 30_000L)
PillarboxPlayer provides methods to observe and access credits:
val player = PillarboxExoPlayer(context, Default)
player.addListener(object : Listener {
override fun onCreditChanged(credit: Credit?) {
when (credit) {
is Credit.Opening -> Unit // Show "Skip intro" button
is Credit.Closing -> Unit // Show "Skip credits" button
else -> Unot // Hide button
}
}
})
val credits = player.getCurrentCredits()
val currentCredit = player.getCreditAtPosition()
val creditAtPosition = player.getCreditAtPosition(5_000L)
Chapters can be added to a MediaItem via its metadata:
val mediaMetadata = MediaMetadata.Builder()
.setCredits(listOf(openingCredits, closingCredits))
.build()
val mediaItem = MediaItem.Builder()
.setMediaMetadata(mediaMetadata)
.build()
Known issues
Playing DRM content on two instances of PillarboxPlayer is not supported on all devices.
Known affected devices: Samsung Galaxy A13, Huawei Nova 5i Pro, Huawei P40 Lite.
Related issue: androidx/media#1877.
Further reading
As PillarboxExoPlayer extends from ExoPlayer, all documentation related to ExoPlayer is also valid for Pillarbox. Here are some useful links to get more information about ExoPlayer:
You can check the following pages for a deeper understanding of Pillarbox concepts: