feat(pip): add Picture-in-Picture support for Android and iOS#995
Conversation
|
Found a small thing I missed - when minimized and then home is pressed it's not getting into pip, just exit. I'll be able to tackle it on Sunday |
|
This looks like an awesome feature. I'm looking forward to seeing this live! |
I'm using this PR version for a while now without issues, and it's really the thing I missed on the official version (watching mainly on the road with some context switch while watching) |
PartyDonut
left a comment
There was a problem hiding this comment.
Thanks for having a go at implementing this, looks and works great. One small change request, after that we can merge it.
Adds the long-requested PiP feature for the Flutter-side video players (libMPV/libMDK). Native ExoPlayer activity is out of scope. Behavior: - The PiP icon button in the player controls is always visible on Android/iOS and lets users manually enter PiP at any time. - A new 'Auto Picture-in-Picture' setting (Settings → Player) controls only whether the OS should also auto-enter PiP when the app is backgrounded from the player. Defaults to on. - While in PiP, the controls overlay hides so only the video surface is captured for the PiP window. - Lifecycle: on player dispose, auto-enter is turned off so the user is not auto-PiP'd when backgrounding from unrelated screens. Architecture: - New PipManager wrapper (lib/wrappers/pip_manager.dart) hides package:pip behind a narrow PipClient interface so it can be unit-tested without the native plugin. Exposes enable/enter/disable/ dispose and a Stream<bool> for the current PiP state. 9 unit tests. - Riverpod providers (lib/providers/pip_provider.dart): pipManagerProvider and pipStateProvider. - VideoPlayer integration listens to the auto-enter preference and re-applies it live if the user toggles the setting mid-playback. Platform plumbing: - pubspec: add pip ^0.0.3. - Android manifest: supportsPictureInPicture=true on MainActivity. configChanges already covers screenSize|smallestScreenSize|screenLayout. - iOS Info.plist: add 'audio' to UIBackgroundModes (required for iOS PiP per package:pip docs). Caveats: - PiP aspect ratio is hardcoded 16:9. PlayerState does not currently expose video width/height; plumbing real ratios through every backend is deferred. - The native player (separate VideoPlayerActivity) already has supportsPictureInPicture in its manifest but no logic — out of scope. Closes DonutWare#494.
Move PiP setup/teardown out of the fullscreen VideoPlayer state into a top-level PipLifecycleController driven by mediaPlaybackProvider.state. Previously, minimizing the player ran disable() and the mini-player never re-enabled auto-enter, so pressing home from mini-mode exited the app instead of entering PiP. While in PiP from minimized state, swap the app tree for a fullscreen video + subtitles on black so Android captures just the video rather than the home screen and mini-player chrome.
PartyDonut
left a comment
There was a problem hiding this comment.
Looks good to me thanks for the quick fixes
Thank you for this amazing app! It replaced all my previous android & tv & desktop apps for Jellyfin! When should we expect a new version? |
Thanks good to hear you like the application 😄 Release will be in the next few days probably, depends on how quickly I can fix the small remaining issues with the music player and offline playback |
Adds Picture-in-Picture (PiP) support for the Flutter-rendered video players (
libMPV/libMDK) on Android, with the iOS plumbing in place but untested. The standalone native ExoPlayerVideoPlayerActivityis intentionally out of scope.Behavior
Architecture
lib/wrappers/pip_manager.dart— newPipManagerwrapper hidespackage:pipbehind a narrowPipClientinterface so it can be unit-tested without the native plugin. Exposesenable/enter/disable/disposeand aStream<bool>for the current PiP state. 9 unit tests.lib/providers/pip_provider.dart—pipManagerProvider+pipStateProvider.VideoPlayerintegration listens to the auto-enter preference and re-applies it live if the user toggles the setting mid-playback.Platform plumbing
pubspec.yaml: addpip: ^0.0.3(only file inlib/that importspackage:pipis the wrapper).android/app/src/main/AndroidManifest.xml: addandroid:supportsPictureInPicture="true"toMainActivity.configChangesalready coversscreenSize|smallestScreenSize|screenLayout.ios/Runner/Info.plist: addaudiotoUIBackgroundModes(required for iOS PiP perpackage:pipdocs).Decisions taken during implementation
…more button, landscape-only — phone portrait was overflowing the row (RIGHT OVERFLOWED BY 46 PIXELS). Portrait users access PiP via the Home button + the auto-enter setting.Icons.picture_in_picture_alt_outlined(rather than an IconsaxPlusLinear equivalent) — matches the de-facto Flutter standard used by other video apps (frosty, mydia) and YouTube/Netflix; the universal "rectangle-with-corner-rectangle" glyph is more discoverable than a generic maximize icon.PlayerStatedoes not currently expose video width/height. Plumbing real ratios through every backend was deferred to keep the PR focused.VideoPlayerActivity(ExoPlayer/Compose) already hassupportsPictureInPicturein its manifest but no logic; wiring that needs a separate Kotlin-side change and would have doubled the PR's surface area.Never / Always / Only with headphonesfrom the original discussion. Smaller surface, easier to maintain; can be expanded later if there's demand.Caveats & known limitations
Info.plistchange is in, but I do not have an iOS dev environment. The flow should work perpackage:pipdocs but should be validated by someone with an iPhone before being treated as supported.Implementation notes
This PR was implemented with the help of an AI coding assistant (Claude Code). All decisions, UX trade-offs, and code review were human-driven; the AI handled mechanical edits and the wrapper/test scaffolding. Generated code follows the project's existing conventions (
dart format --line-length 120, sparse comments, freezed for settings).Issue Being Fixed
Picture-in-Picture has been requested multiple times across several discussions. This PR addresses the mobile (Android) portion of those asks.
enableDoubleTapSeeksetting). The PiP part — assumed mobile based on context — is addressed.Screenshots / Recordings
Tested On
Checklist
pip ^0.0.3is a 0.0.x release with limited maintenance history; flagging as a known trade-off. Cross-platform (Android/iOS) by design. Open to swapping for a more mature alternative if maintainers prefer.