Преобразование NSSound в AVAudioPlayer

У меня есть некоторые объекты NSSound, которые я бы хотел преобразовать в экземпляры AVAudioPlayer. У меня есть пути к файлам (NSURL s), связанные с объектами NSSound, но исходный файл может не существовать. Вот что я до сих пор:

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: ...)?
}

Как преобразовать объект NSSound в экземпляр AVAudioPlayer?

Ответ 1

Итак, я не видел публичный интерфейс, чтобы получить URL-адрес от объекта NSSound, поэтому я перешел через частные заголовки чтобы увидеть, что я могу найти. Оказывается, есть частный метод экземпляра url и _url, который возвращает URL-адрес NSSound. Предположительно, это геттеры для NSURL ivar или свойства.

С Objective-C это будет легко: мы просто добавим методы в новый интерфейс или расширение. С чистыми Swift вещи немного сложнее, и нам нужно разоблачить аксессуар через протокол Objective-C:

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

Так как url - это метод экземпляра, вы можете получить лучшие результаты с помощью func url() -> NSURL? вместо использования переменной. Ваше возражение может отличаться: использование var для подражания поведению свойства только для чтения показалось мне полезным.

Я написал новый инициализатор удобства в расширении на 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
        }
    }
}

Использование:

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

После попытки найти что-либо, связанное с NSData в ivars, методах экземпляра и свойствах NSSound, я пришел к выводу, что часть данных, которую вы используете для инициализации NSSound, запутывается где-то в реализация класса и недоступна, например, URL.