1.livePhoto简介
livePhoto是iOS 9.0 之后系统相机提供的拍摄动态照片的功能,但是仅在6S+,iOS 9.0+设备可用。拍摄完livePhoto之后,只需要在相册按压livePhoto相片即可动态的播放。livePhoto还可以设置为动态壁纸。如果只能用相机拍摄的livePhoto设置为动态壁纸,这不能满足我们的需求了。如果可以将视频转换为livePhoto那就完美了。如果要实现这个功能就要了解live Photo的本质了。
2.livePhoto的本质
其实livePhoto的本质是一张jpg图片+一段mov视频另外再加入一些信息一起写入到相册内即可生成livePhoto,核心的写入代码是我在github找到的,但是Swift版,我将它翻译为OC版。Swift版写入livePhotoDemo。
3.涉及到的技术
1)相册数据的读取与写入;
2)share Extension的使用;
3)视频提取某一帧图片;
4)不同进程间的通讯;
5)PHLivePhotoView展示livePhoto图片;
4.实现livePhoto制作工具
主界面UI如下图,噗。。请原谅我毫无美感的页面设计,勿喷。!
顶部一个AVplayer实现本地视频的播放,AVplayer下面一个UISlider可以选择视频的哪一帧作为livePhoto的封面图。UISlider下面一个PHLivePhotoView按压可以预览livePhoto的效果图。
1)如何提取视频中的某一帧?
/**
获取视频的 某一帧
@param currentTime 某一时刻单位 s
@param path 视频路径
@return return 返回image
*/
- (UIImage *)getVideoImageWithTime:(Float64)currentTime videoPath:(NSURL *)path {
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:path options:nil];
// float fps = [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] nominalFrameRate];
// NSLog(@"视频帧率%f",fps);
AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
gen.appliesPreferredTrackTransform = YES;
gen.requestedTimeToleranceAfter = kCMTimeZero;// 精确提取某一帧,需要这样处理
gen.requestedTimeToleranceBefore = kCMTimeZero;// 精确提取某一帧,需要这样处理
CMTime time = CMTimeMakeWithSeconds(currentTime, 600);
NSError *error = nil;
CMTime actualTime;
CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
UIImage *img = [[UIImage alloc] initWithCGImage:image];
CMTimeShow(actualTime);
CGImageRelease(image);
return img;
}
2)图片与视频分别经过特定的处理然后分别存储为JPG 与 MOV格式。
将视频转为livePhoto存入相册,iOS原生API并没有给出这样的方法。而能将视频转为livePhoto,是源于SwiftlivePhotoDemo作者对livePhoto细心观察。我这里只是借花献佛转化为OC版本。
使用上图中的两个文件可以将图片与视频分别经过特定的处理然后分别存储为JPG 与 MOV格式
3)将处理后的JPG 与 MOV一同存储生成livePhoto
首先引入#import
+ (void)writeLivePhotoWithVideo:(NSURL *)videoPath image:(NSURL *)imagePath result:(void(^)(BOOL res))result {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest * request = [PHAssetCreationRequest creationRequestForAsset];
[request addResourceWithType:PHAssetResourceTypePhoto fileURL:imagePath options:nil];
[request addResourceWithType:PHAssetResourceTypePairedVideo fileURL:videoPath options:nil];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
//保存成功
NSLog(@"保存成功");
}
if (result) {
result(success);
}
}];
}
自此一个简单的livePhoto制作工具完成。但是为了用户体验,我们还要进行素材选取途径拓宽。
4)拓宽素材选取的途径
a.从相册选取素材
从相册选取素材,这里使用了UIImagePickerController来处理,具体代码如下:
- (void)chooseVideoFromPhotoLibraryResult:(ResultBlock)result {
UIImagePickerController *imagePick = [[UIImagePickerController alloc] init];
imagePick.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
imagePick.mediaTypes = @[@"public.movie"];//只获取视频数据
imagePick.delegate = self;
self.result = result;
[[[UIApplication sharedApplication] delegate].window.rootViewController presentViewController:imagePick animated:YES completion:nil];
}
// UIImagePickerController 的选择结果的代理方法。
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissViewControllerAnimated:YES completion:nil];
if ([[info[@"UIImagePickerControllerMediaURL"] absoluteString] length]) {
if (self.result) {
self.result(info[@"UIImagePickerControllerMediaURL"], YES);
}
}
}
b.使用share Extension,让用户可以从其他App中获取素材
我们在项目工程下file-->New-->target-->share Extension-->Nest 创建一个share Extension如下图:
这篇文章详细解读了share Extension,如有需要可以先看一下
由于没有使用原生的share Extension UI所以这里,我们先把系统生成的.h+.m+MainInterface.storyboard这三个文件删除。然后创建一个viewController(继承UIviewController)+Xib。
在share Extension的info.plist的
NSExtension
下增加NSExtensionPrincipalClass
值为我们自己创建的那个viewController。然后在 把NSExtensionActivationRule
改为dictonary类型下面增加NSExtensionActivationSupportsMovieWithMaxCount
值为1,这是说明我们的share Extension支持视频格式而且只能是一个视频。
然后在viewdidload里实现下面的代码,这些代码是为了获取用户选择的视频地址的操作。
__weak typeof (self) ws = self;
[self.extensionContext.inputItems enumerateObjectsUsingBlock:^(NSExtensionItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj.attachments enumerateObjectsUsingBlock:^(NSItemProvider * _Nonnull itemProvider, NSUInteger idx, BOOL * _Nonnull stop) {
if ([itemProvider hasItemConformingToTypeIdentifier:itemProvider.registeredTypeIdentifiers[0]]) {
[itemProvider loadItemForTypeIdentifier:itemProvider.registeredTypeIdentifiers[0] options:nil completionHandler:^(id _Nullable item, NSError * _Null_unspecified error) {
NSLog(@"%@",item);
if ([(NSObject *)item isKindOfClass:[NSURL class]]) {
NSString * lastAppending = [[[(NSURL*)item absoluteString] componentsSeparatedByString:@"/"] lastObject];
NSFileManager *fileManger = [NSFileManager defaultManager];
NSURL *groupFile = [fileManger containerURLForSecurityApplicationGroupIdentifier:@"group.com.livephoto"];
NSURL *fileUrl = [groupFile URLByAppendingPathComponent:lastAppending];
//移除旧的数据
[fileManger removeItemAtURL:fileUrl error:nil];
NSError *error = nil;
[fileManger copyItemAtURL:(NSURL *)item toURL:fileUrl error:&error];
if (!error) {
ws.lastAppending = lastAppending;
dispatch_async(dispatch_get_main_queue(), ^{
[ws initVideoWithPath:fileUrl];
});
NSLog(@"存入成功!!!");
}
}
}];
*stop = YES;
}
}];
*stop = YES;
}];
这里要注意,由于Extension 与 宿主app分别属于不同的进程,由于iOS是沙盒存储机制。所以不同的进程是不能直接相互访问数据的。但是同一个公司的App 可以通过AppGroup来设置一个公共的存储空间,从而达到 不同的进行相互访问数据。这里我们也是使用AppGroup 把素材资源存储在公共的区域。方便宿主app访问素材数据。具体AppGroup如何来实现?可以参考这篇文文章:iOS App Group实现数据共享
如何唤醒宿主App处理素材数据?
其实iOS提供的Extension当中只有today widget 可以通过url schemes的方式唤醒。其他都是不能唤醒宿主App的。但是通过下面的方法还是可以强行通过url schemes的方式唤醒宿主app的,但是不知道是否可以通过审核?我看到京东的 拍照购 是可以唤醒京东app 的。
- (IBAction)handleVideo:(UIButton *)sender {
UIResponder *responder = self;
while (responder) {
if ([responder respondsToSelector:@selector(openURL:)]) {
[responder performSelector:@selector(openURL:) withObject:[NSURL URLWithString:[NSString stringWithFormat:@"livePhoto://data=%@",_lastAppending]]];
break;
}
responder = [responder nextResponder];
}
}
5.注意点
demo下载请点这里,运行demo前请先配置好自己的环境,以及把App Group换成自己的,感谢好用给个star,最后看一下项目运行效果吧。