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,选择如下

1554803362569.jpg

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


1554803494224.jpg

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


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];
        }
    }];
}

点击之后效果如下:


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 <NSString *, NSObject <NSCoding> *> *)setupInfo API_DEPRECATED("No longer supported", ios(10.0,11.0),tvos(10.0,11.0));

iOS11
- (void)completeRequestWithBroadcastURL:(NSURL *)broadcastURL setupInfo:(nullable NSDictionary <NSString *, NSObject <NSCoding> *> *)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----进行调试
如下图:


1554806204717.jpg

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

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


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<NSString *,NSObject *> *)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 <NSCoding>
/*  @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这种,更多的是为了直播而进行的服务。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容