// // OralCavityManager.m // OralCavityFramework // // Created by ss on 2024/8/30. // #import "OralCavityManager.h" //#import #import #import #import #import "cf680SDK.h" #import "AAPLEAGLLayerCustom.h" #define WeakSelf __weak typeof(self) weakSelf = self; #define kSCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width) #define kSCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height) @interface OralCavityManager () @property (nonatomic, strong) cf680SDK *sdk; @property (nonatomic, strong) UIView *videoView; @property (nonatomic, strong) AAPLEAGLLayerCustom *bfLayer; @property (nonatomic, copy) NSString *imageName; @property (nonatomic, copy) NSString *recordName; @property (nonatomic, assign) int connect; @end @implementation OralCavityManager + (instancetype)sharedInstance { static OralCavityManager *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; // [sharedInstance startVideo]; }); return sharedInstance; } - (cf680SDK *)sdk { WeakSelf; dispatch_async(dispatch_get_main_queue(), ^{ if (!weakSelf.sdk) { //初始化视频view UIView *videoView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kSCREEN_WIDTH, kSCREEN_WIDTH * 9 / 16)]; weakSelf.videoView = videoView; weakSelf.sdk = [[cf680SDK alloc] init]; //设置view w/h = 16 / 9 [weakSelf.sdk SetVideoRect:weakSelf.videoView.frame viewlay:weakSelf.videoView.layer]; [weakSelf.sdk SetVideoFullScreenRect:CGRectMake(0, 0, 16 / 9 * kSCREEN_HEIGHT, kSCREEN_HEIGHT)]; [weakSelf.sdk ShowVideoRect:NO]; static NSString *const notificationName = @"cf680SDK"; NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; [defaultCenter addObserver:weakSelf selector:@selector(handleNotification:) name:notificationName object:nil]; NSString *filepath = [weakSelf MakeJPGFold:@"jpgimage"]; const char *jpgfold = [filepath UTF8String]; [weakSelf.sdk SetCapturePath:(char *)jpgfold]; [[UIApplication sharedApplication] setIdleTimerDisabled: YES]; //初始化相册名字 weakSelf.imageName = @""; weakSelf.recordName = @""; // //获取pixelBuffer // _bfLayer = _videoView.layer.sublayers[0]; // // 定时器,用于定时获取帧数据 // CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallback:)]; // [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; if (weakSelf.getVideoView) { // self.getVideoView(weakSelf.videoView); weakSelf.getVideoView(weakSelf.videoView.layer.sublayers[0]); } //申请相册权限 /*if (@available(iOS 14, *)) { [PHPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelReadWrite handler:^(PHAuthorizationStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ if (status == PHAuthorizationStatusLimited) { NSLog(@"status == %ld",(long)status); } else if (status == PHAuthorizationStatusDenied || status == PHAuthorizationStatusRestricted){ NSLog(@"status == %ld",(long)status); } else if(status == PHAuthorizationStatusAuthorized){ NSLog(@"status == %ld",(long)status); } }); }]; } else { [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ if (status == PHAuthorizationStatusDenied || status == PHAuthorizationStatusRestricted){ NSLog(@"status == %ld",(long)status); } else if(status == PHAuthorizationStatusAuthorized){ } }); }]; }*/ } }); return _sdk; } //照片路径 - (NSString *)MakeJPGFold:(NSString *)jpgfoldname { NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentPath = [path objectAtIndex:0]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *imageDocPath = [documentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@",jpgfoldname]]; NSLog(@"====>name:%@",imageDocPath); [fileManager createDirectoryAtPath:imageDocPath withIntermediateDirectories:YES attributes:nil error:nil]; return imageDocPath; } ////获取视频pixelBuffer //- (void)displayLinkCallback:(CADisplayLink *)sender { // // if (_bfLayer.pixelBuffer && self.getPixelBuffer) { // //block回调 // self.getPixelBuffer(_bfLayer.pixelBuffer); // } //} //1: 成功 -4 10秒链接超时。-1 错误 - (int)connectToServer { WeakSelf; //异步调用 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ char Version[32]; weakSelf.connect = [weakSelf.sdk GetDeviceVersion:Version]; }); return _connect; } //开始视频 - (void)startVideo { [self stopVideo]; char ipaddr[16]; //[SDK FindDevice:ipaddr]; memset(ipaddr,0,sizeof(ipaddr)); strcpy(ipaddr,"192.168.1.1"); int ret = [self.sdk StartVideo:ipaddr]; if(ret != 0) { printf("connect device error!\n"); return; } } //停止视频 - (void)stopVideo { [self.sdk StopVideo]; } //拍照 - (void)takePhoto:(NSString *)str { //删除临时文件 [self deleteTmpFileisPhoto:YES]; //拍照 char filename[255]; struct tm *ptm; time_t timep; time(&timep); ptm = localtime(&timep); memset(filename,0,sizeof(filename)); sprintf(filename,"%04d%02d%02d%02d%02d%02d.jpg", (ptm->tm_year+1900),ptm->tm_mon+1,ptm->tm_mday,ptm->tm_hour,ptm->tm_min,ptm->tm_sec); [self.sdk ManualCapture:(char *)filename]; // _imageName = str; _imageName = @""; } //拍照结果 - (void)handleNotification:(NSNotification *)notify { NSString *param = notify.userInfo[@"content"]; NSLog(@"recv: %@",param); //please handle result.... // if ([param rangeOfString:@"jpg"].location == NSNotFound) //点击设备拍照按钮 if (![param containsString:@"jpg"]) { //device button key0 // self.imageName = @""; //点击设备拍照按钮,通知flutter去拍照 if (self.onDeviceTakePhoto) { self.onDeviceTakePhoto(); } } //调用拍照方法 else { // UIImage *image = [[UIImage alloc] initWithContentsOfFile:param]; //1.保存照片 // [self saveImgAndVideo:param isPhoto:YES]; //2.检查文件是否存在 存在返回路径,不存在返回失败 NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:param]) { if (self.getPhotoPath) { self.getPhotoPath(param); } } else { if (self.getPhotoPath) { self.getPhotoPath(@"error:获取照片路径失败"); } } } } //开始录像 - (void)startRecord:(NSString *)str { //删除临时文件 [self deleteTmpFileisPhoto:NO]; //启动录像. 是否推送到相册 0: 不用 1:推送 [self.sdk JPGStartRecord:0]; _recordName = str; } //停止录像 - (void)stopRecord { [self.sdk JPGStopRecord]; //1.保存视频 // [weakSelf saveImgAndVideo:@"" isPhoto:NO]; //2.返回视频地址 延迟1.0s,以防没有获取完整视频地址 WeakSelf; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 获取应用沙盒中的 Documents 目录路径 NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; // 拼接目标文件夹的路径 NSString * targetFolderPath = [documentsPath stringByAppendingPathComponent:@"jpgvideo/"]; // 创建文件管理器实例 NSFileManager *fileManager = [NSFileManager defaultManager]; // 获取目标文件夹中的所有内容 NSError *error = nil; NSArray *contents = [fileManager contentsOfDirectoryAtPath:targetFolderPath error:&error]; if (error) { NSLog(@"Error accessing folder contents: %@", error.localizedDescription); if (weakSelf.getVideoPath) { weakSelf.getVideoPath(@"error:获取视频路径失败"); } } else { //从document中获取路径,找到不带下划线的 NSString *tmpPath = [targetFolderPath stringByAppendingPathComponent:contents.lastObject]; for (NSString *tmpStr in contents) { if (![tmpStr containsString:@"_"]) { tmpPath = [targetFolderPath stringByAppendingPathComponent:tmpStr]; } } // 检查文件是否存在 if ([fileManager fileExistsAtPath:tmpPath]) { if ([tmpPath containsString:weakSelf.recordName]) { NSLog(@"无需重命名"); } else { //重命名 NSRange range = [tmpPath rangeOfString:@"jpgvideo/"]; NSMutableString *mutableString = [NSMutableString stringWithString:tmpPath]; [mutableString insertString:weakSelf.recordName atIndex:range.location + range.length]; NSString *newFilePath = [mutableString copy];; // 使用 moveItemAtPath:toPath:error: 方法重命名文件 BOOL success = [fileManager moveItemAtPath:tmpPath toPath:newFilePath error:&error]; if (success) { NSLog(@"文件重命名成功: %@ -> %@", tmpPath, newFilePath); tmpPath = newFilePath; //返回视频地址 if (weakSelf.getVideoPath) { weakSelf.getVideoPath(tmpPath); } } else { NSLog(@"文件重命名失败: %@", error.localizedDescription); if (weakSelf.getVideoPath) { weakSelf.getVideoPath(@"error:文件重命名失败"); } } } } else { NSLog(@"文件不存在: %@", tmpPath); if (weakSelf.getVideoPath) { weakSelf.getVideoPath(@"error:文件不存在"); } } } }); } //保存照片 数组,两个元素,0:临时视频路径,1:编辑后视频路径 - (void)savePhoto:(NSArray *)arr { [self newSaveImgAndVideo:arr isPhoto:YES]; } //保存视频 数组,两个元素,0:临时视频路径,1:编辑后视频路径 - (void)saveRecord:(NSArray *)arr { [self newSaveImgAndVideo:arr isPhoto:NO]; } //保存照片,视频到相册 - (void)newSaveImgAndVideo:(NSArray *)path isPhoto:(BOOL)isPhoto { WeakSelf; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //删除临时的 if (path[0]) { if ([path[0] isEqualToString:path[1]]) { } //链接不一致时删除 else { [weakSelf deleteFile:path[0]]; } } if (path[1]) { //保存新的 检查文件是否存在 NSString * tmpPath = path[1]; // 获取自定义相册的集合 NSString *albumName = @"dental"; PHFetchResult *collections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil]; __block PHAssetCollection *customAlbum = nil; [collections enumerateObjectsUsingBlock:^(PHAssetCollection * _Nonnull collection, NSUInteger idx, BOOL * _Nonnull stop) { if ([collection.localizedTitle isEqualToString:albumName]) { customAlbum = collection; *stop = YES; } }]; // 如果相册不存在,创建一个新相册 if (!customAlbum) { [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:albumName]; } completionHandler:^(BOOL success, NSError * _Nullable error) { if (success) { NSLog(@"Successfully created album: %@", albumName); [weakSelf newSaveImgAndVideo:path isPhoto:isPhoto]; // 重新调用自己保存 } else { NSLog(@"Error creating album: %@", error.localizedDescription); if (isPhoto) { if (weakSelf.getSavePhotoPath) { weakSelf.getSavePhotoPath(@"error:创建相册失败"); } } else{ if (weakSelf.getSaveRecordPath) { weakSelf.getSaveRecordPath(@"error:创建相册失败"); } } } }]; return; } // 相册存在 开始保存文件到相册 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ PHAssetCollectionChangeRequest *albumChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:customAlbum]; // 图片文件 if (isPhoto) { UIImage *image = [UIImage imageWithContentsOfFile:tmpPath]; if (image) { PHAssetChangeRequest *assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image]; [albumChangeRequest addAssets:@[[assetChangeRequest placeholderForCreatedAsset]]]; } } //视频文件 else { NSURL *fileURL = [NSURL fileURLWithPath:tmpPath]; PHAssetChangeRequest *assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:fileURL]; [albumChangeRequest addAssets:@[[assetChangeRequest placeholderForCreatedAsset]]]; } } completionHandler:^(BOOL success, NSError * _Nullable error) { if (success) { NSLog(@"Successfully saved images to album: %@", albumName); //回到主线程回传 dispatch_async(dispatch_get_main_queue(), ^{ if (isPhoto) { if (weakSelf.getSavePhotoPath) { weakSelf.getSavePhotoPath(@"保存相册成功"); } } else { if (weakSelf.getSaveRecordPath) { weakSelf.getSaveRecordPath(@"保存相册成功"); } } }); } else { NSLog(@"Error saving images: %@", error.localizedDescription); if (isPhoto) { if (weakSelf.getSavePhotoPath) { weakSelf.getSavePhotoPath(@"error:保存相册失败"); } } else { if (weakSelf.getSaveRecordPath) { weakSelf.getSaveRecordPath(@"error:保存相册失败"); } } } }]; } }); } //保存照片,视频到相册 - (void)saveImgAndVideo:(NSString *)path isPhoto:(BOOL)isPhoto { WeakSelf; __block NSString *tmpPath = path; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //重命名 保存照片 if (isPhoto) { //替换文件名添加前缀 // 创建文件管理器实例 NSFileManager *fileManager = [NSFileManager defaultManager]; // 获取目标文件夹中的所有内容 NSError *error = nil; // 检查文件是否存在 if ([fileManager fileExistsAtPath:tmpPath]) { if ([tmpPath containsString:weakSelf.imageName]) { NSLog(@"无需重命名"); } else { //插入文件名 NSRange range = [tmpPath rangeOfString:@"jpgimage/"]; NSMutableString *mutableString = [NSMutableString stringWithString:tmpPath]; [mutableString insertString:weakSelf.imageName atIndex:range.location + range.length]; NSString *newFilePath = [mutableString copy];; // 使用 moveItemAtPath:toPath:error: 方法重命名文件 BOOL success = [fileManager moveItemAtPath:tmpPath toPath:newFilePath error:&error]; if (success) { NSLog(@"文件重命名成功: %@ -> %@", tmpPath, newFilePath); tmpPath = newFilePath; } else { NSLog(@"文件重命名失败: %@", error.localizedDescription); } } } else { NSLog(@"文件不存在: %@", tmpPath); } } //重命名 保存视频 else { // 获取应用沙盒中的 Documents 目录路径 NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; // 拼接目标文件夹的路径 NSString * targetFolderPath = [documentsPath stringByAppendingPathComponent:@"jpgvideo/"]; // 创建文件管理器实例 NSFileManager *fileManager = [NSFileManager defaultManager]; // 获取目标文件夹中的所有内容 NSError *error = nil; NSArray *contents = [fileManager contentsOfDirectoryAtPath:targetFolderPath error:&error]; if (error) { NSLog(@"Error accessing folder contents: %@", error.localizedDescription); if (weakSelf.getVideoPath) { weakSelf.getVideoPath(@"error:获取视频路径失败"); } return; } //从document中获取路径 tmpPath = [targetFolderPath stringByAppendingPathComponent:contents.firstObject]; // 检查文件是否存在 if ([fileManager fileExistsAtPath:tmpPath]) { if ([tmpPath containsString:weakSelf.recordName]) { NSLog(@"无需重命名"); } else { //重命名 NSRange range = [tmpPath rangeOfString:@"jpgvideo/"]; NSMutableString *mutableString = [NSMutableString stringWithString:tmpPath]; [mutableString insertString:weakSelf.recordName atIndex:range.location + range.length]; NSString *newFilePath = [mutableString copy];; // 使用 moveItemAtPath:toPath:error: 方法重命名文件 BOOL success = [fileManager moveItemAtPath:tmpPath toPath:newFilePath error:&error]; if (success) { NSLog(@"文件重命名成功: %@ -> %@", tmpPath, newFilePath); tmpPath = newFilePath; } else { NSLog(@"文件重命名失败: %@", error.localizedDescription); } } } else { NSLog(@"文件不存在: %@", tmpPath); } } // 获取自定义相册的集合 NSString *albumName = @"dental"; PHFetchResult *collections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil]; __block PHAssetCollection *customAlbum = nil; [collections enumerateObjectsUsingBlock:^(PHAssetCollection * _Nonnull collection, NSUInteger idx, BOOL * _Nonnull stop) { if ([collection.localizedTitle isEqualToString:albumName]) { customAlbum = collection; *stop = YES; } }]; // 如果相册不存在,创建一个新相册 if (!customAlbum) { [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:albumName]; } completionHandler:^(BOOL success, NSError * _Nullable error) { if (success) { NSLog(@"Successfully created album: %@", albumName); [weakSelf saveImgAndVideo:tmpPath isPhoto:isPhoto]; // 重新调用自己保存 } else { NSLog(@"Error creating album: %@", error.localizedDescription); if (isPhoto) { if (weakSelf.getPhotoPath) { weakSelf.getPhotoPath(@"error:创建相册失败"); } } else{ if (weakSelf.getVideoPath) { weakSelf.getVideoPath(@"error:创建相册失败"); } } } }]; return; } // 开始保存文件到相册 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ PHAssetCollectionChangeRequest *albumChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:customAlbum]; // 图片文件 if (isPhoto) { UIImage *image = [UIImage imageWithContentsOfFile:tmpPath]; if (image) { PHAssetChangeRequest *assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image]; [albumChangeRequest addAssets:@[[assetChangeRequest placeholderForCreatedAsset]]]; } } //视频文件 else { NSURL *fileURL = [NSURL fileURLWithPath:tmpPath]; PHAssetChangeRequest *assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:fileURL]; [albumChangeRequest addAssets:@[[assetChangeRequest placeholderForCreatedAsset]]]; } } completionHandler:^(BOOL success, NSError * _Nullable error) { if (success) { NSLog(@"Successfully saved images to album: %@", albumName); //回到主线程回传 dispatch_async(dispatch_get_main_queue(), ^{ if (isPhoto) { //获取照片路径 if (weakSelf.getPhotoPath) { weakSelf.getPhotoPath(tmpPath); } } else { //返回视频路径 if (weakSelf.getVideoPath) { weakSelf.getVideoPath(tmpPath); } } }); } else { NSLog(@"Error saving images: %@", error.localizedDescription); if (isPhoto) { if (weakSelf.getPhotoPath) { weakSelf.getPhotoPath(@"error:保存相册失败"); } } else { if (weakSelf.getVideoPath) { weakSelf.getVideoPath(@"error:保存相册失败"); } } } }]; }); } //获取图片列表 - (NSArray *)getPhotoList { return [self fetchPhotosAndVideosFromAlbum:@"jpgimage"]; } //获取视频列表 - (NSArray *)getVideoList { return [self fetchPhotosAndVideosFromAlbum:@"jpgvideo"]; } //获取照片视频文件列表 - (NSArray *)fetchPhotosAndVideosFromAlbum:(NSString *)str { // 获取应用沙盒中的 Documents 目录路径 NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; // 拼接目标文件夹的路径 NSString * targetFolderPath = [documentsPath stringByAppendingPathComponent:str]; // 创建文件管理器实例 NSFileManager *fileManager = [NSFileManager defaultManager]; // 获取目标文件夹中的所有内容 NSError *error = nil; NSArray *contents = [fileManager contentsOfDirectoryAtPath:targetFolderPath error:&error]; //删除临时文件 for (NSString *str in contents) { if ([str containsString:@"_"]) { }else { [fileManager removeItemAtPath:[targetFolderPath stringByAppendingPathComponent:str] error:&error]; } } //重新获取目标文件夹下内容 contents = [fileManager contentsOfDirectoryAtPath:targetFolderPath error:&error]; //拼接完整地址 NSMutableArray *tmpArr = [NSMutableArray new]; for (NSString *tmpStr in contents) { [tmpArr addObject:[targetFolderPath stringByAppendingPathComponent:tmpStr]]; } if (error) { NSLog(@"Error accessing folder contents: %@", error.localizedDescription); return @[]; } return tmpArr.copy; } //删除临时文件 - (void)deleteTmpFileisPhoto:(BOOL)isPhoto { // 获取应用沙盒中的 Documents 目录路径 NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; // 拼接目标文件夹的路径 NSString * targetFolderPath = [documentsPath stringByAppendingPathComponent: isPhoto == YES ? @"jpgimage/" : @"jpgvideo/"]; // 创建文件管理器实例 NSFileManager *fileManager = [NSFileManager defaultManager]; // 获取目标文件夹中的所有内容 NSError *error = nil; NSArray *contents = [fileManager contentsOfDirectoryAtPath:targetFolderPath error:&error]; //删除临时文件 for (NSString *str in contents) { if ([str containsString:@"_"]) { }else { [fileManager removeItemAtPath:[targetFolderPath stringByAppendingPathComponent:str] error:&error]; } } } //删除指定文件 - (BOOL)deleteFile:(NSString *)filePath { //1.删除沙盒中照片 // 创建NSFileManager实例 NSFileManager *fileManager = [NSFileManager defaultManager]; // 删除文件 NSError *error; [fileManager removeItemAtPath:filePath error:&error]; if (error) { NSLog(@"Error: %@", [error localizedDescription]); return NO; } else { NSLog(@"File deleted successfully."); return YES; } } //横屏 - (void)getLandscapeView:(NSArray *)arr { [self.sdk SetVideoFullScreenRect:CGRectMake(0, 0, [arr[0] floatValue], [arr[1] floatValue])]; [self.sdk SetVideoWindows:1]; } //竖屏 - (void)getPortraitView { [self.sdk SetVideoWindows:0]; } @end