I'm building a Capacitor iOS app with a plain <video> element playing an MP4 file inline. I want Picture-in-Picture to activate automatically when the user goes home — swipe up from the bottom edge of the screen (on an iPhone with Face ID) or press the Home button (on an iPhone with a Home button).
Fullscreen → background works perfectly — iOS automatically enters Picture-in-Picture. But I need this to work from inline playback without requiring the user to enter fullscreen first.
Setup
<video id="video" playsinline autopictureinpicture controls
src="http://podcasts.apple.com/resources/462787156.mp4">
</video>
// AppDelegate.swift
let audioSession = AVAudioSession.sharedInstance()
try? audioSession.setCategory(.playback, mode: .moviePlayback)
try? audioSession.setActive(true)
UIBackgroundModes: audioin Info.plistallowsPictureInPictureMediaPlaybackistrue(Apple default)- iOS 26.3.1, WKWebView via Capacitor
What I've tried
1. autopictureinpicture attribute
<video playsinline autopictureinpicture ...>
WKWebView doesn't honor this attribute from inline playback. It only works when transitioning from fullscreen.
2. requestPictureInPicture() on visibilitychange
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden' && !video.paused) {
video.requestPictureInPicture();
}
});
Result: Fails with "not triggered by user activation". The visibilitychange event doesn't count as a user gesture.
3. webkitSetPresentationMode('picture-in-picture') on visibilitychange
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden' && !video.paused) {
video.webkitSetPresentationMode('picture-in-picture');
}
});
Result: No error thrown. The webkitpresentationmodechanged event fires with value picture-in-picture. But the PIP window never actually appears. The API silently accepts the call but nothing renders.
4. await play() then webkitSetPresentationMode
document.addEventListener('visibilitychange', async () => {
if (document.visibilityState === 'hidden') {
await video.play();
video.webkitSetPresentationMode('picture-in-picture');
}
});
Result: play() succeeds (audio resumes in background), but PIP still doesn't open.
5. Auto-resume on system pause + PIP on visibilitychange
iOS fires pause before visibilitychange when backgrounding. I tried resuming in the pause handler, then requesting PIP in visibilitychange:
video.addEventListener('pause', () => {
if (document.visibilityState === 'hidden') {
video.play(); // auto-resume
}
});
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden' && !video.paused) {
video.webkitSetPresentationMode('picture-in-picture');
}
});
Result: Audio resumes successfully, but PIP still doesn't open.
6. Native JS eval from applicationDidEnterBackground
func applicationDidEnterBackground(_ application: UIApplication) {
webView?.evaluateJavaScript(
"document.querySelector('video').requestPictureInPicture()"
)
}
Result: Same failure — no user activation context.
Observations
- The event order on background is:
pause→visibility: hidden webkitSetPresentationModereports success (event fires, no error) but the PIP window never rendersrequestPictureInPicture()consistently requires user activation, even from native JS eval- Audio can be resumed in background via
play(), but PIP is a separate gate - Fullscreen → background automatically enters Picture-in-Picture, confirming the WKWebView PIP infrastructure is functional
Question
Is there any way to programmatically enter PIP from inline playback when a WKWebView app goes to background? Or is this intentionally restricted by WebKit to fullscreen-only transitions?
Any pointers appreciated. Thanks!