Проверить состояние воспроизведения AVPlayer

Ответы:

57

Чтобы получить уведомление о достижении конца элемента (через Apple ):

[[NSNotificationCenter defaultCenter] 
      addObserver:<self>
      selector:@selector(<#The selector name#>)
      name:AVPlayerItemDidPlayToEndTimeNotification 
      object:<#A player item#>];

А отслеживать игру вы можете:

«отслеживать изменения положения курсора воспроизведения в объекте AVPlayer» с помощью addPeriodicTimeObserverForInterval: queue: usingBlock: или addBoundaryTimeObserverForTimes: queue: usingBlock: .

Пример от Apple:

// Assume a property: @property (retain) id playerObserver;

Float64 durationSeconds = CMTimeGetSeconds([<#An asset#> duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 1);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 1);
NSArray *times = [NSArray arrayWithObjects:[NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird], nil];

self.playerObserver = [<#A player#> addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
    // Passing NULL for the queue specifies the main queue.

    NSString *timeDescription = (NSString *)CMTimeCopyDescription(NULL, [self.player currentTime]);
    NSLog(@"Passed a boundary at %@", timeDescription);
    [timeDescription release];
}];
Тодд Хопкинсон
источник
Вы можете добавить логический флаг, чтобы проверить статус воспроизведения.
moonman239
Уведомления могут вызвать проблемы, если вы измените элемент игрока, например, с помощью -replaceCurrentItemWithPlayerItem:, и не обработаете его должным образом. Для меня более надежный способ - отслеживать AVPlayerстатус с помощью KVO. Подробнее см. Мой ответ ниже: stackoverflow.com/a/34321993/3160561
maxkonovalov
2
Также нужно уведомление об ошибке? AVPlayerItemFailedToPlayToEndTimeNotification
ToolmakerSteve
332

Вы можете сказать, что он играет, используя:

AVPlayer *player = ...
if ((player.rate != 0) && (player.error == nil)) {
    // player is playing
}

Расширение Swift 3:

extension AVPlayer {
    var isPlaying: Bool {
        return rate != 0 && error == nil
    }
}
маз
источник
29
Не обязательно, он не обрабатывает ситуации, когда игрок останавливается из-за ошибки в файле. Кое-что я выяснил на собственном горьком опыте ...
Дермот
7
Как сказал Дермот, если вы попытаетесь сыграть во что-нибудь в режиме полета, скорость AVPlayer все равно будет установлена ​​на 1.0, поскольку это подразумевает намерение играть.
фи
4
AVPlayer имеет свойство error, просто убедитесь, что оно не равно нулю, а также проверьте, что скорость не равна нулю :)
Джеймс Кэмпбелл,
23
Обратите внимание, что ответ был обновлен, чтобы предотвратить сценарий @Dermot. Так что это действительно работает.
jomafer 03
2
Не будет работать при воспроизведении видеоматериала в обратном направлении ( - [AVPlayer setRate:-1.0]), потому что -1.0оно меньше 0.
Джулиан Ф. Вайнерт
49

В iOS10 для этого теперь есть встроенное свойство: timeControlStatus

Например, эта функция воспроизводит или приостанавливает avPlayer в зависимости от его статуса и соответствующим образом обновляет кнопку воспроизведения / паузы.

@IBAction func btnPlayPauseTap(_ sender: Any) {
    if aPlayer.timeControlStatus == .playing {
        aPlayer.pause()
        btnPlay.setImage(UIImage(named: "control-play"), for: .normal)
    } else if aPlayer.timeControlStatus == .paused {
        aPlayer.play()
        btnPlay.setImage(UIImage(named: "control-pause"), for: .normal)
    }
}

Что касается вашего второго вопроса, чтобы узнать, достиг ли avPlayer конца, проще всего настроить уведомление.

NotificationCenter.default.addObserver(self, selector: #selector(self.didPlayToEnd), name: .AVPlayerItemDidPlayToEndTime, object: nil)

Например, когда он дойдет до конца, вы можете перемотать его на начало видео и сбросить кнопку «Пауза» на «Воспроизведение».

@objc func didPlayToEnd() {
    aPlayer.seek(to: CMTimeMakeWithSeconds(0, 1))
    btnPlay.setImage(UIImage(named: "control-play"), for: .normal)
}

Эти примеры полезны, если вы создаете свои собственные элементы управления, но если вы используете AVPlayerViewController, элементы управления будут встроены.

Трэвис М.
источник
Простота использования для iOS 10+
technerd
2
@objc func didPlayToEnd () для Swift 4.2
Шон Стейнс,
21

rateэто НЕ способ , чтобы проверить , является ли видео воспроизведения (он может приостанавливаться). Из документации rate:

Указывает желаемую скорость воспроизведения; 0.0 означает "приостановлено", 1.0 указывает на желание играть с естественной скоростью текущего элемента.

Ключевые слова «желание играть» - скорость 1.0не означает, что видео проигрывается.

Решение, начиная с iOS 10.0, заключается в использовании, AVPlayerTimeControlStatusкоторое можно увидеть на сайте AVPlayer timeControlStatus.

Решение до iOS 10.0 (9.0, 8.0 и т. Д.) Состоит в том, чтобы развернуть собственное решение. Скорость 0.0означает, что воспроизведение видео приостановлено. Когда rate != 0.0это означает, что видео либо воспроизводится, либо застопорилось.

Вы можете узнать разницу, наблюдая за временем игрока: func addPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: @escaping (CMTime) -> Void) -> Any

Блок возвращает текущее время игрока CMTime, поэтому сравнение lastTime(время, которое было в последний раз получено от блока) и currentTime(время, которое только что сообщил блок) покажет, играет ли игрок или остановился. Например, если lastTime == currentTimeи rate != 0.0, то игрок остановился.

Как отмечали другие, выяснение того, закончилось ли воспроизведение, обозначается значком AVPlayerItemDidPlayToEndTimeNotification.

Кгаидис
источник
18

Более надежная альтернатива NSNotification- добавить себя в качестве наблюдателя к rateсобственности игрока .

[self.player addObserver:self
              forKeyPath:@"rate"
                 options:NSKeyValueObservingOptionNew
                 context:NULL];

Затем проверьте, равно ли новое значение наблюдаемой скорости нулю, что означает, что воспроизведение остановлено по какой-то причине, например, по достижении конца или остановке из-за пустого буфера.

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context {
    if ([keyPath isEqualToString:@"rate"]) {
        float rate = [change[NSKeyValueChangeNewKey] floatValue];
        if (rate == 0.0) {
            // Playback stopped
        } else if (rate == 1.0) {
            // Normal playback
        } else if (rate == -1.0) {
            // Reverse playback
        }
    }
}

На rate == 0.0всякий случай, чтобы узнать, что именно вызвало остановку воспроизведения, вы можете выполнить следующие проверки:

if (self.player.error != nil) {
    // Playback failed
}
if (CMTimeGetSeconds(self.player.currentTime) >=
    CMTimeGetSeconds(self.player.currentItem.duration)) {
    // Playback reached end
} else if (!self.player.currentItem.playbackLikelyToKeepUp) {
    // Not ready to play, wait until enough data is loaded
}

И не забудьте остановить игрока, когда он доходит до конца:

self.player.actionAtItemEnd = AVPlayerActionAtItemEndPause;

максконовалов
источник
Я думаю, также нужно уведомление о невозможности достичь конца - AVPlayerItemFailedToPlayToEndTimeNotification. Если эта ошибка произойдет, никогда не дойдет до конца времени. Или, прочитав ответ maz, добавьте в свой код проверку player.error != nil, и в этом случае воспроизведение "закончилось" из-за ошибки.
ToolmakerSteve
Кстати, что вы считаете «ненадежным» в использовании NSNotification of AVPlayerItemDidPlayToEndTimeNotification?
ToolmakerSteve
ToolmakerSteve, спасибо за замечание, в мой ответ добавлена ​​проверка ошибок. Для ненадежных уведомлений - я сам столкнулся с проблемой при реализации повторяющихся и многократно используемых представлений игроков в представлении коллекции, и KVO прояснил ситуацию, так как он позволил сохранить все более локальным, чтобы уведомления разных игроков не мешали друг другу.
maxkonovalov
17

Для Swift :

AVPlayer :

let player = AVPlayer(URL: NSURL(string: "http://www.sample.com/movie.mov"))
if (player.rate != 0 && player.error == nil) {
   println("playing")
}

Обновление :
player.rate > 0условие изменено на, player.rate != 0потому что, если видео воспроизводится в обратном направлении, оно может быть отрицательным, спасибо Джулиану за указание.
Примечание : это может выглядеть так же, как и ответ выше (Maz), но в Swift '! Player.error' выдавал мне ошибку компилятора, поэтому вам нужно проверить наличие ошибки, используя 'player.error == nil' в Swift. (Потому что свойство error не относится к типу Bool)

AVAudioPlayer:

if let theAudioPlayer =  appDelegate.audioPlayer {
   if (theAudioPlayer.playing) {
       // playing
   }
}

AVQueuePlayer:

if let theAudioQueuePlayer =  appDelegate.audioPlayerQueue {
   if (theAudioQueuePlayer.rate != 0 && theAudioQueuePlayer.error == nil) {
       // playing
   }
}
Акс
источник
2
AVPlayer! = AVAudioPlayer
quemeful
2
Предостережения: все упомянутое выше в ответе, появившемся за два года до этого.
Дэн Розенстарк
1
@Yar Я знаю, что также проголосовал за вышеупомянутый ответ, но это для быстрого и в Swift '! Player.error' не работал у меня, это разрешено только для типов bool, поэтому я добавил ответ, упс, проголосовал за ваш комментарий по ошибке, чтобы дать ответ, используя это приложение для переполнения стека :)
Акс
Меня беспокоит то, что я не знаю, насколько хорошо на самом деле работает player.error, отличная от nil.
Дэн Розенстарк
1
Не будет работать при воспроизведении видеозаписи в обратном направлении ( player.rate = -1.0), потому что -1,0 меньше 0.
Джулиан Ф. Вайнерт
9

Расширение Swift на основе ответа maz

extension AVPlayer {

    var isPlaying: Bool {
        return ((rate != 0) && (error == nil))
    }
}
Марк Бриджес
источник
6

Swift-версия ответа maxkonovalov такова:

player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.New, context: nil)

и

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if keyPath == "rate" {
        if let rate = change?[NSKeyValueChangeNewKey] as? Float {
            if rate == 0.0 {
                print("playback stopped")
            }
            if rate == 1.0 {
                print("normal playback")
            }
            if rate == -1.0 {
                print("reverse playback")
            }
        }
    }
}

Спасибо максконовалов!

Г-н Станев
источник
Здравствуйте, господин Станев, спасибо за ваш ответ. У меня это не работает (он же ничего не печатает в консоли?)
Чезаре
Да ладно, это работает - мой плеер был nil! Но мне нужно знать, когда просмотр начинается (а не заканчивается). Как это сделать?
Чезаре
3

В настоящее время в Swift 5 самый простой способ проверить, играет ли игрок или приостановлен, - это проверить переменную .timeControlStatus .

player.timeControlStatus == .paused
player.timeControlStatus == .playing
аземи
источник
1

Ответ: цель C

if (player.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
    //player is playing
}
else if (player.timeControlStatus == AVPlayerTimeControlStatusPaused) {
    //player is pause
}
else if (player.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate) {
    //player is waiting to play
}
iOS Lifee
источник
0
player.timeControlStatus == AVPlayer.TimeControlStatus.playing
Нода Хикару
источник
1
Этот предоставленный ответ может быть правильным, но для него может быть полезно объяснение . Ответы только на код не считаются "хорошими" ответами. Вот несколько советов о том, как написать хороший ответ? . Из обзора .
MyNameIsCaleb