Search code examples
swiftmacosaudioavaudioplayeravqueueplayer

Mac - Swift 3 - queuing audio files and playing


I would like to write an app in swift 3 in order to play queued audio files without any gap, crack or noise when passing from one to another.

My first try was using AvAudioPlayer and AvAudioDelegate (AVAudioPlayer using array to queue audio files - Swift), but I don't know how to preload the next song to avoid gap. Even if I know how to do it, I am not certain it is the best way to achieve my goal. AVQueuePlayer seems to be a better candidate for the job, it is made for that purpose, but I don't find any example to help me out. Maybe it is only a problem of preloading or buffering? I am a bit lost in this ocean of possibilities.

Any suggestion is welcomed.


Solution

  • It is far to be perfect, specially if you want to do it twice or more ("file exist" error), but it can serve as a base.

    What it does is taking two files (mines are aif samples of ap. 4 sec.), encode them in one file and play the resulting files. If you have hundreds of them, assembled aleatory or not, it can make great fun.

    All credits for the mergeAudioFiles function goes to @Peyman and @Pigeon_39. Concatenate two audio files in Swift and play them

    Swift 3

    import Cocoa
    import AVFoundation
    
    var action = AVAudioPlayer()
    let path = Bundle.main.path(forResource: "audiofile1.aif", ofType:nil)!
    let url = URL(fileURLWithPath: path)
    let path2 = Bundle.main.path(forResource: "audiofile2.aif", ofType:nil)!
    let url2 = URL(fileURLWithPath: path2)
    let array1 = NSMutableArray(array: [url, url2])
    
    
    class ViewController: NSViewController, AVAudioPlayerDelegate
    {
    
        @IBOutlet weak var LanceStop: NSButton!
    
        override func viewDidLoad()
        {
            super.viewDidLoad()
        }
        override var representedObject: Any?
        {
            didSet
            {
            // Update the view, if already loaded.
            }
        }
    
        @IBAction func Lancer(_ sender: NSButton)
        {
          mergeAudioFiles(audioFileUrls: array1)
            let url3 = NSURL(string: "/Users/ADDUSERNAMEHERE/Documents/FinalAudio.m4a")
    
            do
            {
                action = try AVAudioPlayer(contentsOf: url3 as! URL)
                action.delegate = self
                action.numberOfLoops = 0
                action.prepareToPlay()
                action.volume = 1
                action.play()
            }
            catch{print("error")}
    
        }
    
    
        func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
        {
            if flag == true
            {
    
            }
        }
    
        var mergeAudioURL = NSURL()
    
        func mergeAudioFiles(audioFileUrls: NSArray) {
            //audioFileUrls.adding(url)
            //audioFileUrls.adding(url2)
            let composition = AVMutableComposition()
    
            for i in 0 ..< audioFileUrls.count {
    
                let compositionAudioTrack :AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID())
    
                let asset = AVURLAsset(url: (audioFileUrls[i] as! NSURL) as URL)
    
                let track = asset.tracks(withMediaType: AVMediaTypeAudio)[0]
    
                let timeRange = CMTimeRange(start: CMTimeMake(0, 600), duration: track.timeRange.duration)
    
                try! compositionAudioTrack.insertTimeRange(timeRange, of: track, at: composition.duration)
            }
    
            let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! as NSURL
            self.mergeAudioURL = documentDirectoryURL.appendingPathComponent("FinalAudio.m4a")! as URL as NSURL
    
            let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)
            assetExport?.outputFileType = AVFileTypeAppleM4A
            assetExport?.outputURL = mergeAudioURL as URL
            assetExport?.exportAsynchronously(completionHandler:
                {
                    switch assetExport!.status
                    {
                    case AVAssetExportSessionStatus.failed:
                        print("failed \(assetExport?.error)")
                    case AVAssetExportSessionStatus.cancelled:
                        print("cancelled \(assetExport?.error)")
                    case AVAssetExportSessionStatus.unknown:
                        print("unknown\(assetExport?.error)")
                    case AVAssetExportSessionStatus.waiting:
                        print("waiting\(assetExport?.error)")
                    case AVAssetExportSessionStatus.exporting:
                        print("exporting\(assetExport?.error)")
                    default:
                        print("Audio Concatenation Complete")
                    }
            })
        }
    }