From a778c8771692ffe577a6077d90b20949a8fabb06 Mon Sep 17 00:00:00 2001 From: uladzislau-snitsarenka Date: Mon, 26 Aug 2019 18:47:32 +0300 Subject: [PATCH 1/5] Add handler for stalled AVPlayer state --- .../AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift | 10 ++++++++++ .../AVPlayerWrapper/AVPlayerWrapperDelegate.swift | 1 + .../AVPlayerWrapper/AVPlayerWrapperProtocol.swift | 1 + ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift | 8 ++++++++ .../Observer/AVPlayerItemNotificationObserver.swift | 6 ++++++ .../AudioPlayer/Observer/AVPlayerItemObserver.swift | 2 +- 6 files changed, 27 insertions(+), 1 deletion(-) mode change 100755 => 100644 ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift mode change 100755 => 100644 ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperDelegate.swift mode change 100755 => 100644 ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift mode change 100755 => 100644 ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift mode change 100755 => 100644 ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemNotificationObserver.swift mode change 100755 => 100644 ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemObserver.swift diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift old mode 100755 new mode 100644 index fae56d994..cab346a37 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift @@ -16,6 +16,7 @@ public enum PlaybackEndedReason: String { case skippedToNext case skippedToPrevious case jumpedToIndex + case stalled } class AVPlayerWrapper: AVPlayerWrapperProtocol { @@ -27,6 +28,7 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { // MARK: - Properties let avPlayer: AVPlayer + var isStalled: Bool? let playerObserver: AVPlayerObserver let playerTimeObserver: AVPlayerTimeObserver let playerItemNotificationObserver: AVPlayerItemNotificationObserver @@ -188,6 +190,10 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate { // MARK: - AVPlayerObserverDelegate func player(didChangeTimeControlStatus status: AVPlayer.TimeControlStatus) { + guard !(self.isStalled == true && status == .paused) else { + self.isStalled = false + return + } switch status { case .paused: if currentItem == nil { @@ -246,6 +252,10 @@ extension AVPlayerWrapper: AVPlayerItemNotificationObserverDelegate { delegate?.AVWrapper(itemPlaybackDoneWithReason: .playedUntilEnd) } + func didStalled() { + delegate?.AVWrapper(didStalled: true) + } + } extension AVPlayerWrapper: AVPlayerItemObserverDelegate { diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperDelegate.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperDelegate.swift old mode 100755 new mode 100644 index 412641286..52373a34d --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperDelegate.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperDelegate.swift @@ -12,6 +12,7 @@ protocol AVPlayerWrapperDelegate: class { func AVWrapper(didChangeState state: AVPlayerWrapperState) func AVWrapper(itemPlaybackDoneWithReason: PlaybackEndedReason) + func AVWrapper(didStalled: Bool) func AVWrapper(secondsElapsed seconds: Double) func AVWrapper(failedWithError error: Error?) func AVWrapper(seekTo seconds: Int, didFinish: Bool) diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift old mode 100755 new mode 100644 index a6136ac3b..96bbe6454 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift @@ -21,6 +21,7 @@ protocol AVPlayerWrapperProtocol { var reasonForWaitingToPlay: AVPlayer.WaitingReason? { get } + var isStalled: Bool? { get set } var rate: Float { get set } diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift old mode 100755 new mode 100644 index 56d371887..529bf5ded --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift @@ -27,6 +27,14 @@ public protocol AudioPlayerDelegate: class { } public class AudioPlayer: AVPlayerWrapperDelegate { + func AVWrapper(didStalled: Bool) { + self._wrapper.isStalled = true + guard self.rate == 0 && self.currentTime > 0 && self.currentTime < self.duration && self.wrapper.currentItem?.isPlaybackBufferEmpty == true else { + return + } + try? self.play() + } + private var _wrapper: AVPlayerWrapperProtocol diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemNotificationObserver.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemNotificationObserver.swift old mode 100755 new mode 100644 index b55a2e064..7f7a280d6 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemNotificationObserver.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemNotificationObserver.swift @@ -11,6 +11,7 @@ import AVFoundation protocol AVPlayerItemNotificationObserverDelegate: class { func itemDidPlayToEndTime() + func didStalled() } /** @@ -35,6 +36,7 @@ class AVPlayerItemNotificationObserver { stopObservingCurrentItem() observingItem = item notificationCenter.addObserver(self, selector: #selector(itemDidPlayToEndTime), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item) + notificationCenter.addObserver(self, selector: #selector(didStalled), name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: item) } /** @@ -43,6 +45,7 @@ class AVPlayerItemNotificationObserver { func stopObservingCurrentItem() { if let observingItem = observingItem { notificationCenter.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: observingItem) + notificationCenter.removeObserver(self, name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: observingItem) } observingItem = nil } @@ -51,4 +54,7 @@ class AVPlayerItemNotificationObserver { delegate?.itemDidPlayToEndTime() } + @objc private func didStalled() { + delegate?.didStalled() + } } diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemObserver.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemObserver.swift old mode 100755 new mode 100644 index fa851e33f..fea236f6b --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemObserver.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemObserver.swift @@ -24,7 +24,7 @@ class AVPlayerItemObserver: NSObject { private static var context = 0 private let main: DispatchQueue = .main - + private let isStalled: Bool = false private struct AVPlayerItemKeyPath { static let duration = #keyPath(AVPlayerItem.duration) static let loadedTimeRanges = #keyPath(AVPlayerItem.loadedTimeRanges) From a3c3fa4dde1f938200c2705b8467e07711e6dda1 Mon Sep 17 00:00:00 2001 From: uladzislau-snitsarenka Date: Mon, 26 Aug 2019 19:08:41 +0300 Subject: [PATCH 2/5] Remove unused player state constant --- .../Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift index cab346a37..6c1c8937c 100644 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift @@ -16,7 +16,6 @@ public enum PlaybackEndedReason: String { case skippedToNext case skippedToPrevious case jumpedToIndex - case stalled } class AVPlayerWrapper: AVPlayerWrapperProtocol { From ebbb29397ab695e7b736007e11fbe6e03c588351 Mon Sep 17 00:00:00 2001 From: uladzislau-snitsarenka Date: Mon, 26 Aug 2019 19:21:41 +0300 Subject: [PATCH 3/5] Remove redundant ItemObserver field --- .../Vendor/AudioPlayer/Observer/AVPlayerItemObserver.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemObserver.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemObserver.swift index fea236f6b..058685c44 100644 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemObserver.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/Observer/AVPlayerItemObserver.swift @@ -24,7 +24,6 @@ class AVPlayerItemObserver: NSObject { private static var context = 0 private let main: DispatchQueue = .main - private let isStalled: Bool = false private struct AVPlayerItemKeyPath { static let duration = #keyPath(AVPlayerItem.duration) static let loadedTimeRanges = #keyPath(AVPlayerItem.loadedTimeRanges) From 0d83304d277e8c74f3434cc84849588d790d2175 Mon Sep 17 00:00:00 2001 From: uladzislau-snitsarenka Date: Tue, 27 Aug 2019 12:42:56 +0300 Subject: [PATCH 4/5] Add handler logic for slow network --- index.d.ts | 5 ++++- ios/RNTrackPlayer/RNTrackPlayer.swift | 8 ++++++++ .../AVPlayerWrapper/AVPlayerWrapper.swift | 6 ++++-- .../AVPlayerWrapperProtocol.swift | 4 +++- .../Vendor/AudioPlayer/AudioPlayer.swift | 16 ++++++++++++++-- lib/index.js | 5 +++++ 6 files changed, 38 insertions(+), 6 deletions(-) diff --git a/index.d.ts b/index.d.ts index 0c565c0ce..6b09adb99 100644 --- a/index.d.ts +++ b/index.d.ts @@ -148,7 +148,10 @@ declare namespace RNTrackPlayer { export const STATE_PAUSED: State; export const STATE_STOPPED: State; export const STATE_BUFFERING: State; - + + export const PLAYBACK_STALLED_SLOW_NETWORK: State; + export const PLAYBACK_STALLED_BEFORE_SONG_END: State; + export const RATING_HEART: RatingType; export const RATING_THUMBS_UP_DOWN: RatingType; export const RATING_3_STARS: RatingType; diff --git a/ios/RNTrackPlayer/RNTrackPlayer.swift b/ios/RNTrackPlayer/RNTrackPlayer.swift index fa1d3af4f..a8a04b1e0 100644 --- a/ios/RNTrackPlayer/RNTrackPlayer.swift +++ b/ios/RNTrackPlayer/RNTrackPlayer.swift @@ -29,6 +29,10 @@ public class RNTrackPlayer: RCTEventEmitter, AudioPlayerDelegate { sendEvent(withName: "playback-state", body: ["state": player.playerState.rawValue]) } + public func audioPlayer(playerDidStalled message: String) { + sendEvent(withName: "playback-stalled", body: ["message": message]) + } + public func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason) { if reason == .playedUntilEnd && player.nextItems.count == 0 { sendEvent(withName: "playback-queue-ended", body: [ @@ -103,6 +107,9 @@ public class RNTrackPlayer: RCTEventEmitter, AudioPlayerDelegate { "CAPABILITY_SET_RATING": "NOOP", "CAPABILITY_JUMP_FORWARD": Capability.jumpForward.rawValue, "CAPABILITY_JUMP_BACKWARD": Capability.jumpBackward.rawValue, + + "PLAYBACK_STALLED_SLOW_NETWORK": PlaybackStalledReason.slowNetwork.rawValue, + "PLAYBACK_STALLED_BEFORE_SONG_END": PlaybackStalledReason.beforeSongEnd.rawValue ] } @@ -113,6 +120,7 @@ public class RNTrackPlayer: RCTEventEmitter, AudioPlayerDelegate { "playback-state", "playback-error", "playback-track-changed", + "playback-stalled", "remote-stop", "remote-pause", diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift index 6c1c8937c..afb659ef1 100644 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift @@ -27,7 +27,8 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { // MARK: - Properties let avPlayer: AVPlayer - var isStalled: Bool? + var isStalled: Bool + var playAttempts: Int let playerObserver: AVPlayerObserver let playerTimeObserver: AVPlayerTimeObserver let playerItemNotificationObserver: AVPlayerItemNotificationObserver @@ -52,7 +53,8 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { self.playerTimeObserver = AVPlayerTimeObserver(player: avPlayer, periodicObserverTimeInterval: timeEventFrequency.getTime()) self.playerItemNotificationObserver = AVPlayerItemNotificationObserver() self.playerItemObserver = AVPlayerItemObserver() - + self.playAttempts = 0 + self.isStalled = false self.playerObserver.delegate = self self.playerTimeObserver.delegate = self self.playerItemNotificationObserver.delegate = self diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift index 96bbe6454..d4b946db6 100644 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift @@ -21,7 +21,9 @@ protocol AVPlayerWrapperProtocol { var reasonForWaitingToPlay: AVPlayer.WaitingReason? { get } - var isStalled: Bool? { get set } + var isStalled: Bool { get set } + + var playAttempts: Int { get set } var rate: Float { get set } diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift index 529bf5ded..71c7ebba3 100644 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift @@ -10,12 +10,19 @@ import MediaPlayer public typealias AudioPlayerState = AVPlayerWrapperState +public enum PlaybackStalledReason: String { + case beforeSongEnd + case slowNetwork +} + public protocol AudioPlayerDelegate: class { func audioPlayer(playerDidChangeState state: AudioPlayerState) func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason) + func audioPlayer(playerDidStalled: String) + func audioPlayer(secondsElapsed seconds: Double) func audioPlayer(failedWithError error: Error?) @@ -28,10 +35,15 @@ public protocol AudioPlayerDelegate: class { public class AudioPlayer: AVPlayerWrapperDelegate { func AVWrapper(didStalled: Bool) { - self._wrapper.isStalled = true - guard self.rate == 0 && self.currentTime > 0 && self.currentTime < self.duration && self.wrapper.currentItem?.isPlaybackBufferEmpty == true else { + guard self.rate == 0 && self.currentTime > 0 && self.currentTime < self.duration && self.wrapper.currentItem?.isPlaybackBufferEmpty == true && self._wrapper.playAttempts < 5 else { + self._wrapper.playAttempts = 0 + self._wrapper.isStalled = false + self.delegate?.audioPlayer(playerDidStalled: PlaybackStalledReason.slowNetwork.rawValue) return } + self._wrapper.isStalled = true + self.delegate?.audioPlayer(playerDidStalled: PlaybackStalledReason.beforeSongEnd.rawValue) + self._wrapper.playAttempts += 1 try? self.play() } diff --git a/lib/index.js b/lib/index.js index c0b8c989d..1bab9e5a6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -92,6 +92,7 @@ function registerEventHandler(handler) { 'playback-error', 'playback-queue-ended', 'playback-track-changed', + 'playback-stalled', 'remote-play', 'remote-pause', @@ -126,6 +127,10 @@ module.exports.STATE_PAUSED = TrackPlayer.STATE_PAUSED; module.exports.STATE_STOPPED = TrackPlayer.STATE_STOPPED; module.exports.STATE_BUFFERING = TrackPlayer.STATE_BUFFERING; +// Playback Stalled Messages +module.exports.PLAYBACK_STALLED_SLOW_NETWORK = TrackPlayer.PLAYBACK_STALLED_SLOW_NETWORK; +module.exports.PLAYBACK_STALLED_BEFORE_SONG_END = TrackPlayer.PLAYBACK_STALLED_BEFORE_SONG_END; + // Capabilities module.exports.CAPABILITY_PLAY = TrackPlayer.CAPABILITY_PLAY; module.exports.CAPABILITY_PLAY_FROM_ID = TrackPlayer.CAPABILITY_PLAY_FROM_ID; From b549fb29e0d1d1b2717ccc45b9a80694466118ab Mon Sep 17 00:00:00 2001 From: uladzislau-snitsarenka Date: Wed, 28 Aug 2019 14:14:30 +0300 Subject: [PATCH 5/5] Update send event logic for PlaybackStalled event --- ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift b/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift index 71c7ebba3..7313e6e75 100644 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AudioPlayer.swift @@ -42,7 +42,9 @@ public class AudioPlayer: AVPlayerWrapperDelegate { return } self._wrapper.isStalled = true - self.delegate?.audioPlayer(playerDidStalled: PlaybackStalledReason.beforeSongEnd.rawValue) + if(self._wrapper.playAttempts == 1) { + self.delegate?.audioPlayer(playerDidStalled: PlaybackStalledReason.beforeSongEnd.rawValue) + } self._wrapper.playAttempts += 1 try? self.play() }