I'm building an OTT video streaming app using Flutter and the video_player package. I want to implement Picture-in-Picture (PiP) on iOS.
Flutter doesn’t provide native PiP support for iOS directly, so I implemented it using Swift native code through a custom plugin.
The issue I’m facing is:
When I manually tap my custom PiP button, PiP starts successfully and works fine.
But when I put the app in the background (home button / swipe up) and try to start PiP automatically, PiP doesn’t appear — even though it seems to start internally.
This is my IosPipManager(swift)
import Foundation
import AVKit
import Flutter
public class IosPipManager: NSObject, FlutterPlugin {
private var pipController: AVPictureInPictureController?
private var playerLayer: AVPlayerLayer?
private var methodChannel: FlutterMethodChannel?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = IosPipManager()
let channel = FlutterMethodChannel(name: "ios_pip_manager", binaryMessenger: registrar.messenger())
instance.methodChannel = channel
registrar.addMethodCallDelegate(instance, channel: channel)
NotificationCenter.default.addObserver(
instance,
selector: #selector(instance.appDidEnterBackground),
name: UIApplication.didEnterBackgroundNotification,
object: nil
)
}
@objc private func appDidEnterBackground() {
guard let controller = pipController else {
print("❌ No PiP controller when entering background")
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if !controller.isPictureInPictureActive {
print("🎥 Starting PiP automatically on background")
controller.startPictureInPicture()
}
}
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "initializePip":
print("🎬 Initializing PiP from Flutter video_player")
guard let viewController = UIApplication.shared.delegate?.window??.rootViewController else {
result(FlutterError(code: "NO_VIEW", message: "No root view controller", details: nil))
return
}
if let playerLayer = findPlayerLayer(in: viewController.view.layer) {
print("✅ Found AVPlayerLayer from Flutter player")
self.playerLayer = playerLayer
self.pipController = AVPictureInPictureController(playerLayer: playerLayer)
result(true)
} else {
print("❌ Couldn’t find AVPlayerLayer from Flutter video player")
result(FlutterError(code: "NO_PLAYER_LAYER", message: "Couldn't find AVPlayerLayer from Flutter video player", details: nil))
}
case "startPip":
print("▶️ startPip called")
startPip(result: result)
case "stopPip":
print("⏹ stopPip called")
stopPip(result: result)
default:
result(FlutterMethodNotImplemented)
}
}
private func startPip(result: @escaping FlutterResult) {
guard let controller = pipController else {
result(FlutterError(code: "NOT_INITIALIZED", message: "PiP controller not initialized", details: nil))
return
}
if !controller.isPictureInPictureActive {
controller.startPictureInPicture()
}
result(true)
}
private func stopPip(result: @escaping FlutterResult) {
guard let controller = pipController else {
result(FlutterError(code: "NOT_INITIALIZED", message: "PiP controller not initialized", details: nil))
return
}
if controller.isPictureInPictureActive {
controller.stopPictureInPicture()
}
result(true)
}
private func findPlayerLayer(in layer: CALayer) -> AVPlayerLayer? {
if let playerLayer = layer as? AVPlayerLayer {
return playerLayer
}
for sublayer in layer.sublayers ?? [] {
if let found = findPlayerLayer(in: sublayer) {
return found
}
}
return nil
}
}
extension IosPipManager: AVPictureInPictureControllerDelegate {
public func pictureInPictureControllerDidStartPictureInPicture(\_ pictureInPictureController: AVPictureInPictureController) {
methodChannel?.invokeMethod("onPipStarted", arguments: nil)
}
public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
methodChannel?.invokeMethod("onPipStopped", arguments: nil)
}
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
methodChannel?.invokeMethod("onPipError", arguments: error.localizedDescription)
}
}
Akhil George
9701 gold badge10 silver badges33 bronze badges
New contributor
Rupam Das is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
-
For android some packages are there, but in ios no package is workingDibyendu– Dibyendu2025年10月16日 10:25:51 +00:00Commented yesterday
default