fix(player): restart the native renderer when returning from background audio
With native video on, coming back from background audio left a black screen: a play overlay pinned at 0:00, a seek bar at zero, and a play button that did nothing. Nothing crashed — the process stayed up and the frontend kept logging — the transition was simply dropped. The two render paths resume by different means, and exitBackgroundAudioHandoff only ever performed one of them. The webview <video> reloads off its stream URL: an $effect watches it, reinitialises HLS or sets element.src, and canplay drives the seek and play. ExoPlayer owns no element and nothing watches the URL on its behalf — native playback is only ever started by an explicit player_play_item plus adapter load, which the component issues once, from onMount. So reassigning the URL restarted precisely nothing, and since player_exit_background_audio had already stopped the handoff's audio player, the backend came back holding no item at all. That is why the play button was inert: there was nothing loaded to play. The return now re-issues that pair on the native path, in the same order as the initial load, carrying the position the audio reached. Subtitle configurations are reused from the ones resolved at mount — ExoPlayer sideloads them as MediaItem.SubtitleConfigurations and cannot accept one after prepare(). Which path to take is decided by planHandoffReturn, a pure helper in backgroundAudioHandoff.ts, so the branch is unit-testable without mounting the player. It also folds in shouldResumeOnForeground, so a pause taken on the lockscreen during the handoff still wins over the snapshot captured on the way out. Verified on device (HONOR ROD2-W09, Android 16): handoff to audio-only at 69:54, return restored native video playing at 70:18. Previously the same sequence left the player idle and black. The requirements count pin in extract-traces.test.ts moves with the new DR-196.
This commit is contained in:
@@ -74,3 +74,50 @@ export function shouldResumeOnForeground(
|
||||
): boolean {
|
||||
return wasPlaying && nativeStateKind !== "paused";
|
||||
}
|
||||
|
||||
/** What has to be restarted to put picture back on screen, and how. */
|
||||
export interface HandoffReturn {
|
||||
/** Which renderer must be brought back. */
|
||||
target: "html5-element" | "native-backend";
|
||||
/** Absolute position the background audio reached. */
|
||||
position: number;
|
||||
/** Whether playback should be running once it is back. */
|
||||
shouldPlay: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* How to come back when the app returns to the foreground.
|
||||
*
|
||||
* The two render paths resume by completely different means, and conflating
|
||||
* them is what broke the native one:
|
||||
*
|
||||
* - **html5-element** — assigning the stream URL is enough. An `$effect` in the
|
||||
* component watches it, (re)initialises HLS or sets `videoElement.src`, and
|
||||
* `canplay` then drives the seek and play.
|
||||
* - **native-backend** — ExoPlayer owns no element, and nothing reacts to the
|
||||
* stream URL on its behalf. Native playback is only ever started by an
|
||||
* explicit backend load, which the component issues once, from `onMount`. So
|
||||
* the return has to re-issue it; reassigning the URL restarts nothing.
|
||||
*
|
||||
* The component previously did only the URL assignment, for both paths. On the
|
||||
* native path that left the backend holding no item at all: a black screen with
|
||||
* a play overlay, a play button that did nothing, and the position pinned at
|
||||
* 0:00 — the handoff's own audio player having been stopped on the way out.
|
||||
*
|
||||
* `shouldPlay` folds in [shouldResumeOnForeground], so a lockscreen pause during
|
||||
* the handoff still wins over the snapshot taken on the way out.
|
||||
*
|
||||
* TRACES: UR-040, UR-003 | DR-196 | UT-060
|
||||
*/
|
||||
export function planHandoffReturn(opts: {
|
||||
useHtml5Element: boolean;
|
||||
position: number;
|
||||
wasPlaying: boolean;
|
||||
nativeStateKind: string | undefined;
|
||||
}): HandoffReturn {
|
||||
return {
|
||||
target: opts.useHtml5Element ? "html5-element" : "native-backend",
|
||||
position: opts.position > 0 ? opts.position : 0,
|
||||
shouldPlay: shouldResumeOnForeground(opts.wasPlaying, opts.nativeStateKind),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user