Improve autoplay handling after PiP and fullscreen on iOS

Adds logic to attempt autoplay when entering Picture-in-Picture or fullscreen modes, particularly to address autoplay restrictions on iOS. Introduces a flag to track if autoplay should be retried after loading, and ensures the UI updates appropriately if autoplay remains blocked.
This commit is contained in:
Pas 2025-11-09 13:48:28 -07:00
parent 3b5a1bb76e
commit c7dcec2560

View file

@ -96,6 +96,7 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
let lastVolume = 1;
let lastValidDuration = 0; // Store the last valid duration to prevent reset during source switches
let lastValidTime = 0; // Store the last valid time to prevent reset during source switches
let shouldAutoplayAfterLoad = false; // Flag to track if we should autoplay after loading completes
const languagePromises = new Map<
string,
@ -335,6 +336,15 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
isPictureInPicture = isInWebkitPip;
// Use native tracks in WebKit PiP mode for iOS compatibility
emit("needstrack", isInWebkitPip);
// On iOS, entering PiP may allow autoplay that was previously blocked
if (isInWebkitPip && videoElement.paused && shouldAutoplayAfterLoad) {
shouldAutoplayAfterLoad = false;
videoElement.play().catch(() => {
// If still blocked, emit pause to show play button
emit("pause", undefined);
});
}
}
function setSource() {
@ -356,7 +366,22 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
});
videoElement.addEventListener("playing", () => emit("play", undefined));
videoElement.addEventListener("pause", () => emit("pause", undefined));
videoElement.addEventListener("canplay", () => emit("loading", false));
videoElement.addEventListener("canplay", () => {
emit("loading", false);
// Attempt autoplay if this was an autoplay transition (startAt = 0)
if (shouldAutoplayAfterLoad && startAt === 0 && videoElement) {
shouldAutoplayAfterLoad = false; // Reset the flag
// Try to play - this will work on most platforms, but iOS may block it
const playPromise = videoElement.play();
if (playPromise !== undefined) {
playPromise.catch(() => {
// Play was blocked (likely iOS), emit that we're not playing
// The AutoPlayStart component will show a play button
emit("pause", undefined);
});
}
}
});
videoElement.addEventListener("waiting", () => emit("loading", true));
videoElement.addEventListener("volumechange", () =>
emit(
@ -465,6 +490,20 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
!!(document as any).webkitFullscreenElement; // safari
emit("fullscreen", isFullscreen);
if (!isFullscreen) emit("needstrack", false);
// On iOS, entering fullscreen may allow autoplay that was previously blocked
if (
isFullscreen &&
videoElement &&
videoElement.paused &&
shouldAutoplayAfterLoad
) {
shouldAutoplayAfterLoad = false;
videoElement.play().catch(() => {
// If still blocked, emit pause to show play button
emit("pause", undefined);
});
}
}
fscreen.addEventListener("fullscreenchange", fullscreenChange);
@ -472,6 +511,20 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
isPictureInPicture = !!document.pictureInPictureElement;
// Use native tracks in PiP mode for better compatibility with iOS and other platforms
emit("needstrack", isPictureInPicture);
// Entering PiP may allow autoplay that was previously blocked
if (
isPictureInPicture &&
videoElement &&
videoElement.paused &&
shouldAutoplayAfterLoad
) {
shouldAutoplayAfterLoad = false;
videoElement.play().catch(() => {
// If still blocked, emit pause to show play button
emit("pause", undefined);
});
}
}
document.addEventListener("enterpictureinpicture", pictureInPictureChange);
@ -502,6 +555,8 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
source = ops.source;
emit("loading", true);
startAt = ops.startAt;
// Set autoplay flag if starting from beginning (indicates autoplay transition)
shouldAutoplayAfterLoad = ops.startAt === 0;
setSource();
},
changeQuality(newAutomaticQuality, newPreferredQuality) {