背景:AVPlayer播放視頻時(shí)砾省,可通過seekToTime:方法跳轉(zhuǎn)到視頻指定時(shí)間,通常會(huì)綁定進(jìn)度條通過拖動(dòng)來實(shí)現(xiàn)混槐。細(xì)心的人會(huì)發(fā)現(xiàn)编兄,當(dāng)拖動(dòng)是前進(jìn)方向時(shí),視頻畫面是非常流暢的声登,反之拖動(dòng)后退時(shí)狠鸳,就會(huì)出現(xiàn)卡頓揣苏,卡頓的程度與視頻質(zhì)量、長度等或有關(guān)系碰煌。(卡頓原因有待思考舒岸,知道的讀者望評(píng)論告知)
時(shí)間進(jìn)度條
時(shí)間進(jìn)度條
解決方法:我為AVPlayer寫了一個(gè)分類(SeekSmoothly)绅作,使用ss_seekToTime:替換原本的seekToTime:方法便可芦圾。源碼如下:
`
AVPlayer+SeekSmoothly.h
#import <AVFoundation/AVFoundation.h>
@interface AVPlayer (SeekSmoothly)
- (void)ss_seekToTime:(CMTime)time;
- (void)ss_seekToTime:(CMTime)time
toleranceBefore:(CMTime)toleranceBefore
toleranceAfter:(CMTime)toleranceAfter
completionHandler:(void (^)(BOOL))completionHandler;
@end
AVPlayer+SeekSmoothly.m
#import "AVPlayer+SeekSmoothly.h"
#import <objc/runtime.h>
@interface AVPlayerSeeker : NSObject
{
CMTime targetTime;
BOOL isSeeking;
}
@property (weak, nonatomic) AVPlayer *player;
@end
@implementation AVPlayerSeeker
- (instancetype)initWithPlayer:(AVPlayer *)player {
self = [super init];
if (self) {
self.player = player;
}
return self;
}
- (void)seekSmoothlyToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter completionHandler:(void (^)(BOOL))completionHandler {
targetTime = time;
if (!isSeeking) {
[self trySeekToTargetTimeWithToleranceBefore:toleranceBefore toleranceAfter:toleranceAfter completionHandler:completionHandler];
}
}
- (void)trySeekToTargetTimeWithToleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter completionHandler:(void (^)(BOOL))completionHandler {
if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
[self seekToTargetTimeToleranceBefore:toleranceBefore toleranceAfter:toleranceAfter completionHandler:completionHandler];
}
}
- (void)seekToTargetTimeToleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter completionHandler:(void (^)(BOOL))completionHandler {
isSeeking = YES;
CMTime seekingTime = targetTime;
[self.player seekToTime:seekingTime toleranceBefore:toleranceBefore
toleranceAfter:toleranceAfter completionHandler:
^(BOOL isFinished) {
if (CMTIME_COMPARE_INLINE(seekingTime, ==, targetTime)) {
// seek completed
isSeeking = NO;
if (completionHandler) {
completionHandler(isFinished);
}
} else {
// targetTime has changed, seek again
[self trySeekToTargetTimeWithToleranceBefore:toleranceBefore toleranceAfter:toleranceAfter completionHandler:completionHandler];
}
}];
}
@end
static NSString *seekerKey = @"ss_seeker";
@implementation AVPlayer (SeekSmoothly)
- (void)ss_seekToTime:(CMTime)time {
[self ss_seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:nil];
}
- (void)ss_seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter completionHandler:(void (^)(BOOL))completionHandler {
AVPlayerSeeker *seeker = objc_getAssociatedObject(self, &seekerKey);
if (!seeker) {
seeker = [[AVPlayerSeeker alloc] initWithPlayer:self];
objc_setAssociatedObject(self, &seekerKey, seeker, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[self pause];
[seeker seekSmoothlyToTime:time toleranceBefore:toleranceBefore toleranceAfter:toleranceAfter completionHandler:completionHandler];
}
@end
`
簡析源碼:
分類中通過運(yùn)行時(shí)關(guān)聯(lián)AVPlayerSeeker對(duì)象,
在AVPlayerSeeker中記錄seek狀態(tài)isSeeking俄认,避免AVPlayer seekToTime:方法同時(shí)多次調(diào)用个少,
并記錄目標(biāo)時(shí)間targetTime,保證最后視頻正確跳轉(zhuǎn)眯杏。
參考:
https://developer.apple.com/library/archive/qa/qa1820/_index.html