ReplayKit - ios 录屏直播

3,689 阅读6分钟

WWDC 2018 - 601 Live Screen Broadcast with ReplayKit

转自: mp.weixin.qq.com/s/9WIQqNh8t…

0x00 简介

ReplayKit 可以做什么?

支持手机屏幕录屏,并推流到到第三方直播服务

直接从设备上直播音视频(屏幕+系统播放的声音)

支持使用麦克风和摄像头进行解说

内容是安全的,整个过程只会推到第三方直播服务

开一些脑洞?

直播游戏到 Mobcrush 或者 Youtube 等平台,国内的映客,花椒等也支持

使用 WebEx 进行网络会议的时候,同步桌面显示

使用 TeamViewerQS 进行客户服务

直播一个画画的 app 到 Facebook

可以通过录屏直播,远程实时教父母使用手机

ReplyKit VS ReplayKit 2
ReplayKit

先回顾一下 ReplayKit 的交互流程:

image.png

首先 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 2 的流程

image.png

相对于 ReplayKit,ReplayKit 2 用户是从控制中心发起的直播(就是录屏的那个按钮,重按可以选择直播到第三方直播平台)。后面的流程对于开发者来说和 ReplayKit 差不多。因此也称为 iOS System Broadcast。ReplayKit 2 需要 iOS 11+.

ReplayKit 2 有几个优势:

用户可以直接从控制中心开启和停止直播,是系统级别的操作,和 app 无关,用户可以无缝跨 app 进行录屏直播,不用担心退后台或者切换 app 的场景。

你的 app 不需要提供 app 的 UI 交互了。也就是说,相对于 ReplayKit,你可以不做任何事情,就让你的 app 支持直播到第三方平台。

image.png

iOS 12 ReplayKit 有什么更新?

image.png

一图胜千言,相信不用我多说了吧。。不过既然可以从自己 app 发起,那会像 ReplayKit 1 里面自己提供自定义的开始/停止直播按钮,并控制直播开始停止逻辑吗?答案是不用,直接用系统提供的 RPSystemBroadcastPickerView 即可。

1. System broardcast picker 实现

创建一个 RPSystemBroadcastPickerView

上面说 iOS 12 里可以从自己的 app 发起系统级别的录屏直播了。那我们 app 里就需要提供一个开始的按钮。ReplayKit 提供了 RPSystemBroadcastPickerView 这个类,我们需要做的非常简单,就是创建一个 RPSystemBroadcastPickerView 的实例,添加到父视图上即可。

RPSystemBroadcastPickerView 内部会负责调用 ReplayKit 2 API 进行开始/停止直播的逻辑。so easy!

image.png

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。

image.png

2 开发 broadcast extension

如果你是一个游戏直播平台,那你需要提供一个 upload extension。先来看一下数据流图:

image.png

Upload extension 的职责:

接受音视频 sample(未编码)

对音视频 Sample 进行编码,并推流到 CDN

处理屏幕旋转事件

标记直播中 app 切换事件,app 切换的时候提供 app 切换的信息(Cooool!)

如何创建一个 upload extension?

image.png

创建完 upload extension target 之后,Xcode 生成了一个代码模版。方法代表着 extension 的生命周期函数。方法名是自解释的,不需多说了。

SampleHandler


// 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]) }

image.png

我们主要看一下处理环节,对应着 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! mp.weixin.qq.com/s/9WIQqNh8t…