0x00 简介
ReplayKit 可以做什么?
-
支持手机屏幕录屏,并推流到到第三方直播服务
-
直接从设备上直播音视频(屏幕+系统播放的声音)
-
支持使用麦克风和摄像头进行解说
-
内容是安全的,整个过程只会推到第三方直播服务
开一些脑洞?
-
直播游戏到 Mobcrush 或者 Youtube 等平台,国内的映客,花椒等也支持
-
使用 WebEx 进行网络会议的时候,同步桌面显示
-
使用 TeamViewerQS 进行客户服务
-
直播一个画画的 app 到 Facebook
-
可以通过录屏直播,远程实时教父母使用手机
ReplyKit VS ReplayKit 2
先回顾一下 ReplayKit 的交互流程: 首先 API 从 iOS 10 开始提供。假设你有个游戏 app,想让用户在玩游戏的同时,可以直播到某个第三方直播平台上,比如国外的 Mobcrush 或者 Twitch 等。
首先你的 app 需要做下面几件事情:
-
实现 开始/暂停/停止直播 等 UI 交互
-
使用 ReplayKit 实现 开始/暂停/停止直播 等业务逻辑
第三方直播平台需要做的:
-
提供登录,设置直播标题等UI (Broadcast UI extension)
-
实现推流逻辑,把屏幕和音频等传输到服务器(Broadcast Upload extension)
用户开启直播流程是:
用户进入到你的游戏 app,点击 app 内提供的录制按钮,然后会弹出选择直播平台的提示。选择之后,会显示第三方 app UI extension 提供的页面,进行登录和设置直播标题等设置。完成之后,ReplayKit 开始录屏,并把音视频的 sample 交给第三方 app 的 upload extension 进行编码推流。
因为用户必须要到你的 app 才能开始直播,所以也被称为 In-App Broadcast
很明显,In-App Broadcast 有个缺陷是,如果用户离开了 app,比如退到后台或者切换到别的 app,直播就暂停了。
ReplayKit 2 的流程
相对于 ReplayKit,ReplayKit 2 用户是从控制中心发起的直播(就是录屏的那个按钮,重按可以选择直播到第三方直播平台)。后面的流程对于开发者来说和 ReplayKit 差不多。因此也称为 iOS System Broadcast。ReplayKit 2 需要 iOS 11+.
ReplayKit 2 有几个优势:
-
用户可以直接从控制中心开启和停止直播,是系统级别的操作,和 app 无关,用户可以无缝跨 app 进行录屏直播,不用担心退后台或者切换 app 的场景。
-
你的 app 不需要提供 app 的 UI 交互了。也就是说,相对于 ReplayKit,你可以不做任何事情,就让你的 app 支持直播到第三方平台。
iOS 12 ReplayKit 有什么更新?
呵呵。。一图胜千言,相信不用我多说了吧。。不过既然可以从自己 app 发起,那会像 ReplayKit 1 里面自己提供自定义的开始/停止直播按钮,并控制直播开始停止逻辑吗?答案是不用,直接用系统提供的 RPSystemBroadcastPickerView 即可。
0x01 System broardcast picker
创建一个 RPSystemBroadcastPickerView
上面说 iOS 12 里可以从自己的 app 发起系统级别的录屏直播了。那我们 app 里就需要提供一个开始的按钮。ReplayKit 提供了 RPSystemBroadcastPickerView 这个类,我们需要做的非常简单,就是创建一个 RPSystemBroadcastPickerView 的实例,添加到父视图上即可。RPSystemBroadcastPickerView 内部会负责调用 ReplayKit 2 API 进行开始/停止直播的逻辑。so easy!
OR
import ReplayKit.broadcastclass ViewController: UIViewController {
var broadcastPicker: RPSystemBroadcastPickerView?
override func viewDidLoad() {
super.viewDidLoad()
broadcastPicker = RPSystemBroadcastPickerView(frame: kPickerFrame)
view.addSubview(broadcastPicker)
}
}
preferredExtension
另外 RPSystemBroadcastPickerView 有个属性:preferredExtension,可以指定你倾向的第三方平台。
broadcastPicker.preferredExtension = “com.your-app.broadcast.extension”
iOS 12 ReplayKit 交互流程图
不管是从控制中心还是 app 里发起的直播,都是系统级别的。通过 status bar 可以停止录制。也可见,没有提供通过直接控制开始/停止直播的 API。

0x2 开发 broadcast extension
如果你是一个游戏直播平台,那你需要提供一个 upload extension。先来看一下数据流图: Upload extension 的职责:
-
接受音视频 sample(未编码)
-
对音视频 Sample 进行编码,并推流到 CDN
-
处理屏幕旋转事件
-
标记直播中 app 切换事件,app 切换的时候提供 app 切换的信息(Cooool!) ### 如何创建一个 upload extension?
创建完 upload extension target 之后,Xcode 生成了一个代码模版。方法代表着 extension 的生命周期函数。方法名是自解释的,不需多说了。
// SampleHandler created by Xcode templates for Upload Extension class SampleHandler: RPBroadcastSampleHandler {
// User has requested to start the broadcast
override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?)
// User has requested to finish the broadcast
override func broadcastFinished()
// Handle the sample buffer here
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType)
// Use details of application to annotate the broadcast
override func broadcastAnnotated(withApplicationInfo info: [String : NSObject]) }
我们主要看一下处理环节,对应着 processSampleBuffer 这个方法。
// Both audio and video samples are handled by processSampleBuffer routine override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
switch sampleBufferType {
case RPSampleBufferType.video:
//处理录制的 app 的视频帧
var imageBuffer:CVImageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
var pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) as CMTime
//使用 VideoToolbox 对视频采样进行硬件编码
VTCompressionSessionEncodeFrame(session, imageBuffer, pts, kCMTimeInvalid, nil, nil, nil)
break
case RPSampleBufferType.audioApp: //处理 app 内音频,代码这里省略了
case RPSampleBufferType.audioMic:
//处理从麦克风采集到的音频,代码这里省略了
}
}
如何处理 app 切换?
在直播的过程中,用户可能会切换 app,那怎么能知道用户当前在使用什么 app 呢?
// Use application details to help users find your broadcast override func broadcastAnnotated(withApplicationInfo applicationInfo: [AnyHashable : Any]) {
var bundleIdentifier = applicationInfo[RPApplicationInfoBundleIdentifierKey]
if (bundleIdentifier != nil) {
session.addMetadataWithApplicationInfo(bundleIdentifier)
}
}
当用户切换 app 后,会在 broadcastAnnotated 这个回调里告诉我们当前 app 的信息,可以通过 RPApplicationInfoBundleIdentifierKey 这个 key 拿到当前 app 的 bundle ID。可以根据这些内容来提醒当前正在观看直播的用户。可以说 ReplayKit 想的很周到了。
0x3 内容保护App 里可能会有一些内容,不想被录屏或者直播出去。可以检查当前是否正在录屏,如果正在录屏,隐藏敏感的信息或者停止播放敏感的视频或者音频。那么怎么检查当前是不是正在录屏呢?
-
检查 UIScreen.captured 以及 UIScreen.screens.count
-
注册 UIScreenCapturedDidChangeNotification 通知
Sample Code:
import UIKit
class func handleScreenCapturedChange() {
let isScreenMirroring = UIScreen.screens.count > 1
if (UIScreen.isCaptured && !isScreenMirroring) {
// stop audio playback and remove sensitive content from the screen
}
}
原本我也很好奇为什么要检查 UIScreen.screens.count. 看了一下 UIScreen.isCaptured 的文档,发现当在录屏,镜像或者通过 AirPlay 投屏时,isCaptured 都返回 YES。那这里的 UIScreen.isCaptured && !isScreenMirroring 就可以保证是录屏,而不是正在镜像投屏。
拓展阅读:
-
WWDC 2016 - Session 601 - Go Live with ReplayKit (群文件里有分享的 PDF)
-
WWDC 2017 - Session 606 - What's New with Screen Recording and Live Broadcast
That's all, thank you!