Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert NSSound to AVAudioPlayer

I have some NSSound objects that I'd like to convert to AVAudioPlayer instances. I have file paths (NSURLs) associated with the NSSound objects, but the original file may not exist. Here is what I have so far:

class SoundObj: NSObject {
    var path: NSURL?
    var sound: NSSound?
    var player: AVAudioPlayer
}

let aSound = SoundObj()
aSound.path = NSURL(fileURLWithPath: "file:///path/to/sound.m4a")
aSound.sound = NSSound(contentsOfURL: aSound.path)!

do {
    try aSound.player = AVAudioPlayer(contentsOfURL: aSound.path)
} catch {
    // perhaps use AVAudioPlayer(data: ...)?
}

How do I convert an NSSound object to an AVAudioPlayer instance?

like image 632
Matt Avatar asked Apr 27 '16 09:04

Matt


1 Answers

So I didn't see a public interface to get the URL from an NSSound object, so I went digging through the private headers to see what I could find. Turns out there are private instance method url and _url which return the URL of an NSSound. Presumably these are getters for an NSURL ivar or property.

With Objective-C this would be easy: we would just add the methods to a new interface or extension. With pure Swift things are a little trickier, and we need to expose the accessor via an Objective-C protocol:

@objc protocol NSSoundPrivate {
    var url: NSURL? { get }
}

Since url is an instance method, you may get better results with func url() -> NSURL? instead of using a variable. Your milage may vary: using a var to emulate the behavior of a read-only property seemed to work for me.

I wrote a new convenience initializer in an extension on AVAudioPlayer:

extension AVAudioPlayer {
    convenience init?(sound: NSSound) throws {    
        let privateSound = unsafeBitCast(sound, NSSoundPrivate.self)    
        guard let url = privateSound.url else { return nil }
        do {
            try self.init(contentsOfURL: url)
        } catch {
            throw error
        }
    }
}

Usage:

let url = NSURL(...)    
if let sound = NSSound(contentsOfURL: url, byReference: true) {
    do {
        let player = try AVAudioPlayer(sound: sound)
        player?.play()
    } catch {
        print(error)
    }        
}

After attempting to find anything related to NSData in the ivars, instance methods, and properties of an NSSound, I have come to the conclusion that the data portion of whatever you use to initialize an NSSound is obfuscated somewhere in the implementation of the class, and is not available like the URL is.

like image 143
JAL Avatar answered Sep 20 '22 16:09

JAL