KitePlayer

The player.

One media item at a time, played through a backend that is passed in rather than discovered. Everything about what is happening is read from the four flows below; everything a caller can ask for is one of the calls below. There is nothing else, and that is the point: a member here means the engine implements it, and a feature that is not implemented is absent rather than present and ignored.

What it is made of

The state and the decisions live in one session actor, with workers beside it for demux, video decode, audio decode, audio feed, video schedule, subtitle raster and release. Each of the eight runs on a serial lane of its own, so it does one thing at a time, but a lane is not a thread: on the JVM, Android and the native targets all eight are lanes over the shared Dispatchers.Default and Dispatchers.IO pools, and on the web they share the page's one thread. This class is the outside of that. Accepted state-changing commands are actor messages: awaited calls carry one reply each, while fire-and-forget calls discard or omit theirs. The two close routes instead share one terminal result. After the actor returns, its independent close finalizer alone publishes the terminal snapshot and result. Calling from any thread or coroutine is safe and no two callers can race each other into a state neither asked for.

Which calls wait

A call that takes time suspends until it is done: open, seek, stop, the queue and chapter moves, selectTrack and the others marked suspend. play, pause and the setters only change what the player is asked to do, so they return at once and the state flows show the result. requestSeek is the seek that does not wait, for a dragged seek bar. close returns at once because AutoCloseable requires it, and closeAndAwait waits for the teardown.

State against events

state, progress and stats are state: they conflate, and a collector that misses an intermediate value still ends up correct. events is for occurrences that are the information themselves. Nothing a caller must count is delivered as an event, and a failure is on state as well as in events, because a collector that subscribes after the failure would otherwise never learn of it.

Errors

A suspending call that fails throws PlaybackException, which carries the typed PlaybackError. Cancelling the caller's coroutine stays a CancellationException and is never turned into a playback failure. A call made in the wrong order, or with a value outside its documented range, throws IllegalStateException, IllegalArgumentException or UnsupportedOperationException instead: those are mistakes in the calling code, not failures of the media or the device, and telling them apart is what lets an application decide whether to show the user anything.

What is not here

External subtitles, filter chains, the open-option escape hatch, chapters, the queue, shuffle, a secondary subtitle track, frame stepping and the gapless queue handoff were on this list and are all here now: addExternalSubtitle, MediaItem.videoFilter, MediaItem.openOptions, chapterAt with seekToChapter, openQueue with next and previous, setShuffle, selectSecondarySubtitle, stepFrame, setBalance and QueueConfig. A member that describes something still unbuilt says so in its own documentation.

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard
val coverArt: StateFlow<CoverArt?>

The open item's own cover picture, or null when it carries none, until the player has read it, and once nothing is open (#425). The media session shows it on the lock screen and in the notification when the application gives no picture of its own.

Link copied to clipboard
val events: SharedFlow<PlayerEvent>

Warnings, failures and the occurrences worth naming. Replays nothing to a late collector.

Link copied to clipboard

The KeyframeChoice the next keyframe seek takes; see setKeyframeChoice.

Link copied to clipboard

The same events as events, with none ever dropped (#414).

Link copied to clipboard
val progress: StateFlow<Progress>

Position and buffered extent, republished on PlayerConfig.progressInterval.

Link copied to clipboard
val state: StateFlow<PlayerSnapshot>

Everything about the player that changes rarely. Position is deliberately not in it.

Link copied to clipboard
val stats: StateFlow<PlaybackStats>

Diagnostics, republished on PlayerConfig.statsInterval.

Link copied to clipboard
val subtitleCues: StateFlow<List<SubtitleCue>>

The subtitle cues showing right now, in draw order. Empty when none are.

Link copied to clipboard

A count of the transport commands callers gave: play, pause, stop, and every open or queue move. It only grows. A guard that pauses the player on its own, for a call or for the screen going off, reads it right after its pause and resumes later only if the count has not moved, so it never undoes a play or a pause somebody made in between.

Functions

Link copied to clipboard

Loads a subtitle FILE during playback, appends it to PlayerSnapshot.tracks as an external track, selects it, and returns its id once it is really showing.

Link copied to clipboard
suspend fun addToQueue(item: MediaItem, index: Int? = null)

Inserts one item. See the list overload for everything else.

suspend fun addToQueue(items: List<MediaItem>, index: Int? = null)

Inserts items at index, or at the end when index is null.

Link copied to clipboard

Hands every block of decoded audio to tap on its way to the speaker. Legal at any time, including before anything is open and while playing, and the tap stays attached across opens until detachAudioTap. Attaching the same tap twice changes nothing.

Link copied to clipboard

Attaches a renderer, or replaces the one attached. Legal at any time, including while playing.

Link copied to clipboard
suspend fun attachRendererAndAwait(renderer: VideoRenderer)

attachRenderer, returning once the engine has attached renderer or refused it.

Link copied to clipboard

A coherent audible clock for visualisation and synchronised UI, independent of the seek bar.

Link copied to clipboard
suspend fun awaitClose()

Suspends until this player is asked to close, through close or closeAndAwait from anywhere, and returns at once when that already happened.

Link copied to clipboard
suspend fun captureFrame(withSubtitles: Boolean = false): CapturedFrame

Returns the newest presented frame as an owned, software-readable copy: the documented use of io.github.yuroyami.kiteplayer.spi.SoftwareReadableFrame.

Link copied to clipboard
fun chapterAt(position: Duration): Chapter?

The chapter whose span holds position, or null before the first chapter or in media with no chapter table. Pure over the published snapshot; pair it with position for the chapter now playing.

Link copied to clipboard
suspend fun clearQueue()

Removes every item except the one playing, which is left at index 0 and is not reopened.

Link copied to clipboard
open override fun close()

Requests terminal close. Idempotent, and returns at once without proving teardown completed.

Link copied to clipboard
suspend fun closeAndAwait()

Closes the player and returns only after the session actor and teardown have completed.

Link copied to clipboard

Stops handing audio to tap. A tap that is not attached is ignored.

Link copied to clipboard

Detaches the current renderer. Playback continues without a picture. See attachRenderer.

Detaches expected only while it is still the attached renderer; a stale call is a no-op. This is the safe form for presentation code whose teardown can race a newer attach.

Link copied to clipboard

Everything a bug report needs, in one string: the resolved configuration, the backends by name, tracks and selections, the three published snapshots, the KD artifacts attached to the session, and the bounded warning history. Safe from any thread at any moment, including after a failure, which is when it is usually wanted.

Link copied to clipboard
suspend fun inspect(media: MediaItem): MediaInspection

Reads what open would publish about media, and plays nothing.

Link copied to clipboard

Where playback is, as one value: the queue, the item, the position and every setting the player holds, including balance, the equaliser, and every picture and subtitle setting. Store it however you like and hand it back to restore. A player that opened a single item reports a queue of one; a player with nothing open reports an empty queue and an index of -1, which restore refuses.

Link copied to clipboard
suspend fun moveInQueue(from: Int, to: Int)

Moves the item at from so that it sits at to in the new order.

Link copied to clipboard
suspend fun next()

Opens the next queue item, keeping the play or pause intent.

Link copied to clipboard
suspend fun nextChapter()

Seeks to the start of the chapter after the one holding the current position, with seek's contract. Does nothing at the last chapter, and in media with no chapter table.

Link copied to clipboard
suspend fun open(media: MediaItem)

Opens media and returns once the first frame is on screen and the player is paused on it.

Link copied to clipboard
suspend fun openPlaylist(uri: String, startIndex: Int = 0, headers: Map<String, String> = emptyMap())

Opens the playlist file at uri as the queue, starting at startIndex: readPlaylist and then openQueue, with what each throws.

Link copied to clipboard
suspend fun openQueue(items: List<MediaItem>, startIndex: Int = 0)

Opens items as the queue, starting at startIndex, and returns paused on its first frame exactly like open.

Link copied to clipboard
fun pause()

Asks for a pause, and returns at once.

Link copied to clipboard
fun play()

Asks for playback, and returns at once.

Link copied to clipboard

The position now, without waiting for the next progress sample.

Link copied to clipboard
suspend fun previous()

Opens the previous queue item, keeping the play or pause intent.

Link copied to clipboard
suspend fun previousChapter()

Seeks to the start of the current chapter, or of the previous one when less than three seconds into the current one: the rule every music player has, so a second press means "the one before". At the first chapter both readings restart it. In a gap between chapters the last chapter to have started counts as the current one. Does nothing in media with no chapter table.

Link copied to clipboard
suspend fun readPlaylist(uri: String, headers: Map<String, String> = emptyMap()): List<MediaItem>

The items of the playlist file at uri, for openQueue once the application has filtered or ordered them (#490). See Playlists for what is read.

Link copied to clipboard

Draws the picture on screen again (#438), for a surface that was replaced and came back empty, as an Android view's does when its application returns to the foreground. A paused or ended player decodes the picture it shows once more and presents it, and its position and its status stay as they are, so an ended player stays PlaybackStatus.Ended and play still starts it from the beginning. A playing player needs nothing, because its next frame comes on its own, and a source that cannot seek cannot decode a past picture again. Fire and forget.

Link copied to clipboard
suspend fun reloadExternalSubtitle(track: TrackId, encoding: String? = null)

Reads an external subtitle track's file again, in encoding, and puts the new reading in place of the old one (#515).

Link copied to clipboard
suspend fun removeFromQueue(index: Int)

Removes the item at index.

Link copied to clipboard
fun requestSeek(to: Duration, mode: SeekMode = SeekMode.KeyframeThenRefine)

Asks for a seek and returns at once, merging requests that arrive faster than the pipeline can serve them.

Link copied to clipboard
suspend fun restore(memento: PlayerMemento)

Takes the player back to a memento: applies every setting, opens its queue at its index, goes to its position, then picks the audio and subtitle tracks by language where the new container has them. Ends paused, like every open. A track the memento names and the container lacks leaves the container's own choice in place.

Link copied to clipboard
suspend fun scanAudio(media: MediaItem, track: TrackId? = null, range: AudioScanRange? = null, sink: AudioScanSink): AudioScanResult

scanAudio through this player's backend, with the reader rules of playback: the item's own reader factory, then a configured resolver, then automatic network providers. The playback byte cache is not shared. Null track picks what an open would, including preferred languages.

Link copied to clipboard
suspend fun seek(to: Duration, mode: SeekMode = SeekMode.Precise)

Seeks and returns when the target frame is on screen, or when a later request replaced this one.

Link copied to clipboard
fun seekLater(to: Duration, mode: SeekMode = SeekMode.KeyframeThenRefine)

The old name of requestSeek. It never waited, which the name did not say.

Link copied to clipboard
suspend fun seekToChapter(index: Int)

Seeks to the start of chapter index and returns when its first frame is on screen, with seek's exact contract.

Link copied to clipboard
suspend fun seekToSubtitleLine(offset: Int = 0): Duration

Seeks to the start of a subtitle line and returns it once the line's first frame is on screen, with seek's contract (#491). offset 0 is the line showing, or the last line before now when none shows, so a learner who missed a line hears it again; -1 and +1 are the previous and the next line. Lines are the selected subtitle track's cues as far as the player has read them, every cue of an external file and those an embedded track has delivered, at the times they show with the subtitle delay. Repeated calls move one line each, a paused player included, because each counts from where the last one went.

Link copied to clipboard
suspend fun selectProgram(number: Int?)

Plays the channel numbered number of Tracks.programs, or chooses one again, the first with a picture, when number is null (#505). The media opens again on that channel, through the same rebuild as a video track change, and the picture, the sound and the subtitles are all chosen again from its tracks by the usual rules, unless a track change waiting beside it asks for one. The player keeps playing or stays paused. The choice is kept on the item as DemuxPolicy.program, so a later rebuild keeps it too.

Link copied to clipboard

Shows a second subtitle track where SubtitleConfig.secondaryPlacement puts it, at the top of the picture by default, or clears it with null. SubtitleConfig.secondaryLanguages can choose one at each open instead.

Link copied to clipboard
suspend fun selectTrack(kind: TrackKind, track: TrackId?): TrackChange

Selects a track, or deselects the kind entirely with a null track, and says what happened.

Link copied to clipboard
suspend fun selectVariant(index: Int?)

Plays the variant at index of Tracks.variants, or chooses one again by the item's DemuxPolicy when index is null. The media opens again on that variant at the current position, through the same rebuild as a video track change, and keeps playing or stays paused. The choice is kept on the item, so a later rebuild keeps it too.

Link copied to clipboard
fun setAbLoop(a: Duration?, b: Duration? = null)

Arms or clears the A-B loop: while armed, playback that reaches b jumps straight back to a and keeps going, which is how a phrase is practised and a scene is studied frame by frame. Independent of setLoop; while armed, the A-B loop owns the end of the region and the end of the media both.

Link copied to clipboard

Delays the sound against the picture by value, mpv's audio-delay sign. A positive value presents every video frame that much earlier. It is for sound that reaches the ear early.

Link copied to clipboard
fun setBalance(value: Float)

Sets the stereo balance: -1 is hard left, 0 is centre, 1 is hard right.

Link copied to clipboard
Link copied to clipboard
fun setDuckLevel(level: Float)

Lowers the sound by level, a factor from 1, no change, down to 0, without touching the volume. The media session guards use it to duck under a notification: the volume the listener set, and every control bound to it, stays where it is. A duck multiplies the volume, so it can only make the sound quieter.

Link copied to clipboard

Sets the ten-band equaliser. EqualizerSettings.Flat turns it off.

Link copied to clipboard

Makes playback follow clock, or nothing when it is null. Legal at any time, and it lasts across items until it is replaced. See ExternalClock for how each answer is followed, and PlayerConfig.externalClock for the largest speed change it may use.

Link copied to clipboard

Draws only the forced pictures of a Blu-ray or DVD subtitle track, or every picture again (#513), as mpv's sub-forced-events-only. SubtitleConfig.forcedPicturesOnly says what that means and is where the player starts. Applies to the subtitles showing now, with no reselection; published as PlayerSnapshot.forcedPicturesOnly.

Link copied to clipboard

Sets how HDR video reaches the screen. A renderer that can show HDR applies the change to its next frame, and the Android Surface path to its next open. PlayerSnapshot.videoDynamicRange says what the screen shows.

Link copied to clipboard
suspend fun setItemDetails(title: String?, artist: String?, album: String?)

Replaces the title, the artist and the album of the item that is playing, without opening it again (#423), for a radio that publishes its song list somewhere else, or a stream whose details the application learns later. Null clears a field. The item in PlayerSnapshot.media and in the queue changes, and the media session and the notification follow.

Link copied to clipboard

Chooses which keyframe a SeekMode.Keyframe seek lands on, for the seeks asked for from now on. KeyframeChoice.InSeekDirection is what a skip button wants in a file whose keyframes are far apart. The precise modes are not affected.

Link copied to clipboard
fun setLoop(mode: LoopMode)

Sets what happens at the end of the media.

Link copied to clipboard
fun setMarkers(markers: List<Marker>)

Positions to announce with PlayerEvent.MarkerReached as playback crosses them. Sorted by the engine and replaced wholesale, so an empty list clears them. They belong to the player rather than to the item: a new item starts with every marker armed.

Link copied to clipboard
fun setMuted(value: Boolean)

Silences the sound without losing the setVolume setting. Ramped the same way.

Link copied to clipboard

Turns the night mode on or off (#442): the quiet parts of the sound are brought up and the loud parts down, so speech can be followed at a volume that does not wake the house, as a receiver's night mode or mpv's dynaudnorm does. It acts on the output after the downmix, with a gentle attack and release, and never passes full scale. Off, the default, costs nothing and leaves every sample as it was. A change glides in and out, so it never clicks, and is heard once the audio already buffered has played, as a setBalance change is. Published as PlayerSnapshot.nightMode.

Link copied to clipboard
fun setPitch(semitones: Double)

Moves the pitch by semitones, up or down to an octave, without changing how fast the media plays (#465), for a singer practising in another key or a learner following a voice that is hard to hear, as VLC's pitch control does. It works with any setSpeed, and with setPreservePitch false the speed's own pitch change adds to it. The position, the clock and the picture's sync are untouched, because a pitch changes no timing. Zero, the default, costs nothing and leaves every sample as it was. A change has no seam and no gap, and is heard once the audio already buffered has played, as a speed change is. Published as PlayerSnapshot.pitchSemitones.

Link copied to clipboard

Chooses whether setSpeed keeps pitch.

Link copied to clipboard

Sets how much work the renderer spends on the picture beyond decoding it correctly: dithering, debanding, and which kernel resamples the frame.

Link copied to clipboard
fun setShuffle(enabled: Boolean, seed: Long? = null)

Plays the queue in a shuffled order, or puts it back in the order it was given.

Link copied to clipboard

Shortens the silent stretches of the sound (#429), for a podcast or an audiobook: every pause longer than a fifth of a second is cut down to a fifth of a second, with a short fade at each side of the cut so it never clicks, as Media3's skip silence and the trim silence of podcast players do. A pause shorter than that, between two words, is left as it is. It works with any setSpeed, and the two together are what a listener in a hurry turns on.

Link copied to clipboard
fun setSleepTimer(timer: SleepTimer?, fade: Duration = DEFAULT_SLEEP_FADE)

Stops playback later, fading the sound down first. Null cancels an armed timer.

Link copied to clipboard
fun setSpeed(value: Double)

Sets the playback rate as a multiplier of real time, within SPEED_MIN to SPEED_MAX.

Link copied to clipboard

Sets what the two front speakers play (#462): each its own side, both the average of the two, both the left or both the right, or the two swapped. It acts after the downmix, so a surround film folded to two speakers obeys it too. A change crossfades, so it never clicks, and is heard once the audio already buffered has played, as a setBalance change is. Published as PlayerSnapshot.stereoMode.

Link copied to clipboard

Shifts subtitle timing by value. Positive shows cues later. Applies to the cues already on screen at the next pass, no reopen and no reselection.

Link copied to clipboard

Moves the subtitles up the screen: value is where the implicit bottom stack anchors, as a fraction of the viewport height, mpv's sub-pos over 100. 1.0, the default, is the ordinary bottom edge; 0.9 lifts the stack a tenth of the screen, which is what a viewer with a player bar over their subtitles wants. Explicitly positioned cues are the author's word and do not move. The active cues re-rasterise immediately; published as PlayerSnapshot.subtitlePosition.

Link copied to clipboard

Keeps subtitles inside the safe area of the output: value holds an inset from each edge, each a fraction of the output. The built-in text drawing lays cues out inside what is left, as if it were the whole output, and a typeset ASS track keeps its author's placement. The active cues re-rasterise immediately. docs/subtitle-placement.md has the whole rule.

Link copied to clipboard

Scales subtitle text over the authored size. The active cues re-rasterise at the new size immediately; 1.0 is the authored size.

Link copied to clipboard

Overrides the authored subtitle style with the viewer's own, or clears the override.

Link copied to clipboard

Sets the live picture controls: brightness, contrast, saturation, hue and gamma, mpv's eq.

Link copied to clipboard
fun setVideoEnabled(enabled: Boolean)

Parks or resumes video decoding in place, without reopening anything.

Link copied to clipboard
Link copied to clipboard

Sets the framing controls: a forced display aspect, magnification, and pan, mpv's video-aspect-override, video-zoom and video-pan-x/-y.

Link copied to clipboard
fun setVolume(value: Float)

Sets the volume, from silence at 0 to unity at 1, and up to AudioConfig.volumeCeiling when that has been raised to allow a boost.

Link copied to clipboard
suspend fun startRecording(path: String)

Starts copying what plays into a Matroska file at path, with no re-encode.

Link copied to clipboard
suspend fun stepFrame(direction: StepDirection = StepDirection.Forward)

Steps a PAUSED player by exactly one decoded frame and returns with it on screen.

Link copied to clipboard
suspend fun stepSubtitleDelay(offset: Int): Duration

Shifts the subtitle delay so the line offset lines away starts now (#491), for subtitles that are out of sync by a line or so: +1 brings the next line forward to now, -1 holds the previous one back to now, and 0 starts the line showing now. The result is the new delay, published as PlayerSnapshot.subtitleDelay like a setSubtitleDelay.

Link copied to clipboard
suspend fun stop()

Stops playback, tears the session down and returns to Idle.

Link copied to clipboard
suspend fun stopRecording()

Stops the recording and finishes its file. Does nothing when no recording runs.

Link copied to clipboard

The dump plus a platform block, with every path trimmed to its basename: what a user pastes into a bug report without leaking their filesystem.

Link copied to clipboard
suspend fun thumbnailAt(position: Duration): StreamThumbnail?

The seek bar picture for position of the item that plays, or null when the item carries no pictures or none stands for that position (#433). The pictures come from the item's MediaItem.thumbnails file, or else from the stream itself, a DASH thumbnail set or an HLS image playlist, which Tracks.thumbnails lists. An image downloads only when it is asked for, and a few recent ones are kept, so asking at every step of a scrub costs one download for each image. The picture's times count from the item's start, as position does.

Link copied to clipboard

The last warnings this player emitted, oldest first, capped.