【iOS】ReplayKit库,iOS原生直播神器

版权声明:本文为博主原创,如需转载请注明出处。

前言

ReplayKit 是WWDC15推出的苹果原生录屏 API。在iOS9的时候主要提供的是录屏,录制完成后可以进行查看、编辑、通过指定方式分享出去。

在WWDC16上新版的 ReplayKit 提出了了 live 功能,简单说就是通过 ReplayKit 可以进行录屏直播。这对于苹果的手游直播行业有着很重要的意义。

首先给出视频地址和API文档

简单测试

弹出可以接收广播的服务列表

新建工程,然后加入ReplayKit.frameword

添加一个按钮,然后按钮点击事件弹出广播服务的列表:

1
2
3
4
5
6
- (IBAction)displayServiceViewController:(id)sender {
[RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithHandler:^(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error) {
broadcastActivityViewController.delegate = self;
[self presentViewController:broadcastActivityViewController animated:YES completion:nil];
}];
}
![](http://ww1.sinaimg.cn/mw690/a9c4d5f6gw1f805jduzhvj20ku112jsf.jpg)

Mobcrush

所以想要直播的游戏本身添加这样一个逻辑,弹出服务列表即可。而直播软件也只需要注册为直播服务,就可以直播任何支持的游戏,软件。国外最火最先支持的就是示例中左边的Mobcrush,官网,里面有手机游戏Tower Dash的直播,就是使用这个技术实现的,Tower Dash游戏直播页面为 - 这里这里,需要科学上网。

我录制了动态图展示:

![](http://ww3.sinaimg.cn/mw690/a9c4d5f6gw1f8066cxc9eg20dc08uqv7.gif)

动态图中可以看出支持摄像头录制,当然还有麦克风,这些已经满足了日常主播的基本需求。

国内

国内现在映客直播安装就直接有注册为广播服务,所以截图中列表里就有。我还安装了熊猫TV,虎牙直播,虎牙助手,虎牙手游。熊猫TV的主播权限还没有申请下来。虎牙手游貌似使用的也是这个技术,但是实现不一样,虎牙手游直接是在虎牙手游APP内部打开直播,提示成功之后,直接就进入了录屏模式,然后退出返回到手游界面开始游戏就可以。查看了虎牙直播平台,已经有主播使用了iPhone7进行王者荣耀直播,熊猫TV暂时还没有看到用iPhone直播手游的。

WWDC 2016

下面是观看WWDC16 记录的知识片段。

ReplayKit

新特性:

  • Apple TV support
  • Live Broadcasting 直播广播,这个很有用,就是要研究的直播功能
  • 可以记录 Face Time摄像头的内容,增强了麦克风记录API
![](http://ww2.sinaimg.cn/mw690/a9c4d5f6gw1f7xzlp5yiij212i0j4q8w.jpg)

录播功能

对于录播功能之前就已经有了一个典型的demo,可以直接看下面代码,放进原始功能的第一个viewcontroller里面就Ok了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425


#import "ViewController.h"
#import <ReplayKit/ReplayKit.h>

static NSString *StartRecord = @"开始";
static NSString *StopRecord = @"结束";

#if TARGET_IPHONE_SIMULATOR
#define SIMULATOR 1
#elif TARGET_OS_IPHONE
#define SIMULATOR 0
#endif

#define AnimationDuration (0.3)


@interface ViewController () <RPPreviewViewControllerDelegate>
{

}
@property (nonatomic, strong)UIButton *btnStart;
@property (nonatomic, strong)UIButton *btnStop;
@property (nonatomic, strong)NSTimer *progressTimer;
@property (nonatomic, strong)UIProgressView *progressView;
@property (nonatomic, strong)UIActivityIndicatorView *activity;
@property (nonatomic, strong)UIView *tipView;
@property (nonatomic, strong)UILabel *lbTip;
@property (nonatomic, strong)UILabel *lbTime;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

}

- (void)viewDidAppear:(BOOL)animated {
BOOL isVersionOk = [self isSystemVersionOk];

if (!isVersionOk) {
NSLog(@"系统版本需要是iOS9.0及以上才支持ReplayKit");
return;
}
if (SIMULATOR) {
[self showSimulatorWarning];
return;
}

UILabel *lb = nil;
CGSize screenSize = [UIScreen mainScreen].bounds.size;


//标题
lb = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 140)];
lb.font = [UIFont boldSystemFontOfSize:32];
lb.backgroundColor = [UIColor clearColor];
lb.textColor = [UIColor blackColor];
lb.textAlignment = NSTextAlignmentCenter;
lb.numberOfLines = 3;
lb.text = @"苹果ReplayKit Demo";
lb.center = CGPointMake(screenSize.width/2, 80);
[self.view addSubview:lb];

//创建按钮
UIButton *btn = [self createButtonWithTitle:StartRecord andCenter:CGPointMake(screenSize.width/2 - 100, 200)];
[self.view addSubview:btn];
self.btnStart = btn;

btn = [self createButtonWithTitle:StopRecord andCenter:CGPointMake(screenSize.width/2 + 100, 200)];
[self.view addSubview:btn];
self.btnStop = btn;
[self setButton:btn enabled:NO];

//loading指示
UIActivityIndicatorView *activity = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 280, 80)];
[self.view addSubview:view];
view.backgroundColor = [UIColor redColor];
view.layer.cornerRadius = 8.0f;
view.center = CGPointMake(screenSize.width/2, 300);
activity.center = CGPointMake(30, view.frame.size.height/2);
[view addSubview:activity];
[activity startAnimating];
self.activity = activity;
lb = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 280, 80)];
lb.font = [UIFont boldSystemFontOfSize:20];
lb.backgroundColor = [UIColor clearColor];
lb.textColor = [UIColor blackColor];
lb.layer.cornerRadius = 4.0;
lb.textAlignment = NSTextAlignmentCenter;
[view addSubview:lb];
self.lbTip = lb;
self.tipView = view;
[self hideTip];


//显示时间(用于看录制结果时能知道时间)
lb = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 40)];
lb.font = [UIFont boldSystemFontOfSize:20];
lb.backgroundColor = [UIColor redColor];
lb.textColor = [UIColor blackColor];
lb.layer.cornerRadius = 4.0;
NSDateFormatter * dateFormat = [[NSDateFormatter alloc] init] ;
[dateFormat setDateFormat: @"HH:mm:ss"];
NSString *dateString = [dateFormat stringFromDate:[NSDate date]];
lb.text = dateString;
lb.center = CGPointMake(screenSize.width/2, screenSize.height/2 + 100);
lb.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:lb];
self.lbTime = lb;

//进度条 (显示动画,不然看不出画面的变化)
UIProgressView *progress = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width*0.8, 10)];
progress.center = CGPointMake(screenSize.width/2, screenSize.height/2 + 150);
progress.progressViewStyle = UIProgressViewStyleDefault;
progress.progress = 0.0;
[self.view addSubview:progress];
self.progressView = progress;

//计时器
//更新时间
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:@selector(updateTimeString)
userInfo:nil
repeats:YES];
}

#pragma mark - UI控件
//显示 提示信息
- (void)showTipWithText:(NSString *)tip activity:(BOOL)activity{
[self.activity startAnimating];
self.lbTip.text = tip;
self.tipView.hidden = NO;
if (activity) {
self.activity.hidden = NO;
[self.activity startAnimating];
} else {
[self.activity stopAnimating];
self.activity.hidden = YES;
}
}
//隐藏 提示信息
- (void)hideTip {
self.tipView.hidden = YES;
[self.activity stopAnimating];
}

//创建按钮
- (UIButton *)createButtonWithTitle:(NSString *)title andCenter:(CGPoint)center {

CGRect rect = CGRectMake(0, 0, 160, 60);
UIButton *btn = [[UIButton alloc] initWithFrame:rect];
btn.layer.cornerRadius = 5.0;
btn.layer.borderWidth = 2.0;
btn.layer.borderColor = [[UIColor blackColor] CGColor];
btn.backgroundColor = [UIColor lightGrayColor];
btn.center = center;
[btn setTitle:title forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(onBtnPressed:) forControlEvents:UIControlEventTouchDown];
return btn;

}

//设置按钮是否可点击
- (void)setButton:(UIButton *)button enabled:(BOOL)enabled {
if (enabled) {
button.alpha = 1.0;
} else {
button.alpha = 0.2;
}
button.enabled = enabled;
}

//提示不支持模拟器
- (void)showSimulatorWarning {
UIAlertAction *actionOK = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){

}];
UIAlertAction *actionCancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){

}];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"ReplayKit不支持模拟器" message:@"请使用真机运行这个Demo工程" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:actionCancel];
[alert addAction:actionOK];

[self presentViewController:alert animated:NO completion:nil];
}

//显示弹框提示
- (void)showAlert:(NSString *)title andMessage:(NSString *)message {
if (!title) {
title = @"";
}
if (!message) {
message = @"";
}
UIAlertAction *actionCancel = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleCancel handler:nil];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:actionCancel];
[self presentViewController:alert animated:NO completion:nil];
}

//显示视频预览页面,animation=是否要动画显示
- (void)showVideoPreviewController:(RPPreviewViewController *)previewController withAnimation:(BOOL)animation {

__weak ViewController *weakSelf = self;

//UI需要放到主线程
dispatch_async(dispatch_get_main_queue(), ^{

CGRect rect = [UIScreen mainScreen].bounds;

if (animation) {

rect.origin.x += rect.size.width;
previewController.view.frame = rect;
rect.origin.x -= rect.size.width;
[UIView animateWithDuration:AnimationDuration animations:^(){
previewController.view.frame = rect;
} completion:^(BOOL finished){

}];

} else {
previewController.view.frame = rect;
}

[weakSelf.view addSubview:previewController.view];
[weakSelf addChildViewController:previewController];


});

}

//关闭视频预览页面,animation=是否要动画显示
- (void)hideVideoPreviewController:(RPPreviewViewController *)previewController withAnimation:(BOOL)animation {

//UI需要放到主线程
dispatch_async(dispatch_get_main_queue(), ^{

CGRect rect = previewController.view.frame;

if (animation) {

rect.origin.x += rect.size.width;
[UIView animateWithDuration:AnimationDuration animations:^(){
previewController.view.frame = rect;
} completion:^(BOOL finished){
//移除页面
[previewController.view removeFromSuperview];
[previewController removeFromParentViewController];
}];

} else {
//移除页面
[previewController.view removeFromSuperview];
[previewController removeFromParentViewController];
}
});
}

#pragma mark - 按钮 回调
//按钮事件
- (void)onBtnPressed:(UIButton *)sender {

//点击效果
sender.transform = CGAffineTransformMakeScale(0.8, 0.8);
float duration = 0.3;
[UIView animateWithDuration:duration
animations:^{
sender.transform = CGAffineTransformMakeScale(1.1, 1.1);
}completion:^(BOOL finish){
[UIView animateWithDuration:duration
animations:^{
sender.transform = CGAffineTransformMakeScale(1.0, 1.0);
}completion:^(BOOL finish){ }];
}];

NSString *function = sender.titleLabel.text;
if ([function isEqualToString:StartRecord]) {
[self startRecord];
}
else if ([function isEqualToString:StopRecord]) {
[self stopRecord];
}
}


- (void)startRecord {

// [self setButton:self.btnStart enabled:NO];

NSLog(@"ReplayKit只支持真机录屏,支持游戏录屏,不支持录avplayer播放的视频");
NSLog(@"检查机器和版本是否支持ReplayKit录制...");
if ([[RPScreenRecorder sharedRecorder] isAvailable]) {
NSLog(@"支持ReplayKit录制");
} else {
NSLog(@"!!不支持支持ReplayKit录制!!");
return;
}

__weak ViewController *weakSelf = self;

NSLog(@"%@ 录制", StartRecord);
[self showTipWithText:@"录制初始化" activity:YES];


[[RPScreenRecorder sharedRecorder] startRecordingWithHandler:^(NSError *error){
NSLog(@"录制开始...");
[weakSelf hideTip];
if (error) {
NSLog(@"错误信息 %@", error);
[weakSelf showTipWithText:error.description activity:NO];
} else {
//其他处理
[weakSelf setButton:self.btnStop enabled:YES];
[weakSelf setButton:self.btnStart enabled:NO];

[weakSelf showTipWithText:@"正在录制" activity:NO];
//更新进度条
weakSelf.progressTimer = [NSTimer scheduledTimerWithTimeInterval:0.05f
target:self
selector:@selector(changeProgressValue)
userInfo:nil
repeats:YES];
}
}];

}

- (void)stopRecord {
NSLog(@"%@ 录制", StopRecord);

[self setButton:self.btnStart enabled:YES];
[self setButton:self.btnStop enabled:NO];

__weak ViewController *weakSelf = self;
[[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController *previewViewController, NSError * error){


if (error) {
NSLog(@"失败消息:%@", error);
[weakSelf showTipWithText:error.description activity:NO];
} else {

[weakSelf showTipWithText:@"录制完成" activity:NO];

//显示录制到的视频的预览页
NSLog(@"显示预览页面");
previewViewController.previewControllerDelegate = weakSelf;

//去除计时器
[weakSelf.progressTimer invalidate];
weakSelf.progressTimer = nil;

[self showVideoPreviewController:previewViewController withAnimation:YES];
}
}];
}

#pragma mark - 视频预览页面 回调
//关闭的回调
- (void)previewControllerDidFinish:(RPPreviewViewController *)previewController {
[self hideVideoPreviewController:previewController withAnimation:YES];
}

//选择了某些功能的回调(如分享和保存)
- (void)previewController:(RPPreviewViewController *)previewController didFinishWithActivityTypes:(NSSet <NSString *> *)activityTypes {

__weak ViewController *weakSelf = self;
if ([activityTypes containsObject:@"com.apple.UIKit.activity.SaveToCameraRoll"]) {

dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showAlert:@"保存成功" andMessage:@"已经保存到系统相册"];
});
}
if ([activityTypes containsObject:@"com.apple.UIKit.activity.CopyToPasteboard"]) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showAlert:@"复制成功" andMessage:@"已经复制到粘贴板"];
});
}
}

#pragma mark - 计时器 回调

//改变进度条的显示的进度
- (void)changeProgressValue {
float progress = self.progressView.progress + 0.01;
[self.progressView setProgress:progress animated:NO];
if (progress >= 1.0) {
self.progressView.progress = 0.0;
}
}

//更新显示的时间
- (void)updateTimeString {
NSDateFormatter * dateFormat = [[NSDateFormatter alloc] init] ;
[dateFormat setDateFormat: @"HH:mm:ss"];
NSString *dateString = [dateFormat stringFromDate:[NSDate date]];
self.lbTime.text = dateString;
}

#pragma mark - 其他
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

//判断对应系统版本是否支持ReplayKit
- (BOOL)isSystemVersionOk {
if ([[UIDevice currentDevice].systemVersion floatValue] < 9.0) {
return NO;
} else {
return YES;
}
}

@end

新博客文章地址:ReplayKit库,iOS原生直播神器
CSDN文章地址:ReplayKit库,iOS原生直播神器