swiftaudiocloudkitavaudiofileckasset

How can I store and retrieve audio files to and from CloudKit?


I am attempting to record voice messages and store them as CKAssets on CloudKit. I convert them into an AVAudioFile and save them to CloudKit as one of several properties on a Post record.

var recordedMessage: AVAudioFile?

var recordedMessageURL: URL? {
    didSet {
        if let recordedMessageURL = recordedMessageURL {
            recordedMessage = try? AVAudioFile(forReading: recordedMessageURL)
        }
    }
}

This part of the process seems to work, although I’m unable to play back the audio when I access the file through my CloudKit dashboard. (Double-clicking on it opens iTunes, but it won’t play)

I’ve created a fetchPosts function that successfully brings down the image tied to the record, but when I attempt to play the audio, it gives me the following error:

ExtAudioFile.cpp:192:Open: about to throw -43: open audio file

I believe it has to do with my being unable to properly convert the asset into an AVAudioFile. It doesn’t seem to be as clean-cut as converting image assets.

    var foundAudio: AVAudioFile?
    if let audioAsset = ckRecord[PostStrings.audioAssetKey] as? CKAsset {
        do {
            let audioData = try Data(contentsOf: audioAsset.fileURL!)
            let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
            guard let destinationPath = NSURL(fileURLWithPath: documentsPath).appendingPathComponent("filename.m4a", isDirectory: false) else {return nil}

            FileManager.default.createFile(atPath: destinationPath.path, contents: audioData, attributes: nil)

            foundAudio = try AVAudioFile(forReading: destinationPath)
        } catch {
            print("Could not transform asset into data")
        }
    }

Can anyone tell me where I’m going wrong?


Solution

  • It took many days of changing things around, but I've got a working solution that doesn't involve directly manipulating the data as Data. Instead I use a URL. I initialized the audioAsset as a URL:

    var opComment: URL?
    var audioAsset: CKAsset? {
      get {
        guard let audioURL = opComment else { return nil }
        return CKAsset(fileURL: audioURL)
        }
        set {
          opComment = newValue?.fileURL
        }
      }
    

    I pulled the ckRecord back down with a convenience initializer:

    var opCommentURL: URL?
        if let audioAsset = ckRecord[PostStrings.audioAssetKey] as? CKAsset {
                opCommentURL = audioAsset.fileURL
        }
    

    And I fed it into my AVAudioPlayer as a URL:

            guard let audioURL = post.opComment else {return}
    
        if !isTimerRunning {
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
                audioPlayer.play()
            } catch {
                print("Error in \(#function) : \(error.localizedDescription) /n---/n \(error)")
            }
        } else {
            audioPlayer.pause()
            isTimerRunning = false
        }
    

    (If you're reading this, Sam. Thanks for the gentle push in the right direction!)