Skip to content

Commit

Permalink
Update documentation in pillarbox-ui (#787)
Browse files Browse the repository at this point in the history
  • Loading branch information
MGaetan89 authored Nov 11, 2024
1 parent 1f22bb5 commit 533f607
Show file tree
Hide file tree
Showing 16 changed files with 328 additions and 228 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ class PillarboxAndroidLibraryPublishingPlugin : Plugin<Project> {
includes.from("docs/README.md")
}

externalDocumentationLinks.register("kotlinx.coroutines") {
url.set(URI("https://kotlinlang.org/api/kotlinx.coroutines"))
packageListUrl.set(URI("https://kotlinlang.org/api/kotlinx.coroutines/package-list"))
}

externalDocumentationLinks.register("kotlinx.serialization") {
url.set(URI("https://kotlinlang.org/api/kotlinx.serialization"))
packageListUrl.set(URI("https://kotlinlang.org/api/kotlinx.serialization/package-list"))
Expand Down
13 changes: 0 additions & 13 deletions pillarbox-ui/Module.md

This file was deleted.

180 changes: 100 additions & 80 deletions pillarbox-ui/docs/README.md
Original file line number Diff line number Diff line change
@@ -1,126 +1,146 @@
[![Pillarbox logo](https://github.com/SRGSSR/pillarbox-apple/blob/main/docs/README-images/logo.jpg)](https://github.com/SRGSSR/pillarbox-android)
[![Last release](https://img.shields.io/github/v/release/SRGSSR/pillarbox-android?label=Release)](https://github.com/SRGSSR/pillarbox-android/releases)
[![Android min SDK](https://img.shields.io/badge/Android-21%2B-34A853)](https://github.com/SRGSSR/pillarbox-android)
[![License](https://img.shields.io/github/license/SRGSSR/pillarbox-android?label=License)](https://github.com/SRGSSR/pillarbox-android/blob/main/LICENSE)
# Module pillarbox-ui

# Pillarbox UI module
Provides UI Compose components and helpers.

Provides UI Compose components:
This includes:

- PlayerSurface
- Exoplayer views compose wrappers
- ProgressTrackers to connect the player to a progress bar or slider.
- [PlayerSurface][ch.srgssr.pillarbox.ui.widget.player.PlayerSurface], to display a player on a surface, texture, or spherical surface.
- Compose wrapper for ExoPlayer `View`s.
- [ProgressTracker][ch.srgssr.pillarbox.ui.ProgressTrackerState] to connect the player to a progress bar or slider.

## Integration

```gradle
implementation("ch.srgssr.pillarbox:pillarbox-ui:$LATEST_RELEASE_VERSION")
```
To use this module, add the following dependency to your project's `build.gradle`/`build.gradle.kts` file:

More information can be found on the [top level README](../docs/README.md)
```kotlin
implementation("ch.srgssr.pillarbox:pillarbox-ui:<pillarbox_version>")
```

## Getting started

### Drawing a simple video surface
### Display a `Player`

```kotlin
@Composable
fun SimplePlayer(player: Player) {
Box(modifier = Modifier) {
PlayerSurface(player = player)
}
fun SimplePlayer(
player: Player,
modifier: Modifier = Modifier,
) {
PlayerSurface(
player = player,
modifier = modifier,
)
}
```

### Create a simple player with controls and subtitles
### Create a `Player` with controls and subtitles

In this example, we are drawing controls and subtitles on top of the player surface. To add subtitles use `ExoPlayerSubtitleView` and for controls
you can use the Exoplayer version, `ExoPlayerControlView`.
In this example, we are drawing controls and subtitles on top of the [Player][androidx.media3.common.Player]. To add controls, you can use
[ExoPlayerControlView][ch.srgssr.pillarbox.ui.exoplayer.ExoPlayerControlView]. And for subtitles, you can use
[ExoPlayerSubtitleView][ch.srgssr.pillarbox.ui.exoplayer.ExoPlayerSubtitleView].

```kotlin
@Composable
fun MyPlayer(player: Player) {
val defaultAspectRatio = 1.0f
fun MyPlayer(
player: Player,
modifier: Modifier = Modifier,
) {
Box(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.background(color = Color.Black),
contentAlignment = Alignment.Center
contentAlignment = Alignment.Center,
) {
PlayerSurface(
modifier = Modifier,
player = player,
scaleMode = ScaleMode.Fit,
defaultAspectRatio = defaultAspectRatio,
surfaceType = SurfaceType.Surface, // By default
defaultAspectRatio = 1f,
)

ExoPlayerControlView(
player = player,
modifier = Modifier.matchParentSize(),
)

ExoPlayerSubtitleView(
player = player,
modifier = Modifier.matchParentSize(),
)
ExoPlayerControlView(modifier = Modifier.matchParentSize(), player = player)
ExoPlayerSubtitleView(modifier = Modifier.matchParentSize(), player = player)
}
}
```

The `defaultAspectRatio` is used while the video is loading or if the player doesn't play a video.

In this example we use `ScaleMode.Fit` to fit the content to the parent container but there are more scales modes :

- `ScaleMode.Fit` : Fit player content to the parent container and keep aspect ratio.
- `ScaleMode.Fill` : Fill player content to the parent container.
- `ScaleMode.Crop` : Crop player content inside the parent container and keep aspect ratio. Content outside the parent container will be clipped.
The `defaultAspectRatio` is used while the video is loading or if the [Player][androidx.media3.common.Player] doesn't play a video.

### Surface types
### Scale mode

`PlayerSurface` allows choosing between multiple types of surface:
You can customize how the [Player][androidx.media3.common.Player] scales in the [PlayerSurface][ch.srgssr.pillarbox.ui.widget.player.PlayerSurface],
by setting the `scaleMode` argument.

- `SurfaceType.Surface` (default): the player is linked to a `SurfaceView`. This option is the most optimized version, and supports playing any
content including DRM protected content.
- `SurfaceType.Texture`: the player is linked to a `TextureView`. This option may be interesting when dealing with animation, and the `Surface`
option doesn't work as expected.
- `SurfaceType.Spherical`: the player is linked to a `SphericalGLSurfaceView`. This surface type is suited when playing 360° video content.
```kotlin
PlayerSurface(
player = player,
scaleMode = ScaleMode.Fit,
)
```

> [!NOTE]
> The last two surface types are not suited when playing DRM protected content.
- [ScaleMode.Fit][ch.srgssr.pillarbox.ui.ScaleMode.Fit] (default): resizes the [Player][androidx.media3.common.Player] to fit within its parent while
maintaining its aspect ratio.
- [ScaleMode.Fill][ch.srgssr.pillarbox.ui.ScaleMode.Fill]: stretches the [Player][androidx.media3.common.Player] to fill its parent, ignoring the
defined aspect ratio.
- [ScaleMode.Crop][ch.srgssr.pillarbox.ui.ScaleMode.Crop]: trims the [Player][androidx.media3.common.Player] to fill its parent while maintaining its
aspect ratio.

### Listen to player states
### Surface type

To listen to player states _Pillarbox_ provides some extensions `PlayerCallbackFlow.kt` and some Compose extensions `ComposablePlayer.kt`.
[PlayerSurface][ch.srgssr.pillarbox.ui.widget.player.PlayerSurface] lets you set the type of surface used to render its content, using its
`surfaceType` argument.

```kotlin
PlayerSurface(
player = player,
surfaceType = SurfaceType.Surface,
)
```

@Composable
fun MyPlayerView(player: Player) {
val defaultAspectRatio = 1.0f
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.background(color = Color.Black),
contentAlignment = Alignment.Center
) {
PlayerSurface(
modifier = Modifier,
player = player,
scaleMode = ScaleMode.Fit,
defaultAspectRatio = defaultAspectRatio
)
// Displays current position periodically
val currentPosition = player.currentPositionAsState()
Text(text = "Position = $currentPosition ms", modifier = Modifier.align(Alignment.TopStart))
- [SurfaceType.Surface][ch.srgssr.pillarbox.ui.widget.player.SurfaceType.Surface] (default): the [Player][androidx.media3.common.Player] is attached
to a [SurfaceView][android.view.SurfaceView]. This is the most optimized option, and supports playing any content including DRM protected content.
- [SurfaceType.Texture][ch.srgssr.pillarbox.ui.widget.player.SurfaceType.Texture]: the [Player][androidx.media3.common.Player] is attached to
a [TextureView][android.view.TextureView]. This option may be interesting when dealing with animation, and
the [SurfaceType.Surface][ch.srgssr.pillarbox.ui.widget.player.SurfaceType.Surface] option doesn't work as expected. This does not work with DRM
content.
- [SurfaceType.Spherical][ch.srgssr.pillarbox.ui.widget.player.SurfaceType.Spherical]: the [Player][androidx.media3.common.Player] is attached to
a [SphericalGLSurfaceView][androidx.media3.exoplayer.video.spherical.SphericalGLSurfaceView]. This option is suited when playing 360° video
content. This does not work with DRM content.

val duration = player.durationAsState()
Text(text = "Duration = $duration ms", modifier = Modifier.align(Alignment.TopEnd))
### Observe `Player` states

val isPlaying = player.isPlayingAsState()
Button(modifier = Modififer.align(Alignement.Center), onClick = { togglePlayingBack() }) {
Text(text = if (isPlaying) "Pause" else "Play")
}
}
The [ch.srgssr.pillarbox.ui.extension][ch.srgssr.pillarbox.ui.extension] package provides a collection of extensions to observe a
[Player][androidx.media3.common.Player]'s state through Compose's [State][androidx.compose.runtime.State] instances.

```kotlin
@Composable
fun MyPlayer(player: Player) {
val currentPosition: Long by player.currentPositionAsState()
val duration: Long by player.durationAsState()
val isPlaying: Boolean by player.isPlayingAsState()
}
```

## Compose

To learn more about compose, you can read the [Official documentation](https://developer.android.com/jetpack/compose)


[android.view.SurfaceView]: https://developer.android.com/reference/android/view/SurfaceView
[android.view.TextureView]: https://developer.android.com/reference/android/view/TextureView
[androidx.compose.runtime.State]: https://developer.android.com/reference/kotlin/androidx/compose/runtime/State.html
[androidx.media3.common.Player]: https://developer.android.com/reference/androidx/media3/common/Player
[androidx.media3.exoplayer.video.spherical.SphericalGLSurfaceView]: https://developer.android.com/reference/androidx/media3/exoplayer/video/spherical/SphericalGLSurfaceView
[ch.srgssr.pillarbox.ui.exoplayer.ExoPlayerControlView]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui.exoplayer/-exo-player-control-view.html
[ch.srgssr.pillarbox.ui.exoplayer.ExoPlayerSubtitleView]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui.exoplayer/-exo-player-subtitle-view.html
[ch.srgssr.pillarbox.ui.extension]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui.extension/index.html
[ch.srgssr.pillarbox.ui.widget.player.PlayerSurface]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui.widget.player/-player-surface.html
[ch.srgssr.pillarbox.ui.widget.player.SurfaceType.Spherical]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui.widget.player/-surface-type/-spherical/index.html
[ch.srgssr.pillarbox.ui.widget.player.SurfaceType.Surface]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui.widget.player/-surface-type/-surface/index.html
[ch.srgssr.pillarbox.ui.widget.player.SurfaceType.Texture]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui.widget.player/-surface-type/-texture/index.html
[ch.srgssr.pillarbox.ui.ProgressTrackerState]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui/-progress-tracker-state/index.html
[ch.srgssr.pillarbox.ui.ScaleMode]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui/-scale-mode/index.html
[ch.srgssr.pillarbox.ui.ScaleMode.Crop]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui/-scale-mode/-crop/index.html
[ch.srgssr.pillarbox.ui.ScaleMode.Fill]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui/-scale-mode/-fill/index.html
[ch.srgssr.pillarbox.ui.ScaleMode.Fit]: https://android.pillarbox.ch/api/pillarbox-ui/ch.srgssr.pillarbox.ui/-scale-mode/-fit/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,24 @@ import kotlinx.coroutines.flow.StateFlow
import kotlin.time.Duration

/**
* Interface used to subscribe to and update the [Player] progression.
* This interface allows subscribing to the current [Player] progress and provides callbacks for manual progress adjustments.
*/
interface ProgressTrackerState {
/**
* Emits the current progress, which can be either the value being manually set, or the actual [Player] progress.
* A [StateFlow] emitting the current progress, which can either be the progress manually set, or the actual progress of the underlying [Player].
*/
val progress: StateFlow<Duration>

/**
* Callback to invoke when the progress is being manually changed.
*
* @param progress The new progress of the media being played. It must be between 0ms and [Player.getDuration].
* @param progress The new progress of the media being played. It must be between 0ms and the total duration of the media, as returned by
* [Player.getDuration].
*/
fun onChanged(progress: Duration)

/**
* Callback to invoke when the progress is no longer being changed.
* Callback to invoke when the progress operation has finished.
*/
fun onFinished()
}
14 changes: 10 additions & 4 deletions pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/ScaleMode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,28 @@
*/
package ch.srgssr.pillarbox.ui

import androidx.media3.common.Player

/**
* Scale mode
* Represents the scaling mode for the [Player] content within its parent container.
*/
enum class ScaleMode {
/**
* Fit player content to the parent container and keep aspect ratio.
* Resizes the [Player][androidx.media3.common.Player] content to fit within the parent while maintaining its aspect ratio. This ensures the
* entire content is visible, but may result in black bars (letterboxing or pillarboxing) on the sides or top/bottom if the aspect ratios of the
* media and container do not match.
*/
Fit,

/**
* Fill player content to the parent container.
* Stretches the [Player][androidx.media3.common.Player] to fill its parent, ignoring the defined aspect ratio. This may cause the content to be
* distorted horizontally or vertically to match the parent's dimensions.
*/
Fill,

/**
* Crop player content inside the parent container and keep aspect ratio.
* Trims the [Player][androidx.media3.common.Player] to fill its parent while maintaining its aspect ratio. Any content that extends beyond the
* bounds of the container will be clipped.
*/
Crop,
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

/**
* [Player] progress tracker that only updated the player's actual progress when [onFinished] is called.
* A [ProgressTrackerState] implementation that updates the [Player] progress only when [onFinished] is called.
*
* @param player The [Player] whose current position must be tracked.
* @param coroutineScope
* @param player The [Player] whose progress needs to be tracked.
* @param coroutineScope The [CoroutineScope] used for managing [StateFlow]s.
*/
class SimpleProgressTrackerState(
private val player: Player,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import kotlinx.coroutines.flow.StateFlow
import kotlin.time.Duration

/**
* [Player] progress tracker that updates the player's actual progress everytime that [onChanged] is called.
* A [ProgressTrackerState] implementation that updates the [Player] progress every time [onChanged] is called.
*
* @param player The [Player] whose current position must be tracked.
* @param coroutineScope
* @param player The [Player] whose progress needs to be tracked.
* @param coroutineScope The [CoroutineScope] used for managing [StateFlow]s.
*/
class SmoothProgressTrackerState(
private val player: PillarboxExoPlayer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import androidx.media3.common.Player
import androidx.media3.ui.PlayerControlView

/**
* Composable basic version of [PlayerControlView] from Media3 (Exoplayer)
* A Composable function that displays an ExoPlayer [PlayerControlView].
*
* @param player The player to bind to the controls.
* @param modifier The modifier to be applied to the layout.
* @param player The [Player] instance to be controlled.
* @param modifier The [Modifier] to apply to this layout.
*/
@Composable
fun ExoPlayerControlView(
Expand Down
Loading

0 comments on commit 533f607

Please sign in to comment.