ios – How to stop or override instance of AVPlayer?

0
103


Here I’ve created a common AudioPlayer for all kind of music lists, But when music is already playing, and play another track then instead of overwrite or stop old track both the instance of player runs simultaneously and played double of music. To overcome this issue , already tried to Stop the player, and also setting nil for AudioPlayer but still facing issue.

class SessionManager {

    static let shared = SessionManager()

    var isPlayingMusic: Bool = false
    var isAdFree: Bool = false

    var audioPlayer: AudioPlayer?
    var updateShuffleBtnUI: (() -> Void)?
    var didShuffleList: Bool = false
    var isShuffleOn: Bool = false {
        didSet {
            updateShuffleBtnUI?()
        }
    }

    func setTheme() {
        UIView.transition(with: appDelegate?.window ?? UIWindow(), duration: 0.5, options: [.preferredFramesPerSecond60]) {
            appDelegate?.window?.overrideUserInterfaceStyle = self.isDarkTheme ? .dark : .light
        }
    }

    func clear() {
        isPlayingMusic = false
        audioPlayer?.clearPlayerInstance()

    }
}

Note:- Some of methods and objects has been removed from below class, to keep question in format and maintain quality of question.

import Foundation
import AVFoundation
import UIKit
import MediaPlayer


class AudioPlayer: NSObject {

    public var audioPlayerConfig: [String: Any] = [
        "loop": false,
        "volume": 1.0
    ]

    private var playerViewControllerKVOContext = 0

    var audioQueueObserver: NSKeyValueObservation?
    var audioQueueStatusObserver: NSKeyValueObservation?
    var audioQueueBufferEmptyObserver: NSKeyValueObservation?
    var audioQueueBufferAlmostThereObserver: NSKeyValueObservation?
    var audioQueueBufferFullObserver: NSKeyValueObservation?
    var audioQueueStallObserver: NSKeyValueObservation?
    var audioQueueWaitingObserver: NSKeyValueObservation?
    var assetPoolObserver: NSKeyValueObservation?

    var playbackLikelyToKeepUpKeyPathObserver: NSKeyValueObservation?
    var playbackBufferEmptyObserver: NSKeyValueObservation?
    var playbackBufferFullObserver: NSKeyValueObservation?

    /////
    var audioItem: AVPlayerItem?
    let onError = Signal<(message: String, error: Error)>()
    let onCollectionReady = Signal<Bool>()

    // Data Transfer Closure
    var updatePlayPauseStatus: ((Bool) -> Void)?
    var updateSliderAndTime: ((_ listenedTime: String, _ sliderProgress: Float, _ passTimeStr: String?, _ totalDuration: Float?) -> Void)?
    var updateRepeatBtnUI: (() -> Void)?
    var loadingStatus: ((Bool) -> Void)?
    var loadBottomBarViewInTab: ((Bool, _ newItemMetaData: SongModel?) -> Void)?
    var firstAssetIsLoaded: (() -> Void)?
    var songDetailClosure: ((SongModel?) -> Void)?
    var mediaItemChange: ((_ newItemMetaData: SongModel?) -> Void)?

    // Media observers
    private var mediaTimeObserver: Any?
    var streamPosition: TimeInterval?
    var streamDuration: TimeInterval?

    var playingItemIndex = 0

    // Repeat Vars
    var isRepeat: Bool = false {
        didSet {
            updateRepeatBtnUI?()
        }
    }
    var playerLooper: NSObject?
    var repeatItem: AVPlayerItem?
    var repeatItemIndex: Int?

    // Shuffle Vars
    var shuffleCompletionClosure: (() -> Void)?

    // Media controls in notification bar
    var nowPlayingInfo = [String: Any]()

    var trackArr: [String] = []

    var musicMetaDataList = [SongModel]()
    var songObj: SongModel? {
        didSet {
            setupNowPlayingInfo()
        }
    }

    var viewModel = MusicViewModel()
    var playerType: PlayerType = .audio

    //////////////////
    let assetQueue = DispatchQueue(label: "randomQueue", qos: .utility)
    let group = DispatchGroup()

    let assetKeysRequiredToPlay = [
        "playable"
    ]

    var player = AVPlayer()
    var playerQueue: AVQueuePlayer?
    var seeking: Bool = false

    var AVItemPool: [AVPlayerItem] = [] {
        didSet {
            print("item was added", self.AVItemPool.count)
            if self.AVItemPool.count == trackArr.count {
                self.onCollectionReady.fire(true)
            }
        }
    }

    var asset: AVURLAsset? {
        didSet {
            guard let newAsset = asset else { return }

            asynchronouslyLoadURLAsset(newAsset, appendDirectly: false)
        }
    }

    var dynamicAsset: AVURLAsset? {
        didSet {
            guard let newDAsset = dynamicAsset else { return }

            asynchronouslyLoadURLAsset(newDAsset, appendDirectly: true)
        }
    }

    public var duration: Double {
        guard let currentItem = player.currentItem else { return 0.0 }

        return CMTimeGetSeconds(currentItem.duration)
    }

    var rate: Float {
        get {
            return player.rate
        }
        set {
            player.rate = newValue
        }
    }

    /*
     A formatter for individual date components used to provide an appropriate
     value for the `startTimeLabel` and `durationLabel`.
     */
    let timeRemainingFormatter: DateComponentsFormatter = {
        let formatter = DateComponentsFormatter()
        formatter.zeroFormattingBehavior = .pad
        formatter.allowedUnits = [.minute, .second]

        return formatter
    }()

    /** Play previous track from queue */
    func previousTrack() {
        playingItemIndex -= 1
        var index = 0
        clearRepeat()
        if let currentItem = playerQueue?.currentItem {
            index = (AVItemPool.firstIndex(of: currentItem) ?? 0)
            play(at: (index > 0) ? (index - 1) : index)
        } else {
            play(at: AVItemPool.count-2)
        }
        resume()
    }

    /** Play next track from queue */
    func nextTrack() {
        var index = 0
        playingItemIndex += 1
        clearRepeat()
        if let currentItem = playerQueue?.currentItem {
            index = (AVItemPool.firstIndex(of: currentItem) ?? 0)
            play(at: (index < (AVItemPool.count - 1)) ? (index + 1) : index)
        } else {
            play(at: AVItemPool.count-1)
        }
        resume()
    }

    /** Play track from queue at any index */
    func play(at index: Int) {
        playerQueue?.removeAllItems()
        for i in index..<AVItemPool.count {
            let obj = AVItemPool[i]
            if playerQueue?.canInsert(obj, after: nil) == true {
                obj.seek(to: .zero)
                playerQueue?.insert(obj, after: nil)
            }
        }
    }
   
    /**
     Loads and appends directly a track to the currently playing queue
     */
    public func appendQueueItems(track: String) {
        self.assetQueue.async {
            self.group.wait()
            self.group.enter()
            let fileURL = NSURL(string: track)
            self.dynamicAsset = AVURLAsset(url: fileURL! as URL, options: nil)
        }
    }


    /**
     Return if player is currently playing a track
     */
    public func isPlaying() -> Bool {
        return (self.playerQueue?.rate ?? 0.0) > Float(0.0)
    }

    /**
     Pause playback of audio player
     */
    public func pause() {
        self.viewModel.event.set(.idle())
        self.playerQueue?.pause()
    }

    /**
     Play or Resume playback of current audio player
     */
    public func resume() {
        self.viewModel.event.set(.idle())
        self.playerQueue?.play()
    }

    /// Integer conversion to Hours/Minutes/Seconds
    func secondsToHoursMinutesSeconds(_ seconds: Int) -> (String, String, String) {
        var hour = String(seconds / 3600)
        var minute = String((seconds % 3600) / 60)
        var second = String((seconds % 3600) % 60)
        (hour.count == 1) ? hour.insert("0", at: hour.startIndex) : Void()
        (minute.count == 1) ? minute.insert("0", at: minute.startIndex) : Void()
        (second.count == 1) ? second.insert("0", at: second.startIndex) : Void()

        return (hour, minute, second)
    }

    /// Get Current Playing Item
    func getCurrentPlayingItem() -> AVPlayerItem? {
        if let item = playerQueue?.currentItem {
            return item
        }
        return nil
    }

    /// Clear All Repeat Track Related properties clear method
    func clearRepeat() {
        self.isRepeat = false
        self.playerLooper = nil
        self.repeatItem = nil
        self.repeatItemIndex = nil
    }


    public func initialize(config: [String: Any]?) {

        if config != nil {
            self.audioPlayerConfig = config ?? [String: Any]()
            loadingStatus?(true)
        }

        // load assets as PlayerItems
        self.group.enter()
        var counter = 0
        for item in self.trackArr {

            print("adding asset: \(item)")
            if counter > 0 {
                self.assetQueue.async {
                    self.group.wait()
                    self.group.enter()
                    let fileURL = NSURL(string: item)
                    self.asset = AVURLAsset(url: fileURL! as URL, options: nil)
                }
            } else {
                self.assetQueue.async {
                    let fileURL = NSURL(string: item)
                    self.asset = AVURLAsset(url: fileURL! as URL, options: nil)
                }
            }

            counter += 1
        }
    }

    func clearPlayerInstance() {
        SessionManager.shared.audioPlayer?.playerQueue?.removeAllItems()
        MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
        SessionManager.shared.audioPlayer = nil
    }
   

    override init() {
        super.init()
    }

    deinit {
        /// Remove any KVO observer.
        self.audioQueueObserver?.invalidate()
        self.audioQueueStatusObserver?.invalidate()
        self.audioQueueBufferEmptyObserver?.invalidate()
        self.audioQueueBufferAlmostThereObserver?.invalidate()
        self.audioQueueBufferFullObserver?.invalidate()
        var audioQueueStallObserver: NSKeyValueObservation?
        var audioQueueWaitingObserver: NSKeyValueObservation?
        var mediaTimeObserver: Any?
        self.onCollectionReady.cancelAllSubscriptions()
    }

    // MARK: - Error Handling

    func handleErrorWithMessage(_ message: String?, error: Error? = nil) {
        NSLog("Error occured with message: \(String(describing: message)), error: \(String(describing: error)).")

       

      
    }

    // MARK: Convenience

    func createTimeString(time: Float) -> String {
        let components = NSDateComponents()
        components.second = Int(max(0.0, time))

        return timeRemainingFormatter.string(from: components as DateComponents)!
    }

    func findIndex(ofPlayerItem valueToFind: AVPlayerItem, in array: [AVPlayerItem]) -> Int? {
        for (index, value) in array.enumerated() {
            if value == valueToFind {
                return index
            }
        }
        return nil
    }

}
  • Code of navigating for Playing track

func openMusicPlayer(type: PlayerType, songObj: SongModel? = nil, musicUrlList: [SongModel]? = nil) {
        let musicPlayer = Router.musicPlayer.instance as? MusicPlayerVC ?? MusicPlayerVC()
        musicPlayer.type = type
        musicPlayer.songObj = songObj
        musicPlayer.musicUrlList = musicUrlList
        musicPlayer.hidesBottomBarWhenPushed = true
        
        // In here below i'm getting isPlayingMusic = true, but when i tried to remove old instance Player or try to remove old track from player. Getting "AudioPlayer = nil"

        if SessionManager.shared.isPlayingMusic {
            SessionManager.shared.audioPlayer?.audioQueueObserver?.invalidate()
            SessionManager.shared.audioPlayer?.audioQueueStatusObserver?.invalidate()
            SessionManager.shared.audioPlayer?.audioQueueBufferEmptyObserver?.invalidate()
            SessionManager.shared.audioPlayer?.audioQueueBufferAlmostThereObserver?.invalidate()
            SessionManager.shared.audioPlayer?.audioQueueBufferFullObserver?.invalidate()
            var audioQueueStallObserver: NSKeyValueObservation?
            var audioQueueWaitingObserver: NSKeyValueObservation?
            var mediaTimeObserver: Any?
            SessionManager.shared.audioPlayer?.onCollectionReady.cancelAllSubscriptions()

            SessionManager.shared.isPlayingMusic = false
            SessionManager.shared.audioPlayer?.pause()
            SessionManager.shared.audioPlayer?.player.replaceCurrentItem(with: nil)
            SessionManager.shared.audioPlayer = nil
        }

        if type == .audio {
            navigationController?.pushViewController(musicPlayer, animated: true)
        } else {
            showLoginRequiredAlert { [weak self] in
                guard let self = self else { return }
                self.navigationController?.pushViewController(musicPlayer, animated: true)
            }
        }
    }