美文网首页
Broadcast Upload Extension / Bro

Broadcast Upload Extension / Bro

作者: 小凡凡520 | 来源:发表于2020-09-07 16:28 被阅读0次
    一、概述

    通过ReplayKit的RPScreenRecorder可以实现应用内录屏,在iOS10上录制完还可以编辑和查看录制内容,在iOS11上可以直接边录制一遍获取到音视频数据sampleBuffer。但是没办法实现应用外录屏功能。

    要想实现这个手机的录屏,并不受App限制,比如王者荣耀录屏,直接直播到斗鱼App上这种需求,就需要Broadcast Upload Extension和Broadcast Setup UI Extension的支持,最起码的需要Broadcast Upload Extension。iOS10苹果推出Broadcast Upload Extension和Broadcast Setup UI Extension来支持手机的录屏直播功能。

    二、Extension 介绍

    extension必须寄生在宿主app中,会随着宿主 app的安装而安装,同时随着宿主 app的卸载而卸载,但是extension却可以独立生存,即使宿主app没有启动,extension也可以为其他app提供相关服务。

    Broadcast Upload Extension和Broadcast Setup UI Extension都是对录屏功能的扩展。在我们的App中,在TARGET下,点击“+”,选择iOS下的Broadcast Upload Extension,然后创建一个新的Target,XXBroadcastUploadExtension。创建时,可以选择同时创建Broadcast Setup UI Extension。

    这两个对应用的扩展就被添加在了我们主TARGET下,同时在项目中会生成对应的文件,这些文件是有用的。

    我们知道每个TARGET是对应一个工程,我们新创建的两个Terget,引入了两个新的工程,也就是两个新的App,这两个新的App是依赖于我们的App的,随着我们的App的安装也会被安装,随着我们App的卸载被卸载。我们把这两个子target叫做子App,把我们的App叫做宿主App,子App是依赖于宿主App的。

    在录屏直播中,虽然我们的App是主App,但是宿主App可单独运行的。在添加Broadcast Upload Extension后,在手机的录屏上,会出现我们的扩展Broadcast Upload Extension。

    20190614115513960.png
    三、Broadcast Upload Extension

    创建Broadcast Upload Extension后,项目里会生成一个类SampleHandler,这个类继承RPBroadcastSampleHandler并需要实现了父类的几个方法。

    // 开始录屏
    - (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
       // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
    }
    
    // 暂停录屏
    - (void)broadcastPaused {
       // User has requested to pause the broadcast. Samples will stop being delivered.
    }
    
    // 结束录屏
    - (void)broadcastFinished {
       // User has requested to finish the broadcast.
    }
    
    // 录屏中
    - (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
       
    }
    

    对于录屏直播功能来说,不实现Broadcast Setup UI Extension也可以,Broadcast Upload Extension是必须要实现的,否则,没办法没拿屏幕数据,也就没法直播。

    四、Broadcast Setup UI Extension

    创建这个扩展会生成一个继承UIVieController的BroadcastSetupViewController,在这个contrller里你可以自己添加一些按钮啊输入框之类的东西,它就是一个简单的contrller,不要想的太复杂。其具体作用就是,在开始录屏的时候,弹出来这个controller,让用户如输入一些用户名啊什么的,然后把这些信息通过以下方法,通过setupInfo传到Broadcast Upload Extension的broadcastStartedWithSetupInfo这个方法里,然后开始录屏推流。

    // Call this method when the user has finished interacting with the view controller and a broadcast stream can start
    - (void)userDidFinishSetup {
        
        // URL of the resource where broadcast can be viewed that will be returned to the application
        NSURL *broadcastURL = [NSURL URLWithString:@"http://apple.com/broadcast/streamID"];
        
        // Dictionary with setup information that will be provided to broadcast extension when broadcast is started
        NSDictionary *setupInfo = @{ @"broadcastName" : @"example" };
        
        // Tell ReplayKit that the extension is finished setting up and can begin broadcasting
        [self.extensionContext completeRequestWithBroadcastURL:broadcastURL setupInfo:setupInfo];
    }
    

    这个注释注意一下:Call this method when the user has finished interacting with the view controller and a broadcast stream can start在这个界面上起码得有一个开始直播的操作吧,开始直播后调用userDidFinishSetup这个方法将数据传给Broadcast Upload Extension。

    Broadcast Setup UI Extension可以不要,但是也不是多余的,某些用户可能有这个需求,直播前先填写直播房间id,昵称什么的,然后开始直播,这种就需要Broadcast Setup UI 这个Extension了。反正是个controller,界面自己怎么定义都行。

    五、通信

    iOS10系统和iOS11系统的屏幕录制和直播,涉及到extensions和host app、containing app之间的通信,其中host app一端需要集成ReplayKit2,从而可以发起录制和直播请求,而containing app需要集成extensions,实现对其他可以录制的app的直播功能的支持。extension和host app之间可以通过extensionContext属性直接通信,extension和宿主containing app之间是通过IPC或基于group的文件共享来实现的。

    对于iOS10和iOS11,屏幕录制区别较大,前者只能录制app内的内容,后者可以录制整个系统的内容,而且前者可以通过代码控制录制的启动,而后者只能通过用户的操作(控制中心,点击圆点,选择app)启动录制。

    • iOS 10
      在iOS10系统中,想要录制当前app内的内容,必须通过其他app的extension,而启动这个extension必须通过集成replaykit的api
    @interface RPBroadcastActivityViewController : UIViewController
    + (void)loadBroadcastActivityViewControllerWithHandler:(void(^)(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error))handler;
    + (void)loadBroadcastActivityViewControllerWithPreferredExtension:(NSString * _Nullable)preferredExtension handler:(nonnull void(^)(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error))handler API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvOS);
    @end
    
    @protocol RPBroadcastActivityViewControllerDelegate <NSObject>
    - (void)broadcastActivityViewController:(RPBroadcastActivityViewController *)broadcastActivityViewController didFinishWithBroadcastController:(nullable RPBroadcastController *)broadcastController error:(nullable NSError *)error API_AVAILABLE(ios(10.0), tvos(10.0));
    @end
    

    按照前文流程,当host app一端想要将app或系统内容广播给他人观看时,需要首先选择一个app的extension来帮他广播,就是需要展示出支持广播的app列表。这点通过调用ReplayKit2的RPBroadcastActivityViewController类的load相关api来实现。可以看到上面有两个api可供使用

    -(void)loadBroadcastActivityViewControllerWithHandler:(void(^)(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error))handler;
    

    这时iOS系统将会寻找系统内已经集成了屏幕录制和直播extensions的containing app,并将这些app列表展示出来,用户可以在列表中选择containing app,点击选择之后,将通过containing app的extension中的UI-extension来展示相关的界面(可以自定义),让用户输入信息,一般用来鉴权或者保存用户信息,用户点击ok按钮之后,可以通过相关方法来调用[self.extensionContext completeRequestWithBroadcastURL:broadcastURL broadcastConfiguration:broadcastConfig setupInfo:setupInfo];,这个方法中将传递一些信息给host app,RPBroadcastActivityViewControllerDelegate的代理方法didFinishWithBroadcastController将会回调调用,这时我们可以获取到用于广播的controller,相当于与containing app已经建立起了通信链路,然后调用broadcastController 的startBroadcastWithHandler接口即可启动录制

    2204252-86c165d8681f6f5d.png
    -(void)loadBroadcastActivityViewControllerWithPreferredExtension:(NSString * _Nullable)preferredExtension handler:(nonnull void(^)(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error))handler
    

    第二个api是ios11新增的。可以通过参数preferredExtension,直接打开指定使用的app,只需要preferredExtension传递相应app extension的bundle id

    RPBroadcastActivityViewControllerDelegate的代理方法didFinishWithBroadcastController将会回调调用,这时我们可以获取到用于广播的controller,相当于与containing app已经建立起了通信链路,然后调用broadcastController 的startBroadcastWithHandler接口即可启动录制

    2204252-b785b98ff1a2a900.png
    • iOS11
      在iOS10系统中,只能用户自己手动启动录制,并且无法通过代码控制录制进程的启动,所以被录制端host app其实无需集成replaykit,而只需要宿主app集成两个extension。
      与iOS10不同的是,用户手动选择录制app后,宿主app的extension相关方法将自动开始回调。
    六、录制进程

    通过上面的形式,启动录制后,我们可以在extension中自建出来的SampleHandler文件中相关代理方法中获取到屏幕采集的进度

    @interface RPBroadcastSampleHandler : RPBroadcastHandler
    
    /*! @abstract Method is called when the RPBroadcastController startBroadcast method is called from the broadcasting application.
        @param setupInfo Dictionary that can be supplied by the UI extension to the sample handler.
      屏幕采集工作已经开始启动,在此方法中一般进行初始化工作
     */
    - (void)broadcastStartedWithSetupInfo:(nullable NSDictionary <NSString *, NSObject *> *)setupInfo;
    
    /*! @abstract Method is called when the RPBroadcastController pauseBroadcast method is called from the broadcasting application. */
    - (void)broadcastPaused;
    
    /*! @abstract Method is called when the RPBroadcastController resumeBroadcast method is called from the broadcasting application. */
    - (void)broadcastResumed;
    
    /*! @abstract Method is called when the RPBroadcastController finishBroadcast method is called from the broadcasting application. */
    - (void)broadcastFinished;
    
    /*! @abstract Method is called when broadcast is started from Control Center and provides extension information about the first application opened or used during the broadcast.
        @param applicationInfo Dictionary that contains information about the first application opened or used buring the broadcast.
     */
    - (void)broadcastAnnotatedWithApplicationInfo:(NSDictionary *)applicationInfo API_AVAILABLE(ios(11.2)) API_UNAVAILABLE(tvOS);
    
    /*! @abstract Method is called as video and audio data become available during a broadcast session and is delivered as CMSampleBuffer objects.
        @param sampleBuffer CMSampleBuffer object which contains either video or audio data.
        @param sampleBufferType Determine's the type of the sample buffer defined by the RPSampleBufferType enum.
    采集到数据的实时回调,此方法中的sampleBuffer数据结构中有视频和音频数据,我们通过相关推流方法将数据推送给服务器,即实现了录制和推流。
     */
    - (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType;
    
    /*! @abstract Method that should be called when broadcasting can not proceed due to an error. Calling this method will stop the broadcast and deliver the error back to the broadcasting app through RPBroadcastController's delegate.
        @param error NSError object that will be passed back to the broadcasting app through RPBroadcastControllerDelegate's broadcastController:didFinishWithError: method.
     */
    - (void)finishBroadcastWithError:(NSError *)error;
    
    @end
    
    七、文件读写

    尽管extension的bundle是放在containing app的bundle中,但是他们是两个完全独立的进程,之间不能直接通信。不过extension可以通过openURL的方式启动containing app(当然也能启动其它app),不过extension中是无法直接使用openURL的,必须通过extensionContext借助host app来实现。extension和containing app可以共同读写一个被称为Shared resources的存储区域,这是通过App Groups实现的,用于同一group下的app共享同一份读写空间,以实现数据共享。

    • 首先需要在apple开发网站上对profile文件进行配置,将group数据共享配置,并设置group id(dns域名反写),用户app和extension之间

    • 然后app中配置这个profile,并设置app的group,通过TARGETS-->App-->Capabilities-->App Groups,选择正确的group id

    • 同时,在extension中也要通过TARGETS-->App-->Capabilities-->App Groups,选择同样的group id

    • 通过NSUserDefaults共享数据,通过下面的形式

      - (void)saveTextByNSUserDefaults
      {
          NSUserDefaults *shared = [[NSUserDefaults alloc]     initWithSuiteName:@"group.cmcc.ShareScreen"];
        [shared setObject:_textField.text forKey:@"cmcc"];
        [shared synchronize];
      }
      
    • 读写文件时,也需要通过指定group id的形式,才能将文件写入共享的数据区,或者从共享数据区读出来

      - (NSString *)readTextByNSFileManager
      {
          NSError *err = nil;
          NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.cmcc.ShareScreen "];
          containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"];
          NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&err];
          return value;
      }
      - (bool)writeTextByNSFileManager
      {
          NSError *err = nil;
          NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.cmcc.ShareScreen "];
          containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"];
      
          NSString *value = @"just test";
          BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&err];
          return result;
      }
      

    注意:containing app需要配置带有group配置的profile, extension可以配置自动,但是bundle id不能和containing app相同

    八、调试

    由于涉及到extensions作为独立target,所以调试时,需要单独编译运行,即我们想要调试containing app那就需要将xcode切换到containing app,然后重新运行,如果需要调试upload 或 setupUI的extension,那就需要需要切换到extension的target,在重新运行,这样才能在sampleHandler相关的方法中断点调试

    2204252-72b0142b2711e780.png

    userDidFinishSetup(通过extensionContext与host app通信的方法)必须在viewDidAppear后,而不能放在viewDidLoad之后,否则导致无法将事件传递给SampleHandler,它的代理方法不会回调

    - (void)viewDidAppear:(BOOL)animated
    {
        [super viewDidAppear:animated];
        [self userDidFinishSetup];
    }
    

    相关文章

      网友评论

          本文标题:Broadcast Upload Extension / Bro

          本文链接:https://www.haomeiwen.com/subject/qmclektx.html