I am currently trying to play audio from my firebase database. I have two view controllers, the first is a tableviewcontroller and the second is a view controller with an AVAudioPlayer. I am trying to figure out how to sucessfully play the audio url that is grabbed from firebase that is then passed to the second view controller to play when the audio is selected in the tableview. Here is my current code which returns "Fatal error: Unexpectedly found nil while unwrapping an Optional value" on my second viewcontroller when the audio is selected from the tableview controller.
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()
}
}
Second ViewController:
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 Class:
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)
}
}
UPDATED
Func Configure Code:
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 {
}
}