fix: add device diagnostic log (miniplayer_diag.log in Documents) to trace playback issues

This commit is contained in:
yumoqing 2026-07-02 22:58:57 +08:00
parent 37d7338823
commit e81af97437

View File

@ -122,6 +122,28 @@ final class PlayerBridge: ObservableObject {
#if os(iOS)
let hlsRecorder = HLSRecorder()
let pipController = PiPController()
//
private var diagLogURL: URL?
private var diagLogHandle: FileHandle?
private func startDiagnosticLog() {
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
diagLogURL = docs.appendingPathComponent("miniplayer_diag.log")
//
try? "".write(to: diagLogURL!, atomically: true, encoding: .utf8)
diagLogHandle = try? FileHandle(forWritingTo: diagLogURL!)
diagLogHandle?.seekToEndOfFile()
diagLog("=== MiniPlayer diagnostic log started ===")
}
func diagLog(_ msg: String) {
let line = "\(Date()): \(msg)\n"
print("[MiniPlayer] \(msg)")
if let data = line.data(using: .utf8) {
try? diagLogHandle?.write(contentsOf: data)
}
}
#endif
// MARK: -
@ -132,6 +154,8 @@ final class PlayerBridge: ObservableObject {
#elseif os(iOS)
// iOS: + PiP
configureAudioSession()
//
startDiagnosticLog()
#endif
player.volume = volume
@ -242,7 +266,7 @@ final class PlayerBridge: ObservableObject {
currentIndex = index
let item = queue[index]
print("[MiniPlayer] playIndex(\(index)): \(item.url.absoluteString)")
diagLog("playIndex(\(index)): \(item.url.lastPathComponent)")
//
if item.url.isFileURL {
@ -250,7 +274,7 @@ final class PlayerBridge: ObservableObject {
let exists = FileManager.default.fileExists(atPath: path)
let readable = FileManager.default.isReadableFile(atPath: path)
let fileSize = (try? FileManager.default.attributesOfItem(atPath: path)[.size] as? Int) ?? 0
print("[MiniPlayer] Local file check: exists=\(exists), readable=\(readable), size=\(fileSize), path=\(path)")
diagLog("Local file check: exists=\(exists) readable=\(readable) size=\(fileSize)")
}
// KVO
@ -271,7 +295,7 @@ final class PlayerBridge: ObservableObject {
player.replaceCurrentItem(with: playerItem)
player.play()
isPlaying = true
print("[MiniPlayer] player.play() called, rate=\(player.rate), timeControlStatus=\(player.timeControlStatus.rawValue)")
diagLog("play() called, rate=\(player.rate) timeControlStatus=\(player.timeControlStatus.rawValue)")
cachedDuration = 0
progressRatio = 0
@ -304,7 +328,7 @@ final class PlayerBridge: ObservableObject {
// KVO 线 DispatchQueue.main.async
itemStatusObserver = playerItem.observe(\.status, options: [.new]) { [weak self] pi, _ in
print("[MiniPlayer] KVO status changed: \(pi.status.rawValue), error=\(pi.error?.localizedDescription ?? "none")")
self?.diagLog("KVO status: \(pi.status.rawValue)")
DispatchQueue.main.async {
switch pi.status {
case .readyToPlay:
@ -326,9 +350,7 @@ final class PlayerBridge: ObservableObject {
let errMsg = err?.localizedDescription ?? "Unknown error"
let errCode = (err as NSError?)?.code ?? 0
let isLocalFile = item.url.isFileURL
NSLog("[MiniPlayer] ❌ AVPlayerItem failed: error=%@, code=%d, domain=%@, localFile=%d, url=%@",
errMsg, errCode, (err as NSError?)?.domain ?? "none", isLocalFile ? 1 : 0,
item.url.absoluteString)
self?.diagLog("AVPlayerItem FAILED: \(errMsg) code=\(errCode) localFile=\(isLocalFile)")
self?.playerStatus = .error(errMsg)
self?.loadingTimer?.invalidate()
self?.loadingTimer = nil
@ -574,8 +596,10 @@ final class PlayerBridge: ObservableObject {
func addItem(url: URL, name: String, type: String) {
let urlString = url.absoluteString
print("[MiniPlayer] addItem: name=\(name), type=\(type), scheme=\(url.scheme ?? "nil")")
// URL
if queue.contains(where: { $0.url.absoluteString == urlString }) {
print("[MiniPlayer] addItem: already in queue, skipping")
showToast("⚠️ \(name) already in queue")
return
}
@ -583,7 +607,11 @@ final class PlayerBridge: ObservableObject {
let libItem = library.addItem(url: urlString, name: name, type: type)
queue.append(MediaItem(id: UUID().uuidString, url: url, name: name,
mediaType: type, libraryID: libItem.id))
if queue.count == 1 { playIndex(0) }
print("[MiniPlayer] addItem: added to queue, count=\(queue.count)")
if queue.count == 1 {
print("[MiniPlayer] addItem: first item, calling playIndex(0)")
playIndex(0)
}
}
func removeItem(at index: Int) {
@ -729,8 +757,11 @@ final class PlayerBridge: ObservableObject {
/// iOS
func handleFileImport(_ urls: [URL]) {
diagLog("handleFileImport: \(urls.count) URLs")
for url in urls {
diagLog("processing: \(url.lastPathComponent)")
let didStartAccessing = url.startAccessingSecurityScopedResource()
diagLog("startAccessingSecurityScopedResource: \(didStartAccessing)")
defer {
if didStartAccessing { url.stopAccessingSecurityScopedResource() }
}
@ -747,13 +778,15 @@ final class PlayerBridge: ObservableObject {
let ext = url.pathExtension
let newURL = docsDir.appendingPathComponent("\(nameWithoutExt)_\(ts).\(ext)")
try FileManager.default.copyItem(at: url, to: newURL)
diagLog("copied to: \(newURL.lastPathComponent)")
addItem(url: newURL, name: url.lastPathComponent, type: "file")
} else {
try FileManager.default.copyItem(at: url, to: localURL)
diagLog("copied to: \(localURL.lastPathComponent)")
addItem(url: localURL, name: url.lastPathComponent, type: "file")
}
} catch {
// URL iCloud
diagLog("copy failed: \(error.localizedDescription)")
addItem(url: url, name: url.lastPathComponent, type: "file")
}
}