iOS屏幕录制ReplayKit 10+系统版本

本篇文章仅针对iOS10+系统,如果需要支持iOS9请参考下面链接

iOS9屏幕录制请参考

因为10+部分涉及知识面较多,所以这篇文章主要围绕录屏,其他涉及到的我会另开篇幅。

将涉及其它知识面:
1.App Extension
2.App Group Share
3.H264与CMSampleBufferRef结构分析
4.VideoToolBox硬编码

录屏整体流程如下:
1.触发录屏
2.准备工作
3.开始录屏
4.处理数据流保存到指定文件
5.结束录制
6.Group共享到主App中(或者直接对此文件进行操作)

进入正题:
苹果在iOS9已经支持了屏幕录制,但相比较安卓的来说开发者的可操作性又少又差。在iOS10之后,ReplayKit又开放了一系列的API,给了开发者更多的操作空间,但从实际体验来说友好性并不理想。
iOS10之后的一些API更多地偏向于流数据的处理,通过App Extension的配合完成屏幕数据流或者其他数据流的采集,通过对数据流的采集和处理我们能做很多事情,比如进行推流直播或者编码保存信息,所以屏幕录制其实只是在此基础上延伸出了一个使用方式,而且我认为10-12之间使用App Extension的方式并不是一个好的录屏解决方案,相比iOS9他的流程更加繁琐和不可控,在使用方向上个人觉得更适合于直播的方向。

iOS10和iOS11屏幕录制

把这两个系统版本放在了一起是因为他们非常的相近。

1.创建App Extension
选择File-New-Target,选择如下

iOS屏幕录制ReplayKit 10+系统版本_第1张图片
1554803362569.jpg

下一步中勾选一下UI(不勾选也是可以的,勾选的话方便我们进行宿主App的验证)


iOS屏幕录制ReplayKit 10+系统版本_第2张图片
1554803494224.jpg

创建好之后我们会发现工程中多了一些东西:


iOS屏幕录制ReplayKit 10+系统版本_第3张图片
1554803599493.jpg

上面的文件夹包含的是主要的功能----录制状态的变化和数据流的获取都在这里面,下面的文件夹包含的主要是从选中Sheet的Item到开始录制中的一个过渡VC,在这个VC中你可以加入一些账号验证或者其他想要做的事情,对整体功能来说可有可无。

2.选择App Extension
在几年前,跨App的直播在苹果上是不存在,想做游戏直播一般都是电脑或者安卓机。玩过直播App的同学可能留意到,市面上的一些做直播的App很多已经提供了直播Extension功能,即只使用其App的一个Extension功能进行跨App的游戏直播。
像下面这样这些App都提供了这个功能:

其实在操作完第一步之后,我们的App也具备展示在Sheet中的能力,下面我们来布局代码。
iOS10中在需要触发录屏按钮的地方触发这个方法:

- (void)startREC_showExtension {
    [RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithHandler:^(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error) {
        if (broadcastActivityViewController) {
            broadcastActivityViewController.delegate = self;
            broadcastActivityViewController.modalPresentationStyle = UIModalPresentationPopover;
            [self presentViewController:broadcastActivityViewController animated:YES completion:nil];
        }
    }];
}

点击之后效果如下:


iOS屏幕录制ReplayKit 10+系统版本_第4张图片
516D1B971690ABED382ADF0B12F34A9F.jpg

这个方法将加载当前支持Broadcast Upload Extension的扩展,RPBroadcastActivityViewController是弹出的Sheet,设置好代理之后之后我们选中了某个Item就会进入到过度的一个VC中,就是上面图中的BroadcastSetupViewController。
在这个VC中我们可以进行宿主App的校验或者账号信息的登陆(因为所有的需要直播的App都能调出我们的这个Extension)。

iOS11中我们可以直接启动对应的item跳过选择这一步:

+ (void)loadBroadcastActivityViewControllerWithPreferredExtension:(NSString * _Nullable)preferredExtension handler:(nonnull void(^)(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error))handler API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);

上面这个方法中preferredExtension参数指的是我们创建的BroadcastVCExtension中Info.plist的bundleID,你也可以从工程----Target----BroadcastSetupUI(名称可能不一样)中找到这个ID。
我们我们操作停当之后需要触发userDidFinishSetup这个方法才能回调下一步,可以看下这个.m文件中userDidFinishSetup这个方法:

iOS10
- (void)completeRequestWithBroadcastURL:(NSURL *)broadcastURL broadcastConfiguration:(RPBroadcastConfiguration *)broadcastConfiguration setupInfo:(nullable NSDictionary  *> *)setupInfo API_DEPRECATED("No longer supported", ios(10.0,11.0),tvos(10.0,11.0));

iOS11
- (void)completeRequestWithBroadcastURL:(NSURL *)broadcastURL setupInfo:(nullable NSDictionary  *> *)setupInfo API_AVAILABLE(ios(11.0),tvos(11.0));

上面两个一样的作用,表示设置完成,userInfo可以传递设置的参数,方法完成后触发RPBroadcastActivityViewController的代理如下:

#pragma mark RPBroadcastActivityViewControllerDelegate
- (void)broadcastActivityViewController:(RPBroadcastActivityViewController *)broadcastActivityViewController didFinishWithBroadcastController:(nullable RPBroadcastController *)broadcastController error:(nullable NSError *)error {
    dispatch_async(dispatch_get_main_queue(), ^{
        [broadcastActivityViewController dismissViewControllerAnimated:YES completion:nil];
    });
    self.broadcastController = broadcastController;
    [broadcastController startBroadcastWithHandler:^(NSError * _Nullable error) {

        if (!error) {
            NSLog(@"启动");
        } else {
            NSLog(@"error: %@", error);
        }
    }];
}

到此为止,我们已经完成了从调出Sheet到触发屏幕录制这个阶段,下面进行屏幕录制数据流的处理。
请注意,在Extension文件中操作的调试需要选择对应的Target,因为他是工程中一个独立的Target,否则无法调试!!!

选择Target----选择宿主App----进行调试
如下图:


iOS屏幕录制ReplayKit 10+系统版本_第5张图片
1554806204717.jpg

以上方法在录屏的时候触发的录屏是在应用内,比如你下拉一下通知栏就会自动暂停,再次进入App内提示会自动提示你是否继续直播屏幕,而iOS11触发的录屏可以是在App外,进行一个跨App的屏幕录制,所以iOS能进行跨App的录屏是在iOS11之后。

iOS11录屏要想在应用外使用下面的方法触发:
首先将录屏功能加到控制面板里:设置-控制中心-屏幕录制加上
然后上拉或下划调出控制中心,长按录制按钮,调出录屏控制面板,选择对应直播功能的Extension,如下图:


iOS屏幕录制ReplayKit 10+系统版本_第6张图片
A6F7D749E201D01C34648FEEE037A78B.jpg

iOS11有一组专门对屏幕录制的API,这组API和iOS9上的一组非常相似,不同的是数据是以流的形式返回,同样的,这组API只能对App内进行屏幕录制,无法跨App进行录制(任何使App挂起的操作都会打断屏幕录制),好处是避开了Extension,简化了操作。

- (void)startCaptureWithHandler:(nullable void(^)(CMSampleBufferRef sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error))captureHandler completionHandler:(nullable void(^)(NSError * _Nullable error))completionHandler API_AVAILABLE(ios(11.0), tvos(11.0));

- (void)stopCaptureWithHandler:(nullable void(^)(NSError * _Nullable error))handler API_AVAILABLE(ios(11.0), tvos(11.0));

两个方法一个是开始,另一个是结束,开始的方法中回调了屏幕的数据流,我们需要对这组数据流进行编码操作,推流或者保存。如果你只是要进行App内屏幕录制,这组方法无疑是非常好的。

3.流程控制和数据流的采集处理

我们切到第一个文件夹中的SampleHandle.m文件中,可以看到.m中的方法分为两部分,一部分是对录制流程的控制另一部分是数据流的采集。
流程控制这部分代码:

- (void)broadcastStartedWithSetupInfo:(NSDictionary *)setupInfo {
    NSLog(@"1");
    // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional. 
}

上面这个方法是对过度VC中参数的传递,我们可以在这个方法中拿到过度页面的传值。

- (void)broadcastPaused {
    NSLog(@"2");
    // User has requested to pause the broadcast. Samples will stop being delivered.
}

- (void)broadcastResumed {
    NSLog(@"3");
    // User has requested to resume the broadcast. Samples delivery will resume.
}

- (void)broadcastFinished {
    NSLog(@"4");
    // User has requested to finish the broadcast.
}

上面三个方法就很简单了,暂停、恢复和停止。

- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
    switch (sampleBufferType) {
        case RPSampleBufferTypeVideo:
            // Handle video sample buffer
            break;
        case RPSampleBufferTypeAudioApp:
            // Handle audio sample buffer for app audio
            break;
        case RPSampleBufferTypeAudioMic:
            // Handle audio sample buffer for mic audio
            break;
            
        default:
            break;
    }
}

我们在这个方法里拿到数据流,这里的数据流是未编码的,通常情况下如果要做推流或者文件保存,我们会将其进行一个H264的编码。
需要注意的是这个方法的回调是有限制的,仅当你的屏幕发生了实质性的变化才会触发,如果你的屏幕一直静止则不会触发,在调试的时候需要多注意。

数据流的硬编码使用VideoToolBox,此处不再过多叙述会另开篇幅。
拿到编码后的流数据我们可以做一些推流或者文件写入的操作。

4.停止录屏
在上述App内屏幕录制时想要主动停止需要broadcastController调用下面这个方法

- (void)finishBroadcastWithHandler:(void(^)(NSError * _Nullable error))handler;

(由于App内录制的特殊性,当你的App切换到后台被挂起时会自动暂停录制,当你激活App时会收到是否继续直播的提示,当你选择否时也会停止录屏。)

录屏停止时在SampleHandler.m中会回调下面的方法:

- (void)broadcastFinished {
    NSLog(@"STOP");
    // User has requested to finish the broadcast.
}

我们在这个方法中可以整理数据和进行收尾工作。

当你使用的是跨App的方式进行录制的时候,想要主动结束就需要用户手动点击关闭控制面板的录屏按钮(暂时没找到如何在程序中主动关闭),在SampleHandler的实例方法中有一个下面的方法,似乎可以主动关闭录屏(但我测试的时候却无法真正关闭),而且我不知道如何通知到这个Extension去关闭录屏(有同学提议用进程间通知的方式暂时还没尝试),这里Mark一下。

- (void)finishBroadcastWithError:(NSError *)error;

5.录屏文件数据的共享
每个Extension都需要一个宿主App,并且有自己的沙盒,当我们把录屏文件保存到沙盒中时宿主App是无法获取到的,那么只有采用共享的方式才能让宿主App拿到录屏文件。
App Group Share帮我们解决了这个问题,通过设置组间共享的模式,使得同一个Group下面的App可以共享资源,解决了沙盒的限制。

到此为止,基于iOS10和11的录屏操作已经完整告一段落,从中我们就可以看出,想用iOS10和11的API去进行录屏操作不但反用户操作行为而且反开发行为,冗长的流程和各种限制使得录屏体验非常差,所以市面上使用这套API的大部分App只做直播方向。

iOS12屏幕录制

到了iOS12,苹果又开放了一部分API,这使得录屏变得可行并且易于操作。
给予我们极大帮助的是这个类

RPSystemBroadcastPickerView

点击去看:

@interface RPSystemBroadcastPickerView : UIView 
/*  @abstract Bundle identifier of extension that should be used for broadcast. Default is nil which means that all extensions will be presented */
@property (nonatomic, strong, nullable) NSString *preferredExtension;
/*  @abstract Indicates whether the Microphone button is visible in broadcast picker view. Default is YES. */
@property (nonatomic, assign) BOOL showsMicrophoneButton;
@end

RPSystemBroadcastPickerView继承与UIView只有两个属性,第一个表示要启动的Extension标识,nil则全部弹出供选择,第二个标识是否显示麦克风按钮,如果显示并且选择则会把声音一同录入。

_broadPickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
_broadPickerView.preferredExtension = nil;
[self.view addSubview:_broadPickerView];

请注意,这里有一点不同,当你preferredExtension=nil时你需要选择要用的直播Extension,选择好之后进入了10和11对流处理的模式;当你指定了preferredExtension=@“XXX”的时候,直接开始录制,开发者不进行流数据处理,录制完成之后自动保存到相册。

对于单纯的录屏来说,指定preferredExtension无意是最便捷的方式,虽然API并没有暴露录屏开始和结束的回调方法,但我们可以将RPSystemBroadcastPickerView进行一个响应事件的传递从我们自定义的View中传递给RPSystemBroadcastPickerView。当然严格地来说这样并不能精确地判断,还需要加入一些逻辑上的检测。例如:

[[RPScreenRecorder sharedRecorder] isRecording];

最后总结一下,如果需要的只是App内录制,那么iOS9的API、iOS11的不包含Extension的API、iOS12的RPSystemBroadcastPickerView是比较好的选择;如果你需要跨App的录屏,那么建议尽量从iOS12开始支持;而Extension这种,更多的是为了直播而进行的服务。

你可能感兴趣的:(iOS屏幕录制ReplayKit 10+系统版本)