URL is nil in AVAudioFile

心已入冬 提交于 2019-12-11 05:28:54

问题


I am trying to play multiple audio files on top of a background audio file using AVAudioEngine. When I try to initialize backgroundAudioFile, the app crashes saying :Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=com.apple.coreaudio.avfaudio Code=2003334207 "(null)" UserInfo={failed call=ExtAudioFileOpenURL((CFURLRef)fileURL, &_extAudioFile)}.

The url passed to the initializer is valid. It is printed out.

import Foundation
import AVFoundation

class AudioPlayer {

  var timeIndex = 0 // keeps track of the index of the item transveersed within `timesToTransverse` by `player?.addBoundaryTimeObserver`


var topAudioFiles: [AVAudioFile] = []
var engine:AVAudioEngine
var backgroundAudioNode: AVAudioPlayerNode
var backgroundAudioFile: AVAudioFile
var topAudioAudioNodes = [AVAudioPlayerNode]()
var mixer: AVAudioMixerNode
var timer: Timer!
var urls: [URL] = []
var player: AVPlayer!
var timesToTransverse = [NSValue]() //contains timeValues in seconds such as ["1.54",2.64, 67.20]
var delays = [UInt64]()

fileprivate var timeObserverToken: Any?

init (url: URL, urls: [URL] = [], timesToTransverse: [NSValue]) {

    self.urls = urls
    self.timesToTransverse = timesToTransverse
    topAudioFiles = urls.map { try! AVAudioFile(forReading: $0) }

   print("the remote url is \(url)")
 // it prints the url is https://firebasestorage.googleapis.com/v0/b/salsaworld-658f3.appspot.com/o/adminAudioFiles%2F-LWv5rnKiLawXvsSsQgG.m4a?alt=media&token=19e8eac0-2b47-49e2-acd3-9a459903f84b

    //it crashes on this line
    backgroundAudioFile = try! AVAudioFile(forReading: url)


    player = AVPlayer(url: url)
    engine = AVAudioEngine()
    mixer = AVAudioMixerNode()

    engine.attach(mixer)
    engine.connect(mixer, to: engine.outputNode, format: nil)
    backgroundAudioNode = AVAudioPlayerNode()

    initTopAudioNodes()
    try! engine.start()
}


func initTopAudioNodes() {
    for _ in topAudioFiles {
        topAudioAudioNodes += [AVAudioPlayerNode()]
    }

    for node in topAudioAudioNodes {
        engine.attach(node)
        engine.connect(node, to: mixer, format: nil)
     }
 }//end initTopAudioNodes


 func playWithAudioPlayerAndNodes() {
    player.play()
    var i = 1

  timeObserverToken =  player.addBoundaryTimeObserver(forTimes: timesToTransverse, queue: nil) {
    [weak self] in

    guard let self = self else {return}

        let index = i % self.topAudioAudioNodes.count
        let node = self.topAudioAudioNodes[index]
        node.scheduleFile(self.topAudioFiles[index], at: nil, completionHandler: nil)
        node.play()
        i += 1

    /* Because there are no time signature changes, 
     we can simply increment  timeIndex with + 1 every time 
    `addBoundaryTimeObserver`'s completion handler is called. 
    Then, we subscript timesToTransverse with timeIndex 
    in order to get the subsequent timeInSeconds
   */
    guard self.timeIndex < self.timesToTransverse.count else {return}
    print("timeIndex is now \(self.timeIndex)")

    let timeElement = self.timesToTransverse[self.timeIndex]
    let timeInSeconds = CMTimeGetSeconds(timeElement.timeValue)

   //use reminder operator to determine the beat count
     let beat = (self.timeIndex + 1) % 8 == 0 ? 8 : ((self.timeIndex + 1) % 8)
    print("Beat would be: ", beat)

   /*
     0: (0 + 1) % 8 = 1
     1: (1 + 1) % 8 = 2
     6: (6 + 1) % 8 = 7
     7: (7 + 1) % 8 = 0
     */

    self.timeIndex += 1

}//end class AudioPlayer




  // create instance of class AudioPlayer and call func playWithAudioPlayerAndNodes



   class HomeViewController {

      override func viewDidLoad() {
        super.viewDidLoad()

       let bundle = Bundle.main
       let one = bundle.url(forResource: "1", withExtension: "wav")!
       let two = bundle.url(forResource: "2", withExtension: "wav")!
       let three = bundle.url(forResource: "3", withExtension: "wav")!
       let five = bundle.url(forResource: "5", withExtension: "wav")!
       let six = bundle.url(forResource: "6", withExtension: "wav")!
       let seven = bundle.url(forResource: "7", withExtension: "wav")!

       // mediaArray contains string URL's downloaded from FirebaseDatabse
       let mediaItem = mediaArray[tableIndexPath.row]
        guard let backgroundAudio = URL(string: mediaItem.mediaAudioUrlStringRepresentation ?? "") else {return}

    let audioPlayer = AudioPlayer(url:
    backgroundAudio,
    urls: [one, two, three, five, six, seven],
    timesToTransverse: timesToTransverse)

     //start playing
    audioPlayer.playWithAudioPlayerAndNodes()
      }
  }

回答1:


You cannot call AVAudioFile(forReading:) on a remote file. You need to download the binary data and parse it into packets using audio file stream services. That way, you can supply the packets to a buffer and play from the buffer thru the audio engine.




回答2:


you can try:

let one = try! AVAudioFile(forReading: URL(fileURLWithPath: (Bundle.main.path(forResource: "1", ofType: "wav", inDirectory: "YOUKNOW!")!)))

it works fine with me



来源:https://stackoverflow.com/questions/55688901/url-is-nil-in-avaudiofile

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!