FilmstripTimeline
A ruler, a scrolling strip, a playhead and an optional trim window, assembled.
The ruler carries the scrub gesture and the strip scrolls under a finger, which keeps the two apart: a drag on the ruler moves the playhead, a drag on the strip moves the view. The strip follows the playhead during playback and stops as soon as the user scrolls it.
One track is drawn. A composition with several renders as the timeline they lay out on, with no clip edges marked.
Nothing here needs a player. position is read from whatever clock the host runs, so a preview it draws itself keeps a timeline as honest as a VideoPlayer does, and a timeline with no scrub is simply one a drag on the ruler does not seek.
Assemble the parts directly for anything this does not lay out the way it is wanted. Nothing here is doing work the public pieces cannot.
var trimRange by remember(source) { mutableStateOf(TimeRange.from(Duration.ZERO)) }
val composition = remember(source) { compositionOf { clip(source) } }
val player = rememberFilmstripPlayer(filmstrip, composition)
val playerState by player.state.collectAsState()
val playhead = rememberPlayheadState(player)
Column {
VideoSurface(player, Modifier.weight(1f))
FilmstripTimeline(
renderer = filmstrip,
composition = composition,
duration = playhead.duration ?: Duration.ZERO,
position = playhead.positionProvider(),
isPlaying = playerState.isPlaying,
scrub = rememberScrubState(player),
trim = trimRange,
// Confines playback to the window while the tiles keep covering the whole source, so a
// drag does not rebuild the composition the player is running.
onTrimChange = { trimRange = it; player.setPlaybackRange(it) },
)
}
// Where the trim finally reaches the edit, on the way to the encoder.
compositionOf { clip(source) { trim(trimRange) } }Parameters
What renders the tiles, usually the Filmstrip itself.
The edit to draw.
How much source time the strip spans. With a sourceOffset this is the whole source the tiles cover rather than the shorter run the player reports.
Where the playhead sits, from PlayheadState.positionProvider or from whatever clock the host drives its own transport with.
Whether the player is advancing, from PlayerState.isPlaying .
Modifier for the timeline.
The protocol the ruler's gestures drive, or null for a timeline a drag on the ruler does not seek.
How far in the timeline is drawn, or null to fit the whole composition on screen.
Where the trim sits. The window is drawn only when this and onTrimChange are both given, so a timeline without one leaves both out.
Called with the range a trim gesture asks for, already constrained. The handles move only where trim next puts them.
Everything optional. This applies a pinch and a modified scroll wheel to the strip; keyboard control does not go through here, since dev.jordond.filmstrip.compose.ui.interaction.timelineKeys needs focus and a component that took it on composition would be stealing it from whatever the host had focused before. A host that wants the keys applies it itself, such as to an element it places in overlay, where TimelineState is at hand.
What the timeline paints with.
Where the player's zero sits on the timeline's clock, so timelineTime == playerTime + sourceOffset() . The default suits a player that runs over the whole composition the strip draws.
The export the strip stands in for, which renders its tiles at the HDR grade that export resolves to.
What fills a tile whose frame has not arrived.
Drawn over the tiles and under the trim window and the playhead, for decoration and for chrome that reads the timeline's own state.