使用 AVAudioPlayer 将 URL 从 tableview 控制器传递到视图控制器

Posted

技术标签:

【中文标题】使用 AVAudioPlayer 将 URL 从 tableview 控制器传递到视图控制器【英文标题】:Passing a URL from a tableview controller to a view controller with an AVAudioPlayer 【发布时间】:2020-03-21 21:03:51 【问题描述】:

我目前正在尝试从我的 firebase 数据库播放音频。我有两个视图控制器,第一个是 tableviewcontroller,第二个是带有 AVAudioPlayer 的视图控制器。我试图弄清楚如何成功播放从 firebase 抓取的音频 url,然后在 tableview 中选择音频时将其传递给第二个视图控制器进行播放。这是我当前的代码,当从 tableview 控制器中选择音频时,它在我的第二个 viewcontroller 上返回“致命错误:在展开可选值时意外发现 nil”。

TableViewController:

import UIKit
import Firebase

class HeaderViewTableViewController: UITableViewController 


    @IBOutlet weak var trackImage: UIImageView!
    @IBOutlet weak var trackName: UILabel!
    @IBOutlet weak var artistName: UILabel!
    @IBOutlet weak var trackHeaderImage: UIImageView!

    var image: UIImageView?
    var image2: UIImageView?

    var songs = [String]()
    var urls = [URL]()

    var song:SongPost?

    private let tableHeaderViewHeight: CGFloat = 350.0
    private let tableHeaderViewCutAway: CGFloat = 0.1

    var headerView: HeaderView!
    var headerMaskLayer: CAShapeLayer!

    override func viewDidLoad() 
        super.viewDidLoad()



        print(song)

        trackName.text = song?.title

        ImageService.getImage(withURL: song!.coverImage)  image in
         self.trackImage.image = image
            self.trackHeaderImage.image = image
        
        artistName.text = song?.author.fullname




        tableView.tableFooterView = UIView()

        headerView = tableView.tableHeaderView as! HeaderView
        headerView.imageView = trackImage
        tableView.tableHeaderView = nil
        tableView.addSubview(headerView)

        tableView.contentInset = UIEdgeInsets(top: tableHeaderViewHeight, left: 0, bottom: 0, right: 0)
        tableView.contentOffset = CGPoint(x: 0, y: -tableHeaderViewHeight + 64)

        //cut away header view
        headerMaskLayer = CAShapeLayer()
        headerMaskLayer.fillColor = UIColor.black.cgColor
        headerView.layer.mask = headerMaskLayer

        let effectiveHeight = tableHeaderViewHeight - tableHeaderViewCutAway/2
        tableView.contentInset = UIEdgeInsets(top: effectiveHeight, left: 0, bottom: 0, right: 0)
        tableView.contentOffset = CGPoint(x: 0, y: -effectiveHeight)

        updateHeaderView()
    

    func updateHeaderView() 
        let effectiveHeight = tableHeaderViewHeight - tableHeaderViewCutAway/2
        var headerRect = CGRect(x: 0, y: -effectiveHeight, width: tableView.bounds.width, height: tableHeaderViewHeight)

        if tableView.contentOffset.y < -effectiveHeight 
            headerRect.origin.y = tableView.contentOffset.y
            headerRect.size.height = -tableView.contentOffset.y + tableHeaderViewCutAway/2
        

        headerView.frame = headerRect

        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0, y:0))
        path.addLine(to: CGPoint(x: headerRect.width, y: 0))
        path.addLine(to: CGPoint(x: headerRect.width, y: headerRect.height))
        path.addLine(to: CGPoint(x: 0, y: headerRect.height - tableHeaderViewCutAway))

        headerMaskLayer?.path = path.cgPath
    


      override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) 
          self.tableView.decelerationRate = UIScrollView.DecelerationRate.fast
      

      override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat 
          return UITableView.automaticDimension
      



    @IBAction func backButton(_ sender: Any) 
        dismiss(animated: true, completion: nil)
    



extension HeaderViewTableViewController 


    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return (song?.audioName.count)!
    

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "SongCell", for: indexPath) as! SongNameTableViewCell

        cell.set(song: self.song!)

        cell.songName.text = song?.audioName[indexPath.row]
        return cell
    

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 

        let vc = storyboard?.instantiateViewController(identifier: "ViewController2") as? ViewController2
        vc?.song = song
        vc?.audioName = (song?.audioName[indexPath.row])!

        self.present(vc!, animated: true, completion: nil)
        vc?.url = song?.audioUrl[indexPath.row]

    




extension HeaderViewTableViewController 
    override func scrollViewDidScroll(_ scrollView: UIScrollView) 
        updateHeaderView()
    

第二个视图控制器:

import UIKit
import AVFoundation
import MediaPlayer
import Firebase

class ViewController2: UIViewController, UITabBarControllerDelegate 



    var song2 = [SongPost]()

    var song: SongPost?
    var audioName : String?
    var url:URL?

    @IBOutlet weak var progressBar: UISlider!
    @IBOutlet weak var downChevron: UIButton!
    var timer:Timer?
    var player = AVAudioPlayer()

    @IBOutlet weak var currentTime: UILabel!
    @IBOutlet weak var durationTime: UILabel!

    @IBOutlet weak var coverImage: UIImageView!
    @IBOutlet weak var backgroundImage: UIImageView!
    @IBOutlet weak var songName: UILabel!
    @IBOutlet weak var artistName: UILabel!
    var trackID: Int = 0
    var toggleState = 1

    var ref: DatabaseReference?


    @IBOutlet weak var playButton: UIButton!


    override func viewDidLoad() 
        super.viewDidLoad()

        setUI()


        tabBarController?.tabBar.barTintColor = UIColor.black
        tabBarController?.tabBar.isTranslucent = false

        tabBarController?.tabBar.tintColor = .white
        tabBarController?.tabBar.unselectedItemTintColor = .gray


        do 

            let songURL = url

            print(songURL)
            try player = AVAudioPlayer(contentsOf: songURL!)
            progressBar.maximumValue = Float(player.duration)
            timer = Timer.scheduledTimer(timeInterval: 0.00001, target: self, selector: #selector(changeSliderValueFollowPlayerCurtime), userInfo: nil, repeats: true)
            player.delegate = self

            player.play()


            MPNowPlayingInfoCenter.default().nowPlayingInfo = [
                MPMediaItemPropertyTitle : "The Box",
                MPMediaItemPropertyArtist: "Roddy Ricch",
                MPMediaItemPropertyPlaybackDuration: player.duration,

            ]
            UIApplication.shared.beginReceivingRemoteControlEvents()
            becomeFirstResponder()


         catch 


        

        do 
             try AVAudiosession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: .defaultToSpeaker)
         catch 

        

    


    func setUI() 

        ImageService.getImage(withURL: song!.coverImage)  image in
                       self.coverImage.image = image
                          self.backgroundImage.image = image
                      
               songName.text = song?.title
               artistName.text = song?.author.fullname
    


    override func remoteControlReceived(with event: UIEvent?) 
        if let event = event 
            if event.type == .remoteControl 
                switch event.subtype 
                case .remoteControlPlay:
                    player.play()
                case .remoteControlPause:
                    player.pause()
                case .remoteControlNextTrack:
                    print("next")
                case .remoteControlPreviousTrack:
                    print("previous")
                default:
                    print("error")
                
            
        
    

    @objc func changeSliderValueFollowPlayerCurtime() 

        let curValue = Float(player.currentTime)
        progressBar.value = curValue

    


    @IBAction func sliderUsed(_ sender: Any) 
        player.pause()
        let curTime = progressBar.value
        player.currentTime = TimeInterval(curTime)
        player.play()
    

    @IBAction func playPauseButtonTapped(_ sender: Any) 
        let playBtn = sender as! UIButton
               if toggleState == 1 
                  player.pause()
                toggleState = 2
                playBtn.setImage(UIImage(named:"play 5.png"),for:UIControl.State.normal)

                else 
                player.play()
                   toggleState = 1
                playBtn.setImage(UIImage(named:"pause 4.png"),for:UIControl.State.normal)

               
    


    @IBAction func nextButton(_ sender: Any) 


    

    @IBAction func backButton(_ sender: Any) 
        if trackID != 0 || trackID > 0 

            trackID -= 1
        

        do 
            let audioPath = Bundle.main.path(forResource: "the box", ofType: "mp3")
            try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
         catch 


        


    

    @IBAction func downChevronTapped(_ sender: Any) 

        dismiss(animated: true, completion: nil)
    



extension ViewController2: AVAudioPlayerDelegate 
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) 
        if flag 
            do 

                    let audioPath = Bundle.main.path(forResource: "the box", ofType: "mp3")
                try self.player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)

                    progressBar.maximumValue = Float(player.duration)
                    timer = Timer.scheduledTimer(timeInterval: 0.00001, target: self, selector: #selector(changeSliderValueFollowPlayerCurtime), userInfo: nil, repeats: true)
                    player.delegate = self

                    player.play()
                 catch 

                
        
    

SongPost 类:

import Foundation

class SongPost 
    var id: String
    var author: UserProfile
    var title: String
    var coverImage: URL
    var audioUrl: [URL]
    var audioName : [String]
    var createdAt:Date



    init(id:String, author:UserProfile, title:String, coverImage:URL, audioUrl:[URL], audioName: [String] ,timestamp:Double) 
        self.id = id
        self.author = author
        self.coverImage = coverImage
        self.audioUrl = audioUrl
        self.audioName = audioName

        self.title = title
        self.createdAt = Date(timeIntervalSince1970: timestamp / 1000)
    


更新

Func 配置代码:

 func configure(song: SongPost, audioName: String, url:URL) 

          do 

                    let songURL = url

                    print(songURL)
        //            let audioPath = Bundle.main.path(forResource: "the box", ofType: "mp3")
            try player = AVAudioPlayer(contentsOf: songURL)
                    progressBar.maximumValue = Float(player.duration)
                    timer = Timer.scheduledTimer(timeInterval: 0.00001, target: self, selector: #selector(changeSliderValueFollowPlayerCurtime), userInfo: nil, repeats: true)
                    player.delegate = self

                    player.play()


                    MPNowPlayingInfoCenter.default().nowPlayingInfo = [
                        MPMediaItemPropertyTitle : "The Box",
                        MPMediaItemPropertyArtist: "Roddy Ricch",
                        MPMediaItemPropertyPlaybackDuration: player.duration,

                    ]
                    UIApplication.shared.beginReceivingRemoteControlEvents()
                    becomeFirstResponder()


                 catch 


                

                do 
                     try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: .defaultToSpeaker)
                 catch 

                

    

【问题讨论】:

【参考方案1】:

如果您不确定 optional 不是 nil,请避免在代码中使用过多的展开。

在调用instantiateViewController时,可能会调用ViewController2的viewDidLoad方法。

let vc = storyboard?.instantiateViewController(identifier: "ViewController2") 
                                                                       as? ViewController2

这意味着您将在打开 songURL 的行上出现错误,该行为 nil。

try player = AVAudioPlayer(contentsOf: songURL!)

所以viewDidLoad 不是在您的情况下设置视图的好地方。

ViewController2 中创建一个configure 函数,它接受来自之前viewController 的参数,然后您可以在调用configure 时设置播放器,这意味着您的参数不为零。

class ViewController2: UIViewController, UITabBarControllerDelegate 

   func configure(song: SongPost, audioName : String, url:URL) 
       //setup your view
   

   //...... other methods, viewDidload etc.

//

extension HeaderViewTableViewController 

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 

        let vc = storyboard?.instantiateViewController(identifier: "ViewController2") 
                                                                  as? ViewController2
        self.present(vc!, animated: true, completion: nil)
        vc?.configure(
             song: song, 
             audioName: (song?.audioName[indexPath.row])!, 
             url: song?.audioUrl[indexPath.row])

    


【讨论】:

感谢您的详细解释,我已经更新了我的问题以反映我的 func 配置代码的样子,看看这是否是调用 AVAudioPlayer 的正确方法?如果是这样,那么我会在 VIewDidLoad 内部调用该函数以便它开始播放吗?如果是这样,那将如何正确完成,因为我尝试像这样在侧面 viewdidload 中调用该函数:configure(song: SongPost, audioName: String, url: URL) 但这给了我一个错误说Cannot convert value of type 'SongPost.Type' to expected argument type 'SongPost' 你不需要在你的viewDidLoad 中调用configure 函数,我已经分享了你调用configuredidSelectRowAtconfigure 将在用户点击前一个 tableviewcontroller 上的一行时调用。

以上是关于使用 AVAudioPlayer 将 URL 从 tableview 控制器传递到视图控制器的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift 3 中使用带有动态 URL 的 AVAudioPlayer 导致线程错误

AVAUdioPlayer - 更改当前 URL

如何将歌曲从 iPod 库复制到应用程序并使用 AVAudioPlayer 播放?

Mac 上的 AVAudioPlayer 无法播放/崩溃

无法在 swift 4 中使用 avaudioplayer 播放 http 音频

iOS:将 AVAudioPlayer 的实例从一个变量移动到另一个变量