问题
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