WWDC 2018 - 601 使用 ReplayKit 录屏直播

9,003 阅读6分钟
原文链接: mp.weixin.qq.com

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  就可以保证是录屏,而不是正在镜像投屏。

拓展阅读:

  1. WWDC 2016 - Session 601 - Go Live with ReplayKit (群文件里有分享的 PDF)

  2. WWDC 2017 - Session 606 - What's New with Screen Recording and Live Broadcast

That's all, thank you!