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 封装


相关推荐
洪大宇2 小时前
Mac 使用蓝牙功能
macos
Benny的老巢12 小时前
Mac上用XAMPP搭建局域网可访问的开发环境,让局域网内其他设备通过域名访问
nginx·macos·apache·xampp·php开发环境
2501_9418814020 小时前
在墨西哥城复杂流量环境下构建高稳定性API网关的架构设计与实现实践分享
macos·golang·xcode
wadesir20 小时前
macOS Sequoia与macOS Tahoe全面对比:功能、性能与升级教程(新手入门指南)
macos
FreeBuf_1 天前
新型TCC绕过漏洞:macOS面临自动化攻击风险
macos·自动化·策略模式
Alice1 天前
Remote control Mac ios
macos
huaiyanchen1 天前
mac Navicat 下载及安装
macos
梁辰兴1 天前
计算机网络基础:MAC 地址
计算机网络·macos·计算机·mac地址·计算机网络基础·梁辰兴·物理地址
酒书2 天前
mac电脑idea更改git用户名和密码
git·macos·intellij-idea