ios - View 消失后如何获取 AVPlayer 当前正在运行的实例?

标签 ios swift avplayer avplayeritem

我使用 swift 在 iOS 中构建了一个应用程序,其中列出了客户端的图像、歌曲和 PDF。用户可以下载这些文件并使用它们。对于歌曲,功能是让用户下载文件,下载完成后,让他/她播放该文件。这些歌曲按专辑名称在 UITableView 中列出,单击一个单元格时,我调用 .show 来显示另一个包含该歌曲的 UITableView专辑。单击表中的单元格时,我检查文件是否已下载。如果已下载,我将使用 .show 打开一个 View Controller ,其中包含所有媒体播放器控件和媒体元数据。

我已经实现了所有媒体控制,包括重复和随机播放功能,并且播放器连续播放所有下载的轨道。当我在导航栏上单击返回时,我会销毁 AVPlayer 的实例,并且不会在后台播放歌曲。我这样做是因为如果我不这样做,歌曲就会在后台继续播放,并且我不知道如何获得歌曲播放的播放控制。

这是我的媒体播放器 View Controller

import Foundation
import UIKit
import AVFoundation
import MediaPlayer

class AudioPlayer: UIViewController, AVAudioPlayerDelegate {

    var downloadedTracks = [URL]()
    var playableAudioItems = [PlayableTrack]()
    var playableAudioItemsUnshuffled = [PlayableTrack]()
    var serviceAudioItems = [AudioItem]()
    var currentTrack: PlayableTrack? = nil
    var topLevelLabel = " "
    var avPlayerItem: AVPlayerItem?
    var player: AVPlayer?
    var currentTrackIndex = 0
    var commandCenter = MPRemoteCommandCenter.shared()
    var mpic = MPNowPlayingInfoCenter.default()
    var isRepeated = false
    var isShuffled = false

    @IBOutlet weak var albumArt: UIImageView!
    @IBOutlet weak var rewindButton: UIButton!
    @IBOutlet weak var playButton: UIButton!
    @IBOutlet weak var forwardButton: UIButton!
    @IBOutlet weak var titleTrack: UILabel!
    @IBOutlet weak var progressSlider: UISlider!
    @IBOutlet weak var previousTrack: UIButton!
    @IBOutlet weak var nextTrack: UIButton!
    @IBOutlet weak var albumName: UILabel!
    @IBOutlet weak var shuffleButton: UIButton!
    @IBOutlet weak var repeatButton: UIButton!

    override func viewDidLoad(){
        downloadedTracks = CommonUtils.fetchAudioPaths()
        if (UserDefaults.standard.object(forKey: "AudioItems") as? Data) != nil {
            self.serviceAudioItems = UserDefaults.standard.retrieve(object: Array<AudioItem>.self, fromKey: "AudioItems")!
        }

        if UserDefaults.standard.object(forKey: "TracksRepeatable") != nil {
            self.isRepeated = UserDefaults.standard.bool(forKey: "TracksRepeatable")
        }

        if UserDefaults.standard.object(forKey: "TracksShuffled") != nil {
            self.isShuffled = UserDefaults.standard.bool(forKey: "TracksShuffled")
        }

        self.mapValuesFromServiceToPlayable()
        if self.isShuffled {
            self.playableAudioItems.shuffle()
        }
        UIApplication.shared.beginReceivingRemoteControlEvents()
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
            try AVAudioSession.sharedInstance().setActive(true)
        } catch _ {
            return print("error")
        }
        if currentTrack != nil {
            self.currentTrackIndex = self.playableAudioItems.firstIndex(of: self.currentTrack!) ?? 0
        }
    }

    func mapValuesFromServiceToPlayable() {
        self.playableAudioItems = [PlayableTrack]()
        for serviceTrack in self.serviceAudioItems {
            let fileName = URL(string: serviceTrack.trackURL)!.lastPathComponent
            for mp3FileURL in self.downloadedTracks {
                if mp3FileURL.lastPathComponent == fileName {
                    let track = PlayableTrack(
                        albumName: serviceTrack.albumName,
                        albumArt: serviceTrack.albumArtURL,
                        trackName: serviceTrack.trackName,
                        trackURL: mp3FileURL)
                    self.playableAudioItems.append(track)
                }
            }
        }
        self.playableAudioItemsUnshuffled = self.playableAudioItems
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func viewDidAppear(_ animated: Bool) {
        self.commandCenter = MPRemoteCommandCenter.shared()
        self.mpic = MPNowPlayingInfoCenter.default()
        self.initialisePlayerAndPlay()
    }

    override func viewWillAppear(_ animated: Bool) {
        NotificationCenter.default.addObserver(self, selector: #selector(self.finishedPlaying(myNotification:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.avPlayerItem)
    }

    override func viewWillDisappear(_ animated: Bool) {
        self.player?.pause()
        NotificationCenter.default.removeObserver(self)
        self.destroyPlay()
    }

    func initialisePlayerAndPlay() {
        let playableTrack = playableAudioItems[self.currentTrackIndex]
        if self.player != nil {
            if self.player!.rate != 0.0 {
                self.destroyPlay()
            }
            self.progressSlider.setValue(0.0, animated: true)
            let origPlayImage = UIImage(named: "baseline_play_circle_outline_black_48pt")
            let tintedPlayImage = origPlayImage?.withRenderingMode(.alwaysTemplate)
            self.playButton.setBackgroundImage(tintedPlayImage, for: .normal)
            self.playButton.tintColor = KeyConstants.apsmDark
        }

        let imageURL = URL(string: playableTrack.albumArt.trimmingCharacters(in: .whitespacesAndNewlines))
        self.albumArt.sd_setShowActivityIndicatorView(true)
        self.albumArt.sd_setIndicatorStyle(.whiteLarge)
        self.albumArt.sd_setImage(with: imageURL)
        self.titleTrack.text = playableTrack.trackName
        self.albumName.text = playableTrack.albumName

        let origRewindImage = self.rewindButton.backgroundImage(for: .normal)
        let tintedRewindImage = origRewindImage?.withRenderingMode(.alwaysTemplate)
        self.rewindButton.setBackgroundImage(tintedRewindImage, for: .normal)
        self.rewindButton.tintColor = KeyConstants.apsmDark

        let origPlayImage = self.playButton.backgroundImage(for: .normal)
        let tintedPlayImage = origPlayImage?.withRenderingMode(.alwaysTemplate)
        self.playButton.setBackgroundImage(tintedPlayImage, for: .normal)
        self.playButton.tintColor = KeyConstants.apsmDark

        let origForwardImage = self.forwardButton.backgroundImage(for: .normal)
        let tintedForwardImage = origForwardImage?.withRenderingMode(.alwaysTemplate)
        self.forwardButton.setBackgroundImage(tintedForwardImage, for: .normal)
        self.forwardButton.tintColor = KeyConstants.apsmDark

        let origPreviousImage = self.previousTrack.backgroundImage(for: .normal)
        let tintedPreviousImage = origPreviousImage?.withRenderingMode(.alwaysTemplate)
        self.previousTrack.setBackgroundImage(tintedPreviousImage, for: .normal)
        self.previousTrack.tintColor = KeyConstants.apsmDark

        let origNextImage = self.nextTrack.backgroundImage(for: .normal)
        let tintedNextImage = origNextImage?.withRenderingMode(.alwaysTemplate)
        self.nextTrack.setBackgroundImage(tintedNextImage, for: .normal)
        self.nextTrack.tintColor = KeyConstants.apsmDark

        let origShuffledImage = self.shuffleButton.backgroundImage(for: .normal)
        let tintedShuffledImage = origShuffledImage?.withRenderingMode(.alwaysTemplate)
        self.shuffleButton.setBackgroundImage(tintedShuffledImage, for: .normal)
        if self.isShuffled {
            self.shuffleButton.tintColor = KeyConstants.apsmLight
        } else {
            self.shuffleButton.tintColor = KeyConstants.apsmDark
        }

        let origRepeatImage = self.repeatButton.backgroundImage(for: .normal)
        let tintedRepeatImage = origRepeatImage?.withRenderingMode(.alwaysTemplate)
        self.repeatButton.setBackgroundImage(tintedRepeatImage, for: .normal)
        if self.isRepeated {
            self.repeatButton.tintColor = KeyConstants.apsmLight
        } else {
            self.repeatButton.tintColor = KeyConstants.apsmDark
        }

        let asset = AVAsset(url: playableTrack.trackURL)

        let assetKeys = [
            "playable",
            "hasProtectedContent"
        ]

        self.avPlayerItem = AVPlayerItem(asset: asset,
                                         automaticallyLoadedAssetKeys: assetKeys)
        self.player = AVPlayer(playerItem: self.avPlayerItem)
        let playerLayer=AVPlayerLayer(player: player!)
        playerLayer.frame=CGRect(x: 0, y: 0, width: 300, height: 50)
        self.view.layer.addSublayer(playerLayer)

        self.progressSlider.minimumValue = 0
        let duration : CMTime = self.avPlayerItem!.asset.duration
        let seconds : Float64 = CMTimeGetSeconds(duration)
        self.progressSlider.maximumValue = Float(seconds)
        self.progressSlider.isContinuous = false
        self.progressSlider.addTarget(self, action: #selector(self.playbackSliderValueChanged(_:)), for: .valueChanged)

        self.player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main) { (CMTime) -> Void in
            if self.player != nil && self.player!.currentItem?.status == .readyToPlay {
                let time : Float64 = CMTimeGetSeconds(self.player!.currentTime());
                self.progressSlider!.value = Float ( time );
            }
        }

        self.playTrack(self.playButton!)

        var image = UIImage(named: "baseline_audiotrack_black_48pt")
        if self.albumArt.image != nil {
            image = self.albumArt.image
        }
        let albumArt = MPMediaItemArtwork.init(boundsSize: image!.size, requestHandler: { (size) -> UIImage in
            return image!
        })
        self.mpic.nowPlayingInfo = [MPMediaItemPropertyTitle: playableTrack.albumName + playableTrack.trackName, MPMediaItemPropertyArtwork: albumArt]

        self.commandCenter.playCommand.isEnabled = true
        self.commandCenter.playCommand.addTarget(self, action: #selector(self.playTrackFromLock))
        self.commandCenter.pauseCommand.isEnabled = true
        self.commandCenter.pauseCommand.addTarget(self, action: #selector(self.pauseTrack))
        self.commandCenter.stopCommand.isEnabled = true
        self.commandCenter.stopCommand.addTarget(self, action: #selector(self.destroyPlay))
        self.commandCenter.nextTrackCommand.isEnabled = true
        self.commandCenter.nextTrackCommand.addTarget(self, action: #selector(self.clickNextTrack(_:)))
        self.commandCenter.previousTrackCommand.isEnabled = true
        self.commandCenter.previousTrackCommand.addTarget(self, action: #selector(self.clickPreviousTrack(_:)))
        self.commandCenter.seekForwardCommand.isEnabled = true


    }

    @IBAction func playTrack(_ sender: Any) {
        if self.player!.rate == 0.0
        {
            self.playTrackFromLock()

        } else {
            self.pauseTrack()
        }
    }

    @objc func playTrackFromLock() {
        self.player?.play()
        let origPlayImage = UIImage(named: "baseline_pause_circle_outline_black_48pt")
        let tintedPlayImage = origPlayImage?.withRenderingMode(.alwaysTemplate)
        self.playButton.setBackgroundImage(tintedPlayImage, for: .normal)
        self.playButton.tintColor = KeyConstants.apsmDark
    }

    @objc func pauseTrack() {
        self.player?.pause()
        let origPlayImage = UIImage(named: "baseline_play_circle_outline_black_48pt")
        let tintedPlayImage = origPlayImage?.withRenderingMode(.alwaysTemplate)
        self.playButton.setBackgroundImage(tintedPlayImage, for: .normal)
        self.playButton.tintColor = KeyConstants.apsmDark
    }

    @IBAction func rewind(_ sender: Any) {
        let preferredTimeScale : Int32 = 1
        self.player?.seek(to: self.player!.currentTime()-CMTime(seconds: 5.0, preferredTimescale: preferredTimeScale))
    }

    @IBAction func forward(_ sender: Any) {
        let preferredTimeScale : Int32 = 1
        self.player?.seek(to: self.player!.currentTime()+CMTime(seconds: 5.0, preferredTimescale: preferredTimeScale))
    }

    @IBAction func clickNextTrack(_ sender: Any) {
        self.player?.pause()
        var toPlayNext = true
        self.currentTrackIndex += 1
        if self.currentTrackIndex >= self.playableAudioItems.count {
            if self.isRepeated {
                self.currentTrackIndex = 0
            } else {
                self.currentTrackIndex = self.playableAudioItems.count - 1
                toPlayNext = false
            }
        }

        if toPlayNext {
            self.initialisePlayerAndPlay()
        } else {
            self.destroyPlay()
        }
    }

    @IBAction func clickPreviousTrack(_ sender: Any) {
        self.player?.pause()
        var toPlayNext = true
        self.currentTrackIndex -= 1
        if self.currentTrackIndex < 0 {
            if self.isRepeated {
                self.currentTrackIndex = self.playableAudioItems.count - 1
            } else {
                self.currentTrackIndex = 0
                toPlayNext = false
            }

        }

        if toPlayNext {
            self.initialisePlayerAndPlay()
        } else {
            self.destroyPlay()
        }
    }

    @IBAction func clickSuffle(_ sender: Any) {
        if self.isShuffled {
            self.isShuffled = false
        } else {
            self.isShuffled = true
        }
        if self.isShuffled {
            self.playableAudioItems.shuffle()
        } else {
            self.playableAudioItems = self.playableAudioItemsUnshuffled
        }
        let origShuffledImage = self.shuffleButton.backgroundImage(for: .normal)
        let tintedShuffledImage = origShuffledImage?.withRenderingMode(.alwaysTemplate)
        self.shuffleButton.setBackgroundImage(tintedShuffledImage, for: .normal)
        if self.isShuffled {
            self.shuffleButton.tintColor = KeyConstants.apsmLight
        } else {
            self.shuffleButton.tintColor = KeyConstants.apsmDark
        }
        UserDefaults.standard.set(self.isShuffled, forKey: "TracksShuffled")
    }

    @IBAction func clickRepeat(_ sender: Any) {
        if self.isRepeated {
            self.isRepeated = false
        } else {
            self.isRepeated = true
        }
        let origRepeatImage = self.repeatButton.backgroundImage(for: .normal)
        let tintedRepeatImage = origRepeatImage?.withRenderingMode(.alwaysTemplate)
        self.repeatButton.setBackgroundImage(tintedRepeatImage, for: .normal)
        if self.isRepeated {
            self.repeatButton.tintColor = KeyConstants.apsmLight
        } else {
            self.repeatButton.tintColor = KeyConstants.apsmDark
        }
        UserDefaults.standard.set(self.isRepeated, forKey: "TracksRepeatable")
    }

    @objc func playbackSliderValueChanged(_ playbackSlider:UISlider)
    {

        let seconds : Int64 = Int64(playbackSlider.value)
        let targetTime:CMTime = CMTimeMake(value: seconds, timescale: 5)

        self.player!.seek(to: targetTime)

        if self.player!.rate == 0
        {
            self.player?.play()
            let origPlayImage = UIImage(named: "baseline_pause_circle_outline_black_48pt")
            let tintedPlayImage = origPlayImage?.withRenderingMode(.alwaysTemplate)
            self.playButton.setBackgroundImage(tintedPlayImage, for: .normal)
            self.playButton.tintColor = KeyConstants.apsmDark
        }
    }

    @objc func finishedPlaying(myNotification:NSNotification) {
        self.clickNextTrack(self.nextTrack!);
    }

    @objc func destroyPlay() {
        self.player?.pause()
        self.avPlayerItem = nil
        self.player = nil

        self.mpic.nowPlayingInfo = [MPMediaItemPropertyTitle: ""]
        self.commandCenter.playCommand.isEnabled = false
        self.commandCenter.pauseCommand.isEnabled = false
        self.commandCenter.nextTrackCommand.isEnabled = false
        self.commandCenter.previousTrackCommand.isEnabled = false
    }
}

这就是我调用上述 Controller 的方式:


let nextViewController = storyboard?.instantiateViewController(withIdentifier: "AudioPlayer") as! AudioPlayer
        nextViewController.currentTrack = currentTrack
        self.show(nextViewController, sender: Any?.self)

我真正想要的是获取正在后台播放的歌曲的 AVPlayerAVPlayerItem 实例,以便我可以重新控制播放。类似于 Amazon Prime Music 播放器,播放器在后退按钮上最小化,并在单击播放器时再次展开。

最佳答案

制作“var avPlayerItem:AVPlayerItem?”"var player: AVPlayer?" 在类"AudioPlayer"之外作为全局变量在其他 View Controller 上访问它

关于ios - View 消失后如何获取 AVPlayer 当前正在运行的实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56922020/

相关文章:

c# - Unity iOS 异常中的 XML 序列化

ios - Objective-C 日期格式化程序。 yyyy-MM-dd hh :mm:ss change to yyyy-MM-dd

ios - 嵌入式 AVPlayer 不播放视频

ios - 使用 Swift 分析录制的音频文件以进行语音转文本

ios - 添加和检索单元格到字典丢失单元格信息 swift3

ios - 以编程方式在 UIToolbar 中显示或隐藏 UIBarButtonItem

swift - 如何传递 tumblr api GDPR 信息并获取 JSON?

ios - 加载 ViewController 时 UISlider 的 AVPlayer currentTime 更新

swift - AVPlayerViewController 不播放视频?

ios - InputAccessoryView 覆盖底栏