使用 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
函数,我已经分享了你调用configure
的didSelectRowAt
。 configure
将在用户点击前一个 tableviewcontroller 上的一行时调用。以上是关于使用 AVAudioPlayer 将 URL 从 tableview 控制器传递到视图控制器的主要内容,如果未能解决你的问题,请参考以下文章
在 Swift 3 中使用带有动态 URL 的 AVAudioPlayer 导致线程错误
如何将歌曲从 iPod 库复制到应用程序并使用 AVAudioPlayer 播放?