AVKit框架详细解析(二) —— 基于视频播放器的画中画实现(一)

版本记录

版本号 时间
V1.0 2021.08.14 星期六

前言

AVKit框架为媒体播放创建视图级别的服务,包含用户控件,章节导航以及对字幕和隐藏式字幕的支持。接下来几篇我们就一起看一下这个框架。感兴趣的可以看下面几篇文章。
1. AVKit框架详细解析(一) —— 基本概览(一)

开始

首先看下主要内容:

了解如何为所有应用平台的默认和自定义视频播放器实现画中画,内容来自翻译

接着看下写作环境:

Swift 5, iOS 14, Xcode 12

下面就是正文了。

如今,用户希望能够使用Picture in Picture (PiP) - 画中画播放视频。 PiP 模式将视频内容最小化到一个小窗口中,允许用户进行多任务处理。 在本教程中,您将学习如何向使用 UIKit 构建的现有视频应用程序添加画中画支持。

具体来说,您将了解:

  • Background modes
  • 设置 AVAudioSession
  • 控制画中画显示
  • PIP与自定义播放器控制器结合使用

本教程使用iPhone,但示例应用程序是跨平台的,也适用于tvOSmacOSPiPAVKit 的一部分,可在所有平台上使用。

您需要一个物理设备来学习本教程。 如果您没有可用的 iPhone、iPad 或 Apple TV,您可以使用 Mac 使用 Xcode 中的 My Mac target来测试画中画功能。

下载项目材料。

构建并运行启动项目:RickTV 应用程序。

RickTV 有各种各样的内容,但出于某种原因,无论您选择什么视频,都只会播放 Rick AstleyNever Gonna Give You Up。 该死的那些互联网巨魔。

行。 是时候学习如何在PiP中观看 RickTV


Adding Background Modes

要在您的应用程序中启用画中画功能,您需要添加Background Modes功能。

在项目导航器中单击 RickTV 项目,然后单击Signing & Capabilities

注意:对 RickTV target执行以下步骤时,Xcode 可能会崩溃。 如果发生这种情况,只需重新启动它。

您需要为 RickTVRickTV-iOStargetss重复以下步骤:

  • 1) 选择 RickTVRickTV-iOS target
  • 2) 单击 + Capabilit
  • 3) 搜索Background Modes,然后双击将其添加为功能。
  • 4) 在新添加的Background Modes部分,选中Audio, AirPlay, and Picture in Picture复选框。

很好! 现在您已经设置了所有内容,您可以在您的应用程序中实现画中画。


Implementing PiP

打开 AppDelegate.swift

AppDelegate 内的 application(_:didFinishLaunchingWithOptions:) 中,添加以下代码:

let audioSession = AVAudioSession.sharedInstance()

在上面的代码中,您引用了 AVAudioSession 的共享实例。

接下来,将以下内容添加到您在上一步中添加的代码中:

do {
  try audioSession.setCategory(.playback, mode: .moviePlayback)
} catch {
  print("Failed to set audioSession category to playback")
}

通过这样做,您将音频会话的类别设置为 .playback,将播放模式设置为 .moviePlayback。 此操作可能会失败,因此您将其包装在 do catch 块中。

构建并运行。 播放视频,您将在播放器控制器中看到画中画图标。

成功! 点按画中画图标以查看它是否有效。

你已经看到,如果你使用标准的 AVPlayerViewController,画中画几乎是自动的。 如果您的应用程序具有自定义播放控制器,则您需要做一些额外的工作来支持画中画。 接下来您将了解这一点。


Enabling PiP in a Custom Player Controller

你很幸运——示例项目有一个内置的自定义播放器控制器。 要使用它而不是默认的 AVPlayerViewController,您需要更改点击视频调用的代码行。

打开 CategoryListViewController.swift 并滚动到标有注释的 UICollectionViewDataSourceImplementation部分。

collectionView(_:didSelectItemAt:)的最后一行是呈现播放器控制器的方法:

presentPlayerController(with: player, customPlayer: false)

customPlayer 更改为 true 以使用自定义播放器控制器。

构建并运行。 点击视频以显示自定义播放器控制器。

很好! 视频在自定义控制器中播放。 但是……如果您点击画中画按钮,则什么也不会发生。 别担心,你现在会解决这个问题的。

打开 CustomPlayerViewController.swift。 在 viewDidLoad()中,在 view.layer.addSublayer(playerLayer)下,添加以下代码:

pictureInPictureController = AVPictureInPictureController(
  playerLayer: playerLayer)
pictureInPictureController?.delegate = self

此代码初始化pictureInPictureController 并设置其代理。

接下来,您将添加功能,以便您的用户可以在自定义播放器控制器中启动和停止画中画。

1. Starting and Stopping PiP

要允许您的用户停止和启动 PiP 模式,请转到实现 CustomPlayerControlsViewDelegateCustomPlayerViewController 的扩展。

你会看到两个相关的方法:controlsViewDidRequestStartPictureInPicture(_:)controlsViewDidRequestStopPictureInPicture(_:)

controlsViewDidRequestStartPictureInPicture(_:)中,将// Start PiP 替换为:

pictureInPictureController?.startPictureInPicture()

然后,在 controlViewDidRequestStopPictureInPicture(_:) 中,将// Stop PiP替换为:

pictureInPictureController?.stopPictureInPicture()

当用户点击适当的按钮时,这些方法告诉画中画控制器启动或停止画中画。

确保仅在收到用户输入时调用关联的 AVPictureInPictureController 方法。 如果您违反此规则,App Review 将不会批准您的应用!

构建并运行。 打开视频并点击按钮以启动画中画。

太棒了! PiP 开始在自定义控制器中播放,但您还没有完成。如果用户选择播放视频画中画,可以合理地假设他们不希望您的应用程序的屏幕显示有关视频现在如何播放画中画的大量信息。他们可能想继续使用您的应用程序的其余部分。此外,如果您点击按钮从画中画返回标准播放,则不会发生任何事情。接下来您将解决这些问题中的第一个。


Dismissing the Custom Player Controller When PiP Starts

当用户启动画中画时,您可以假设这是因为他们想在继续欣赏视频的同时在您的应用程序中执行其他操作。目前,当视频在画中画窗口中播放时,示例应用程序会显示一条消息。您可以使用画中画控制器代理中的方法来控制画中画播放开始和结束时发生的情况。

CustomPlayerViewController.swift 中,滚动到标有 AVPictureInPictureDelegate 的扩展。代理方法都带有空实现,以节省您的输入时间!

首先,在pictureInPictureControllerDidStartPictureInPicture(_:)中,添加以下代码:

dismiss(animated: true, completion: nil)

在这里,您可以在画中画启动时关闭自定义播放器控制器。 但是,如果您构建并运行并尝试此操作,您将看到画中画窗口立即关闭。 这是因为您的自定义播放器对象被释放,这是唯一保留画中画控制器的东西,因此也被释放。 为了防止这种情况,将以下代码添加到 pictureInPictureControllerWillStartPictureInPicture(_:)

activeCustomPlayerViewControllers.insert(self)

activeCustomPlayerViewControllers 是一个全局 Set,它将您的播放器对象保存在内存中,这意味着您可以安全地关闭它。

如果画中画控制器出现故障或被用户关闭,您需要清理它。

1. Handling PiP controller failure and closing

当用户使用关闭按钮关闭画中画或画中画模式失败时,您需要从活动控制器集中删除自定义播放器控制器。

pictureInPictureController(_:failedToStartPictureInPictureWithError:)中,添加以下代码:

activeCustomPlayerViewControllers.remove(self)

这会在画中画无法启动时从活动控制器集中删除自定义控制器。

接下来,在pictureInPictureControllerDidStopPictureInPicture(_:) 中,写入同一行:

activeCustomPlayerViewControllers.remove(self)

这与上面的工作相同,但在用户关闭画中画窗口时调用。

现在,构建并运行。 播放视频并进入画中画模式。

现在启动画中画会关闭自定义播放器控制器,并关闭画中画窗口。 但是,如果您点按按钮以从画中画返回标准全屏播放,继续播放相同的视频,则没有任何反应。 你现在会处理这个问题。


Restoring the Player Controller

现在,当您开始以画中画模式播放视频时,您可以完全关闭窗口,但无法返回全屏。 这对于默认的 AVPlayerViewController 和自定义播放器控制器都是如此。 要摆脱困境,您需要添加播放器控制器恢复功能。

CustomPlayerViewController.swiftpictureInPictureController(_:restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:)中,插入以下代码:

delegate?.playerViewController(
  self,
  restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: 
    completionHandler)

CustomPlayerViewController 有一个代理,它反映了 AVPlayerViewControllerDelegate 中包含的许多方法。 您在此处调用的方法等效于当用户请求从画中画返回标准播放时标准播放器将调用的方法。

现在打开 CategoryListViewController.swift。 在文件的底部,你会看到一个类的扩展,它有一个方法:restore(playerViewController:completionHandler:)

对于这两种类型的播放器控制器,当用户在画中画窗口中点击Restore时,代理扩展会调用此方法。

在方法内部,添加以下代码:

// 1
if let presentedViewController = presentedViewController {
  // 2
  presentedViewController.dismiss(animated: false) { [weak self] in
    // 3
    self?.present(playerViewController, animated: false) {
      completionHandler(true)
    }
  }
} else {
  // 4
  present(playerViewController, animated: false) {
    completionHandler(true)
  }
}

下面是上面代码中发生的事情:

  • 1) 检查是否已经存在任何其他视图控制器。 也许您的用户正在同时观看两个视频,它们的效果如何!
  • 2) 如果有一个展示的控制器,在没有动画的情况下关闭它,因为用户希望尽快让他们的视频恢复正常并且对任何视图控制器动画不感兴趣。
  • 3) 一旦关闭完成,呈现原始播放器控制器,再次没有动画,然后调用completion block,以便系统知道将回放手动返回到原始播放器层。
  • 4) 如果没有展示控制器,只需再次呈现原始控制器并调用completion block

构建并运行。

上面的 GIF显示了两个代码路径:

  • 1) 进入画中画然后恢复继续全屏显示画中画视频。
  • 2)进入画中画,开始第二个视频,然后恢复画中画会用画中画内容替换全屏视频。

要使用 AVPlayerViewController 而不是自定义播放器控制器来测试画中画,请修改 CategoryListViewControllercollectionView(_:didSelectItemAt:) 最后一行中的 customPlayer,将其更改为 false

presentPlayerController(with: player, customPlayer: false)

这将显示系统播放器控制器而不是您的控制器,您可以看到相同的播放器恢复行为也有效。

要了解有关画中画的更多信息,请查看 WWDC 2020 的 Master Picture in Picture on tvOS

您还可以了解有关 AVKit 的更多信息learn more about AVKit,它支持 Apple 平台上的视频播放。

后记

本篇主要讲述了基于视频播放器的画中画实现,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容