【问题标题】:Passing a URL from a tableview controller to a view controller with an AVAudioPlayer使用 AVAudioPlayer 将 URL 从 tableview 控制器传递到视图控制器
【发布时间】: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 {

                }

    }

【问题讨论】:

    标签: ios swift xcode firebase avaudioplayer


    【解决方案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 上的一行时调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-23
    • 1970-01-01
    • 2018-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多