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 old mode 100755 new mode 100644 index fae56d994..afb659ef1 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapper.swift @@ -27,6 +27,8 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { // MARK: - Properties let avPlayer: AVPlayer + var isStalled: Bool + var playAttempts: Int let playerObserver: AVPlayerObserver let playerTimeObserver: AVPlayerTimeObserver let playerItemNotificationObserver: AVPlayerItemNotificationObserver @@ -51,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 @@ -188,6 +191,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 +253,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..d4b946db6 --- a/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift +++ b/ios/RNTrackPlayer/Vendor/AudioPlayer/AVPlayerWrapper/AVPlayerWrapperProtocol.swift @@ -21,6 +21,9 @@ protocol AVPlayerWrapperProtocol { var reasonForWaitingToPlay: AVPlayer.WaitingReason? { get } + 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 old mode 100755 new mode 100644 index 56d371887..7313e6e75 --- 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?) @@ -27,6 +34,21 @@ public protocol AudioPlayerDelegate: class { } public class AudioPlayer: AVPlayerWrapperDelegate { + func AVWrapper(didStalled: Bool) { + 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 + if(self._wrapper.playAttempts == 1) { + self.delegate?.audioPlayer(playerDidStalled: PlaybackStalledReason.beforeSongEnd.rawValue) + } + self._wrapper.playAttempts += 1 + 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..058685c44 --- 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 struct AVPlayerItemKeyPath { static let duration = #keyPath(AVPlayerItem.duration) static let loadedTimeRanges = #keyPath(AVPlayerItem.loadedTimeRanges) 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;