fix: use playerItem.tracks for HLS audio track discovery
HLS virtual asset returns empty tracks from asset.tracks/loadTracks. Use playerItem.tracks + assetTrack to get actual track IDs.
This commit is contained in:
parent
1880eeb3d3
commit
1eff22a1ad
@ -318,101 +318,95 @@ final class PlayerRecorder: NSObject {
|
||||
|
||||
// MARK: - Audio Tap Setup
|
||||
|
||||
/// 异步安装音频 Tap(HLS 流需要异步加载 tracks)
|
||||
/// 安装音频 Tap(使用 playerItem.tracks 获取轨道,支持 HLS 虚拟 asset)
|
||||
private func installAudioTap(on playerItem: AVPlayerItem) {
|
||||
// 确保全局上下文存在
|
||||
if gTapContext == nil {
|
||||
gTapContext = AudioTapContext()
|
||||
}
|
||||
guard let ctx = gTapContext else { return }
|
||||
|
||||
Task { @MainActor in
|
||||
guard let ctx = gTapContext else { return }
|
||||
|
||||
print("[Recorder:DIAG] === installAudioTap START (async) ===")
|
||||
print("[Recorder:DIAG] asset class: \(type(of: playerItem.asset))")
|
||||
|
||||
// HLS 流的 tracks 是异步加载的,必须用 loadTracks
|
||||
let audioTracks: [AVAssetTrack]
|
||||
do {
|
||||
audioTracks = try await playerItem.asset.loadTracks(withMediaType: .audio)
|
||||
} catch {
|
||||
print("[Recorder:DIAG] ⚠️ loadTracks failed: \(error)")
|
||||
// 写诊断日志
|
||||
writeDiagLog("loadTracks error: \(error)")
|
||||
return
|
||||
print("[Recorder:DIAG] === installAudioTap START ===")
|
||||
|
||||
// HLS 虚拟 asset 的 asset.tracks 为空,必须用 playerItem.tracks
|
||||
let itemTracks = playerItem.tracks
|
||||
print("[Recorder:DIAG] playerItem.tracks count: \(itemTracks.count)")
|
||||
|
||||
// 找到音频轨道
|
||||
var audioTrackID: CMPersistentTrackID = kCMPersistentTrackID_Invalid
|
||||
for (i, t) in itemTracks.enumerated() {
|
||||
let tid = t.assetTrack?.trackID ?? -1
|
||||
let mediaType = t.assetTrack?.mediaType.rawValue ?? "?"
|
||||
let enabled = t.isEnabled
|
||||
print("[Recorder:DIAG] itemTrack[\(i)]: trackID=\(tid) mediaType=\(mediaType) enabled=\(enabled)")
|
||||
if t.assetTrack?.mediaType == .audio && t.isEnabled {
|
||||
audioTrackID = tid
|
||||
}
|
||||
|
||||
guard let audioTrack = audioTracks.first else {
|
||||
print("[Recorder:DIAG] ⚠️ No audio track found after async load!")
|
||||
writeDiagLog("No audio tracks found")
|
||||
return
|
||||
}
|
||||
|
||||
print("[Recorder:DIAG] Using audio trackID=\(audioTrack.trackID)")
|
||||
|
||||
// 列出所有 tracks 用于诊断
|
||||
let allTracks = try? await playerItem.asset.load(.tracks)
|
||||
if let tracks = allTracks {
|
||||
for (i, t) in tracks.enumerated() {
|
||||
print("[Recorder:DIAG] track[\(i)]: ID=\(t.trackID) mediaType=\(t.mediaType.rawValue)")
|
||||
}
|
||||
}
|
||||
|
||||
// 创建 MTAudioProcessingTap
|
||||
var callbacks = MTAudioProcessingTapCallbacks(
|
||||
version: kMTAudioProcessingTapCallbacksVersion_0,
|
||||
clientInfo: Unmanaged.passUnretained(ctx).toOpaque(),
|
||||
init: tapInit,
|
||||
finalize: tapFinalize,
|
||||
prepare: tapPrepare,
|
||||
unprepare: tapUnprepare,
|
||||
process: tapProcess
|
||||
)
|
||||
|
||||
var tap: MTAudioProcessingTap?
|
||||
let tapStatus = MTAudioProcessingTapCreate(
|
||||
kCFAllocatorDefault, &callbacks,
|
||||
kMTAudioProcessingTapCreationFlag_PreEffects,
|
||||
&tap
|
||||
)
|
||||
print("[Recorder:DIAG] MTAudioProcessingTapCreate status=\(tapStatus) (noErr=\(noErr))")
|
||||
guard tapStatus == noErr, let tapRef = tap else {
|
||||
print("[Recorder:DIAG] ⚠️ Tap creation failed!")
|
||||
return
|
||||
}
|
||||
|
||||
// 创建 AudioMix
|
||||
let inputParams = AVMutableAudioMixInputParameters()
|
||||
inputParams.trackID = audioTrack.trackID
|
||||
inputParams.audioTapProcessor = tapRef
|
||||
|
||||
let audioMix = AVMutableAudioMix()
|
||||
audioMix.inputParameters = [inputParams]
|
||||
|
||||
playerItem.audioMix = audioMix
|
||||
|
||||
// 验证
|
||||
let verifyMix = playerItem.audioMix
|
||||
print("[Recorder:DIAG] playerItem.audioMix set=\(verifyMix != nil)")
|
||||
if let vMix = verifyMix, let params = vMix.inputParameters.first {
|
||||
print("[Recorder:DIAG] verified trackID=\(params.trackID)")
|
||||
}
|
||||
print("[Recorder:DIAG] === installAudioTap DONE ===")
|
||||
|
||||
ctx.mixTrackID = audioTrack.trackID
|
||||
|
||||
// 写诊断日志
|
||||
var diagLog = "=== installAudioTap Log ===\n"
|
||||
diagLog += "Time: \(Date())\n"
|
||||
diagLog += "Asset class: \(type(of: playerItem.asset))\n"
|
||||
diagLog += "Audio trackID used: \(audioTrack.trackID)\n"
|
||||
diagLog += "TapCreate status: \(tapStatus)\n"
|
||||
diagLog += "audioMix set: \(verifyMix != nil)\n"
|
||||
if let vMix = verifyMix, let params = vMix.inputParameters.first {
|
||||
diagLog += "Verified trackID: \(params.trackID)\n"
|
||||
}
|
||||
writeDiagLog(diagLog)
|
||||
}
|
||||
|
||||
guard audioTrackID != kCMPersistentTrackID_Invalid else {
|
||||
print("[Recorder:DIAG] ⚠️ No enabled audio track in playerItem.tracks!")
|
||||
writeDiagLog("No audio track in playerItem.tracks (count=\(itemTracks.count))")
|
||||
return
|
||||
}
|
||||
|
||||
print("[Recorder:DIAG] Using audio trackID=\(audioTrackID)")
|
||||
|
||||
// 创建 MTAudioProcessingTap
|
||||
var callbacks = MTAudioProcessingTapCallbacks(
|
||||
version: kMTAudioProcessingTapCallbacksVersion_0,
|
||||
clientInfo: Unmanaged.passUnretained(ctx).toOpaque(),
|
||||
init: tapInit,
|
||||
finalize: tapFinalize,
|
||||
prepare: tapPrepare,
|
||||
unprepare: tapUnprepare,
|
||||
process: tapProcess
|
||||
)
|
||||
|
||||
var tap: MTAudioProcessingTap?
|
||||
let tapStatus = MTAudioProcessingTapCreate(
|
||||
kCFAllocatorDefault, &callbacks,
|
||||
kMTAudioProcessingTapCreationFlag_PreEffects,
|
||||
&tap
|
||||
)
|
||||
print("[Recorder:DIAG] MTAudioProcessingTapCreate status=\(tapStatus) (noErr=\(noErr))")
|
||||
guard tapStatus == noErr, let tapRef = tap else {
|
||||
print("[Recorder:DIAG] ⚠️ Tap creation failed!")
|
||||
return
|
||||
}
|
||||
|
||||
// 创建 AudioMix
|
||||
let inputParams = AVMutableAudioMixInputParameters()
|
||||
inputParams.trackID = audioTrackID
|
||||
inputParams.audioTapProcessor = tapRef
|
||||
|
||||
let audioMix = AVMutableAudioMix()
|
||||
audioMix.inputParameters = [inputParams]
|
||||
|
||||
playerItem.audioMix = audioMix
|
||||
|
||||
// 验证
|
||||
let verifyMix = playerItem.audioMix
|
||||
print("[Recorder:DIAG] playerItem.audioMix set=\(verifyMix != nil)")
|
||||
if let vMix = verifyMix, let params = vMix.inputParameters.first {
|
||||
print("[Recorder:DIAG] verified trackID=\(params.trackID)")
|
||||
}
|
||||
print("[Recorder:DIAG] === installAudioTap DONE ===")
|
||||
|
||||
ctx.mixTrackID = audioTrackID
|
||||
|
||||
// 写诊断日志
|
||||
var diagLog = "=== installAudioTap Log ===\n"
|
||||
diagLog += "Time: \(Date())\n"
|
||||
diagLog += "Source: playerItem.tracks\n"
|
||||
diagLog += "playerItem.tracks count: \(itemTracks.count)\n"
|
||||
for (i, t) in itemTracks.enumerated() {
|
||||
diagLog += " itemTrack[\(i)]: trackID=\(t.assetTrack?.trackID ?? -1) mediaType=\(t.assetTrack?.mediaType.rawValue ?? "?") enabled=\(t.isEnabled)\n"
|
||||
}
|
||||
diagLog += "Audio trackID used: \(audioTrackID)\n"
|
||||
diagLog += "TapCreate status: \(tapStatus)\n"
|
||||
diagLog += "audioMix set: \(verifyMix != nil)\n"
|
||||
writeDiagLog(diagLog)
|
||||
}
|
||||
|
||||
private func writeDiagLog(_ content: String) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user