diff --git a/SwiftAudioEx/Classes/AVPlayerWrapper/AVPlayerWrapper.swift b/SwiftAudioEx/Classes/AVPlayerWrapper/AVPlayerWrapper.swift index c9a35c9..5482749 100755 --- a/SwiftAudioEx/Classes/AVPlayerWrapper/AVPlayerWrapper.swift +++ b/SwiftAudioEx/Classes/AVPlayerWrapper/AVPlayerWrapper.swift @@ -31,7 +31,9 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { let playerTimeObserver: AVPlayerTimeObserver let playerItemNotificationObserver: AVPlayerItemNotificationObserver let playerItemObserver: AVPlayerItemObserver - + let additionalAVPlayer: AVPlayer + public var preloadedAssets: [String: AVAsset] + /** True if the last call to load(from:playWhenReady) had playWhenReady=true. */ @@ -55,6 +57,9 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { self.playerItemNotificationObserver = AVPlayerItemNotificationObserver() self.playerItemObserver = AVPlayerItemObserver() + self.additionalAVPlayer = AVPlayer(); + self.preloadedAssets = [String: AVAsset](); + self.playerObserver.delegate = self self.playerTimeObserver.delegate = self self.playerItemNotificationObserver.delegate = self @@ -183,7 +188,13 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { recreateAVPlayer() } - self._pendingAsset = AVURLAsset(url: url, options: options) + if (self.preloadedAssets[url.absoluteString] != nil){ + self._pendingAsset = self.preloadedAssets[url.absoluteString] + self.loadAssetIntoPlayer(); + return + } else { + self._pendingAsset = AVURLAsset(url: url, options: options) + } if let pendingAsset = _pendingAsset { self._state = .loading @@ -200,20 +211,7 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { let isPendingAsset = (self._pendingAsset != nil && pendingAsset.isEqual(self._pendingAsset)) switch status { case .loaded: - if isPendingAsset { - let currentItem = AVPlayerItem(asset: pendingAsset, automaticallyLoadedAssetKeys: [Constants.assetPlayableKey]) - currentItem.preferredForwardBufferDuration = self.bufferDuration - self.avPlayer.replaceCurrentItem(with: currentItem) - - // Register for events - self.playerTimeObserver.registerForBoundaryTimeEvents() - self.playerObserver.startObserving() - self.playerItemNotificationObserver.startObserving(item: currentItem) - self.playerItemObserver.startObserving(item: currentItem) - for format in pendingAsset.availableMetadataFormats { - self.delegate?.AVWrapper(didReceiveMetadata: pendingAsset.metadata(forFormat: format)) - } - } + self.loadAssetIntoPlayer(); break case .failed: @@ -233,6 +231,25 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol { }) } } + + func loadAssetIntoPlayer() { + if let pendingAsset = _pendingAsset { + + let isPendingAsset = (self._pendingAsset != nil && pendingAsset.isEqual(self._pendingAsset)) + + if isPendingAsset { + let currentItem = AVPlayerItem(asset: pendingAsset, automaticallyLoadedAssetKeys: [Constants.assetPlayableKey]) + currentItem.preferredForwardBufferDuration = self.bufferDuration + self.avPlayer.replaceCurrentItem(with: currentItem) + + // Register for events + self.playerTimeObserver.registerForBoundaryTimeEvents() + self.playerObserver.startObserving() + self.playerItemNotificationObserver.startObserving(item: currentItem) + self.playerItemObserver.startObserving(item: currentItem) + } + } + } func load(from url: URL, playWhenReady: Bool, initialTime: TimeInterval? = nil, options: [String : Any]? = nil) { _initialTime = initialTime @@ -311,6 +328,53 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate { break } } + + func cancelPreload(item: AudioItem) { + let url = item.getSourceUrl(); + self.preloadedAssets[url]?.cancelLoading(); + self.preloadedAssets[url] = nil; + } + + func cancelAllPreloads() { + for asset in self.preloadedAssets { + if (self.preloadedAssets[asset.key] != nil){ + self.preloadedAssets[asset.key]?.cancelLoading(); + self.preloadedAssets[asset.key] = nil; + } + } + } + + func preload(item: AudioItem) { + let urlString = item.getSourceUrl() + guard let url = URL(string: urlString) else { return } + + let options = (item as? AssetOptionsProviding)?.getAssetOptions() + let asset = AVURLAsset(url: url, options: options) + + let keys = ["playable", "tracks", "duration"] + + asset.loadValuesAsynchronously(forKeys: keys, completionHandler: { + var _: NSError? = nil + + for key in keys { + let status = asset.statusOfValue(forKey: key, error: nil) + if status == AVKeyValueStatus.failed { + return + } + } + + if( self.preloadedAssets[urlString] == nil){ + let playerItem = AVPlayerItem(asset: asset); + self.additionalAVPlayer.replaceCurrentItem(with: playerItem) + + self.additionalAVPlayer.play(); + self.additionalAVPlayer.pause(); + DispatchQueue.main.async { + self.preloadedAssets[urlString] = asset; + } + } + }); + } } diff --git a/SwiftAudioEx/Classes/AVPlayerWrapper/AVPlayerWrapperProtocol.swift b/SwiftAudioEx/Classes/AVPlayerWrapper/AVPlayerWrapperProtocol.swift index d541ddf..c696693 100755 --- a/SwiftAudioEx/Classes/AVPlayerWrapper/AVPlayerWrapperProtocol.swift +++ b/SwiftAudioEx/Classes/AVPlayerWrapper/AVPlayerWrapperProtocol.swift @@ -9,7 +9,7 @@ import Foundation import AVFoundation -protocol AVPlayerWrapperProtocol: class { +protocol AVPlayerWrapperProtocol: AnyObject { var state: AVPlayerWrapperState { get } @@ -52,5 +52,10 @@ protocol AVPlayerWrapperProtocol: class { func load(from url: URL, playWhenReady: Bool, options: [String: Any]?) func load(from url: URL, playWhenReady: Bool, initialTime: TimeInterval?, options: [String: Any]?) - + + func preload(item: AudioItem) + + func cancelAllPreloads() + + func cancelPreload(item: AudioItem) } diff --git a/SwiftAudioEx/Classes/AudioPlayer.swift b/SwiftAudioEx/Classes/AudioPlayer.swift index 6504825..c1f5ebf 100755 --- a/SwiftAudioEx/Classes/AudioPlayer.swift +++ b/SwiftAudioEx/Classes/AudioPlayer.swift @@ -185,7 +185,25 @@ public class AudioPlayer: AVPlayerWrapperDelegate { } enableRemoteCommands(forItem: item) } - + + /** + Preload item. + */ + public func preload(item: AudioItem) { + self.wrapper.preload(item: item); + } + + /** + cancel preload item. + */ + public func cancelPreload(item: AudioItem) { + self.wrapper.cancelPreload(item: item); + } + + public func cancelAllPreloads() { + self.wrapper.cancelAllPreloads(); + } + /** Toggle playback status. */ diff --git a/SwiftAudioEx/Classes/QueuedAudioPlayer.swift b/SwiftAudioEx/Classes/QueuedAudioPlayer.swift index ad67c6c..85bee8e 100755 --- a/SwiftAudioEx/Classes/QueuedAudioPlayer.swift +++ b/SwiftAudioEx/Classes/QueuedAudioPlayer.swift @@ -231,4 +231,12 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate { func onReceivedFirstItem() { self.event.queueIndex.emit(data: (nil, 0)) } + + func preloadNext() { + let nextItems = queueManager.nextItems + + if nextItems.count > 0 { + self.preload(item: nextItems[0]) + } + } }