Morris_
2019.05.08
本篇主要功能:
ReplayKit是苹果在iOS9.0,tvos10.0提功能一个支持录屏功能的库,在目前感觉仍然不是很成熟,但是对于录屏和录屏直播的支持在功能上是完整的。
在iOS9只是提供了RPScreenRecorder、RPPreviewViewController、RPError这几个类,RPScreenRecorder可以实现应用内录屏功能,包括语音的录制。RPPreviewViewController则可以实现对录制内容的展示、保存等操作。
在iOS10又新增了RPBroadcast和RPBroadcastExtension,来达到应用外录屏的功能支持。在之后的每个版本ReplayKit这个库都有更新,废弃了些Api,替换了新的Api。
以下是该库下的所有类:
#import
#import
#import
#import
#import
此框架下定义的录屏错误类,定义了一个RPRecordingErrorCode的枚举,列举了录屏出现的错误码。
这个类是一个单例对象,是苹果提供的可以在应用内对App进行屏幕数据和银屏数据进行录制的类。
看完这个类,大体上应该知道在应用内机型屏幕录制、采集语音、停止录制这些功能。
iOS9提供了如下Api开始录制,录制时设置是否同时录制音频。
- (void)startRecordingWithMicrophoneEnabled:(BOOL)microphoneEnabled handler:(nullable void(^)(NSError * _Nullable error))handler API_DEPRECATED("Use microphoneEnabaled property", ios(9.0, 10.0));
iOS10将microphoneEnabled设置成了一个属性,也就是说在iOS9的时候,一旦录制开始是不支持设置语音的,在iOS10的时候可以录制的同时,通过set方法设置microphoneEnabled。
- (void)startRecordingWithHandler:(nullable void(^)(NSError * _Nullable error))handler API_AVAILABLE(ios(10.0), tvos(10.0));
iOS11又提供了另一个开始录制的方法:
- (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));
这个方法在录制过程中,会通过回调将录制的数据返回来。这里的sampleBuffer就是音频/视频数据,sampleBuffer是苹果对音视频数据的包装。
这里先提前申请一下,这两个代理都是在录制出问题的情况下才会回调。
iOS9和iOS10
- (void)screenRecorder:(RPScreenRecorder *)screenRecorder didStopRecordingWithError:(NSError *)error previewViewController:(nullable RPPreviewViewController *)previewViewController API_DEPRECATED("No longer supported", ios(9.0, 10.0), tvos(10.0,10.0));
iOS11是另外一个方法:
- (void)screenRecorder:(RPScreenRecorder *)screenRecorder didStopRecordingWithPreviewViewController:(nullable RPPreviewViewController *)previewViewController error:(nullable NSError *)error API_AVAILABLE(ios(11.0), tvos(11.0));
表面上看好像没什么分别。
还有一个代理方式是当录制不可用时会回调:
- (void)screenRecorderDidChangeAvailability:(RPScreenRecorder *)screenRecorder;
注意一下第一个代理什么情况下会回调,起初我以为停止录制即会回调,后来我试了我错了,后来我以为调用stopRecording才会回调,再次打脸。这个方法从代码名上来看还真就以为didStop后才会回调。再次看下文档描述:Called when recording has stopped due to an error.
,恍然大悟有么有。
为什么在iOS11改成了didStopRecordingWithPreviewViewController呢?didStopRecordingWithError不是很合理吗?这个方法本来就是录屏出错停止的时候回调的。不出错是不会回调的。
那这么看来这个RPScreenRecorderDelegate基本上没有什么卵用,毕竟我们录屏的时候都不希望它出错。
应用内录制功能答题步骤:
1、获取屏幕录制单例对象,检测设备是否支持屏幕录制;
2、如果支持,则开始录制,设置是否同时录制音频;
3、处理开启录制结果
4、结束录制
要做的最多的可能就是兼容的问题,在iOS9和iOS10或者iOS11的兼容性问题。
到这里,应该大概知道App内录制功能了,使用RPScreenRecorder这个类进行start,开始录制,stop结束录制,在应用内录屏,这个就可以实现了。
这里用一个按钮实现iOS11录制和结束录制,功能代码如下:
- (void)statrButtonClick:(UIButton *)sender {
//停止录屏
if ([[RPScreenRecorder sharedRecorder] isRecording]) {
if (@available(iOS 11.0, *)) {
[[RPScreenRecorder sharedRecorder] stopCaptureWithHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"stopCaptureWithHandler:%@", error.localizedDescription);
} else {
NSLog(@"CaptureWithHandlerStoped");
}
}];
} else {
// Fallback on earlier versions
}
}
//开始录屏
else {
if (@available(iOS 11.0, *))
{
//打开录制音频
[[RPScreenRecorder sharedRecorder] setMicrophoneEnabled:YES];
//开始录屏
[[RPScreenRecorder sharedRecorder] startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
if (error) {
NSLog(@"startCaptureWithHandler:error:%@", error.localizedDescription);
}
else {
//这个block会实时回调录制数据,录制数据是以sampleBuffer的形式被回调。bufferType是数据类型。
}
} completionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"completionHandler:error:%@", error.localizedDescription);
}
}];
} else {
// Fallback on earlier versions
}
}
}
使用startCapture和stopCapture或者startRecording和stopRecording都可以的,配对使用。
在开始录屏后,可以开始推流,回调拿到录屏数据后,用推流的SDK直接将数据进行推流。这样就实现了应用内录屏直播的功能了。
这个类是不能实现应用外录屏的,即不能录制整个手机屏幕,录制整个App的话还需要应用扩展程序,Broadcast Upload Extension和Broadcast Setup UI Extension。
在用RPScreenRecorder录屏结束后,在RPScreenRecorderDelegate的回调方法中,会回调一个RPPreviewViewController的对象。
这个类是继承自UIViewController的,是用来展示/分享RPScreenRecorder录制结束后的视频的。看看文档的描述:View controller that allows the user to preview/edit a movie recorded with ReplayKit. Passed into the completion handler supplied to [RPScreenRecorder stopRecordingWithHandler:].
这个描述直接告诉我们:1、这个类是用来让用户preview/edit录屏文件的。2、在stopRecording这个方法中使用该类,实现对录制结束后的视频进行preview或edit,那在stopCapture这个方法里行不行,当然也是不可以的,因为stopCapture这个方法不会回调previewViewController这个对象。
这么看来,startRecording/stopRecording和startCapture/stopCapture之间的关系和区别了。
相同点:都可以用来实现应用内录屏功能
不同点:startRecording/stopRecording这个对停止录屏后提供了数据可编辑的previewViewController,startCapture/stopCapture没有该功能;startCapture/stopCapture提供了录制时实时获取录屏数据的回调,startRecording/stopRecording只能是在结束后通过previewViewController来访问录屏数据。
[[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) {
NSLog(@"stop error : %@",error);
if (error) {
} else {
previewViewController.previewControllerDelegate = self;
[self presentViewController:previewViewController animated:YES completion:^{
}];
}
}];
在stopRecording后设置previewViewController.previewControllerDelegate = self;,然后将previewViewController显示出来,点击cancel按钮回调第一个代理方法,点击save回掉第二个代理方法。
#pragma mark - RPPreviewViewControllerDelegate
- (void)previewControllerDidFinish:(RPPreviewViewController *)previewController {
//在这里需要将previewController dismiss掉
[previewController dismissViewControllerAnimated:YES completion:^{
}];
}
- (void)previewController:(RPPreviewViewController *)previewController didFinishWithActivityTypes:(NSSet *)activityTypes {
//在这里需要将previewController dismiss掉
[previewController dismissViewControllerAnimated:YES completion:^{
}];
if ([activityTypes containsObject:@"com.apple.UIKit.activity.SaveToCameraRoll"]) {
NSLog(@"保存到相册成功");
}
}
这个activityTypes是做什么用的?这里牵扯到了UIActivity这个类。
RPBroadcastExtension和RPBroadcast初看好像完全不知道做什么用的。RPBroadcastExtension看来是对RPBroadcast的一个扩展类。看看里面都有哪些东西吧先。
RPBroadcast是iOS10推出的类。里面定义了一个RPBroadcastActivityViewController、RPBroadcastActivityViewControllerDelegate、RPBroadcastController、RPBroadcastControllerDelegate。看起来RPBroadcastActivityViewController是继承自RPBroadcastController,其实不然。
继承自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);
这个怎么玩?这样?
- (void)loadBroadcastActivityViewControllerAction:(UIButton *)sender {
RPBroadcastActivityViewController *vc = [[RPBroadcastActivityViewController alloc] init];
vc.view.backgroundColor = [UIColor whiteColor];
vc.delegate = self;
[self presentViewController:vc animated:YES completion:^{
}];
}
这样什么都实现不了啊?。看了下文档:Loads a RPBroadcastActivityViewController instance and returns it in the handler block.
Loads RPBroadcastActivityViewController这个实例,并返回该对象,果然不是上面那么玩的。
- (void)loadBroadcastActivityViewControllerAction:(UIButton *)sender {
[RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithHandler:^(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error) {
if (!error) {
broadcastActivityViewController.delegate = self;
broadcastActivityViewController.view.backgroundColor = [UIColor whiteColor];
[self presentViewController:broadcastActivityViewController animated:YES completion:^{
}];
}
}];
}
调用这个方法,会弹出一个窗,xxx正在直播,”此操作需要直播应用。请在App Store中查找相关应用。点击“查找直播应用”,跳到了App Store里。
然鹅,接下来怎么玩?下载个直播应用吧。我没下载直接返回了,结果这个broadcastActivityViewController还在,无法操作界面了…
这个我后来才知道原来是这么玩的,再看了下文档
view controller will present the user with a list of broadcast services available on the device and allow the user to set up a broadcast with the service through the service's UI.
这个broadcastActivityViewController将会把在手机上实现了broadcast UI的App搜罗出来,显示在broadcastActivityViewController上,提供用户直播用。也就是手机上哪个App实现了Broadcast Setup UI Extension,这个App就会出现在broadcastActivityViewController的列表里。
如果手机里没有应用实现Broadcast Setup UI Extension,则会弹出来前面我们看到的弹窗xxx正在直播,”此操作需要直播应用。请在App Store中查找相关应用。点击“查找直播应用”。
Broadcast Setup UI Extension是做什么用的,这里先不说了。
注意:这里有个问题需要说明一下,如果跳转App Store,再返回来,或者是打开了Broadcast Setup UI Extension App的列表,点击取消,返回到当前界面时,broadcastActivityViewController没法消失了,这个怎么处理。
看了一下下面的文档:
The RPBroadcastActivityViewController can be presented using [UIViewController presentViewController:animated:completion:] and should be dismissed when the delegate's broadcastActivityViewController:didFinishWithBroadcastController:error: method is called. Note that on large screen devices such as iPad, the default presentation style for view controllers is a popover. For an instance of RPBroadcastActivityViewController to present properly on iPad, it needs to have its popoverPresentationController's sourceRect and sourceView configured.
里面说了broadcastActivityViewController的用法。被present出来展示App类表用的,需要在broadcastActivityViewController:didFinishWithBroadcastController:error:代理方法中dismiss掉。
我试了试,这个代理方法不会被执行这个为什么?
这里需要这样处理,才可以:
- (void)loadBroadcastActivityViewControllerAction:(UIButton *)sender {
[RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithHandler:^(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error) {
if (!error) {
broadcastActivityViewController.delegate = self;
broadcastActivityViewController.view.backgroundColor = [UIColor whiteColor];
self.broadcastActivityViewController = broadcastActivityViewController;
[self presentViewController:broadcastActivityViewController animated:YES completion:^{
}];
}
}];
}
将broadcastActivityViewController保存成全局变量,
@property (nonatomic, weak) RPBroadcastActivityViewController *broadcastActivityViewController;
这样broadcastActivityViewController:didFinishWithBroadcastController:error:这个代理方法才会被执行,在代理方法中做dismiss操作。
#pragma mark - RPBroadcastActivityViewControllerDelegate
- (void)broadcastActivityViewController:(RPBroadcastActivityViewController *)broadcastActivityViewController didFinishWithBroadcastController:(nullable RPBroadcastController *)broadcastController error:(nullable NSError *)error API_AVAILABLE(ios(10.0), tvos(10.0)) {
NSLog(@"%s %@",__FUNCTION__,error);
}
这个代理方法中又牵扯出来了另一个类broadcastController。
broadcastController An RPBroadcastController instance that can be used to start and stop broadcasts to a user selected service.
看了看present出来的broadcastActivityViewController,它的上面还有一个controller,显示集成Broadcast Setup UI Extension了的App列表,这个controller就是回调方法中的broadcastController,它随着broadcastActivityViewController而来。
#pragma mark - RPBroadcastActivityViewControllerDelegate
- (void)broadcastActivityViewController:(RPBroadcastActivityViewController *)broadcastActivityViewController didFinishWithBroadcastController:(nullable RPBroadcastController *)broadcastController error:(nullable NSError *)error API_AVAILABLE(ios(10.0), tvos(10.0)) {
__weak typeof(self)weakSelf = self;
[broadcastActivityViewController dismissViewControllerAnimated:YES completion:^{
weakSelf.broadcastActivityViewController = nil;
}];
NSLog(@"%s %@",__FUNCTION__,error);
}
这样处理了一下,又发现,dismiss的时候,broadcastController小时的很慢,感觉主线程被卡主了一样。正是坑,原来这个方法的回调回调到了子线程中。
我们需要将dismiss放在主线程处理就好了:
#pragma mark - RPBroadcastActivityViewControllerDelegate
- (void)broadcastActivityViewController:(RPBroadcastActivityViewController *)broadcastActivityViewController didFinishWithBroadcastController:(nullable RPBroadcastController *)broadcastController error:(nullable NSError *)error API_AVAILABLE(ios(10.0), tvos(10.0)) {
NSLog(@"%s %@",__FUNCTION__,error);
dispatch_async(dispatch_get_main_queue(), ^{
__weak typeof(self)weakSelf = self;
[broadcastActivityViewController dismissViewControllerAnimated:YES completion:^{
weakSelf.broadcastActivityViewController = nil;
}];
});
}
继承自NSObject。
里面提供了一些方法,startBroadcastWithHandler、pauseBroadcast、resumeBroadcast、finishBroadcastWithHandler
是不是很像录屏开始、暂停、恢复、结束。做什么用的,这里先不用管了。
…略
…略
这个Extension中扩展了几个方法,这个注意一下:- (void)completeRequestWithBroadcastURL:(NSURL *)broadcastURL setupInfo:(nullable NSDictionary
定义了一个类RPBroadcastHandler。
定义了一下继承自RPBroadcastHandler的RPBroadcastMP4ClipHandler类。
定义了RPBroadcastSampleHandler继承自RPBroadcastHandler。
这里不知道这个是做什么的,先看看行了。