重播 AVPlayerItem / AVPlayer 无需重新下载
Replaying AVPlayerItem / AVPlayer without re-downloading
我有一个 AVPlayer class 全部设置为流式传输音频文件。它有点长,所以我不能 post 全部放在这里。我坚持的是如何让用户在听完一次音频文件后重播。当它第一次完成时,我正确地收到了通知 AVPlayerItemDidPlayToEndTimeNotification
。当我去重播它时,我立即收到相同的通知,这阻止了我重播它。
我怎样才能重置它,使 AVPlayerItem
不认为它已经播放了音频文件?我可以取消分配所有内容并重新设置,但我相信这会迫使用户再次下载音频文件,这是毫无意义且缓慢的。
以下是 class 中我认为相关的部分内容。我尝试重放文件时得到的输出如下所示。前两行完全符合我的预期,但第三行令人惊讶。
is playing
no timer
audio player has finished playing audio
- (id) initWithURL : (NSString *) urlString
{
self = [super init];
if (self) {
self.isPlaying = NO;
self.verbose = YES;
if (self.verbose) NSLog(@"url: %@", urlString);
NSURL *url = [NSURL URLWithString:urlString];
self.playerItem = [AVPlayerItem playerItemWithURL:url];
self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
[self determineAudioPlayTime : self.playerItem];
self.lengthOfAudioInSeconds = @0.0f;
[self.player addObserver:self forKeyPath:@"status" options:0 context:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];
}
return self;
}
// this is what gets called when the user clicks the play button after they have
// listened to the file and the AVPlayerItemDidPlayToEndTimeNotification has been received
- (void) playAgain {
[self.playerItem seekToTime:kCMTimeZero];
[self toggleState];
}
- (void) toggleState {
self.isPlaying = !self.isPlaying;
if (self.isPlaying) {
if (self.verbose) NSLog(@"is playing");
[self.player play];
if (!timer) {
NSLog(@"no timer");
CMTime audioTimer = CMTimeMake(0, 1);
[self.player seekToTime:audioTimer];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(updateProgress)
userInfo:nil
repeats:YES];
}
} else {
if (self.verbose) NSLog(@"paused");
[self.player pause];
}
}
-(void)itemDidFinishPlaying:(NSNotification *) notification {
if (self.verbose) NSLog(@"audio player has finished playing audio");
[[NSNotificationCenter defaultCenter] postNotificationName:@"audioFinished" object:self];
[timer invalidate];
timer = nil;
self.totalSecondsPlayed = [NSNumber numberWithInt:0];
self.isPlaying = NO;
}
您可以在播放器收到 AVPlayerItemDidPlayToEndTimeNotification
时调用 seekToTime 方法
func itemDidFinishPlaying() {
self.player.seek(to: CMTime.zero)
self.player.play()
}
Apple 建议使用 AVQueueplayer
和 AVPlayerLooper
。
这里是 Apple 的(稍微修改过的)示例代码:
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] init];
AVAsset *asset = // AVAsset with its 'duration' property value loaded
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
// Create a new player looper with the queue player and template item
self.playerLooper = [AVPlayerLooper playerLooperWithPlayer:queuePlayer
templateItem:playerItem];
// Begin looping playback
[queuePlayer play];
AVPlayerLooper
为您完成所有事件的监听和播放,队列播放器用于创建他们所谓的 "treadmill pattern"。这种模式本质上是在队列播放器中链接相同 AVAssetItem
的多个实例,并将每个完成的资产移回队列的开头。
这种方法的优点是它使框架能够在下一个资产(在这种情况下是相同的资产,但它的开始仍然需要预滚动)到达之前预滚动,减少资产结束和循环之间的延迟开始。
这在 ~15:00 的视频中有更详细的描述:https://developer.apple.com/videos/play/wwdc2016/503/
我有一个 AVPlayer class 全部设置为流式传输音频文件。它有点长,所以我不能 post 全部放在这里。我坚持的是如何让用户在听完一次音频文件后重播。当它第一次完成时,我正确地收到了通知 AVPlayerItemDidPlayToEndTimeNotification
。当我去重播它时,我立即收到相同的通知,这阻止了我重播它。
我怎样才能重置它,使 AVPlayerItem
不认为它已经播放了音频文件?我可以取消分配所有内容并重新设置,但我相信这会迫使用户再次下载音频文件,这是毫无意义且缓慢的。
以下是 class 中我认为相关的部分内容。我尝试重放文件时得到的输出如下所示。前两行完全符合我的预期,但第三行令人惊讶。
is playing
no timer
audio player has finished playing audio
- (id) initWithURL : (NSString *) urlString
{
self = [super init];
if (self) {
self.isPlaying = NO;
self.verbose = YES;
if (self.verbose) NSLog(@"url: %@", urlString);
NSURL *url = [NSURL URLWithString:urlString];
self.playerItem = [AVPlayerItem playerItemWithURL:url];
self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
[self determineAudioPlayTime : self.playerItem];
self.lengthOfAudioInSeconds = @0.0f;
[self.player addObserver:self forKeyPath:@"status" options:0 context:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];
}
return self;
}
// this is what gets called when the user clicks the play button after they have
// listened to the file and the AVPlayerItemDidPlayToEndTimeNotification has been received
- (void) playAgain {
[self.playerItem seekToTime:kCMTimeZero];
[self toggleState];
}
- (void) toggleState {
self.isPlaying = !self.isPlaying;
if (self.isPlaying) {
if (self.verbose) NSLog(@"is playing");
[self.player play];
if (!timer) {
NSLog(@"no timer");
CMTime audioTimer = CMTimeMake(0, 1);
[self.player seekToTime:audioTimer];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(updateProgress)
userInfo:nil
repeats:YES];
}
} else {
if (self.verbose) NSLog(@"paused");
[self.player pause];
}
}
-(void)itemDidFinishPlaying:(NSNotification *) notification {
if (self.verbose) NSLog(@"audio player has finished playing audio");
[[NSNotificationCenter defaultCenter] postNotificationName:@"audioFinished" object:self];
[timer invalidate];
timer = nil;
self.totalSecondsPlayed = [NSNumber numberWithInt:0];
self.isPlaying = NO;
}
您可以在播放器收到 AVPlayerItemDidPlayToEndTimeNotification
时调用 seekToTime 方法func itemDidFinishPlaying() {
self.player.seek(to: CMTime.zero)
self.player.play()
}
Apple 建议使用 AVQueueplayer
和 AVPlayerLooper
。
这里是 Apple 的(稍微修改过的)示例代码:
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] init];
AVAsset *asset = // AVAsset with its 'duration' property value loaded
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
// Create a new player looper with the queue player and template item
self.playerLooper = [AVPlayerLooper playerLooperWithPlayer:queuePlayer
templateItem:playerItem];
// Begin looping playback
[queuePlayer play];
AVPlayerLooper
为您完成所有事件的监听和播放,队列播放器用于创建他们所谓的 "treadmill pattern"。这种模式本质上是在队列播放器中链接相同 AVAssetItem
的多个实例,并将每个完成的资产移回队列的开头。
这种方法的优点是它使框架能够在下一个资产(在这种情况下是相同的资产,但它的开始仍然需要预滚动)到达之前预滚动,减少资产结束和循环之间的延迟开始。
这在 ~15:00 的视频中有更详细的描述:https://developer.apple.com/videos/play/wwdc2016/503/