fix: add device diagnostic log (miniplayer_diag.log in Documents) to trace playback issues
This commit is contained in:
parent
37d7338823
commit
e81af97437
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user