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


相关推荐
带娃的IT创业者11 小时前
深度解析:当 MLX 遇上视觉语言模型,Mac 本地推理的新范式
人工智能·macos·语言模型·视觉语言模型·apple silicon·mlx·mac本地推理
wjm0410061 天前
ios内存管理
ios·objective-c·swift·客户端开发
云原生指北1 天前
Apple Container Machine:把 Linux 搬进 Mac
macos·docker
星栈独行1 天前
Rust + Makepad 应用怎么打包发布:Windows、macOS、Linux 全平台交付
windows·程序人生·macos·ui·rust
daly5201 天前
PyCharm怎么下载?2026最新版PyCharm安装教程(Windows/macOS/Linux)
windows·macos·pycharm
音视频牛哥1 天前
iOS如何实现RTSP/RTMP低延迟播放?SmartMediaKit播放器集成说明
objective-c·低延迟rtsp播放器·低延迟rtmp播放器·ios rtmp player·ios rtsp player·ios平台rtsp播放器·ios平台rtmp播放器
元媛媛2 天前
如何安装Claude Code|VS Code Mac版
macos
2601_961845422 天前
法考真题及答案解析|历年真题|资料已整理
linux·windows·ubuntu·macos·centos·gnu
游戏开发爱好者82 天前
iPhone真机调试有哪些方法?一次定位推送权限问题时整理出来的几种方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
Allen Su2 天前
【Mac 教程系列第 20 篇】macOS 鼠须管(Squirrel)皮肤大全(持续更新)
macos·rime·squirrel·rime 输入法皮肤大全