iOS录屏直播(一)初识ReplayKit

									Morris_
									2019.05.08

本篇主要功能:

  • 认识ReplayKit框架
  • RPScreenRecorder实现在应用内录屏功能
  • RPPreviewViewController查看录屏内容
  • RPBroadcastActivityViewController获取可录屏直播的App列表
  • 初识Broadcast Setup UI Extension

ReplayKit是苹果在iOS9.0,tvos10.0提功能一个支持录屏功能的库,在目前感觉仍然不是很成熟,但是对于录屏和录屏直播的支持在功能上是完整的。

在iOS9只是提供了RPScreenRecorder、RPPreviewViewController、RPError这几个类,RPScreenRecorder可以实现应用内录屏功能,包括语音的录制。RPPreviewViewController则可以实现对录制内容的展示、保存等操作。

在iOS10又新增了RPBroadcast和RPBroadcastExtension,来达到应用外录屏的功能支持。在之后的每个版本ReplayKit这个库都有更新,废弃了些Api,替换了新的Api。

以下是该库下的所有类:

#import 
#import 
#import 
#import 
#import 

一、RPError

此框架下定义的录屏错误类,定义了一个RPRecordingErrorCode的枚举,列举了录屏出现的错误码。


二、RPScreenRecorder

这个类是一个单例对象,是苹果提供的可以在应用内对App进行屏幕数据和银屏数据进行录制的类。

看完这个类,大体上应该知道在应用内机型屏幕录制、采集语音、停止录制这些功能。

1、Api

iOS9

iOS9提供了如下Api开始录制,录制时设置是否同时录制音频。

- (void)startRecordingWithMicrophoneEnabled:(BOOL)microphoneEnabled handler:(nullable void(^)(NSError * _Nullable error))handler API_DEPRECATED("Use microphoneEnabaled property", ios(9.0, 10.0));

iOS10

iOS10将microphoneEnabled设置成了一个属性,也就是说在iOS9的时候,一旦录制开始是不支持设置语音的,在iOS10的时候可以录制的同时,通过set方法设置microphoneEnabled。

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

iOS11

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是苹果对音视频数据的包装。

  • 在iOS11以前,可以在应用内录屏,但不能实时获取录制的数据,在iOS11才可以。
  • iOS10还提供了cameraEnabled、cameraPosition、cameraPreviewView这些属性。
  • iOS9和iOS10的停止录制的方法一样,在iOS11的时候,开始和录制的方法都是新提供的。

2、RPScreenRecorderDelegate

这里先提前申请一下,这两个代理都是在录制出问题的情况下才会回调。

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基本上没有什么卵用,毕竟我们录屏的时候都不希望它出错。

3、总结

应用内录制功能答题步骤:

1、获取屏幕录制单例对象,检测设备是否支持屏幕录制;

2、如果支持,则开始录制,设置是否同时录制音频;

3、处理开启录制结果

4、结束录制

要做的最多的可能就是兼容的问题,在iOS9和iOS10或者iOS11的兼容性问题。

4、代码

到这里,应该大概知道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。


三、RPPreviewViewController

在用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这个类。


四、RPBroadcast

RPBroadcastExtension和RPBroadcast初看好像完全不知道做什么用的。RPBroadcastExtension看来是对RPBroadcast的一个扩展类。看看里面都有哪些东西吧先。

RPBroadcast是iOS10推出的类。里面定义了一个RPBroadcastActivityViewController、RPBroadcastActivityViewControllerDelegate、RPBroadcastController、RPBroadcastControllerDelegate。看起来RPBroadcastActivityViewController是继承自RPBroadcastController,其实不然。

1、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);

这个怎么玩?这样?

- (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操作。

2、RPBroadcastActivityViewControllerDelegate

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

3、RPBroadcastController

继承自NSObject。

里面提供了一些方法,startBroadcastWithHandler、pauseBroadcast、resumeBroadcast、finishBroadcastWithHandler

是不是很像录屏开始、暂停、恢复、结束。做什么用的,这里先不用管了。

…略

4、RPBroadcastControllerDelegate

…略

五、RPBroadcastExtension

这个Extension中扩展了几个方法,这个注意一下:- (void)completeRequestWithBroadcastURL:(NSURL *)broadcastURL setupInfo:(nullable NSDictionary *> *)setupInfo

定义了一个类RPBroadcastHandler。

定义了一下继承自RPBroadcastHandler的RPBroadcastMP4ClipHandler类。

定义了RPBroadcastSampleHandler继承自RPBroadcastHandler。

这里不知道这个是做什么的,先看看行了。

你可能感兴趣的:(#,ReplayKit,#,周报)