├── LICENSE
├── README.md
├── ScreenRecorderKit.podspec
└── ScreenRecorderKit
└── Classes
├── RPPreviewViewController+MovieURL.h
├── RPPreviewViewController+MovieURL.m
├── SRErrorHandle.h
├── SRErrorHandle.m
├── SRErrorInfo.h
├── SRErrorInfo.m
├── ScreenRecordManager.h
└── ScreenRecordManager.m
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](http://www.bbc6bae9.com/)
6 |
7 | # ScreenRecorderKit
8 |
9 | ## 注意
10 | 现已支持cocoasPods
11 | ```objective-c
12 | pod "ScreenRecorderKit"
13 | ```
14 |
15 | * 注意在开始录屏之前要先获取“图片”的权限,否则回导致,所有接口回调都会正常,但是回调方法中的NSData为空。后续的将权限的判断和索取加入到框架中
16 | * 如果最近有时间的画也会把录屏直播的东西加入进来
17 |
18 | ## 介绍
19 |
20 | ScreenRecorderKit是一个基于ReplayKit封装的轻量级录屏框架。
21 | WWDC关于Replaykit的部分参考:[Go Live with ReplayKit](https://developer.apple.com/videos/play/wwdc2016/601/)
22 | 如果直接使用官方的ReplayKit,是没有办法直接获取录制视频的二进制数据的,而是会生成一个预览界面由用户选择保存相册或者是分享,这样的话就很难满足一些业务上的需求,ScreenRecorderKit先把视频存储到相册,然后再从相册中取视频资源转成二进制数据,方便业务上做处理。
23 |
24 | 本框架帮助开发者以一种更简单的方式处理App间的录屏冲突,App与系统之间的录屏冲突,以及其他异常的提示。并且可以保存到系统相册,代理方法里面可以获取到录屏视频的NSData数据,方便沙盒存储或者上传服务器。
25 |
26 | ## 特点
27 |
28 | * 轻松的处理App与App的录屏冲突、App与系统录屏的冲突、
29 | * 录屏中的异常问题。
30 | * 开始和结束录屏的方法均有成功和失败的回调。
31 |
32 | ## 使用
33 |
34 | 1. 引用
35 | 在你需要调用录屏功能的地方`ScreenRecordManager.h`
36 |
37 | 设置 ScreenRecordDelegate 代理`[ScreenRecordManager shareManager];
38 | mgr.screenRecordDelegate = self;`
39 |
40 | 2. 开始和结束方法
41 |
42 | 开始录制
43 |
44 | ```objective-c
45 | [[ScreenRecordManager shareManager] screenRecSuc:^{
46 |
47 | NSLog(@"录制启动成功");
48 | [weakself showToast:@"录制启动成功"];
49 |
50 | } failure:^(DUErrorHandle *error) {
51 |
52 | NSLog(@"%@", error.msg);
53 | [weakself showToast: error.msg];
54 |
55 | }];
56 | ```
57 |
58 |
59 | 结束录制
60 |
61 | ```objective-c
62 | [[ScreenRecordManager shareManager] stopRecSuc:^{
63 |
64 |
65 |
66 | } failure:^(DUErrorHandle *error) {
67 |
68 | if (error.code == -2) { // -2是没有可以结束的录屏幕的进程
69 |
70 | }else{
71 |
72 | [weakself showToast: error.msg];
73 |
74 | }
75 |
76 | }];
77 | ```
78 |
79 | 3. 代理方法
80 | `ScreenRecordManager`的录制状态发生了改变
81 |
82 | ```objective-c
83 | -(void)recStateDidChange:(RecState)state withError:(NSError *)error{
84 | if (error) {
85 |
86 | [self showToast:[error localizedDescription]];
87 |
88 | }else{
89 |
90 | if (state == RecState_Rec) {
91 |
92 | self.recBtn.selected = YES;
93 |
94 | }else{
95 |
96 | [self showToast:@"录屏结束"];
97 | self.recBtn.selected = NO;
98 |
99 | }
100 |
101 | }
102 |
103 | }
104 | ```
105 |
106 | `ScreenRecordManager`保存录屏视频到相册成功的代理方法
107 |
108 | ```objective-c
109 | -(void)savedPhotoImage:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
110 |
111 | if(error){
112 |
113 | [self showToast:[error localizedDescription]];
114 |
115 | }else{
116 |
117 | [self showToast:@"保存相册成功"];
118 |
119 | }
120 |
121 | }
122 | ```
123 |
124 | `ScreenRecordManager`录屏视频的二进制文件获取,在这里你可以选择将二进制数转成.mp4文件或者将数据上传至服务器等等。
125 |
126 | ```objective-c
127 | -(void)savedVideoData:(NSData *)data didFinishSavingWithError:(BOOL)isError{
128 |
129 | if (isError) {
130 |
131 | [self showToast:@"生成二进制文件错误"];
132 |
133 | }else{
134 |
135 | [self showToast:@"生成二进制文件成功"];
136 |
137 | }
138 |
139 | }
140 | ```
141 |
142 |
143 |
144 | ## Requirements
145 |
146 | * **iOS 9.0** and up
147 | * **Xcode 7.0** and up
148 |
149 | ## Installation
150 |
151 | ScreenRecorderKit is not available through [CocoaPods](http://cocoapods.org) currently.
152 |
153 | ## Author
154 |
155 | Huanghong, chinahuanghong@gmail.com
156 |
157 | ## Apps Integrated
158 |
159 | * [夺镖体育](https://itunes.apple.com/cn/app/%E5%A4%BA%E9%95%96/id1294273600?mt=8)
160 |
161 | ## License
162 |
163 | ScreenRecorderKit is available under the MIT license. See the LICENSE file for more info.
164 |
--------------------------------------------------------------------------------
/ScreenRecorderKit.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint ScreenRecorderKit.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see https://guides.cocoapods.org/syntax/podspec.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |spec|
10 |
11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
12 | #
13 | # These will help people to find your library, and whilst it
14 | # can feel like a chore to fill in it's definitely to your advantage. The
15 | # summary should be tweet-length, and the description more in depth.
16 | #
17 |
18 | spec.name = "ScreenRecorderKit"
19 | spec.version = "0.0.2"
20 | spec.summary = "ScreenRecorderKit is a delightful screen recorder framework based on ReplayeKit"
21 |
22 | # This description is used to generate tags and improve search results.
23 | # * Think: What does it do? Why did you write it? What is the focus?
24 | # * Try to keep it short, snappy and to the point.
25 | # * Write the description between the DESC delimiters below.
26 | # * Finally, don't worry about the indent, CocoaPods strips it!
27 | spec.description = <<-DESC
28 | ScreenRecorderKit is a delightful screen recorder framework based on ReplayeKit. Hope you like it.
29 | DESC
30 |
31 | spec.homepage = "http://www.bbc6bae9.com/2019/03/11/hahah/"
32 | # spec.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"
33 |
34 |
35 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
36 | #
37 | # Licensing your code is important. See https://choosealicense.com for more info.
38 | # CocoaPods will detect a license file if there is a named LICENSE*
39 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
40 | #
41 |
42 | spec.license = "MIT"
43 | # spec.license = { :type => "MIT", :file => "FILE_LICENSE" }
44 |
45 |
46 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
47 | #
48 | # Specify the authors of the library, with email addresses. Email addresses
49 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
50 | # accepts just a name if you'd rather not provide an email address.
51 | #
52 | # Specify a social_media_url where others can refer to, for example a twitter
53 | # profile URL.
54 | #
55 |
56 | spec.author = { "BBC6BAE9" => "chinahuanghong@gmail.com" }
57 | # Or just: spec.author = "BBC6BAE9"
58 | # spec.authors = { "BBC6BAE9" => "chinahuanghong@gmail.com" }
59 | # spec.social_media_url = "https://twitter.com/BBC6BAE9"
60 |
61 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
62 | #
63 | # If this Pod runs only on iOS or OS X, then specify the platform and
64 | # the deployment target. You can optionally include the target after the platform.
65 | #
66 |
67 | # spec.platform = :ios
68 | # spec.platform = :ios, "5.0"
69 |
70 | # When using multiple platforms
71 | # spec.ios.deployment_target = "5.0"
72 | # spec.osx.deployment_target = "10.7"
73 | # spec.watchos.deployment_target = "2.0"
74 | # spec.tvos.deployment_target = "9.0"
75 |
76 |
77 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
78 | #
79 | # Specify the location from where the source should be retrieved.
80 | # Supports git, hg, bzr, svn and HTTP.
81 | #
82 |
83 | spec.source = { :git => "https://github.com/BBC6BAE9/ScreenRecorderKit.git", :tag => "#{spec.version}" }
84 |
85 |
86 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
87 | #
88 | # CocoaPods is smart about how it includes source code. For source files
89 | # giving a folder will include any swift, h, m, mm, c & cpp files.
90 | # For header files it will include any header in the folder.
91 | # Not including the public_header_files will make all headers public.
92 | #
93 |
94 | spec.source_files = "ScreenRecorderKit", "ScreenRecorderKit/**/*.{h,m}"
95 | spec.exclude_files = "Classes/Exclude"
96 |
97 | # spec.public_header_files = "Classes/**/*.h"
98 |
99 |
100 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
101 | #
102 | # A list of resources included with the Pod. These are copied into the
103 | # target bundle with a build phase script. Anything else will be cleaned.
104 | # You can preserve files from being cleaned, please don't preserve
105 | # non-essential files like tests, examples and documentation.
106 | #
107 |
108 | # spec.resource = "icon.png"
109 | # spec.resources = "Resources/*.png"
110 |
111 | # spec.preserve_paths = "FilesToSave", "MoreFilesToSave"
112 |
113 |
114 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
115 | #
116 | # Link your library with frameworks, or libraries. Libraries do not include
117 | # the lib prefix of their name.
118 | #
119 |
120 | # spec.framework = "SomeFramework"
121 | # spec.frameworks = "SomeFramework", "AnotherFramework"
122 |
123 | # spec.library = "iconv"
124 | # spec.libraries = "iconv", "xml2"
125 |
126 |
127 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
128 | #
129 | # If your library depends on compiler flags you can set them in the xcconfig hash
130 | # where they will only apply to your library. If you depend on other Podspecs
131 | # you can include multiple dependencies to ensure it works.
132 |
133 | # spec.requires_arc = true
134 |
135 | # spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
136 | # spec.dependency "JSONKit", "~> 1.4"
137 |
138 | end
139 |
--------------------------------------------------------------------------------
/ScreenRecorderKit/Classes/RPPreviewViewController+MovieURL.h:
--------------------------------------------------------------------------------
1 | //
2 | // RPPreviewViewController+MovieURL.h
3 | // hschefu-ios-staff
4 | //
5 | // Created by qiu on 2018/8/28.
6 | // Copyright © 2018年 CodeTeam. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface RPPreviewViewController (MovieURL)
12 |
13 | /**
14 | 获取录屏的URL
15 | */
16 | @property (nonatomic,strong) NSURL *movieURL;
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/ScreenRecorderKit/Classes/RPPreviewViewController+MovieURL.m:
--------------------------------------------------------------------------------
1 | //
2 | // RPPreviewViewController+MovieURL.m
3 | // hschefu-ios-staff
4 | //
5 | // Created by qiu on 2018/8/28.
6 | // Copyright © 2018年 CodeTeam. All rights reserved.
7 | //
8 |
9 | #import "RPPreviewViewController+MovieURL.h"
10 |
11 | @implementation RPPreviewViewController (MovieURL)
12 |
13 | @dynamic movieURL;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/ScreenRecorderKit/Classes/SRErrorHandle.h:
--------------------------------------------------------------------------------
1 | //
2 | // SRErrorHandle.h
3 | // Duobiao
4 | //
5 | // Created by huang on 2019/5/25.
6 | // Copyright © 2019 北京夺镖文化有限公司. All rights reserved.
7 | //
8 |
9 | #import "SRErrorInfo.h"
10 |
11 | @interface SRErrorHandle : SRErrorInfo
12 |
13 | - (instancetype)initWithError:(NSError *)error;
14 |
15 | @end
16 |
17 |
--------------------------------------------------------------------------------
/ScreenRecorderKit/Classes/SRErrorHandle.m:
--------------------------------------------------------------------------------
1 | //
2 | // SRErrorHandle.m
3 | // Duobiao
4 | //
5 | // Created by huang on 2019/5/25.
6 | // Copyright © 2019 北京夺镖文化有限公司. All rights reserved.
7 | //
8 |
9 | #import "SRErrorHandle.h"
10 |
11 | @implementation SRErrorHandle
12 |
13 | - (id)initWithDic:(NSDictionary *)dic {
14 | self = [super initWithDic:dic];
15 | if (self) {
16 |
17 | // if (code == 10042 || code == 10008 || code == 10012 || code == 10016) { // 未登录
18 | //
19 | // }else if(code == 10069){ // 账户被冻结
20 | //
21 | // }else if(code == 10004){
22 | // // 服务器暂时无法响应,请稍后再试
23 | // }
24 | }
25 | return self;
26 | }
27 |
28 | - (instancetype)initWithError:(NSError *)error {
29 | if (self = [super init]) {
30 | self.code = error.code;
31 |
32 | if (error.code == -5807) { // -5807的错误是因为上一个extension没有关闭没办法重新打开,经试验,在applicationWillTerminate中退出也不起作用,主要精力放在避免崩溃,而不是放在Replaykit处理这个异常
33 |
34 | self.msg = [NSString stringWithFormat:@"%@,请尝试重新启动手机解决", [error localizedDescription]];
35 |
36 | }else{
37 |
38 | self.msg = [error localizedDescription];
39 |
40 | }
41 |
42 | }
43 | return self;
44 | }
45 |
46 | @end
47 |
--------------------------------------------------------------------------------
/ScreenRecorderKit/Classes/SRErrorInfo.h:
--------------------------------------------------------------------------------
1 | //
2 | // SRErrorInfo.h
3 | // Duobiao
4 | //
5 | // Created by huang on 2019/5/25.
6 | // Copyright © 2019 北京夺镖文化有限公司. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface SRErrorInfo : NSObject
14 | {
15 | NSInteger code;
16 | NSString *msg;
17 | BOOL isError;
18 | }
19 |
20 | /**
21 | 错误码
22 | */
23 | @property (nonatomic, assign) NSInteger code;
24 |
25 | /**
26 | 错误信息
27 | */
28 | @property (nonatomic, copy) NSString *msg;
29 |
30 | /**
31 | 是否有错误
32 | */
33 | @property (nonatomic, assign) BOOL isError;
34 |
35 | - (id)initWithDic:(NSDictionary *)dic;
36 |
37 | - (instancetype)initWithDefault;
38 |
39 | @end
40 |
41 | NS_ASSUME_NONNULL_END
42 |
--------------------------------------------------------------------------------
/ScreenRecorderKit/Classes/SRErrorInfo.m:
--------------------------------------------------------------------------------
1 | //
2 | // SRErrorInfo.m
3 | // Duobiao
4 | //
5 | // Created by huang on 2019/5/25.
6 | // Copyright © 2019 北京夺镖文化有限公司. All rights reserved.
7 | //
8 |
9 | #import "SRErrorInfo.h"
10 |
11 | @implementation SRErrorInfo
12 | @synthesize code;
13 | @synthesize msg;
14 | @synthesize isError;
15 |
16 | - (id)initWithDic:(NSDictionary *)dic
17 | {
18 | if (self = [super init]) {
19 | self.code = [[dic objectForKey: @"error_code"] integerValue];
20 | self.msg = [dic objectForKey: @"error_msg"];
21 | if (self.code == 0) {
22 | isError = NO;
23 | }
24 | else{
25 | isError = YES;
26 | }
27 | }
28 | return self;
29 | }
30 |
31 | - (instancetype)initWithDefault
32 | {
33 | if (self = [super init]) {
34 | self.msg = @"一般错误";
35 | self.code = 1;
36 | }
37 | return self;
38 | }
39 |
40 |
41 | @end
42 |
--------------------------------------------------------------------------------
/ScreenRecorderKit/Classes/ScreenRecordManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // ScreenRecordManager.h
3 | // 录制屏幕框架
4 | //
5 | // Created by huang on 2019/5/24.
6 | // Copyright © 2019 huang. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "SRErrorHandle.h"
11 | #import "SRErrorInfo.h"
12 | #import
13 |
14 | NS_ASSUME_NONNULL_BEGIN
15 | // 录屏状态
16 | typedef NS_ENUM(NSInteger, RecState) {
17 |
18 | RecState_Rec = 0,
19 | RecState_Stop = 1
20 |
21 | };
22 | @protocol ScreenRecordDelegate
23 |
24 | @optional
25 |
26 | /**
27 | 保存到相册的代理方法
28 |
29 | @param image 路径,好像没什么用
30 | @param error 错误信息
31 | @param contextInfo 额外信息
32 | */
33 | - (void)savedPhotoImage:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;
34 |
35 | /**
36 | 生成视频的二进制文件
37 |
38 | @param data 二进制数据
39 | @param isError 是否有错误
40 | */
41 | - (void)savedVideoData:(NSData*)data didFinishSavingWithError:(BOOL)isError;
42 |
43 | /**
44 | 录制状态变化的代理方法
45 |
46 | @param state 状态
47 | @param error 错误信息
48 | */
49 | -(void)recStateDidChange:(RecState)state withError:(NSError *__nullable)error;
50 |
51 | @end
52 |
53 | typedef void(^srerrorinfo)(SRErrorHandle *error);
54 | @interface ScreenRecordManager : NSObject
55 |
56 | + (instancetype)shareManager;
57 |
58 | @property(nonatomic,weak)id screenRecordDelegate;
59 |
60 | /**
61 | 是否正在录制中
62 | */
63 | @property(nonatomic, assign) BOOL isRecording;
64 |
65 | /**
66 | 录制屏幕
67 |
68 | @param suc 成功回调
69 | @param errorInfo 错误信息
70 | */
71 | - (void)screenRecSuc:(void (^)(void))suc failure:(srerrorinfo)errorInfo;
72 |
73 | /**
74 | 停止录制屏幕
75 |
76 | @param suc 成功回调
77 | @param errorInfo 错误信息
78 | */
79 | - (void)stopRecSuc:(void (^)(void))suc failure:(srerrorinfo)errorInfo;
80 |
81 | @end
82 |
83 | NS_ASSUME_NONNULL_END
84 |
--------------------------------------------------------------------------------
/ScreenRecorderKit/Classes/ScreenRecordManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // ScreenRecordManager.m
3 | // 录制屏幕框架
4 | //
5 | // Created by huang on 2019/5/24.
6 | // Copyright © 2019 huang. All rights reserved.
7 | //
8 |
9 | #import "ScreenRecordManager.h"
10 | #import
11 | #import "RPPreviewViewController+MovieURL.h"
12 | #import
13 |
14 | #define kWeakSelf(type) __weak typeof(type) weak##type = type;
15 | #define kStrongSelf(type) __strong typeof(type) type = weak##type;
16 | @interface ScreenRecordManager()
17 |
18 | @end
19 |
20 | @implementation ScreenRecordManager
21 |
22 | static ScreenRecordManager *_screenManager = nil;
23 |
24 | + (instancetype)shareManager{
25 | if (_screenManager == nil) {
26 |
27 | _screenManager = [[ScreenRecordManager alloc] init];
28 |
29 | }
30 | return _screenManager;
31 | }
32 |
33 | + (instancetype)allocWithZone:(struct _NSZone *)zone{
34 |
35 | static dispatch_once_t onceToken;
36 |
37 | dispatch_once(&onceToken, ^{
38 |
39 | _screenManager = [super allocWithZone:zone];
40 |
41 | });
42 |
43 | return _screenManager;
44 | }
45 |
46 | - (id)copyWithZone:(NSZone *)zone{
47 |
48 | return self;
49 |
50 | }
51 |
52 | - (id)mutableCopyWithZone:(NSZone *)zone{
53 |
54 | return self;
55 |
56 | }
57 |
58 | - (instancetype)init{
59 | if (self = [super init]) {
60 |
61 | }
62 | return self;
63 | }
64 |
65 | // 开始录制屏幕
66 | - (void)screenRecSuc:(void (^)(void))suc failure:(srerrorinfo)errorInfo{
67 |
68 | kWeakSelf(self);
69 | RPScreenRecorder* recorder = RPScreenRecorder.sharedRecorder;
70 | recorder.delegate = self;
71 | recorder.microphoneEnabled = YES;
72 |
73 | NSString *version = [UIDevice currentDevice].systemVersion;
74 |
75 | if ([version doubleValue] < 9.0) {
76 |
77 | SRErrorHandle *err = [[SRErrorHandle alloc] init];
78 | err.code = -4;
79 | err.msg = @"iOS9.0之后的系统才可以使用录屏功能";
80 | errorInfo(err);
81 |
82 | }
83 |
84 | // 先检查系统版本
85 | if (@available(iOS 10.0, *)) {
86 |
87 | if (recorder.isRecording) { // 正在录制中不能在录制了
88 |
89 | SRErrorHandle *err = [[SRErrorHandle alloc] init];
90 | err.code = -1;
91 | err.msg = @"录屏正在进行,无法再次开启录屏功能";
92 | errorInfo(err);
93 |
94 | }
95 |
96 | [recorder startRecordingWithHandler:^(NSError * _Nullable error) {
97 | if (!error) {
98 |
99 | dispatch_async(dispatch_get_main_queue(), ^{
100 |
101 | weakself.isRecording = YES;
102 | suc();
103 |
104 | });
105 |
106 | }else{
107 |
108 | dispatch_async(dispatch_get_main_queue(), ^{
109 |
110 | weakself.isRecording = NO;
111 | SRErrorHandle *err = [[SRErrorHandle alloc] initWithError:error];
112 | errorInfo(err);
113 |
114 | });
115 | }
116 | }];
117 | } else {
118 | if (@available(iOS 9.0, *)) {
119 | if (recorder.isRecording) { // 正在录制中不能在录制了
120 |
121 | weakself.isRecording = NO;
122 | SRErrorHandle *err = [[SRErrorHandle alloc] init];
123 | err.code = -1;
124 | err.msg = @"正在录制中";
125 | errorInfo(err);
126 |
127 | }
128 | [recorder startRecordingWithMicrophoneEnabled:YES handler:^(NSError * _Nullable error) {
129 | if (!error) {
130 |
131 | dispatch_async(dispatch_get_main_queue(), ^{
132 |
133 | weakself.isRecording = YES;
134 | suc();
135 |
136 | });
137 |
138 | }else{
139 |
140 | dispatch_async(dispatch_get_main_queue(), ^{
141 |
142 | weakself.isRecording = NO;
143 | SRErrorHandle *err = [[SRErrorHandle alloc] initWithError:error];
144 | errorInfo(err);
145 |
146 | });
147 | }
148 |
149 | }];
150 |
151 | }
152 |
153 | }
154 |
155 | }
156 |
157 | // 停止录制屏幕
158 | - (void)stopRecSuc:(void (^)(void))suc failure:(srerrorinfo)errorInfo{
159 |
160 | NSLog(@"结束开始");
161 | kWeakSelf(self);
162 | if (!RPScreenRecorder.sharedRecorder.isRecording) {
163 |
164 | SRErrorHandle *err = [[SRErrorHandle alloc] init];
165 | err.code = -2;
166 | err.msg = @"没有正在录制的进程";
167 | errorInfo(err);
168 |
169 | }else{
170 |
171 | [[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) {
172 | NSLog(@"结束回调");
173 | if (error) {
174 |
175 | dispatch_async(dispatch_get_main_queue(), ^{
176 | SRErrorHandle *err = [[SRErrorHandle alloc] initWithError:error];
177 | errorInfo(err);
178 | });
179 |
180 | }else{
181 |
182 | dispatch_async(dispatch_get_main_queue(), ^{
183 | weakself.isRecording = NO;
184 | suc();
185 | if ([previewViewController respondsToSelector:@selector(movieURL)]) {
186 | NSURL *videoURL = [previewViewController.movieURL copy];
187 | if (!videoURL) {
188 |
189 | } else {
190 | BOOL compatible = UIVideoAtPathIsCompatibleWithSavedPhotosAlbum([videoURL path]);
191 | if (compatible)
192 | {
193 | UISaveVideoAtPathToSavedPhotosAlbum([videoURL path], self, @selector(savedPhotoImage:didFinishSavingWithError:contextInfo:), nil);
194 | }
195 | }
196 | }
197 | });
198 | }
199 | }];
200 | }
201 | }
202 |
203 |
204 | #pragma mark -
205 | //保存视频完成之后的回调 - Private
206 | - (void)savedPhotoImage:(UIImage*)image didFinishSavingWithError:(NSError *)error contextInfo: (void *)contextInfo {
207 |
208 | if (self.screenRecordDelegate && [self.screenRecordDelegate respondsToSelector:@selector(savedPhotoImage: didFinishSavingWithError: contextInfo:)]){
209 |
210 | [self.screenRecordDelegate savedPhotoImage:image didFinishSavingWithError:error contextInfo:contextInfo];
211 |
212 | }
213 |
214 | __block NSData *data;
215 | __block BOOL isError; // 判断进入didFinishSavingWithError之后有没有错误
216 | if (error) {
217 |
218 | isError = YES;
219 |
220 | } else {
221 |
222 | isError = NO;
223 | PHFetchOptions *options = [[PHFetchOptions alloc] init];
224 | options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]]; //按创建日期获取
225 | PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
226 | PHAsset *phasset = [assetsFetchResults lastObject];
227 | if (phasset){
228 | if (phasset.mediaType == PHAssetMediaTypeVideo) {
229 |
230 | PHImageManager *manager = [PHImageManager defaultManager];
231 | [manager requestAVAssetForVideo:phasset options:nil resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
232 |
233 | isError = NO;
234 | AVURLAsset *urlAsset = (AVURLAsset *)asset;
235 | NSURL *videoURL = urlAsset.URL;
236 | data = [NSData dataWithContentsOfURL:videoURL];
237 |
238 | dispatch_async(dispatch_get_main_queue(), ^{
239 |
240 | // delegate
241 | if (self.screenRecordDelegate && [self.screenRecordDelegate respondsToSelector:@selector(savedVideoData:didFinishSavingWithError:)]){
242 |
243 | [self.screenRecordDelegate savedVideoData:data didFinishSavingWithError:isError];
244 |
245 | }
246 |
247 | });
248 |
249 | }];
250 |
251 | } else {
252 |
253 | isError = YES;
254 | // delegate
255 | if (self.screenRecordDelegate && [self.screenRecordDelegate respondsToSelector:@selector(savedVideoData:didFinishSavingWithError:)]){
256 |
257 | [self.screenRecordDelegate savedVideoData:data didFinishSavingWithError:isError];
258 |
259 | }
260 | }
261 |
262 | }else{
263 |
264 | isError = NO;
265 | // delegate
266 | if (self.screenRecordDelegate && [self.screenRecordDelegate respondsToSelector:@selector(savedVideoData:didFinishSavingWithError:)]){
267 |
268 | [self.screenRecordDelegate savedVideoData:data didFinishSavingWithError:isError];
269 |
270 | }
271 |
272 | }
273 |
274 | }
275 |
276 | }
277 |
278 | //-(void)deletePhoto:(PHAsset *)asset{
279 | // PHFetchOptions *photosOptions = [[PHFetchOptions alloc] init];
280 | // photosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
281 | //
282 | // PHFetchResult *assetSmartCollection=[PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumRecentlyAdded options:nil];
283 | // if (assetSmartCollection.count>0) {
284 | //
285 | // PHAssetCollection*recentlyAddedAlbum= assetSmartCollection.firstObject;
286 | // NSString*albumName= recentlyAddedAlbum.localizedTitle;
287 | // //按照相片的创造时间查询相册里面的图片
288 | // PHFetchOptions *photosOptions = [[PHFetchOptions alloc] init];
289 | // photosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
290 | //
291 | // PHFetchResult *assetResult = [PHAsset fetchAssetsInAssetCollection:recentlyAddedAlbum options:photosOptions];
292 | //
293 | // [assetResult enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
294 | //
295 | // if ([asset isEqual:obj]) {
296 | // //删除图片
297 | // [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
298 | // [PHAssetChangeRequest deleteAssets:@[obj]];
299 | // } completionHandler:^(BOOL success, NSError *error) {
300 | //
301 | // if (success) {
302 | //
303 | // }else{
304 | // NSLog(@"Error: %@", error);
305 | // }
306 | //
307 | // }];
308 | // }
309 | //
310 | // }];
311 | //
312 | // }
313 | //}
314 | #pragma mark - RPScreenRecorder Delegate
315 | -(void)screenRecorder:(RPScreenRecorder *)screenRecorder didStopRecordingWithPreviewViewController:(RPPreviewViewController *)previewViewController error:(NSError *)error{
316 |
317 | if (self.screenRecordDelegate && [self.screenRecordDelegate respondsToSelector:@selector(recStateDidChange:withError:)]){
318 |
319 | RecState state = RecState_Stop;
320 | [self.screenRecordDelegate recStateDidChange:state withError: NULL];
321 |
322 | }
323 |
324 | }
325 |
326 | // 监听replaykit是否可用,比如状态发生变化(比如录制过程中,切入设置,关闭权限。)会回调该方法。
327 | -(void)screenRecorderDidChangeAvailability:(RPScreenRecorder *)screenRecorder{
328 |
329 |
330 | }
331 |
332 | #pragma mark - Setter
333 | - (void)setIsRecording:(BOOL)isRecording{
334 | _isRecording = isRecording;
335 |
336 | dispatch_async(dispatch_get_main_queue(), ^{
337 |
338 | if (self.screenRecordDelegate && [self.screenRecordDelegate respondsToSelector:@selector(recStateDidChange:withError:)]){
339 | RecState state;
340 | if (isRecording) {
341 |
342 | state = RecState_Rec;
343 |
344 | }else{
345 |
346 | state = RecState_Stop;
347 |
348 | }
349 |
350 | [self.screenRecordDelegate recStateDidChange:state withError: NULL];
351 |
352 | }
353 |
354 | });
355 |
356 | }
357 |
358 | @end
359 |
--------------------------------------------------------------------------------