如何在播放音乐文件期间让动画在精确点工作?

How to get animation to work at exact points during playback of a music file?

Question:

In Swift code, apart from using an NSTimer, how can I get animations to start at exact points during playback of a music file played using AVFoundation?


背景

我有一种使用 AVFoundation 播放音乐文件的方法(如下)。我也有 UIView 动画,我想在播放音乐文件期间的确切点开始。​​

我可以实现此目的的一种方法是使用 NSTimer,但这有可能会不同步或不够准确。

有没有一种方法可以让我利用 AVFoundation 访问音乐文件的流逝时间(计时器),以便当音乐播放过程中的某些点到达时,动画开始?

是否有 AVFoundation 触发的事件/通知提供自音乐文件开始播放以来经过的持续时间流?


例如

在0:52.50(52秒1/2),调用startAnimation1(),在1:20.75(1分20秒3/4),调用startAnimation2(),等等?

switch musicPlayingTimeElapsed {

case 0:52.50:
     startAnimation1()

case 1:20.75:
     startAnimation2()

default:
     ()

}

正在使用 AVFoundation

播放音乐
import AVFoundation
var myMusic : AVAudioPlayer?

func playMusic() {
    if let musicFile = self.setupAudioPlayerWithFile("fileName", type:"mp3") {
        self.myMusic = musicFile
    }
    myMusic?.play()
}

func setupAudioPlayerWithFile(file:NSString, type:NSString) -> AVAudioPlayer?  {
    let path = NSBundle.mainBundle().pathForResource(file as String, ofType: type as String)
    let url = NSURL.fileURLWithPath(path!)
    var audioPlayer:AVAudioPlayer?
    do {
        try audioPlayer = AVAudioPlayer(contentsOfURL: url)
    } catch {
        print("AVAudioPlayer not available")
    }
    return audioPlayer
}

您可以尝试使用键值观察来观察声音播放时的持续时间 属性。当持续时间达到您的时间阈值时,您将触发每个动画。您需要使时间阈值匹配时间 >= 触发时间,因为您可能无法与所需时间完美匹配。

不过我不知道它的效果如何。首先,我不确定声音播放器的持续时间是否符合 KVO。

其次,KVO 有点占用资源,如果您的 KVO 侦听器每秒被调用数千次,它可能会使事情陷入困境。至少值得一试。

如果你使用AVPlayer而不是AVAudioPlayer,你可以使用(TBH有点尴尬)addBoundaryTimeObserverForTimes方法:

let times = [
    NSValue(CMTime:CMTimeMake(...)),
    NSValue(CMTime:CMTimeMake(...)),
    NSValue(CMTime:CMTimeMake(...)),
    // etc
];

var observer: AnyObject? = nil   // instance variable

self.observer = self.player.addBoundaryTimeObserverForTimes(times, queue: nil) {
    switch self.player.currentTime() {
    case 0:52.50:
        startAnimation1()
    case 1:20.75:
        startAnimation2()
    default:
        break
    }
}


// call this to stop observer   
self.player.removeTimeObserver(self.observer)

我解决这个问题的方法是事先将音乐分成不同的片段。然后我使用以下两种方法之一:

  • 我一次播放一个片段,每个片段都有自己的音频播放器。当一个片段结束时,音频播放器的代表会收到通知,因此开始下一个片段——以及伴随的动作——由我决定。

  • 或者,我将所有片段排队到 AVQueuePlayer 上。然后我在队列播放器的 currentItem 上使用 KVO。因此,当我们移动到新段时,我会准确地得到通知。