├── 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 | [![Platform](https://img.shields.io/cocoapods/p/RTRootNavigationController.svg?style=flat)](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 | --------------------------------------------------------------------------------