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
|
// MARK: - Audio Tap Setup
|
||||||
|
|
||||||
/// 异步安装音频 Tap(HLS 流需要异步加载 tracks)
|
/// 安装音频 Tap(使用 playerItem.tracks 获取轨道,支持 HLS 虚拟 asset)
|
||||||
private func installAudioTap(on playerItem: AVPlayerItem) {
|
private func installAudioTap(on playerItem: AVPlayerItem) {
|
||||||
// 确保全局上下文存在
|
// 确保全局上下文存在
|
||||||
if gTapContext == nil {
|
if gTapContext == nil {
|
||||||
gTapContext = AudioTapContext()
|
gTapContext = AudioTapContext()
|
||||||
}
|
}
|
||||||
|
guard let ctx = gTapContext else { return }
|
||||||
|
|
||||||
Task { @MainActor in
|
print("[Recorder:DIAG] === installAudioTap START ===")
|
||||||
guard let ctx = gTapContext else { return }
|
|
||||||
|
// HLS 虚拟 asset 的 asset.tracks 为空,必须用 playerItem.tracks
|
||||||
print("[Recorder:DIAG] === installAudioTap START (async) ===")
|
let itemTracks = playerItem.tracks
|
||||||
print("[Recorder:DIAG] asset class: \(type(of: playerItem.asset))")
|
print("[Recorder:DIAG] playerItem.tracks count: \(itemTracks.count)")
|
||||||
|
|
||||||
// HLS 流的 tracks 是异步加载的,必须用 loadTracks
|
// 找到音频轨道
|
||||||
let audioTracks: [AVAssetTrack]
|
var audioTrackID: CMPersistentTrackID = kCMPersistentTrackID_Invalid
|
||||||
do {
|
for (i, t) in itemTracks.enumerated() {
|
||||||
audioTracks = try await playerItem.asset.loadTracks(withMediaType: .audio)
|
let tid = t.assetTrack?.trackID ?? -1
|
||||||
} catch {
|
let mediaType = t.assetTrack?.mediaType.rawValue ?? "?"
|
||||||
print("[Recorder:DIAG] ⚠️ loadTracks failed: \(error)")
|
let enabled = t.isEnabled
|
||||||
// 写诊断日志
|
print("[Recorder:DIAG] itemTrack[\(i)]: trackID=\(tid) mediaType=\(mediaType) enabled=\(enabled)")
|
||||||
writeDiagLog("loadTracks error: \(error)")
|
if t.assetTrack?.mediaType == .audio && t.isEnabled {
|
||||||
return
|
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) {
|
private func writeDiagLog(_ content: String) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user