首页 > 解决方案 > 在用户交互后保持多个 AVAudioPlayer 同步

问题描述

我正在开发一个 iOS 应用程序,它让用户可以播放具有多个曲目的歌曲。每个曲目同时播放,每个曲目的音量可以独立管理。当歌曲在应用程序的生命周期内第一次播放时,所有播放器之间的延迟都很好。但是,当用户停止播放所有曲目并在之后恢复播放时,所有播放器之间的延迟都会增加。

我为我的 MusicPlayer-Class 中的每首曲目创建一个 AVAudioPlayer:

    do {
        let data = try Data.init(contentsOf: url, options: NSData.ReadingOptions.alwaysMapped )
        let player = try AVAudioPlayer(data: data)
        player.prepareToPlay()

        self.audioPlayers.append(player)
    }

加载播放器后,歌曲的播放由 playSong 方法开始:

playerSemaphore.wait()

    NotificationCenter.default.post(playerDidReceiveCommand.getNotification())

    for item in self.audioPlayers {
        item.prepareToPlay()
    }


    let time = self.audioPlayers[0].deviceCurrentTime + 0.2
    for item in self.audioPlayers {
        item.play(atTime: time)
    }

    DispatchQueue.main.async {
        NotificationCenter.default.post(playerFinishedCommand.getNotification())
    }


    playerSemaphore.signal()

当用户决定暂停歌曲或 AudioSession 被中断时, pauseSong-Method 通过将音量设置为 0 、暂停播放器、同步播放时间并重置适当的音量来暂停所有播放器:

func pauseSong() {
    playerSemaphore.wait()

    NotificationCenter.default.post(playerDidReceiveCommand.getNotification())

    DispatchQueue.global().async {

        // Set volumes of all audio tracks to 0, so delay on pause is not noticed
        for audioTrackPlayer in self.audioPlayers {
            audioTrackPlayer.volume = 0
        }

        // Update Times and reset the volumes now the tracks are paused
        for (index, audioTrackPlayer) in self.audioPlayers.enumerated() {
            audioTrackPlayer.pause()
            audioTrackPlayer.currentTime = self.audioPlayers[0].currentTime
            if let currentSongID = self.loadedSongID, let currentVolume = sharedSongManager.getVolumeOfTrackOfSongID(id: currentSongID, track: index), let currentActivation = sharedSongManager.getActivationStatusOfTrackOfSongID(id: currentSongID, track: index) {
                if currentActivation == true {
                    audioTrackPlayer.volume = currentVolume
                }
            }
        }

        DispatchQueue.main.async {
            NotificationCenter.default.post(playerFinishedCommand.getNotification())
        }
    }

    playerSemaphore.signal()
}

为了记录玩家时间,我编写了一个方法,该方法返回所有玩家时间以及该数组的最大值和最小值之差。它使用时间偏移来获得玩家时间的准确值:

let startTime = CACurrentMediaTime()
    var playerTimes = [TimeInterval]()
    var delay: Double?

    for item in self.audioPlayers.enumerated() {
        let playerTime = item.element.currentTime
        let currentTime = CACurrentMediaTime()
        let timeOffset = currentTime - startTime
        let correctedPlayerTime = playerTime - timeOffset
        playerTimes.append(correctedPlayerTime)
    }

    if let maxTime = playerTimes.max(), let minTime = playerTimes.min() {
        delay = Double(maxTime) - Double(minTime)
    }

    return (delay, playerTimes)

以下是暂停前后的延迟列表(最大值 - 玩家时间的负值):

暂停前延迟 暂停后延迟

以下是暂停前后一个日志的播放时间:

暂停前的播放时间 暂停后的播放时间

所有日志都是在装有最新 iOS 的 iPhone X 上创建的。

加载一首新歌曲后,生命周期会重新开始,并且在用户第一次暂停歌曲之前,我的延迟很低。我不喜欢在每次交互后创建新的 AVAudioPlayers 的想法,但我尝试了很多事情,比如像在日志记录函数中那样计算延迟或异步调用播放器实例。但是,大多数情况下,这甚至会增加延迟。有谁知道恢复歌曲后正确同步播放器的方法?

标签: iosswiftavaudioplayeravaudiosession

解决方案


推荐阅读