Background-audio handoff for video + repository/player refactor
Hand video playback off to a native audio-only stream when the app is backgrounded or locked, with no on-device video decode (UR-040). Adds player_enter/exit_background_audio commands, an audio-only stream URL for video items across the repository layer, and the frontend handoff state machine wired into VideoPlayer. Includes accompanying repository/offline/player refactoring and regenerates the traceability matrix.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Pure helpers for the video → background-audio handoff (UR-040).
|
||||
*
|
||||
* TRACES: UR-040 | DR-052 | UT-060
|
||||
*
|
||||
* Kept free of Svelte/DOM so the handoff arithmetic and state transitions are
|
||||
* unit-testable without mounting the player. The component
|
||||
* (VideoPlayer.svelte) owns the actual `<video>` teardown and IPC calls.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Absolute playback position to resume the audio stream at.
|
||||
*
|
||||
* Transcoded HLS playback tracks time as `videoElement.currentTime + seekOffset`
|
||||
* (the element resets to 0 after each transcode reload; `seekOffset` carries the
|
||||
* cumulative offset). Background audio must resume at that ABSOLUTE position, so
|
||||
* both terms are summed here — mirroring the `effectiveTime` used elsewhere in
|
||||
* the player.
|
||||
*/
|
||||
export function computeHandoffPosition(elementCurrentTime: number, seekOffset: number): number {
|
||||
const pos = elementCurrentTime + seekOffset;
|
||||
return pos > 0 ? pos : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The handoff state. `wasPlaying` is captured on the way out so play/pause is
|
||||
* restored when the app returns to the foreground.
|
||||
*/
|
||||
export interface BackgroundAudioState {
|
||||
active: boolean;
|
||||
wasPlaying: boolean;
|
||||
}
|
||||
|
||||
export const initialHandoffState: BackgroundAudioState = {
|
||||
active: false,
|
||||
wasPlaying: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether a background signal should trigger the audio handoff right now.
|
||||
* Only when the toggle is on and we're not already handed off.
|
||||
*/
|
||||
export function shouldEnterBackgroundAudio(
|
||||
toggleOn: boolean,
|
||||
state: BackgroundAudioState
|
||||
): boolean {
|
||||
return toggleOn && !state.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a foreground signal should trigger the return to WebView video.
|
||||
* Only when we actually handed off (regardless of the current toggle value, so
|
||||
* turning the toggle off while backgrounded still returns cleanly).
|
||||
*/
|
||||
export function shouldExitBackgroundAudio(state: BackgroundAudioState): boolean {
|
||||
return state.active;
|
||||
}
|
||||
Reference in New Issue
Block a user