AudioPlayback
The engine's audio half: a device, a ring, and the clock derived from them.
The full player composes this with the video path. On its own it is a complete audio player, which is why it is public: a music application needs exactly this and nothing more.
What it is responsible for
Accepting decoded PCM, holding it so the device's period size is invisible to the decoder, and maintaining the master clock. That last part is the reason this class exists rather than the caller writing to a sink directly. The clock is not counted from samples submitted; it is anchored to the instant the device says a specific frame becomes audible. Every player that instead estimates the device latency ships a fixed audio delay that nothing corrects.
Threading
submitDecoded belongs to one coroutine, the audio feeder, because it is the ring's single producer, and the conversion stage behind it belongs to the same coroutine. The device's real-time callback is the single consumer and touches nothing else in this class, so neither side takes a lock.
anchorClock, position, buffered, underruns, the speed setter and every gain change are guarded by one internal lock. The first two write the media clock, which has one writer by design, and a player reports progress from a thread that is not the one driving playback: two callers re-anchoring the same clock at once is what the lock is for. buffered and underruns take it for a second reason that came with the C callback: they read the ring, and the ring can now be memory close frees. A gain change writes into the ring, so it takes the lock for the same reason. Reading speed is a plain read of one value and needs nothing.
open, play, pause, flush, drain, endOfStream and close are thread confined to the session owner instead. submitDecoded runs on the feed worker and reads the ring FIELD under the lock, so the rule is one sentence again: any member that may run beside another thread touches that field only under the lock. A lock cannot be held across a suspension point, so the suspending ones could not be guarded even in principle, and their contract is confinement. The core's session actor is that owner. The seek path already depends on this: the ring's own flush requires both of its sides to be quiescent first.
close is the one member that is confined AND takes the lock, for one statement. Confinement says no other owner call runs beside it; it says nothing about the four members above, which are documented safe from any thread. Clearing the ring reference inside the lock is what makes those four safe against a teardown that frees a C ring underneath them.
Constructors
Properties
How far, in decibels, the centre channel is raised or lowered where the downmix folds it into other speakers (#442). Applied as audio is written, so it is heard after the ring's depth, as the balance is.
The ten-band equaliser. Flat by default, and free while it is.
The sink's own event feed, surfaced so the engine can warn on device loss.
What the device says it is holding: handed over, not yet audible.
Rendered frames the ring's peak limiter turned down because they would have passed full scale. Under the lock, as buffered is.
The format the device accepted. Null before open.
How far, in semitones, the pitch is moved without changing how fast the sound plays (#465). Applied to the next buffer the feeder converts with no seam, as a speed change is, and dated in the timeline as nothing at all, because it changes no timing.
The sink's platform handle for audio effects, or null. See AudioSink.platformSessionId.
The ReplayGain to apply to the material, as a linear multiplier. 1 applies nothing.
Whether every pause longer than a fifth of a second is cut down to a fifth of a second (#429). Applied as audio is written, so it is heard after the ring's depth, as the balance is. Each cut is a line in the playout timeline, where the output reaches it, so position follows it with no jump of its own. The engine turns it off for an item with a picture and for a live stream.
The playback rate as a multiplier of real time, within TempoStage.MIN_SPEED to TempoStage.MAX_SPEED. Real: the tempo stage in the pipeline makes the sound take 1/speed as long, at its own pitch unless preservePitch is false, and the clock runs to match.
What the two front speakers play: see StereoMode. Applied as audio is written, after the downmix and before the balance, so it is heard after the ring's depth, as the balance is.
Functions
Anchors the clock from what the device last reported.
Quiescence precondition, stated in the same words flush's is: the feeder must not be between a submit call's start and its return when this runs. Confinement alone does not give that, because submit runs on the feed worker rather than the session owner; what gives it is the engine joining the feeder's job before teardown reaches this call. A submit that races a close anyway reads the cleared field under the lock and fails loudly instead of touching freed memory.
Tells the audio path that no more audio is coming.
Pushes the last of the decoded audio out of the DSP stages and into the ring.
Discards everything unplayed and invalidates the clock. This is the seek path.
Opens the device and sizes the ring for it.
Hands decoded audio over, suspending until all of it has been accepted.