Fix mid-stream audio loudness jumps
Root cause: the quality upgrade path called replaceCurrentItem mid-stream, which re-loaded the HLS master manifest and re-picked an audio rendition, producing a perceived loudness jump 10-30s into playback. .moviePlayback mode amplified this by re-initializing cinematic audio processing on each variant change. - Start streams directly at user's desiredResolution; remove scheduleQualityUpgrade, qualityUpgradeTask, and the 504p->best swap. - Switch AVAudioSession mode from .moviePlayback to .default in both MultiStreamView and SingleStreamPlayerView. - Pin the HLS audio rendition by selecting the default audible MediaSelectionGroup option on every new AVPlayerItem, preventing ABR from swapping channel layouts mid-stream. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -57,24 +57,37 @@ private func singleStreamTimeControlDescription(_ status: AVPlayer.TimeControlSt
|
||||
}
|
||||
|
||||
private func makeSingleStreamPlayerItem(from source: SingleStreamPlaybackSource) -> AVPlayerItem {
|
||||
let item: AVPlayerItem
|
||||
if source.httpHeaders.isEmpty {
|
||||
let item = AVPlayerItem(url: source.url)
|
||||
item.preferredForwardBufferDuration = 8
|
||||
return item
|
||||
item = AVPlayerItem(url: source.url)
|
||||
} else {
|
||||
let assetOptions: [String: Any] = [
|
||||
"AVURLAssetHTTPHeaderFieldsKey": source.httpHeaders,
|
||||
]
|
||||
let asset = AVURLAsset(url: source.url, options: assetOptions)
|
||||
item = AVPlayerItem(asset: asset)
|
||||
logSingleStream(
|
||||
"Configured authenticated AVURLAsset headerKeys=\(singleStreamHeaderKeysDescription(source.httpHeaders))"
|
||||
)
|
||||
}
|
||||
|
||||
let assetOptions: [String: Any] = [
|
||||
"AVURLAssetHTTPHeaderFieldsKey": source.httpHeaders,
|
||||
]
|
||||
let asset = AVURLAsset(url: source.url, options: assetOptions)
|
||||
let item = AVPlayerItem(asset: asset)
|
||||
item.preferredForwardBufferDuration = 8
|
||||
logSingleStream(
|
||||
"Configured authenticated AVURLAsset headerKeys=\(singleStreamHeaderKeysDescription(source.httpHeaders))"
|
||||
)
|
||||
pinSingleStreamAudioSelection(on: item)
|
||||
return item
|
||||
}
|
||||
|
||||
/// Lock the HLS audio rendition to the default option so ABR can't swap
|
||||
/// to a different channel layout / loudness mid-stream.
|
||||
private func pinSingleStreamAudioSelection(on item: AVPlayerItem) {
|
||||
let asset = item.asset
|
||||
Task { @MainActor [weak item] in
|
||||
guard let group = try? await asset.loadMediaSelectionGroup(for: .audible),
|
||||
let option = group.defaultOption ?? group.options.first,
|
||||
let item else { return }
|
||||
item.select(option, in: group)
|
||||
logSingleStream("pinAudioSelection selected=\(option.displayName) options=\(group.options.count)")
|
||||
}
|
||||
}
|
||||
|
||||
struct SingleStreamPlaybackScreen: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
let resolveSource: @Sendable () async -> SingleStreamPlaybackSource?
|
||||
@@ -544,9 +557,9 @@ struct SingleStreamPlayerView: UIViewControllerRepresentable {
|
||||
)
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
|
||||
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
logSingleStream("AVAudioSession configured for playback")
|
||||
logSingleStream("AVAudioSession configured for playback mode=default")
|
||||
} catch {
|
||||
logSingleStream("AVAudioSession configuration failed error=\(error.localizedDescription)")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user