仿微信朋友圈視頻剪切功能

寫在前面

公司項目最近有個小視頻功能丰涉,上傳的視頻最長只有15秒企垦,所以需要實現(xiàn)一個視頻剪輯的功能。發(fā)現(xiàn)微信有這個功能涯保,便準(zhǔn)備仿微信的交互寫一個诉濒,結(jié)果遇到不少坑,分享給大家讓大家少走彎路夕春。擼起袖子說干就干未荒。

分析需求

我們先看一看微信的界面

微信效果圖

1.頁面下部拖動左邊和右邊的白色豎條控制剪切視頻的開始和結(jié)束時間,預(yù)覽界面跟隨拖動位置跳到視頻相應(yīng)幀畫面,控制視頻長度最長15秒撇他,最短5秒

2.拖動下部圖片預(yù)覽條茄猫,視頻預(yù)覽畫面跳轉(zhuǎn)到左邊白條停留處的幀畫面

3.下部操作區(qū)域拖動操作時,視頻暫停困肩,松手后視頻播放划纽,播放內(nèi)容為兩個白條之間的內(nèi)容,可以循環(huán)播放

4.界面的“取消”返回锌畸,“確定”后裁剪視頻輸出

先上一個我做完的效果截圖:

仿寫效果圖

我自己設(shè)計的控制條跟微信略有不同勇劣,微信是最長時間時候左右兩個白色豎條離邊框都還有一點距離,我這里設(shè)計的是兩邊白條都貼邊框,返回按鈕和確定裁剪按鈕也不同比默。其實也沒差幻捏,要說微信那樣設(shè)計有特殊考慮的話,我只能說我不是交互和視覺設(shè)計師??

實現(xiàn)

1.我這里完整的拖動選擇視圖是封裝的一個view命咐,上面放一個scrollView來展示小的預(yù)覽圖片篡九,再上面放兩個image來做視頻截取范圍的開始和結(jié)束指示器。首先需要實現(xiàn)下面縮略圖排列以及它的左右滑動醋奠,首先需要找到方法獲取視頻的幀圖片榛臼。找了一下資料,很多窜司,基本都是同一個方法沛善,所以暫時選取了這個方法。為何說暫時塞祈,后面會解釋金刁。

#pragma 獲取想要時間的幀視頻圖片

+(UIImage *)getCoverImage:(NSURL *)outMovieURL atTime:(CGFloat)time isKeyImage:(BOOL)isKeyImage{

AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:outMovieURL options:nil];

NSParameterAssert(asset);

AVAssetImageGenerator *assetImageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];

assetImageGenerator.appliesPreferredTrackTransform = YES;

assetImageGenerator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels;

__block CGImageRef thumbnailImageRef = NULL;

NSError *thumbnailImageGenerationError = nil;

//tips:下面代碼控制時間點的取圖是否為關(guān)鍵幀圖片,系統(tǒng)為了性能是默認取關(guān)鍵幀圖片

CMTime myTime = CMTimeMake(time, 1);

if (!isKeyImage) {

assetImageGenerator.requestedTimeToleranceAfter = kCMTimeZero;

assetImageGenerator.requestedTimeToleranceBefore = kCMTimeZero;

CMTime duration = asset.duration;

myTime = CMTimeMake(time*30,30);

}

thumbnailImageRef = [assetImageGenerator copyCGImageAtTime:myTime actualTime:NULL error:nil];

if (!thumbnailImageRef){

NSLog(@"thumbnailImageGenerationError %@", thumbnailImageGenerationError);

}

UIImage *thumbnailImage = thumbnailImageRef ? [[UIImage alloc]

initWithCGImage:thumbnailImageRef] : nil;

CGImageRelease(thumbnailImageRef);

return thumbnailImage;

}

通常開發(fā)者認為時間的呈現(xiàn)格式應(yīng)該是浮點數(shù)據(jù)议薪,我們一般使用NSTimeInterval尤蛮,實際上它是簡單的雙精度double類型,只是typedef了一下笙蒙,但是由于浮點型數(shù)據(jù)計算很容易導(dǎo)致精度的丟失抵屿,在一些要求高精度的應(yīng)用場景顯然不適合,于是蘋果在Core Media框架中定義了CMTime數(shù)據(jù)類型作為時間的格式

? typedef struct{

CMTimeValue? ? value;

CMTimeScale? ? timescale;

CMTimeFlags? ? flags;

CMTimeEpoch? ? epoch;

} CMTime;

//? 顯然捅位,CMTime定義是一個C語言的結(jié)構(gòu)體轧葛,CMTime是以分數(shù)的形式表示時間,value表示分子艇搀,timescale表示分母尿扯,flags是位掩碼,表示時間的指定狀態(tài)焰雕。CMTimeMake(3, 1)結(jié)果為3衷笋。

我是默認一個完整屏幕寬度為15秒的截取長度,在視頻的每秒取一張幀圖片作為底部預(yù)覽小圖矩屁,起初我是用循環(huán)視頻時長秒數(shù)辟宗,每次用上面方法取一張圖片,再用UIImageView放置這張圖片吝秕,最后再計算imageView的位置添加到scrollView上泊脐。結(jié)果這是一個坑,視頻只有二三十秒還好烁峭,如果比較長則會創(chuàng)建很多個imageView容客,內(nèi)存暴漲秕铛,導(dǎo)致卡頓或者直接crash。后來想到了繪圖缩挑,這樣就不會請求內(nèi)存多次分配空間但两,從而解決內(nèi)存暴漲問題。

@interface WZScrollView : UIScrollView

@property (nonatomic, strong) UIImage *image;

@property (nonatomic, assign) CGRect *rect;

-(void)drawImage:(UIImage *)image inRect:(CGRect)rect;

@end

@implementation WZScrollView

-(void)drawRect:(CGRect)rect{

[super drawRect:rect];

[_image drawInRect:rect];

}

-(void)drawImage:(UIImage *)image inRect:(CGRect)rect{

_image = image;

_rect = &rect

[self setNeedsDisplayInRect:rect];

}

結(jié)果發(fā)現(xiàn)直接畫圖到scrollView在你拖動scrollView的時候它始終會只顯示前面15張圖片的效果供置,o(╯□╰)o=飨妗!芥丧!測試了一下悲关,滾動是有效果的,但是體驗不好啊娄柳。后來把上面的繼承類從UIScrollView改成了UIView,把圖片繪制到view上再加到scrollView上艘绍,設(shè)置好contentSize赤拒,問題解決。

@interface WZScrollView : UIView

接下來就是左右開始和結(jié)束的指示圖片了诱鞠,由于圖片太小會有可能接收不到點擊事件挎挖,所以我這里的切圖在開始處指示圖片的右邊和結(jié)束指示圖片的左邊多裁一部分透明范圍,這樣指示器的面積就比你看到的大了航夺,方便操作蕉朵。接下來就是它們的拖動操作,最開始我使用的是view的touchesMoved:withEvent:來讓圖片改變x值從而跟隨手指移動阳掐。結(jié)果發(fā)現(xiàn)始衅,手速稍快或者觸點稍微偏移就會導(dǎo)致圖片位置改變停止,體驗和性能都不行缭保。后來改用拖動手勢UIPanGestureRecognizer就完美解決了此問題汛闸,這里代碼多是邏輯處理問題,包括拖動范圍何時會讓相應(yīng)圖片進行位置改變的響應(yīng)艺骂,上下的白色線條位置和長度改變等等诸老。但這里需要注意三個問題:a.拖動手勢的回調(diào)方法里面的改變距離和原視圖位置的x值會指數(shù)相加,每次回調(diào)都應(yīng)該將視圖的translation置0钳恕。b.需要每次回調(diào)都計算開始和結(jié)束位置的時間點别伏,讓其有實時性。c.拖動結(jié)束時需要讓播放器循環(huán)播放兩個時間點間的視頻內(nèi)容忧额。

-(void)panAction:(UIPanGestureRecognizer *)panGR{

//偽代碼:根據(jù)需求改變開始和結(jié)束指示圖片的位置

if(panGR.state == UIGestureRecognizerStateChanged){

[panGR setTranslation:CGPointZero inView:self.superview];

}

[self calculateForTimeNodes];//實時計算裁剪時間

if (panGR.state == UIGestureRecognizerStateEnded) {

//偽代碼:指示播放器播放相應(yīng)視頻片段代碼

}

//計算開始結(jié)束時間點

-(void)calculateForTimeNodes{

CGPoint offset = _scrollView.contentOffset;

_startTime =(offset.x+self.startView.frame.origin.x)*15*1.0f/self.bounds.size.width;

_endTime = (offset.x + self.endView.frame.origin.x + KendTimeButtonWidth) * 15 * 1.0f/self.bounds.size.width;

CGFloat imageTime = _startTime;//預(yù)覽時間點

if (_chooseType == imageTypeEnd) {

imageTime = _endTime;

}

if (self.getTimeRange) {

self.getTimeRange(_startTime,_endTime,imageTime);//控制預(yù)覽播放界面的當(dāng)前畫面(這里是一個播放頁傳過來的block的調(diào)用)

}

2.拖動scrollView時厘肮,默認是展示開始時間點的視頻幀畫面,在scrollViewDidScroll:方法中調(diào)用calculateForTimeNodes方法即可實時更新開始宙址、結(jié)束和預(yù)覽3個時間點參數(shù)轴脐,這一步的很多邏輯都封裝到第一步的一些方法中了,所以這一步比較簡單。

3.拖動時暫停播放大咱,松手后播放相應(yīng)時間范圍視頻內(nèi)容恬涧,可以循環(huán)播放。關(guān)于開始和結(jié)束指示圖片的拖動狀態(tài)可以用上面提到的panGR.state == UIGestureRecognizerStateEnded來判斷碴巾,進入判斷說明松手了溯捆,沒有則還在拖動。而scrollView的拖動和停止直接調(diào)用它的代理就行了厦瓢,這里不贅述提揍,不明白可以在demo里面查看。這里遇到個坑是因為前面在視頻預(yù)覽頁面拖動的時候需要有當(dāng)前的視頻幀畫面用作預(yù)覽煮仇,而開始是getCoverImage: atTime: isKeyImage:這個方法來獲取幀圖片的劳跃,當(dāng)拖動時就顯示圖片圖層,停止拖動就隱藏圖片圖層浙垫,進而顯示下面的視頻圖層刨仑。結(jié)果這個方法比較消耗cpu,會導(dǎo)致卡頓情況夹姥,還會經(jīng)常因為cpu過高直接crash杉武。后來發(fā)現(xiàn)AVPlayer里面有一個seekToTime: toleranceBefore: toleranceAfter: completionHandler:方法,作用是讓視頻跳到某個時間點開始播放辙售。我去,這么簡單我卻饒了好大一個彎轻抱,所以大家一定要在使用類的時候要養(yǎng)成多看原類文件的好習(xí)慣,可以少跳抗旦部,囧F硭选!士八!其實AVPlayer還有一個seekToTime:方法,我不使用它的原因是它有一個自己的最小時間單位(貌似是關(guān)鍵幀)夭问,用它不會實時改變播放器畫面。

視頻拖動時:

[_player pause];

[_player seekToTime:CMTimeMake(time*30, 30) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {

}];

拖動停止時:

[_player seekToTime:CMTimeMake(_startTime*30, 30) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {

[_player play];

}];

4.最后就是對視頻進行裁剪了曹铃,這里的這個方法不是我寫的缰趋,是網(wǎng)上找的別人的代碼,但是原代碼有個小問題陕见,就是輸出的視頻文件方向改變了秘血。在這里我用了下面3行代碼來保證輸出視頻的方向跟原視頻保持一致

AVURLAsset *asset = [AVURLAsset assetWithURL:videoUrl];

AVAssetTrack *assetVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo]firstObject];

[compositionVideoTrack setPreferredTransform:assetVideoTrack.preferredTransform];

我這里視頻裁剪后的輸出視頻路徑是固定的,所以我封裝的方法里面的回調(diào)是沒有參數(shù)的评甜,碼友如果需要可以自行改裝:

+ (void)addBackgroundMiusicWithVideoUrlStr:(NSURL *)videoUrl audioUrl:(NSURL *)audioUrl andCaptureVideoWithRange:(TimeRange)videoRange completion:(void(^)(void))completionHandle灰粮;

大家如果下demo來看的話會發(fā)現(xiàn)我在這個方法調(diào)用時在回調(diào)里面多加了一個保存視頻方法到里面,是由于我的項目需求忍坷。這個方法會新建一個以項目名稱命名的相冊粘舟,用來存放剪切后的視頻熔脂,回調(diào)會傳回一個PHAsset對象(項目需求),這個就是贈送節(jié)目了??柑肴。

這里是demo下載地址霞揉,如果對你有用的話別忘了star一個??。

好久沒動swift了晰骑,本來想寫一個swift版練一練适秩,后面再說吧哈哈。硕舆。秽荞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市抚官,隨后出現(xiàn)的幾起案子扬跋,更是在濱河造成了極大的恐慌,老刑警劉巖凌节,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胁住,死亡現(xiàn)場離奇詭異,居然都是意外死亡刊咳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門儡司,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娱挨,“玉大人,你說我怎么就攤上這事捕犬□伟樱” “怎么了有咨?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵树绩,是天一觀的道長。 經(jīng)常有香客問我货葬,道長垢粮,這世上最難降的妖魔是什么贴届? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮蜡吧,結(jié)果婚禮上毫蚓,老公的妹妹穿的比我還像新娘。我一直安慰自己昔善,他們只是感情好元潘,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著君仆,像睡著了一般翩概。 火紅的嫁衣襯著肌膚如雪牲距。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天钥庇,我揣著相機與錄音牍鞠,去河邊找鬼。 笑死上沐,一個胖子當(dāng)著我的面吹牛皮服,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播参咙,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼龄广,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蕴侧?” 一聲冷哼從身側(cè)響起择同,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎净宵,沒想到半個月后敲才,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡择葡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年紧武,在試婚紗的時候發(fā)現(xiàn)自己被綠了敏储。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡妥箕,死狀恐怖畦幢,靈堂內(nèi)的尸體忽然破棺而出缆蝉,到底是詐尸還是另有隱情,我是刑警寧澤刊头,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布芽偏,位于F島的核電站污尉,受9級特大地震影響往产,放射性物質(zhì)發(fā)生泄漏某宪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一蔼囊、第九天 我趴在偏房一處隱蔽的房頂上張望衣迷。 院中可真熱鬧,春花似錦云矫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至腮敌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背斗这。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工表箭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彼水。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓凤覆,卻偏偏與公主長得像拆魏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拥峦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容