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.
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|