Воспроизведение AVPlayerItem/AVPlayer без повторной загрузки

У меня есть класс AVPlayer, который настроен для потоковой передачи аудиофайла. Это немного длиннее, поэтому я не могу опубликовать все это здесь. Я застрял в том, как разрешить пользователю воспроизводить аудиофайл после того, как они его прослушали. Когда он заканчивается в первый раз, я правильно получаю уведомление AVPlayerItemDidPlayToEndTimeNotification. Когда я перехожу к воспроизведению, я сразу получаю то же уведомление, которое блокирует меня от повторного воспроизведения.

Как я могу reset так, чтобы 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;
}

Ответ 1

Вы можете вызвать метод seekToTime, когда ваш игрок получил AVPlayerItemDidPlayToEndTimeNotification

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

Ответ 2

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 выполняет прослушивание и воспроизведение всех событий за вас, а проигрыватель очереди используется для создания так называемого "шаблона беговой дорожки". Этот шаблон по существу объединяет несколько экземпляров одного и того же AVAssetItem в проигрывателе очереди и перемещает каждый законченный ресурс обратно в начало очереди.

Преимущество этого подхода заключается в том, что он позволяет платформе предварительно выполнить переход к следующему активу (который в этом случае является тем же самым активом, но его запуск все еще нуждается в предварительной проверке) до его прибытия, что уменьшает задержку между концом актива и циклическим началом.

Более подробно это описано в 15:00 в видео здесь: https://developer.apple.com/videos/play/wwdc2016/503/