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: audio in Info.plist
allowsPictureInPictureMediaPlayback is true (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
webkitSetPresentationMode reports success (event fires, no error) but the PIP window never renders
requestPictureInPicture() 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!
Selecting any option will automatically load the page