Swift - 每次呈现视图控制器时 AVPlayer 都会重新加载

问题描述

我有一个带有媒体项目列表的 collectionView(一个视频链接和其余音频)。当一个项目被点击时,该 url 被加载到 AVPlayer 中进行播放,并且一个 mediaPlayer 视图控制器以模态呈现与播放控件。当用户向下滑动模式 mediaPlayerVC 时,音频会继续按预期播放。但是,当用户点击媒体项目(可能与正在播放的内容相同或不同)或 nowPlaying 标签时,模式 mediaPlayerVC 将再次呈现,但在加载 url 时音频中存在短暂间隙。很明显,当我在 didSelectRow 和 nowPlayingTapped() 中调用 present(modalVC) 时,avPlayer 被杀死并再次重新加载,因为模式播放器出现。我相信我的问题是在这方面的架构。什么是设置 avPlayer 一次然后让它重新加载一个 url 只有当 url 与当前正在播放的 url 不同时更好的方法?当点击 nowPlaying 时,我只希望 mediaPlayer 以它离开的相同状态再次呈现 - 而不是重新加载 avPlayer。我应该创建一个 AVPlayer 类并以某种方式共享它吗?欢迎提出任何建议。

import AVKit
import MediaPlayer
import UIKit

protocol UpdateNowPlayingButtonDelegate {
func updateNowPlayingButton(station: Station)
}
private let reuseIdentifier = "Cell"

public var avPlayer = AVPlayer()

class MediaCollectionVC: UICollectionViewController,UpdateNowPlayingButtonDelegate {

var stations = [[Station]]()

let mediaVC = MediaPlayerVC()

let stationImages = StationImages()

var nowPlaying: UIButton = {
    let nowPlaying = UIButton()
    nowPlaying.backgroundColor = .systemRed
    nowPlaying.setTitle("Tap a station to play!",for: .normal)
    nowPlaying.isUserInteractionEnabled = false
    nowPlaying.addTarget(self,action: #selector(nowPlayingTapped),for: .touchUpInside)
    return nowPlaying
}()

override func viewDidLoad() {
    super.viewDidLoad()
    fetchRadioStation()

    collectionView.register(MediaCollectionViewCell.self,forCellWithReuseIdentifier: MediaCollectionViewCell.identifier)
    collectionView.register(MediaCollectionSectionReusableView.self,forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,withReuseIdentifier: MediaCollectionSectionReusableView.identifier)
    
    collectionView.backgroundColor = .systemBackground
    self.title = "Live Media"

    navigationController?.modalPresentationStyle = .currentContext

    mediaVC.delegate = self
}

override func numberOfSections(in collectionView: UICollectionView) -> Int { stations.count  }

override func collectionView(_ collectionView: UICollectionView,numberOfItemsInSection section: Int) -> Int { return stations[section].count }

override func collectionView(_ collectionView: UICollectionView,viewForSupplementaryElementOfKind kind: String,at indexPath: IndexPath) -> UICollectionReusableView {
    
    if kind == UICollectionView.elementKindSectionHeader {
        let sectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind,withReuseIdentifier: MediaCollectionSectionReusableView.identifier,for: indexPath) as! MediaCollectionSectionReusableView
        
        switch indexPath.section {
        case 0:
            sectionHeader.label.text = "TV"
            return sectionHeader
        case 1:
            sectionHeader.label.text = "News Radio"
            return sectionHeader
        case 2:
            sectionHeader.label.text = "Entertainment Radio"
            return sectionHeader
        case 3:
            sectionHeader.label.text = "Religious Radio"
            return sectionHeader
        default:
            sectionHeader.label.text = "Section Header Issue"
            return sectionHeader
        }
    } else {
        print("section header issue")
        return UICollectionReusableView()
    }
}

override func collectionView(_ collectionView: UICollectionView,cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MediaCollectionViewCell.identifier,for: indexPath) as! MediaCollectionViewCell
    
    let station = stations[indexPath.section][indexPath.row]
    cell.titleLabel.text = station.name
    cell.imageView.image = stationImages.images[station.name]
    
    return cell
}

override func collectionView(_ collectionView: UICollectionView,didSelectItemAt indexPath: IndexPath) {
    nowPlaying.isUserInteractionEnabled = true
    let station = stations[indexPath.section][indexPath.row]
    
    let position = indexPath

    nowPlaying.setTitle("Now Playing: \(station.name)",for: .normal)
    playStream(station: station)
    mediaVC.stations = stations
    mediaVC.position = position

    present(mediaVC,animated: true)
}

@objc func nowPlayingTapped() {
    present(mediaVC,animated: true)
}

func updateNowPlayingButton(station: Station) {
    nowPlaying.setTitle("Now Playing: \(station.name)",for: .normal)
}

func playStream(station: Station) {
    
    do {
        try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
        try AVAudioSession.sharedInstance().setActive(true)
    
        guard let url = URL(string: station.streamURL) else {
            print("url issue")
            return
        }
        avPlayer = AVPlayer(url: url)
        
        if station.medium == "TV" {
             let playerController = AVPlayerViewController()
             playerController.player = avPlayer
             playerController.allowsPictureInPicturePlayback = true
             self.present(playerController,animated: true)
             avPlayer.play()
        } else {
            avPlayer.play()
        }

    } catch {
        print("error: \(error)")
    }
}
    
func fetchRadioStation() {
    let baseURL = ""

    guard let url = URL(string: baseURL) else {
        print("station list URL invalid")
        return
    }

    let session = URLSession(configuration: .default)

    session.dataTask(with: url) { data,response,error in
        if error != nil {
            print(error ?? "error fetching stations")
            return
        }

        if let safeData = data {
            self.parseJSON(data: safeData)
        }
    }.resume()
}

func parseJSON(data: Data) {
    let decoder = JSONDecoder()

    do {
        let decodedData = try decoder.decode(StationData.self,from: data)
        let newsData = decodedData.stations
        
        DispatchQueue.main.async {
            self.stations.append(newsData.filter { $0.category == "News" && $0.medium == "TV"})
            self.stations.append(newsData.filter { $0.medium == "Radio" && $0.category == "News"})
            self.stations.append(newsData.filter { $0.medium == "Radio" && $0.category == "Entertainment"})
            self.stations.append(newsData.filter { $0.medium == "Radio" && $0.category == "Religious"})
            self.collectionView.reloadData()
        }

        print("all stations loaded successfully")
    } catch {
        print("Error decoding: \(error)")
    }
}
}

// Media Player VC
import AVFoundation
import AVKit
import MediaPlayer
import UIKit

protocol setNowPlaying: MediaCollectionVC {
func didSetNowPlaying(stationName: String)
}

class MediaPlayerVC: UIViewController {

public var position: IndexPath = []
public var stations = [[Station]]()

var stationImages = StationImages()
var player: AVPlayer?

var isPlaying: Bool = true

var delegate: UpdateNowPlayingButtonDelegate?

var nowPlayingInfo = [String : Any]()

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(true)
    configure()
}

override func viewDidLoad() {
    super.viewDidLoad()
    
    view.backgroundColor = .systemBackground


    let station = stations[position.section][position.row]

    setupNotificationView(station: station)

    setupMediaPlayerNotificationView()
    //configure()
}


func updateUI(station: Station) {
    stationImageView.image = stationImages.images[station.name]
    stationNameLabel.text = station.name
}

func configure() {

    let station = stations[position.section][position.row]
    delegate?.updateNowPlayingButton(station: station)
    updateUI(station: station)
    setupNotificationView(station: station)
    
    avPlayer = AVPlayer(url: URL(string: station.streamURL)!)
    avPlayer.play()
    
    playPauseButton.addTarget(self,action: #selector(playPauseButtonTapped(_:)),for: .touchUpInside)
    nextButton.addTarget(self,action: #selector(nextButtonTapped(_:)),for: .touchUpInside)
    backButton.addTarget(self,action: #selector(backButtonTapped(_:)),for: .touchUpInside)

}

func setupMediaPlayerNotificationView() {
    let commandCenter = MPRemoteCommandCenter.shared()
    
    commandCenter.playCommand.addTarget { [unowned self] event in
        avPlayer.play()
        isPlaying.toggle()
        return .success
    }
    
    commandCenter.pauseCommand.addTarget { [unowned self] event in
        avPlayer.pause()
        isPlaying.toggle()
        return .success
    }
    
    commandCenter.previousTrackCommand.addTarget { [unowned self] event in
        self.backButtonTapped(backButton)
        return .success
    }
    
    commandCenter.nextTrackCommand.addTarget { [unowned self] event in
        self.nextButtonTapped(nextButton)
        return .success
    }

}

func setupNotificationView(station: Station) {
    nowPlayingInfo[MPMediaItemPropertyTitle] = "\(station.name)"

    MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo  
}

@objc func playPauseButtonTapped(_ button: UIButton) {
    avPlayer.play()
    isPlaying.toggle()
    if isPlaying == true {
        playPauseButton.setBackgroundImage(UIImage(systemName: "pause"),for: .normal)
    } else {
        avPlayer.pause()
        playPauseButton.setBackgroundImage(UIImage(systemName: "play"),for: .normal)
    }

}

@objc func nextButtonTapped(_ button: UIButton) {
    if position.section == stations.count - 1 {
        if position.row == stations[position.section].count - 1 {
            position.section = 1
            position.row = 0
            configure()
        } else {
            position.row += 1
            configure()
        }
    } else {
        if position.row < stations[position.section].count - 1 {
            position.row += 1
            configure()
        } else {
            position.section += 1
            position.row = 0
            configure()
        }
    }
}

@objc func backButtonTapped(_ button: UIButton) {
    if position != [1,0] {
        if position.row > 0 {
            position.row -= 1
            configure()
        } else {
            position.section -= 1
            position.row = stations[position.section].count - 1
            configure()
        }
    } else {
        position.section = stations.count - 1
        position.row = stations[position.section].count - 1
        configure()
    }
}
}

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...