

RPScreenRecorder//// ViewController////// Created by song on 2022/01/13.// Copyright ? 2022 song. All rights reserved.#import "MainViewController.h"#import <ReplayKit/ReplayKit.h>#import <AVFoundation/AVFoundation.h>#import "SystemScreenRecordController.h"@interface MainViewController ()<RPScreenRecorderDelegate,RPPreviewViewControllerDelegate>@end@implementation MainViewController-(void)viewDidLoad{ [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; [self setupUI]; [self setupScreen];}- (void)setupScreen{ AVAuthorizationStatus microPhoneStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; switch (microPhoneStatus) { case AVAuthorizationStatusDenied: case AVAuthorizationStatusRestricted: { // 被拒绝 [self goMicroPhoneSet]; } break; case AVAuthorizationStatusNotDetermined: { // 没弹窗 [self requestMicroPhoneAuth]; } break; case AVAuthorizationStatusAuthorized: { // 有授权 } break; default: break; } }-(void) goMicroPhoneSet{ UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"您还没有允许麦克风权限" message:@"去设置一下吧" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction * cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { }]; UIAlertAction * setAction = [UIAlertAction actionWithTitle:@"去设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { dispatch_async(dispatch_get_main_queue(), ^{ NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; [UIApplication.sharedApplication openURL:url options:nil completionHandler:^(BOOL success) { }]; }); }]; [alert addAction:cancelAction]; [alert addAction:setAction]; [self presentViewController:alert animated:YES completion:nil];}-(void) requestMicroPhoneAuth{ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) { }];}- (void)setupUI{ self.title= @"录屏Demo"; self.navigationController.navigationBar.tintColor=[UIColor whiteColor]; self.navigationController.navigationBar.barTintColor = [UIColor greenColor]; self.navigationController.navigationBar.barStyle = UIBarStyleBlack; [self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor],NSFontAttributeName:[UIFont systemFontOfSize:25]}]; UIBarButtonItem *leftBar = [[UIBarButtonItem alloc ] initWithTitle:@"开始录屏" style:UIBarButtonItemStylePlain target:self action:@selector(start)]; UIBarButtonItem *playBtn = [[UIBarButtonItem alloc] initWithTitle:@"结束录屏" style:UIBarButtonItemStylePlain target:self action:@selector(stop)]; self.navigationItem.rightBarButtonItem = playBtn; self.navigationItem.leftBarButtonItem = leftBar; UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeSystem]; btn1.frame = CGRectMake(110, 100, 100, 33); btn1.backgroundColor = [UIColor redColor]; [btn1 setTitle:@"点我啊" forState:UIControlStateNormal]; [btn1 addTarget:self action:@selector(systemBtnClick) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btn1];}- (void)systemBtnClick{ SystemScreenRecordController *vc = [[SystemScreenRecordController alloc] init]; vc.hidesBottomBarWhenPushed = YES; [self.navigationController pushViewController:vc animated:YES];}- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"keyPath:%@,change:%@",keyPath,change); if ([keyPath isEqualToString:@"available"] && [change[@"new"] integerValue] == 1) { [self start]; }}- (void)checkout{ if (@available(iOS 9.0, *)) { if ([RPScreenRecorder sharedRecorder].available) { NSLog(@"可以录屏"); [self start]; }else{ NSLog(@"未授权"); [[RPScreenRecorder sharedRecorder] addObserver:self forKeyPath:@"available" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil]; } } else { NSLog(@"不支持录屏"); }}- (void)start{ if ([RPScreenRecorder sharedRecorder].recording) { NSLog(@"录制中..."); }else{ NSLog(@"1---[RPScreenRecorder sharedRecorder].microphoneEnabled:%d",[RPScreenRecorder sharedRecorder].microphoneEnabled); if(![RPScreenRecorder sharedRecorder].microphoneEnabled){ [[RPScreenRecorder sharedRecorder] setMicrophoneEnabled:YES]; } NSLog(@"2---[RPScreenRecorder sharedRecorder].microphoneEnabled:%d",[RPScreenRecorder sharedRecorder].microphoneEnabled); [RPScreenRecorder sharedRecorder].delegate = self; if (@available(iOS 11.0, *)) { [[RPScreenRecorder sharedRecorder] startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) { NSLog(@"拿到流,可以直播推流"); switch (bufferType) { case RPSampleBufferTypeAudioApp: NSLog(@"内部音频流"); break; case RPSampleBufferTypeVideo: NSLog(@"内部视频流"); break; case RPSampleBufferTypeAudioMic: NSLog(@"麦克风音频"); break; default: break; } } completionHandler:^(NSError * _Nullable error) { NSLog(@"startCaptureWithHandler completionHandler"); if (error) { }else{ } }]; } else if (@available(iOS 10.0, *)) { [[RPScreenRecorder sharedRecorder] startRecordingWithHandler:^(NSError * _Nullable error) { NSLog(@"startRecordingWithHandler:%@",error); }]; } else if(@available(iOS 9.0, *)) { [[RPScreenRecorder sharedRecorder] startRecordingWithMicrophoneEnabled:YES handler:^(NSError * _Nullable error) { NSLog(@"startRecordingWithMicrophoneEnabled:%@",error); }]; } } }- (void)stop{ if ([RPScreenRecorder sharedRecorder].recording) { [[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) { NSLog(@"stopRecordingWithHandler"); if (!error) { previewViewController.previewControllerDelegate = self; [self presentViewController:previewViewController animated:YES completion:nil]; } }]; }}#pragma mark - RPScreenRecorderDelegate- (void)screenRecorder:(RPScreenRecorder *)screenRecorder didStopRecordingWithPreviewViewController:(RPPreviewViewController *)previewViewController error:(NSError *)error /*API_AVAILABLE(ios(11.0)*/{ if(@available(iOS 11.0,*)){ NSLog(@"didStopRecordingWithPreviewViewController: %@",error); }}-(void)screenRecorderDidChangeAvailability:(RPScreenRecorder *)screenRecorder{ NSLog(@"screenRecorderDidChangeAvailability:%@",screenRecorder);}- (void)screenRecorder:(RPScreenRecorder *)screenRecorder didStopRecordingWithError:(NSError *)error previewViewController:(RPPreviewViewController *)previewViewController{ if(@available(iOS 9.0,*)){ NSLog(@"didStopRecordingWithError :%@",error); }}#pragma mark - RPPreviewViewControllerDelegate- (void)previewControllerDidFinish:(RPPreviewViewController *)previewController{ NSLog(@"previewControllerDidFinish"); [previewController dismissViewControllerAnimated:YES completion:nil]; }- (void)previewController:(RPPreviewViewController *)previewController didFinishWithActivityTypes:(NSSet<NSString *> *)activityTypes{ NSLog(@"didFinishWithActivityTypes:%@",activityTypes);}@endBonjour 是 Apple 基于标准的网络技术,旨在帮助设备和服务在同一网络上发现彼此。例如,iPhone 和 iPad 设备使用 Bonjour 发现兼容“隔空打印”的打印机,iPhone 和 iPad 设备以及 Mac 电脑使用 Bonjour 发现兼容“隔空播放”的设备(如 Apple TV).
Bonjour
由于bonjour服务是开源的,且iOS系统提供底层API库:DNS-SD,去实现此功能。
Bonjour服务一般用于发布服务全局广播,但如果服务不想被其它机器知道,只有制定机器知道,如何实现:
参考
参考
- 被录制端需要在原有功能的基础上,增加一个唤起广播的入口。- 点击直播会出现直播App选择(实现了ReplayKit Live的APP)- //// SystemScreenRecordController.m// SLQDemo//// Created by song on 2022/01/6.// Copyright ? 2022 了. All rights reserved.//#import "SystemScreenRecordController.h"#import <ReplayKit/ReplayKit.h>@interface SystemScreenRecordController ()<RPBroadcastActivityViewControllerDelegate,RPBroadcastControllerDelegate>@end@implementation SystemScreenRecordController- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor greenColor]; UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeSystem]; btn1.frame = CGRectMake(110, 100, 100, 33); btn1.backgroundColor = [UIColor redColor]; [btn1 setTitle:@"点我啊" forState:UIControlStateNormal]; [btn1 addTarget:self action:@selector(systemBtnClick) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btn1];}- (void)systemBtnClick { [self setupUI];}- (void)setupUI { if (@available(iOS 10.0, *)) { [RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithHandler:^(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error) { if (error) { NSLog(@"loadBroadcastActivityViewControllerWithHandler:%@",error); }else{ broadcastActivityViewController.delegate = self; broadcastActivityViewController.modalPresentationStyle = UIModalPresentationPopover; [self presentViewController:broadcastActivityViewController animated:YES completion:nil]; } }]; } else { NSLog(@"不支持录制系统屏幕"); } }#pragma mark - RPBroadcastActivityViewControllerDelegate- (void)broadcastActivityViewController:(RPBroadcastActivityViewController *)broadcastActivityViewController didFinishWithBroadcastController:(RPBroadcastController *)broadcastController error:(NSError *)error{ NSLog(@"broadcastActivityViewController: didFinishWithBroadcastController:"); dispatch_async(dispatch_get_main_queue(), ^{ [broadcastActivityViewController dismissViewControllerAnimated:YES completion:nil]; }); NSLog(@"Boundle id :%@",broadcastController.broadcastURL); if (error) { NSLog(@"BAC: %@ didFinishWBC: %@, err: %@", broadcastActivityViewController, broadcastController, error); return; } [broadcastController startBroadcastWithHandler:^(NSError * _Nullable error) { if (error) { NSLog(@"startBroadcastWithHandler:%@",error); }else{ NSLog(@"startBroadcast success"); } }];}- (void)broadcastController:(RPBroadcastController *)broadcastController didUpdateServiceInfo:(NSDictionary<NSString *,NSObject<NSCoding> *> *)serviceInfo{ NSLog(@"didUpdateServiceInfo:%@",serviceInfo);}@end

//// SampleHandler.m// broadcast//// Created by song on 2022/01/6.// Copyright ? 2022 了. All rights reserved.//#import "SampleHandler.h"@implementation SampleHandler- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo { // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional. NSLog(@"启动广播"); }- (void)broadcastPaused { // User has requested to pause the broadcast. Samples will stop being delivered. NSLog(@"暂停广播");}- (void)broadcastResumed { // User has requested to resume the broadcast. Samples delivery will resume. NSLog(@"恢复广播");}- (void)broadcastFinished { // User has requested to finish the broadcast. NSLog(@"完成广播");}- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType { switch (sampleBufferType) { case RPSampleBufferTypeVideo: // Handle video sample buffer // 得到YUV数据 NSLog(@"视频流"); break; case RPSampleBufferTypeAudioApp: // Handle audio sample buffer for app audio // 处理app音频 NSLog(@"App音频流"); break; case RPSampleBufferTypeAudioMic: // Handle audio sample buffer for mic audio // 处理麦克风音频 NSLog(@"麦克风音频流"); break; default: break; }}@end


//// BroadcastSetupViewController.m// broadcastSetupUI//// Created by song on 2022/01/07.// Copyright ? 2022 了. All rights reserved.//#import "BroadcastSetupViewController.h"@implementation BroadcastSetupViewController- (void)viewDidLoad{ [super viewDidLoad]; NSLog(@"BroadcastSetupViewController"); self.view.backgroundColor = [UIColor redColor]; UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeSystem]; btn1.frame = CGRectMake(110, 100, 200, 33); btn1.backgroundColor = [UIColor redColor]; [btn1 setTitle:@"点我开始直播" forState:UIControlStateNormal]; [btn1 addTarget:self action:@selector(systemBtnClick) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btn1]; UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeSystem]; btn2.frame = CGRectMake(110, 200, 200, 33); btn2.backgroundColor = [UIColor redColor]; [btn2 setTitle:@"取消直播" forState:UIControlStateNormal]; [btn2 addTarget:self action:@selector(stop) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btn2];}- (void)systemBtnClick { NSLog(@"开始直播"); [self userDidFinishSetup];}- (void)stop { [self userDidCancelSetup];}// Call this method when the user has finished interacting with the view controller and a broadcast stream can start- (void)userDidFinishSetup { NSLog(@"userDidFinishSetup"); // URL of the resource where broadcast can be viewed that will be returned to the application NSURL *broadcastURL = [NSURL URLWithString:@"http://apple.com/broadcast/test1"]; // Dictionary with setup information that will be provided to broadcast extension when broadcast is started NSDictionary *setupInfo = @{ @"broadcastName" : @"App live" }; // Tell ReplayKit that the extension is finished setting up and can begin broadcasting [self.extensionContext completeRequestWithBroadcastURL:broadcastURL setupInfo:setupInfo];}- (void)userDidCancelSetup { // Tell ReplayKit that the extension was cancelled by the user NSLog(@"userDidCancelSetup"); [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"YourAppDomain" code:-1 userInfo:nil]];}@endiOS10只支持app内容录制,所以当app切到后台,录制内容将停止;手机锁屏时,录制进程将停止;这几个方法中的代码不能阻塞(例如写文件等慢操作),否则导致录制进程停止; if (@available(iOS 12.0, *)) { self.broadPickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(110, 100, 100, 100)]; self.broadPickerView.preferredExtension = @"com.ask.answer.live.boradcastr";// nil的话列出所有可录屏的App [self.view addSubview:self.broadPickerView]; }添加以上代码后,就会多出一个黑色按钮,点击就会弹出录制界面


- (void)saveVideoWithUrl:(NSURL *)url { PHPhotoLibrary *photoLibrary = [PHPhotoLibrary sharedPhotoLibrary]; [photoLibrary performChanges:^{ [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url]; } completionHandler:^(BOOL success, NSError * _Nullable error) { if (success) { NSLog(@"已将视频保存至相册"); } else { NSLog(@"未能保存视频到相册"); } }];}- (void)stop{ if ([RPScreenRecorder sharedRecorder].recording) { if (@available(iOS 14.0, *)) { __weak typeof(self) weakSelf = self; NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES) firstObject]; NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/test.mp4",cachesDir]]; [[RPScreenRecorder sharedRecorder] stopRecordingWithOutputURL:url completionHandler:^(NSError * _Nullable error) { NSLog(@"stopRecordingWithOutputURL:%@",url); [weakSelf saveVideoWithUrl:url]; }]; } else { [[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController * _Nullable previewViewController, NSError * _Nullable error) { NSLog(@"stopRecordingWithHandler"); if (!error) { previewViewController.previewControllerDelegate = self; [self presentViewController:previewViewController animated:YES completion:nil]; } }]; } }}预览视频可通过AVPlayerViewController预览视频
也可以直接保存到相册
SampleHandler数据流回调里处理视频
通过AppGroup和宿主app共享数据
//// SampleHandler.m// broadcast//// Created by song on 2022/01/6.// Copyright ? 2022 了. All rights reserved.//#import "SampleHandler.h"#import <AVFoundation/AVFoundation.h>@interface NSDate (Timestamp)+ (NSString *)timestamp;@end @implementation NSDate (Timestamp)+ (NSString *)timestamp { long long timeinterval = (long long)([NSDate timeIntervalSinceReferenceDate] * 1000); return [NSString stringWithFormat:@"%lld", timeinterval];}@end@interface SampleHandler()@property (nonatomic,strong) AVAssetWriter *assetWriter;@property (nonatomic,strong) AVAssetWriterInput *videoInput;@property (nonatomic,strong) AVAssetWriterInput *audioInput;@end@implementation SampleHandler- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo { // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional. NSLog(@"启动广播:%@",setupInfo); [self initData];}- (NSString *)getDocumentPath { static NSString *replaysPath; if (!replaysPath) { NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *documentRootPath = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.com.ask.answer.live"]; replaysPath = [documentRootPath.path stringByAppendingPathComponent:@"Replays"]; if (![fileManager fileExistsAtPath:replaysPath]) { NSError *error_createPath = nil; BOOL success_createPath = [fileManager createDirectoryAtPath:replaysPath withIntermediateDirectories:true attributes:@{} error:&error_createPath]; if (success_createPath && !error_createPath) { NSLog(@"%@路径创建成功!", replaysPath); } else { NSLog(@"%@路径创建失败:%@", replaysPath, error_createPath); } }else{ NSLog(@"%@路径已存在!", replaysPath); } } return replaysPath;}- (NSURL *)getFilePathUrl { NSString *time = [NSDate timestamp]; NSString *fileName = [time stringByAppendingPathExtension:@"mp4"]; NSString *fullPath = [[self getDocumentPath] stringByAppendingPathComponent:fileName]; return [NSURL fileURLWithPath:fullPath];}- (NSArray <NSURL *> *)fetechAllResource { NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *documentPath = [self getDocumentPath]; NSURL *documentURL = [NSURL fileURLWithPath:documentPath]; NSError *error = nil; NSArray<NSURL *> *allResource = [fileManager contentsOfDirectoryAtURL:documentURL includingPropertiesForKeys:@[] options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants) error:&error]; return allResource; }- (void)initData { if ([self.assetWriter canAddInput:self.videoInput]) { [self.assetWriter addInput:self.videoInput]; }else{ NSLog(@"添加input失败"); }}- (AVAssetWriter *)assetWriter{ if (!_assetWriter) { NSError *error = nil; _assetWriter = [[AVAssetWriter alloc] initWithURL:[self getFilePathUrl] fileType:(AVFileTypeMPEG4) error:&error]; NSAssert(!error, @"_assetWriter 初始化失败"); } return _assetWriter;}-(AVAssetWriterInput *)audioInput{ if (!_audioInput) { // 音频参数 NSDictionary *audioCompressionSettings = @{ AVEncoderBitRatePerChannelKey:@(28000), AVFormatIDKey:@(kAudioFormatMPEG4AAC), AVNumberOfChannelsKey:@(1), AVSampleRateKey:@(22050) }; _audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:audioCompressionSettings]; } return _audioInput;}-(AVAssetWriterInput *)videoInput{ if (!_videoInput) { CGSize size = [UIScreen mainScreen].bounds.size; // 视频大小 NSInteger numPixels = size.width * size.height; // 像素比 CGFloat bitsPerPixel = 7.5; NSInteger bitsPerSecond = numPixels * bitsPerPixel; // 码率和帧率设置 NSDictionary *videoCompressionSettings = @{ AVVideoAverageBitRateKey:@(bitsPerSecond),//码率 AVVideoExpectedSourceFrameRateKey:@(25),// 帧率 AVVideoMaxKeyFrameIntervalKey:@(15),// 关键帧最大间隔 AVVideoProfileLevelKey:AVVideoProfileLevelH264BaselineAutoLevel, AVVideoPixelAspectRatioKey:@{ AVVideoPixelAspectRatioVerticalSpacingKey:@(1), AVVideoPixelAspectRatioHorizontalSpacingKey:@(1) } }; CGFloat scale = [UIScreen mainScreen].scale; // 视频参数 NSDictionary *videoOutputSettings = @{ AVVideoCodecKey:AVVideoCodecTypeH264, AVVideoScalingModeKey:AVVideoScalingModeResizeAspectFill, AVVideoWidthKey:@(size.width*scale), AVVideoHeightKey:@(size.height*scale), AVVideoCompressionPropertiesKey:videoCompressionSettings }; _videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoOutputSettings]; _videoInput.expectsMediaDataInRealTime = true; } return _videoInput;}- (void)broadcastPaused { // User has requested to pause the broadcast. Samples will stop being delivered. NSLog(@"暂停广播"); [self stopRecording];}- (void)broadcastResumed { // User has requested to resume the broadcast. Samples delivery will resume. NSLog(@"恢复广播"); [self stopRecording];}- (void)broadcastFinished { // User has requested to finish the broadcast. NSLog(@"完成广播"); [self stopRecording];}- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType { switch (sampleBufferType) { case RPSampleBufferTypeVideo: // Handle video sample buffer // 得到YUV数据 NSLog(@"视频流"); AVAssetWriterStatus status = self.assetWriter.status; if (status == AVAssetWriterStatusFailed || status == AVAssetWriterStatusCompleted || status == AVAssetWriterStatusCancelled) { return; } if (status == AVAssetWriterStatusUnknown) { [self.assetWriter startWriting]; CMTime time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); [self.assetWriter startSessionAtSourceTime:time]; } if (status == AVAssetWriterStatusWriting ) { if (self.videoInput.isReadyForMoreMediaData) { BOOL success = [self.videoInput appendSampleBuffer:sampleBuffer]; if (!success) { [self stopRecording]; } } } break; case RPSampleBufferTypeAudioApp: // Handle audio sample buffer for app audio // 处理app音频 NSLog(@"App音频流"); break; case RPSampleBufferTypeAudioMic: // Handle audio sample buffer for mic audio // 处理麦克风音频 NSLog(@"麦克风音频流"); if (self.audioInput.isReadyForMoreMediaData) { BOOL success = [self.audioInput appendSampleBuffer:sampleBuffer]; if (!success) { [self stopRecording]; } } break; default: break; }}- (void)stopRecording {// if (self.assetWriter.status == AVAssetWriterStatusWriting) { [self.assetWriter finishWritingWithCompletionHandler:^{ NSLog(@"结束写入数据"); }];// [self.audioInput markAsFinished];// }}@end- (void)watchRecord:(UIButton *)sender { NSLog(@"watchRecord"); NSArray<NSURL *> *allResource = [[self fetechAllResource] sortedArrayUsingComparator:^NSComparisonResult(NSURL * _Nonnull obj1, NSURL * _Nonnull obj2) { //排序,每次都查看最新录制的视频 return [obj2.path compare:obj1.path options:(NSCaseInsensitiveSearch)]; }]; AVPlayerViewController *playerViewController; playerViewController = [[AVPlayerViewController alloc] init]; NSLog(@"url%@:",allResource);//// for (NSURL *url in allResource) {// [self saveVideoWithUrl:url];// } playerViewController.player = [AVPlayer playerWithURL:allResource.firstObject]; // playerViewController.delegate = self; [self presentViewController:playerViewController animated:YES completion:^{ [playerViewController.player play]; NSLog(@"error == %@", playerViewController.player.error); }]; }- (NSString *)getDocumentPath { static NSString *replaysPath; if (!replaysPath) { NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *documentRootPath = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.com.ask.answer.live"]; replaysPath = [documentRootPath.path stringByAppendingPathComponent:@"Replays"]; if (![fileManager fileExistsAtPath:replaysPath]) { NSError *error_createPath = nil; BOOL success_createPath = [fileManager createDirectoryAtPath:replaysPath withIntermediateDirectories:true attributes:@{} error:&error_createPath]; if (success_createPath && !error_createPath) { NSLog(@"%@路径创建成功!", replaysPath); } else { NSLog(@"%@路径创建失败:%@", replaysPath, error_createPath); } } } return replaysPath;}- (NSArray <NSURL *> *)fetechAllResource { NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *documentPath = [self getDocumentPath]; NSURL *documentURL = [NSURL fileURLWithPath:documentPath]; NSError *error = nil; NSArray<NSURL *> *allResource = [fileManager contentsOfDirectoryAtURL:documentURL includingPropertiesForKeys:@[] options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants) error:&error]; return allResource; }- (void)saveVideoWithUrl:(NSURL *)url { PHPhotoLibrary *photoLibrary = [PHPhotoLibrary sharedPhotoLibrary]; [photoLibrary performChanges:^{ [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url]; } completionHandler:^(BOOL success, NSError * _Nullable error) { if (success) { NSLog(@"已将视频保存至相册"); } else { NSLog(@"未能保存视频到相册"); } }];}预览视频经常失败,重启手机后恢复正常,也许是API的问题。
参考1
参考2
参考3
AppGroup
本文来自博客园,作者:struggle_time,转载请注明原文链接:https://www.cnblogs.com/songliquan/p/15891392.html