iOS 自定义视频播放器实战:全屏旋转 + 画中画(PiP)+ 多页面切换不中断播放

在很多视频类 App 中,我们都能看到这样一套体验:

  • 视频支持内嵌播放 + 自定义控制栏

  • 一键横屏全屏,并且正确处理状态栏 / Home Indicator

  • 页面切换时,视频自动进入 画中画(PiP)

  • 点击 PiP 恢复按钮,可以自动回到原播放器页面

本文将完整拆解一套 基于 AVPlayer + AVPictureInPictureController 的自定义播放器实现方案 ,并给出可直接复用的架构思路


一、整体架构设计

1️⃣ 页面结构

复制代码
UIWindow
 └── BaseNavigationController
      ├── ViewController(播放器页)
      │    └── HLPlayerView(自定义播放器 View)
      ├── ListViewController
      └── ProfileViewController

核心思想是:

  • 播放器是 View,不是 VC

  • 旋转 / 状态栏 / PiP 恢复 由 VC 统一处理

  • 播放器只负责播放 & UI,不碰系统层逻辑


1️⃣ SceneDelegate 设置根控制器

复制代码
BaseNavigationController *nav =
    [[BaseNavigationController alloc] initWithRootViewController:[ViewController new]];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];

2️⃣ 让状态栏控制权交给顶层 VC

复制代码
- (UIViewController *)childViewControllerForStatusBarHidden {
    return self.topViewController;
}

- (UIViewController *)childViewControllerForStatusBarStyle {
    return self.topViewController;
}

⚠️ 这是自定义全屏播放器必须做的一步

否则 prefersStatusBarHidden 不会生效。


三、播放器页(ViewController)职责划分

主要职责

  • 管理播放器的 全屏 / 竖屏状态

  • 处理 系统旋转

  • 控制 状态栏 & Home Indicator

  • 处理 PiP 恢复 UI 的跳转

关键属性

复制代码
@property (nonatomic, assign) BOOL isFullScreen;
@property (nonatomic, assign) BOOL isStatusHidden;
@property (nonatomic, assign) CGRect portraitFrame;

四、全屏旋转的正确姿势(iOS 16+ & 兼容方案)

1️⃣ 点击全屏按钮 → 通知 VC

复制代码
[self.delegate hlPlayerView:self didTapFullScreenButton:self.isFullScreen];

2️⃣ VC 主动请求系统旋转

复制代码
- (void)rotateToOrientation:(UIInterfaceOrientationMask)orientation {
    if (@available(iOS 16.0, *)) {
        UIWindowScene *windowScene = self.view.window.windowScene;
        UIWindowSceneGeometryPreferencesIOS *preferences =
            [[UIWindowSceneGeometryPreferencesIOS alloc]
             initWithInterfaceOrientations:orientation];
        [windowScene requestGeometryUpdateWithPreferences:preferences errorHandler:nil];
    } else {
        [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
                                    forKey:@"orientation"];
        [UIViewController attemptRotationToDeviceOrientation];
    }
}

3️⃣ 必须重写的系统方法

复制代码
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return self.isFullScreen ?
           UIInterfaceOrientationMaskLandscapeRight :
           UIInterfaceOrientationMaskPortrait;
}

- (BOOL)prefersStatusBarHidden {
    return self.isFullScreen;
}

- (BOOL)prefersHomeIndicatorAutoHidden {
    return self.isFullScreen;
}

五、播放器 View(HLPlayerView)设计要点

1️⃣ 核心组件

  • AVPlayer

  • AVPlayerLayer

  • AVPictureInPictureController

    self.player = [AVPlayer playerWithURL:url];
    self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
    [self.layer addSublayer:self.playerLayer];


2️⃣ 自定义控制栏(不使用 AVPlayerViewController)

  • 播放 / 暂停

  • 进度条

  • 时间显示

  • PiP 按钮

  • 全屏按钮

    self.controlView.alpha = self.controlsHidden ? 0 : 1;

并配合 3 秒自动隐藏


六、进度 & 时间同步

复制代码
[self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1, NSEC_PER_SEC)
                                         queue:dispatch_get_main_queue()
                                    usingBlock:^(CMTime time) {
    float current = CMTimeGetSeconds(time);
    float total = CMTimeGetSeconds(self.player.currentItem.duration);
    self.progressSlider.value = current / total;
}];

七、画中画(PiP)完整闭环方案

1️⃣ 初始化 PiP

复制代码
self.pipController =
    [[AVPictureInPictureController alloc] initWithPlayerLayer:self.playerLayer];
self.pipController.canStartPictureInPictureAutomaticallyFromInline = YES;

2️⃣ 页面切换时自动进入 PiP

复制代码
if ([self.player isPictureInPicturePossible] &&
    ![self.player isPictureInPictureActive]) {
    [self.player startPictureInPicture];
}

3️⃣ 点击 PiP 恢复按钮 → 自动回到播放器页

复制代码
- (void)pictureInPictureController:
    (AVPictureInPictureController *)controller
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:
    (void (^)(BOOL))completionHandler {

    [self.delegate hlPlayerViewDidRequestRestoreUserInterface:self];
    completionHandler(YES);
}

- (void)hlPlayerViewDidRequestRestoreUserInterface:(HLPlayerView *)playerView {
    [self.navigationController popToViewController:self animated:YES];
}

八、多 VC 切换不中断播放的关键点

✅ 播放器 不销毁

✅ 使用 PiP 托管播放

✅ VC 只做页面跳转,不持有播放逻辑

最终效果:

  • 视频在 List / Profile 页面依然播放

  • PiP 点击恢复,准确回到播放器页

  • 不闪屏、不重建播放器


九、适用场景

这套方案非常适合:

  • 🎬 短视频 / 长视频 App

  • 📺 直播播放器

  • 📚 教育 / 课程播放

  • 🎵 音视频 SDK 封装


相关推荐
用户405383693515 小时前
开源语音识别FunASR入门详解
ide·macos·xcode
开开心心就好21 小时前
免费抽奖工具支持批量导入+自定义主题
linux·运维·服务器·macos·pdf·phpstorm·1024程序员节
weixin_462446231 天前
在 Linux / macOS 下使用 Docker 快速部署 PaddlePaddle + 运行 PaddleOCR 表格 PDF 解析示例
linux·macos·docker·paddleocr
TheNextByte11 天前
如何在恢复模式下从 iPhone 恢复照片?
ios·cocoa·iphone
TheNextByte11 天前
【已修复】由于软件版本过旧,无法将备份恢复到此 iPhone
ios·cocoa·iphone
新缸中之脑1 天前
Clawdbot安装:VPS vs.Mac Mini
macos
2501_916007472 天前
不越狱如何查看iOS 应用的详细信息及其文件目录结构
android·macos·ios·小程序·uni-app·cocoa·iphone
芒鸽2 天前
macos上Rust 命令行工具鸿蒙化适配完全攻略
macos·rust·harmonyos
山有木兮啊2 天前
VSCode Remote-SSH 连接Mac卡在初始化VSCode
vscode·macos·ssh
一个写bug的程序员2 天前
Mac自启服务关闭方式
macos