Pano开发者中心
  • 开发者中心
  • 下载中心

iOS屏幕共享

Pano SDK 从 1.3.9 版本开始,支持 iOS 屏幕共享(需要 iOS 11.0 及以上的系统版本),本文介绍如何接入该功能。

实现原理

iOS 上实现系统级屏幕共享需要通过 Broadcast Upload Extension 扩展配合主 App 进行。
Extension 负责接收屏幕数据,并传给主 App,由主 App 负责发送数据。

接入流程

配置 App Group

注意:不配置 App Group 也可以使用屏幕共享功能,但可能影响数据传输稳定性。建议配置 App Group 以获得最佳体验。

配置方法如下(以Xcode 12.4 为例):

  1. Xcode 选择主 App 的「TARGETS」,点击「Signing & Capabilities」,点击下方「+ Capability」。
  2. 在弹出框中双击选择「App Groups」,会自动展示 App Groups 项。
  3. 如果已展示 App Groups 列表,可以选择其中一个。如需创建新的 App Groups,在「App Groups」项底部,点击「+」(如果未选择 Development team,会提示选择,选好后点击「Select」),在弹出的「Add a new container」窗口输入自定义的 App Group ID(注意:系统会自动添加 group. 前缀),点击「OK」保存。

创建 Broadcast Upload Extension

  1. Xcode 点击「TARGETS」列表最底部(窗口最底部)的「+」按钮,在弹出窗口选择「Broadcast Upload Extension」。
  2. 点击「Next」,在弹出窗口中填写相关信息(Extension 的 Bundle Idenifier 需要和主 TARGET 不同),取消勾选「Include UI Extension」,点击「Finish」完成创建。
    创建 Extension 时配置的 Product Name 会自动填充到 General - Identity - Display Name 中,在用户选择屏幕录制的界面上会作为应用名称显示,请按需修改。
    • 如果手工导入 Pano SDK,请将 SDK 包的 PanoReplayKitExt.framework 添加到 Extension 的 TARGET - General - Frameworks and Libraries,将 Embed 属性设置为 Embed & Sign。

      注意:PanoReplayKitExt.framework 只需要添加到 Extension,不要添加到主工程。

    • 如果通过 CocoaPods 自动集成 Pano SDK,请在 Podfile 中添加:

      target 'YourMainTargetName' do
        use_frameworks!
        # 1.3.9 版本 SDK 开始支持屏幕共享
        pod 'PanoRtc_iOS', '~> 1.3.9'
      
        target 'YourExtensionTargetName' do
          platform :ios, '11.0'
          use_frameworks!
          pod 'PanoRtc_iOS/ReplayKitExt'
        end
      end
      
  3. Extension 的 General - Deployment Info 中,可以选择版本「iOS 11.0」以匹配最大兼容性。
  4. (可选)如果已对主 App 配置 App Group,请参考前一节的介绍,对 Extension 的 TARGET 同样配置 App Group(Extension 的 App Group ID 需要和 主 TARGET 的 App Group ID 相同)。

配置 Extension 采集数据

创建 Extension 后,Xcode 会自动生成 SampleHandler 类文件,在其中引入 PanoReplayKitExt 头文件并按照下列说明编写代码,来完成数据采集逻辑。

// 导入 PanoReplayKitExt.h
#import <PanoReplayKitExt/PanoReplayKitExt.h>
// 实现 PanoScreenSharingExtDelegate 协议
@interface SampleHandler () <PanoScreenSharingExtDelegate>
@end
  1. (可选)如果配置了 App Group,在 SampleHandler.m 的 broadcastStartedWithSetupInfo 中配置代理,将 kAppGroupId 替换为 App Group ID:
    - (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *>*)setupInfo {
        [PanoScreenSharingExt.sharedInstance setupWithAppGroup:kAppGroupId delegate:self];
    }
    
  2. 在用户手动停止屏幕共享时通知 PanoReplayKitExt 结束共享:
    - (void)broadcastFinished {
        [PanoScreenSharingExt.sharedInstance finishScreenSharing];
    }
    
  3. 在 processSampleBuffer 方法中将 sampleBuffer 传递给 PanoReplayKitExt:
    - (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
        switch (sampleBufferType) {
            case RPSampleBufferTypeVideo:
                // PanoReplayKitExt 只需接收 Video 数据
                [PanoScreenSharingExt.sharedInstance sendVideoSampleBuffer:sampleBuffer];
                break;
            case RPSampleBufferTypeAudioApp:
                break;
            case RPSampleBufferTypeAudioMic:
                break;
            default:
                break;
        }
    }
    
  4. 实现 PanoScreenSharingExtDelegate 的 -screenSharingFinished: 回调,并处理相关事件:
    - (void)screenSharingFinished:(PanoScreenSharingResult)reason {
         NSString *log;
         switch (reason) {
             case PanoScreenSharingResultVersionMismatch:
                 log = @"Pano SDK 和 PanoReplayKitExt 版本不匹配";
                 break;
             case PanoScreenSharingResultCloseByHost:
                 log = @"屏幕共享被关闭";
                 break;
             case PanoScreenSharingResultDisconnected:
                 log = @"连接异常断开";
                 break;
             default:
                 break;
         }
         NSError *err = [NSError errorWithDomain:error.domain code:error.code userInfo:@{ NSLocalizedFailureReasonErrorKey : log }];
         [self finishBroadcastWithError:err];
    }
    

配置主 App 接收数据

在主 App 中使用 Pano SDK 开启屏幕共享,让主 App 准备接收 Extension 的录屏数据,按照如下步骤进行配置:

注意:使用 Extension 进行屏幕录制,当你的 App 切换到后台时,其他用户会看到你的屏幕共享处于最后一幅画面。为避免此问题,开发者需要让 App 常驻后台,例如,通过调用 Pano SDK 发送声音的接口,或者其他合适的方式。

  1. 确保调用屏幕共享接口前,已经成功加入频道。
  2. 调用 PanoRtcEngineKit 实例的 - startScreenWithAppGroupId:方法,传入 App Group ID(如果没有配置 App Group 则传入 nil)。
  3. 在 PanoRtcEngineDelegate 的 - onScreenStartResult: 回调方法中判断屏幕共享状态,result == kPanoResultOK 表示开启成功。
  4. 等待用户通过 iOS 系统控制中心触发屏幕录制,或者参考下一步的介绍使用系统提供的 RPSystemBroadcastPickerView 来启动。
    控制中心启动需要用户长按屏幕录制按钮,并在弹出列表中选择 Extension 名称来启动。
  5. (可选)通过系统提供的 RPSystemBroadcastPickerView 类可以实现从主 App 内唤起屏幕录制页面,将如下代码添加到需要启动屏幕录制的地方:

    注意:Apple 官方并不推荐此方案,可能随着系统更新被禁止,使用这种方式启动需要考虑此风险。
    RPSystemBroadcastPickerView 需要 iOS 12 以上支持,目前不支持自定义界面,仅能唤起屏幕录制启动页面供用户操作。

    ...
    #import <ReplayKit/ReplayKit.h> // 导入系统库
    ...
    @property (strong, nonatomic) RPSystemBroadcastPickerView *broadcastPickerView;  // 定义一个属性
    ...
    [self launchBroadcastPickerView]; // 在需要启动屏幕共享的时机调用,以自动弹出屏幕录制提示
    ...
    // 方法体
    - (void)launchBroadcastPickerView API_AVAILABLE(ios(12.0)){
        if (!self.broadcastPickerView) {
            RPSystemBroadcastPickerView *pickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
            pickerView.showsMicrophoneButton = NO;
            pickerView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin;
            NSString *pluginPath = [NSBundle mainBundle].builtInPlugInsPath;
            NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:pluginPath error:nil];
            for (NSString *content in contents) {
                if (![content hasSuffix:@".appex"]) {
                    continue;
                }
                NSBundle *bundle = [NSBundle bundleWithPath:[[NSURL fileURLWithPath:pluginPath] URLByAppendingPathComponent:content].path];
                if (bundle) {
                    NSString *identifier = [bundle.infoDictionary valueForKeyPath:@"NSExtension.NSExtensionPointIdentifier"];
                    if ([identifier isEqualToString:@"com.apple.broadcast-services-upload"]) {
                        pickerView.preferredExtension = bundle.bundleIdentifier;
                    }
                }
            }
            self.broadcastPickerView = pickerView;
        }
        
        for (UIView *view in self.broadcastPickerView.subviews) {
            if ([view isKindOfClass:[UIButton class]]) {
                [(UIButton *)view sendActionsForControlEvents:UIControlEventAllEvents];
            }
        }
    }
    
  6. 通过调用 PanoRtcEngineKit 实例的 stopScreen 方法来停止屏幕共享,或者用户也可以通过系统控制中心来手工停止。

屏幕共享事件通知

通过 PanoRtcEngineDelegate 协议的回调方法来处理 Pano SDK 返回的通知。 和屏幕共享有关的事件通知如:

  • - onUserScreenStart: 其他用户开启屏幕共享
  • - onUserScreenStop: 其他用户关闭屏幕共享
  • - onUserScreenMute: 其他用户暂停屏幕共享
  • - onUserScreenUnmute: 其他用户恢复屏幕共享
  • - onUserScreenSubscribe:withResult: 订阅其他用户屏幕共享的结果(仅订阅失败时回调)
  • - onFirstScreenFrameRendered: 收到其他用户的第一个屏幕共享画面
  • - onScreenStartResult: 自己开启屏幕共享的结果

订阅其他用户的屏幕共享

通过 PanoRtcEngineKit 实例的 - subscribeScreen:withView: 或 - subscribeScreen:withDelegate: 方法订阅其他用户的屏幕共享。
前者随带渲染视图,后者随带外置渲染器。

取消订阅屏幕共享

通过 PanoRtcEngineKit 实例的 - unsubscribeScreen: 方法取消订阅其他用户的屏幕共享。

其他用户停止屏幕共享时,Pano SDK 会自动取消订阅,这种情况不必调用取消订阅方法。

示例代码

我们提供示例代码供开发者参考。

  • GitHub
  • Gitee (码云)
Last updated on 2022/1/19
  • 实现原理
  • 接入流程
    • 配置 App Group
    • 创建 Broadcast Upload Extension
    • 配置 Extension 采集数据
    • 配置主 App 接收数据
    • 屏幕共享事件通知
    • 订阅其他用户的屏幕共享
    • 取消订阅屏幕共享
  • 示例代码
浙ICP备20002645号 ©2019-2022 Pano拍乐云