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 <Photos/Photos.h>框架,調用performChanges方法存儲,具體代碼如下
+ (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<NSString *,id> *)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。
在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<NSSecureCoding> _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.項目文件截圖
6.注意點
運行demo前請先配置好自己的環境,以及把App Group換成自己的。最后看一下項目運行效果吧。
iOS開發一個制作Live Photo的工具
注:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權