diff --git a/Sources/PlayerRecorder.swift b/Sources/PlayerRecorder.swift index 3a6d6d3..460bcb6 100644 --- a/Sources/PlayerRecorder.swift +++ b/Sources/PlayerRecorder.swift @@ -339,7 +339,19 @@ final class PlayerRecorder: NSObject { private func setupAudioTap(playerItem: AVPlayerItem, audioWriterInput: AVAssetWriterInput) { // HLS 流的 tracks 是异步加载的 - // 策略:先同步检查 → KVO 观察 → 定时轮询保底 + // 策略:先同步检查 → KVO 观察 → 定时轮询保底(含 async load 备选) + + // 先打印当前 track 状态(诊断) + let tracks = playerItem.tracks + NSLog("[Recorder] setupAudioTap: playerItem.tracks.count=%d", tracks.count) + for (i, track) in tracks.enumerated() { + if let at = track.assetTrack { + NSLog("[Recorder] track[%d]: mediaType=%@, trackID=%d, enabled=%d", + i, at.mediaType.rawValue as NSString, at.trackID, track.isEnabled) + } else { + NSLog("[Recorder] track[%d]: assetTrack=NIL (HLS not loaded yet?)", i) + } + } // 先尝试直接获取(本地文件场景) if let audioTrack = self.findAudioTrackID(in: playerItem) { @@ -354,6 +366,9 @@ final class PlayerRecorder: NSObject { let observation = playerItem.observe(\.tracks, options: [.new]) { [weak self] item, _ in guard let self, self.isRunning, self.audioTap == nil else { return } + let tCount = item.tracks.count + NSLog("[Recorder] KVO tracks changed: count=%d", tCount) + if let trackID = self.findAudioTrackID(in: item) { NSLog("[Recorder] ✓ Audio track via KVO: trackID=%d", trackID) Task { @MainActor in @@ -365,14 +380,16 @@ final class PlayerRecorder: NSObject { } self.tracksObservation = observation - // 保底:在视频抓帧循环中定期检查(HLS KVO 可能不触发) - // 超时保护:20秒后放弃 + // 保底轮询:含 async asset.load(.tracks) 备选 + // 超时保护:30秒后放弃 Task { var attempts = 0 - while attempts < 200 && self.isRunning && self.audioTap == nil { // 20秒 + var didTryAsyncLoad = false + while attempts < 300 && self.isRunning && self.audioTap == nil { // 30秒 try await Task.sleep(nanoseconds: 100_000_000) // 0.1秒 attempts += 1 + // 方式1: 从 playerItem.tracks 查找 if let trackID = self.findAudioTrackID(in: playerItem) { NSLog("[Recorder] ✓ Audio track via polling (attempt %d): trackID=%d", attempts, trackID) await MainActor.run { @@ -382,22 +399,59 @@ final class PlayerRecorder: NSObject { self.tracksObservation = nil return } + + // 方式2: 5秒后尝试 async load asset tracks(HLS 可能通过这个路径加载) + if !didTryAsyncLoad && attempts >= 50 { + didTryAsyncLoad = true + NSLog("[Recorder] Trying async asset.load(.tracks)...") + do { + let assetTracks = try await playerItem.asset.load(.tracks) + NSLog("[Recorder] async load returned %d tracks", assetTracks.count) + for (i, t) in assetTracks.enumerated() { + NSLog("[Recorder] assetTrack[%d]: mediaType=%@, trackID=%d", + i, t.mediaType.rawValue as NSString, t.trackID) + } + if let audioAssetTrack = assetTracks.first(where: { $0.mediaType == .audio }) { + let trackID = audioAssetTrack.trackID + NSLog("[Recorder] ✓ Audio track via async load: trackID=%d", trackID) + await MainActor.run { + guard self.isRunning, self.audioTap == nil else { return } + self.installTap(on: playerItem, trackID: trackID, audioWriterInput: audioWriterInput) + } + self.tracksObservation = nil + return + } + } catch { + NSLog("[Recorder] async load failed: %@", error.localizedDescription) + } + } + + // 每5秒打一次状态日志 + if attempts % 50 == 0 { + let tc = playerItem.tracks.count + NSLog("[Recorder] polling attempt %d: playerItem.tracks.count=%d, tapInstalled=%d", + attempts, tc, self.audioTap != nil ? 1 : 0) + } } if self.audioTap == nil { - NSLog("[Recorder] ❌ No audio track found after %d polling attempts", attempts) + NSLog("[Recorder] ❌ No audio track found after %d polling attempts (30s timeout)", attempts) self.tracksObservation = nil } } } /// 从 playerItem.tracks 查找音频 trackID + /// 对 HLS 流,assetTrack 可能延迟加载或始终为 nil private func findAudioTrackID(in playerItem: AVPlayerItem) -> CMPersistentTrackID? { let itemTracks = playerItem.tracks guard !itemTracks.isEmpty else { return nil } for (i, track) in itemTracks.enumerated() { - guard let assetTrack = track.assetTrack else { continue } - NSLog("[Recorder] track[%d]: mediaType=%@, trackID=%d, isEnabled=%d", + guard let assetTrack = track.assetTrack else { + NSLog("[Recorder] findTrack: track[%d] assetTrack=nil (not loaded)", i) + continue + } + NSLog("[Recorder] findTrack: track[%d] mediaType=%@, trackID=%d, enabled=%d", i, assetTrack.mediaType.rawValue as NSString, assetTrack.trackID, track.isEnabled) if assetTrack.mediaType == .audio { return assetTrack.trackID