iOS screen sharing howto
iOS 屏幕共享功能实现¶
本文档描述基于 EVSDK 实现 iOS 版本客户端应用的会议中屏幕共享功能,其中重点介绍了所使用到的 APP Extension 、EVSDK 等信息,此功能仅支持 iOS12 以上的系统。该功能的实现依赖于 iOS 的 ReplayKit 功能,有关背景内容可以参考 Apple 文档, https://developer.apple.com/videos/play/wwdc2018/601/, https://developer.apple.com/videos/play/wwdc2017/606/ 和 https://developer.apple.com/documentation/replaykit
前期准备工作¶
创建 Extensions Bundle Identifier¶
前往开发者网站 https://developer.apple.com/
- 创建 App Groups, 在Certificates, Identifiers & Profiles 中选择 Identifiers,找到 App Groups 并创建,格式 group+ 域名翻转
//示例: group.com.hexmeet
- 创建两个 Extension 的 Identifiers
提示
创建 Identifiers,格式前缀务必使用与主程序 bundleID 相对应的域名反写,并分别设置为 uploadext 、setupuiext
//示例 Identifiers:com.hexmeet.hjtdebug.uploadext , com.hexmeet.hjtdebug.setupuiext
- 为 bundle identifier 增加 App Groups 设置
Extension 的 bundle identifier 创建完成后,查看详情,并在Capabilities 的 App Groups 中编辑,选择创建好的 App Group 名
然后,查看主程序的 bundle identifier 的 Capabilities 中同样勾选 App Groups,编辑并选择新创建的 App Group 名
- 创建相应的 Extension 描述文件,并更新主程序的描述文件 (因为主程序的 Capabilities 中增加了 App Groups 设置)
项目工程修改¶
程序中创建 APP Extension,设置相关配置
-
创建 tagert,选择 iOS 下的 Application Extension 模块下的 Broadcast Upload Extension,创建过程中如果勾选 Include UI Extension 则系统会自动生成 Broadcast Setup UI Extension。如果没有勾选,请再创建一个 Broadcast Setup UI Extension
-
创建完成之后,开始配置相关功能,选择主程序 target 的 Signing & Capabilities 点击 +Capability,增加 App Groups 功能,增加完成之后勾选之前创建好的 App Group 的 Identifiers。依次选择创建好的两个 Extension 并配置 App Groups,App Groups 的Identifiers 要选择和主程序 App Group 勾选的 Identifiers 一致。
APP Extension 使用¶
屏幕共享依赖于系统提供的 Broadcast Upload Extension 和 Broadcast Setup UI Extension - Broadcast Upload Extension 和 Container app 之间通讯以 APP Group 方式形式进行,此进程为主要收集原始音视频数据以及录制进程状态的通知
-
Broadcast Setup UI Extension 这个进程通常用于让用户输入一些信息来鉴权,或者自定义其他界面,在启动录制进程之间插入的一个交互的页面,这里可以不做操作使用系统提供的配置方法
-
接收原始音视频数据 (Broadcast Upload Extension 提供的 SampleHandler 回调类中),为了方便对原始数据进行内容处理,请将SampleHandler.m 文件修改成 SampleHandler.mm
音频内容原始数据接收方法¶
-(void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType: (RPSampleBufferType) sampleBufferType;
对原始数据进行处理:(复制此方法)¶
Extension 和 Contain app之间通讯以 APP Group 方式形式进行:¶
//示例代码
-(void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
_userDefault = [[NSUserDefaults alloc] initWithSuiteName: @"group.com.hexmeet"];
_containerURL = [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier: @"group.com.hexmeet"] URLByAppendingPathComponent:@"Library/Caches/yuvframe"];
[_userDefault setBool:YES forKey:@"broadcastStarted"];
[_userDefault synchronize];
}
屏幕停止录制系统会通过:¶
-(void)broadcastFinished;
//示例代码
-(void)broadcastFinished {
[_userDefault setBool:YES forKey:@"broadcastFinished"];
[_userDefault synchronize];
}
事项及说明
_userDefault 在屏幕录制共享的数据内容中,我们标记了3种状态方式:broadcastStarted、videobufferready、broadcastFinished,可参考 demo 用法
EVSDK、ReplayKit 使用¶
EVSDK 相关方法¶
-(int) sendContent;
-(int) setLocalContentSourceInfo:(EVContentSourceInfo *_Nonnull)info;
-(int) stopContent;
-(void) onContent:(EVContentInfo *_Nonnull)info;
- (void)onContent:(EVContentInfo * _Nonnull)info 中收到回调
注意
info.status == EVContentDenied 意为禁止分享
当前会议收到回调 status为EVContentDenied,请主动关闭分享操作,具体使用细节请参照 demo
info.dir == EVStreamUpload
当前会议环境若处于双流状态,操作点击分享,收到回调 dir 为 EVStreamUpload,请关闭双流界面,具体使用细节请参照 demo
info.dir == EVStreamDownload
当前会议若处于主动分享状态时,收到回调 dir 为EVStreamDownload,请主动关闭分享操作,改为被动接收分享,具体使用细节请参照 demo
info.dir == EVStreamDownload
当前会议若处于主动分享状态时,收到回调 dir 为EVStreamDownload,请主动关闭分享操作,改为被动接收分享,具体使用细节请参照 demo
sendContent、 stopContent 、setLocalContentSourceInfo 具体使用细节请参照 demo 中提供的 startShareTimer() 和 stopShareTimer()方法
Replaykit 使用¶
录制 UI 界面我们使用 RPSystemBroadcastPickerView,iOS12 之后提供的录屏 UI 控件
//示例代码,注意broadcastPickerView.preferredExtension 为upload extension的bundleID
if #available(iOS 12, *) {
let broadcastPickerView = RPSystemBroadcastPickerView(frame:CGRect(x: 50, y: 50, width: 42, height: 40))
broadcastPickerView.tag = 4567
broadcastPickerView.showsMicrophoneButton = false
broadcastPickerView.preferredExtension = "\(getInfoString("CFBundleIdentifier"))\(".uploadext")"
self.view.addSubview(broadcastPickerView)
broadcastPickerView.isHidden = true
}
具体使用此控件方法:
//示例代码
func shareMGBtnAction(){
if shareMGLb.text == "video.startShare".localized {
//share
isSeletShare = true
if #available(iOS 12, *) {
var btn:UIButton = UIButton()
let broadcastPickerView:RPSystemBroadcastPickerView = self.view.viewWithTag(4567) as! RPSystemBroadcastPickerView
for item:UIView in broadcastPickerView.subviews {
if item.isKind(of: UIButton.classForCoder()) {
btn = item as! UIButton
}
}
if #available(iOS 13, *) {
btn.sendActions(for: UIControl.Event.touchUpInside)
}else{
btn.sendActions(for: UIControl.Event.touchDown)
}
startShareTimer()
}
}else{
stopShareTimer()
}
}
### func startShareTimer() 方法为录制数据内容和evsdk交互的主要方法
//示例代码
func startShareTimer() {
userDefault = UserDefaults.init(suiteName:"group.com.hexmeet")
userDefault.setValue(false, forKey: "broadcastFinished")
userDefault.setValue(false, forKey: "videobufferready")
userDefault.synchronize()
if shareTimer == nil {
shareTimer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(pushShareContent), userInfo: nil, repeats: true)
}
}
此方法 0.2S 即代表每秒 5 帧画面
在定时器的 pushShareContent 方法中对 extension 存储的数据进行处理并传给 EVSDK
//示例代码请参照demo: @objc func pushShareContent() {} 方法
停止录制时¶
//示例代码请参照demo : func stopShareTimer() {}方法
注意
会议挂断请主动再次调用 func stopShareTimer() {}方法,确保会议结束调用 func stopShareTimer() {} 方法