不重新下载,重放 AVPlayerItem / AVPlayer

14
我已经设置好了一个AVPlayer类,用于流媒体播放音频文件。由于代码有点长,我无法在此贴出全部内容。我遇到的问题是如何允许用户在听完一次后重新播放音频文件。当它第一次播放完成时,我会正确地收到通知AVPlayerItemDidPlayToEndTimeNotification。当我想要重新播放时,我立即收到相同的通知,这阻止了我重新播放。
我该如何重置这个问题,使得AVPlayerItem不认为已经播放过这个音频文件?我可以销毁所有东西并重新设置,但我认为这将迫使用户再次下载音频文件,这是无意义和缓慢的。
以下是我认为与此相关的一些类的部分。当我尝试重新播放文件时,得到的输出结果如下所示。前两行是我期望的,但第三行出乎意料。

正在播放
没有计时器
音频播放器已经完成了音频播放

- (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 触发后,你不能重新播放该项。我目前的解决方法是创建一个新的 Item+Player,但这只会重新下载整个内容。 - eyeballz
@eyeballz - 暂时不行。目前,我只是释放它,然后重新实例化。这并不是一个好的解决方案,但现在这是我能做到的最好的。如果你有什么想法,请发表一下。 - AndroidDev
好的,我已经解决了,但是它是视频而不是音频。视频的问题似乎是你必须保留PlayerLayer,但是你没有任何这种类型的层用于音频。 - eyeballz
2个回答

28

当您的播放器接收到AVPlayerItemDidPlayToEndTimeNotification通知时,您可以调用seekToTime方法。

func itemDidFinishPlaying() {
    self.player.seek(to: CMTime.zero)
    self.player.play()
}

在Swift 4+中,kCMTimeZero变成了.zero。 - JAHelia

5

苹果建议使用 AVQueueplayerAVPlayerLooper

以下是苹果稍作修改后的示例代码:

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会为您处理所有事件监听和播放,而队列播放器则用于创建所谓的“跑步机模式”。该模式基本上是将同一AVAssetItem的多个实例连接在队列播放器中,并将每个已完成的资产移回队列的开头。
这种方法的优点在于它使框架能够在下一个资产到达之前(在此情况下为相同的资产,但其开始仍需要预滚)对下一个资产进行预加载,从而减少了资产结束和循环开始之间的延迟。
这在此视频的大约15:00处有更详细的描述:https://developer.apple.com/videos/play/wwdc2016/503/

1
“Apple says XYZ is best for native”这句话是一个相当简单的回答。请尽可能地总结链接视频的论点。 - Nathan Tuggy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接