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,61 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
computeHandoffPosition,
|
||||
initialHandoffState,
|
||||
shouldEnterBackgroundAudio,
|
||||
shouldExitBackgroundAudio,
|
||||
type BackgroundAudioState,
|
||||
} from "./backgroundAudioHandoff";
|
||||
|
||||
// TRACES: UR-040 | DR-052 | UT-060
|
||||
|
||||
describe("backgroundAudioHandoff", () => {
|
||||
describe("computeHandoffPosition", () => {
|
||||
it("sums element time and transcode seekOffset (absolute position)", () => {
|
||||
// Transcoded HLS resets element time to 0 after a reload; seekOffset carries
|
||||
// the cumulative offset. The audio stream must resume at the absolute pos.
|
||||
expect(computeHandoffPosition(12, 180)).toBe(192);
|
||||
});
|
||||
|
||||
it("handles a direct stream with no offset", () => {
|
||||
expect(computeHandoffPosition(45, 0)).toBe(45);
|
||||
});
|
||||
|
||||
it("never returns a negative position", () => {
|
||||
expect(computeHandoffPosition(-5, 0)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldEnterBackgroundAudio", () => {
|
||||
it("enters when toggle is on and not already handed off", () => {
|
||||
expect(shouldEnterBackgroundAudio(true, initialHandoffState)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not enter when the toggle is off", () => {
|
||||
expect(shouldEnterBackgroundAudio(false, initialHandoffState)).toBe(false);
|
||||
});
|
||||
|
||||
it("does not double-enter when already active", () => {
|
||||
const active: BackgroundAudioState = { active: true, wasPlaying: true };
|
||||
expect(shouldEnterBackgroundAudio(true, active)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldExitBackgroundAudio", () => {
|
||||
it("exits when a handoff is active", () => {
|
||||
const active: BackgroundAudioState = { active: true, wasPlaying: false };
|
||||
expect(shouldExitBackgroundAudio(active)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not exit when no handoff happened", () => {
|
||||
expect(shouldExitBackgroundAudio(initialHandoffState)).toBe(false);
|
||||
});
|
||||
|
||||
it("exits even if the toggle was turned off while backgrounded", () => {
|
||||
// shouldExit ignores the toggle by design, so turning it off mid-background
|
||||
// still returns cleanly to video on foreground.
|
||||
const active: BackgroundAudioState = { active: true, wasPlaying: true };
|
||||
expect(shouldExitBackgroundAudio(active)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user