ios - 在用户交互后保持多个 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 的想法,但我尝试了很多事情,比如像在日志记录函数中那样计算延迟或异步调用播放器实例。但是,大多数情况下,这甚至会增加延迟。有谁知道恢复歌曲后正确同步播放器的方法?
解决方案
推荐阅读
- flutter - 使用 Provider 向整个 Flutter 模块全局提供 BLoC
- c# - 插入 ConcurrentDictionary 时使用惰性求值是否会阻止多次计算请求的值?
- kubernetes - 无法设置 HA etcd 集群
- vue.js - 如何使用 Vue 将实际的 html 添加到我的代码中?
- c# - 何从分数百分位获取 LINQ 中的键和值
- r - 中值和箱线图 (R)
- python - 熊猫字符串匹配负前瞻不起作用
- r - 如何使用 apply 或 map 根据另一个给出的范围和内容来更改数据框的内容,特别是在范围重叠的情况下
- powershell - 在PowerShell中有条件地注册存储库
- c - 最小 kmalloc() 分配和内存页大小有什么关系?