react-native-nitro-player 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +5 -3
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +4 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/connection/AndroidAutoConnectionDetector.kt +14 -13
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/NitroPlayerLogger.kt +31 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +142 -95
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadDatabase.kt +7 -6
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadManagerCore.kt +2 -1
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadWorker.kt +1 -2
- package/android/src/main/java/com/margelo/nitro/nitroplayer/equalizer/EqualizerCore.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaBrowserService.kt +25 -24
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaLibraryManager.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +20 -19
- package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +16 -9
- package/ios/HybridAudioRoutePicker.swift +1 -1
- package/ios/HybridDownloadManager.swift +3 -3
- package/ios/HybridEqualizer.swift +3 -3
- package/ios/HybridTrackPlayer.swift +8 -4
- package/ios/core/NitroPlayerLogger.swift +22 -0
- package/ios/core/TrackPlayerCore.swift +195 -256
- package/ios/download/DownloadDatabase.swift +35 -39
- package/ios/download/DownloadFileManager.swift +17 -17
- package/ios/download/DownloadManagerCore.swift +29 -33
- package/ios/equalizer/EqualizerCore.swift +25 -20
- package/ios/playlist/PlaylistManager.swift +19 -9
- package/ios/queue/QueueManager.swift +1 -1
- package/lib/specs/TrackPlayer.nitro.d.ts +1 -0
- package/lib/types/PlayerQueue.d.ts +1 -1
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +5 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +1 -0
- package/nitrogen/generated/android/c++/JReason.hpp +3 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Reason.kt +2 -1
- package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +12 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +8 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +12 -0
- package/nitrogen/generated/ios/swift/Reason.swift +4 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +1 -0
- package/nitrogen/generated/shared/c++/Reason.hpp +4 -0
- package/package.json +1 -1
- package/src/specs/TrackPlayer.nitro.ts +1 -0
- package/src/types/PlayerQueue.ts +1 -1
|
@@ -67,18 +67,16 @@ final class DownloadDatabase {
|
|
|
67
67
|
func isTrackDownloaded(trackId: String) -> Bool {
|
|
68
68
|
return queue.sync {
|
|
69
69
|
guard let record = downloadedTracks[trackId] else {
|
|
70
|
-
|
|
70
|
+
NitroPlayerLogger.log("DownloadDatabase", "🔍 Track \(trackId) NOT found in database")
|
|
71
71
|
return false
|
|
72
72
|
}
|
|
73
73
|
// Verify file still exists
|
|
74
74
|
let absolutePath = resolveAbsolutePath(for: record)
|
|
75
75
|
let exists = FileManager.default.fileExists(atPath: absolutePath)
|
|
76
76
|
if exists {
|
|
77
|
-
|
|
77
|
+
NitroPlayerLogger.log("DownloadDatabase", "✅ Track \(trackId) IS downloaded at \(absolutePath)")
|
|
78
78
|
} else {
|
|
79
|
-
|
|
80
|
-
"❌ DownloadDatabase: Track \(trackId) record exists but file NOT found at \(absolutePath)"
|
|
81
|
-
)
|
|
79
|
+
NitroPlayerLogger.log("DownloadDatabase", "❌ Track \(trackId) record exists but file NOT found at \(absolutePath)")
|
|
82
80
|
}
|
|
83
81
|
return exists
|
|
84
82
|
}
|
|
@@ -121,21 +119,21 @@ final class DownloadDatabase {
|
|
|
121
119
|
|
|
122
120
|
func getDownloadedTrack(trackId: String) -> DownloadedTrack? {
|
|
123
121
|
return queue.sync {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
NitroPlayerLogger.log("DownloadDatabase", "🔍 DownloadDatabase.getDownloadedTrack() for trackId: \(trackId)")
|
|
123
|
+
NitroPlayerLogger.log("DownloadDatabase", " Total records in memory: \(downloadedTracks.count)")
|
|
124
|
+
NitroPlayerLogger.log("DownloadDatabase", " Available trackIds: \(Array(downloadedTracks.keys))")
|
|
127
125
|
|
|
128
126
|
guard let record = downloadedTracks[trackId] else {
|
|
129
|
-
|
|
127
|
+
NitroPlayerLogger.log("DownloadDatabase", " ❌ No record found for trackId: \(trackId)")
|
|
130
128
|
return nil
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
let absolutePath = resolveAbsolutePath(for: record)
|
|
134
|
-
|
|
132
|
+
NitroPlayerLogger.log("DownloadDatabase", " Found record, checking file at: \(absolutePath)")
|
|
135
133
|
|
|
136
134
|
// Verify file still exists
|
|
137
135
|
guard FileManager.default.fileExists(atPath: absolutePath) else {
|
|
138
|
-
|
|
136
|
+
NitroPlayerLogger.log("DownloadDatabase", " ❌ File does NOT exist, cleaning up record")
|
|
139
137
|
// File was deleted externally, clean up record
|
|
140
138
|
queue.async(flags: .barrier) {
|
|
141
139
|
self.downloadedTracks.removeValue(forKey: trackId)
|
|
@@ -144,34 +142,33 @@ final class DownloadDatabase {
|
|
|
144
142
|
return nil
|
|
145
143
|
}
|
|
146
144
|
|
|
147
|
-
|
|
145
|
+
NitroPlayerLogger.log("DownloadDatabase", " ✅ File exists, returning track")
|
|
148
146
|
return recordToDownloadedTrack(record)
|
|
149
147
|
}
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
func getAllDownloadedTracks() -> [DownloadedTrack] {
|
|
153
151
|
return queue.sync {
|
|
154
|
-
|
|
155
|
-
"🎯 DownloadDatabase: getAllDownloadedTracks called, have \(downloadedTracks.count) records")
|
|
152
|
+
NitroPlayerLogger.log("DownloadDatabase", "🎯 getAllDownloadedTracks called, have \(downloadedTracks.count) records")
|
|
156
153
|
|
|
157
154
|
var validTracks: [DownloadedTrack] = []
|
|
158
155
|
var invalidTrackIds: [String] = []
|
|
159
156
|
|
|
160
157
|
for (trackId, record) in downloadedTracks {
|
|
161
158
|
let absolutePath = resolveAbsolutePath(for: record)
|
|
162
|
-
|
|
159
|
+
NitroPlayerLogger.log("DownloadDatabase", " Checking track \(trackId) at path: \(absolutePath)")
|
|
163
160
|
if FileManager.default.fileExists(atPath: absolutePath) {
|
|
164
|
-
|
|
161
|
+
NitroPlayerLogger.log("DownloadDatabase", " ✅ File exists")
|
|
165
162
|
validTracks.append(recordToDownloadedTrack(record))
|
|
166
163
|
} else {
|
|
167
|
-
|
|
164
|
+
NitroPlayerLogger.log("DownloadDatabase", " ❌ File NOT found")
|
|
168
165
|
invalidTrackIds.append(trackId)
|
|
169
166
|
}
|
|
170
167
|
}
|
|
171
168
|
|
|
172
169
|
// Clean up invalid records
|
|
173
170
|
if !invalidTrackIds.isEmpty {
|
|
174
|
-
|
|
171
|
+
NitroPlayerLogger.log("DownloadDatabase", " Cleaning up \(invalidTrackIds.count) invalid records")
|
|
175
172
|
queue.async(flags: .barrier) {
|
|
176
173
|
for trackId in invalidTrackIds {
|
|
177
174
|
self.downloadedTracks.removeValue(forKey: trackId)
|
|
@@ -180,7 +177,7 @@ final class DownloadDatabase {
|
|
|
180
177
|
}
|
|
181
178
|
}
|
|
182
179
|
|
|
183
|
-
|
|
180
|
+
NitroPlayerLogger.log("DownloadDatabase", "🎯 Returning \(validTracks.count) valid tracks")
|
|
184
181
|
return validTracks
|
|
185
182
|
}
|
|
186
183
|
}
|
|
@@ -238,7 +235,7 @@ final class DownloadDatabase {
|
|
|
238
235
|
/// Returns the number of orphaned records that were cleaned up
|
|
239
236
|
func syncDownloads() -> Int {
|
|
240
237
|
return queue.sync(flags: .barrier) {
|
|
241
|
-
|
|
238
|
+
NitroPlayerLogger.log("DownloadDatabase", "🔄 syncDownloads called")
|
|
242
239
|
|
|
243
240
|
var removedCount = 0
|
|
244
241
|
var trackIdsToRemove: [String] = []
|
|
@@ -246,7 +243,7 @@ final class DownloadDatabase {
|
|
|
246
243
|
for (trackId, record) in downloadedTracks {
|
|
247
244
|
let absolutePath = resolveAbsolutePath(for: record)
|
|
248
245
|
if !FileManager.default.fileExists(atPath: absolutePath) {
|
|
249
|
-
|
|
246
|
+
NitroPlayerLogger.log("DownloadDatabase", " ❌ Missing file for track \(trackId): \(absolutePath)")
|
|
250
247
|
trackIdsToRemove.append(trackId)
|
|
251
248
|
}
|
|
252
249
|
}
|
|
@@ -271,9 +268,9 @@ final class DownloadDatabase {
|
|
|
271
268
|
|
|
272
269
|
if removedCount > 0 {
|
|
273
270
|
saveToDisk()
|
|
274
|
-
|
|
271
|
+
NitroPlayerLogger.log("DownloadDatabase", " ✅ Cleaned up \(removedCount) orphaned records")
|
|
275
272
|
} else {
|
|
276
|
-
|
|
273
|
+
NitroPlayerLogger.log("DownloadDatabase", " ✅ All downloads are valid")
|
|
277
274
|
}
|
|
278
275
|
|
|
279
276
|
return removedCount
|
|
@@ -363,14 +360,14 @@ final class DownloadDatabase {
|
|
|
363
360
|
let playlistData = try JSONEncoder().encode(playlistTracksDict)
|
|
364
361
|
UserDefaults.standard.set(playlistData, forKey: Self.playlistTracksKey)
|
|
365
362
|
} catch {
|
|
366
|
-
|
|
363
|
+
NitroPlayerLogger.log("DownloadDatabase", "Failed to save to disk: \(error)")
|
|
367
364
|
}
|
|
368
365
|
}
|
|
369
366
|
|
|
370
367
|
private func loadFromDisk() {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
368
|
+
NitroPlayerLogger.log("DownloadDatabase", "\n" + String(repeating: "📀", count: 40))
|
|
369
|
+
NitroPlayerLogger.log("DownloadDatabase", "📀 LOADING FROM DISK")
|
|
370
|
+
NitroPlayerLogger.log("DownloadDatabase", String(repeating: "📀", count: 40))
|
|
374
371
|
|
|
375
372
|
// Load synchronously to ensure data is available immediately
|
|
376
373
|
// Load downloaded tracks
|
|
@@ -378,7 +375,7 @@ final class DownloadDatabase {
|
|
|
378
375
|
do {
|
|
379
376
|
self.downloadedTracks = try JSONDecoder().decode(
|
|
380
377
|
[String: DownloadedTrackRecord].self, from: tracksData)
|
|
381
|
-
|
|
378
|
+
NitroPlayerLogger.log("DownloadDatabase", "✅ Loaded \(self.downloadedTracks.count) tracks from disk")
|
|
382
379
|
|
|
383
380
|
// Migrate absolute paths → filenames (one-time, for existing installs)
|
|
384
381
|
var needsMigration = false
|
|
@@ -401,15 +398,15 @@ final class DownloadDatabase {
|
|
|
401
398
|
|
|
402
399
|
// Log each downloaded track
|
|
403
400
|
for (trackId, record) in self.downloadedTracks {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
401
|
+
NitroPlayerLogger.log("DownloadDatabase", " 📥 \(trackId)")
|
|
402
|
+
NitroPlayerLogger.log("DownloadDatabase", " Title: \(record.originalTrack.title)")
|
|
403
|
+
NitroPlayerLogger.log("DownloadDatabase", " Path (filename): \(record.localPath)")
|
|
407
404
|
}
|
|
408
405
|
} catch {
|
|
409
|
-
|
|
406
|
+
NitroPlayerLogger.log("DownloadDatabase", "❌ Failed to load tracks from disk: \(error)")
|
|
410
407
|
}
|
|
411
408
|
} else {
|
|
412
|
-
|
|
409
|
+
NitroPlayerLogger.log("DownloadDatabase", "⚠️ No saved tracks found in UserDefaults")
|
|
413
410
|
}
|
|
414
411
|
|
|
415
412
|
// Load playlist associations
|
|
@@ -418,21 +415,20 @@ final class DownloadDatabase {
|
|
|
418
415
|
let playlistTracksDict = try JSONDecoder().decode(
|
|
419
416
|
[String: [String]].self, from: playlistData)
|
|
420
417
|
self.playlistTracks = playlistTracksDict.mapValues { Set($0) }
|
|
421
|
-
|
|
422
|
-
"✅ DownloadDatabase: Loaded \(self.playlistTracks.count) playlist associations from disk")
|
|
418
|
+
NitroPlayerLogger.log("DownloadDatabase", "✅ Loaded \(self.playlistTracks.count) playlist associations from disk")
|
|
423
419
|
|
|
424
420
|
// Log playlist associations
|
|
425
421
|
for (playlistId, trackIds) in self.playlistTracks {
|
|
426
|
-
|
|
422
|
+
NitroPlayerLogger.log("DownloadDatabase", " 📋 Playlist \(playlistId): \(trackIds.count) tracks")
|
|
427
423
|
}
|
|
428
424
|
} catch {
|
|
429
|
-
|
|
425
|
+
NitroPlayerLogger.log("DownloadDatabase", "❌ Failed to load playlist tracks from disk: \(error)")
|
|
430
426
|
}
|
|
431
427
|
} else {
|
|
432
|
-
|
|
428
|
+
NitroPlayerLogger.log("DownloadDatabase", "⚠️ No playlist associations found")
|
|
433
429
|
}
|
|
434
430
|
|
|
435
|
-
|
|
431
|
+
NitroPlayerLogger.log("DownloadDatabase", String(repeating: "📀", count: 40) + "\n")
|
|
436
432
|
}
|
|
437
433
|
|
|
438
434
|
// MARK: - Conversion Helpers
|
|
@@ -60,14 +60,14 @@ final class DownloadFileManager {
|
|
|
60
60
|
suggestedFilename: String? = nil,
|
|
61
61
|
httpResponse: HTTPURLResponse? = nil
|
|
62
62
|
) -> String? {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
NitroPlayerLogger.log("DownloadFileManager", "saveDownloadedFile called for trackId=\(trackId)")
|
|
64
|
+
NitroPlayerLogger.log("DownloadFileManager", " From: \(temporaryLocation.path)")
|
|
65
|
+
NitroPlayerLogger.log("DownloadFileManager", " Original URL: \(originalURL ?? "nil")")
|
|
66
|
+
NitroPlayerLogger.log("DownloadFileManager", " Suggested Filename: \(suggestedFilename ?? "nil")")
|
|
67
67
|
|
|
68
68
|
let destinationDirectory =
|
|
69
69
|
storageLocation == .private ? privateDownloadsDirectory : publicDownloadsDirectory
|
|
70
|
-
|
|
70
|
+
NitroPlayerLogger.log("DownloadFileManager", " Destination directory: \(destinationDirectory.path)")
|
|
71
71
|
|
|
72
72
|
// Determine file extension using headers first, then URL path, then default
|
|
73
73
|
let fileExtension = Self.resolveFileExtension(
|
|
@@ -75,32 +75,32 @@ final class DownloadFileManager {
|
|
|
75
75
|
suggestedFilename: suggestedFilename,
|
|
76
76
|
originalURL: originalURL
|
|
77
77
|
)
|
|
78
|
-
|
|
78
|
+
NitroPlayerLogger.log("DownloadFileManager", " File extension: \(fileExtension)")
|
|
79
79
|
|
|
80
80
|
let fileName = "\(trackId).\(fileExtension)"
|
|
81
81
|
let destinationURL = destinationDirectory.appendingPathComponent(fileName)
|
|
82
|
-
|
|
82
|
+
NitroPlayerLogger.log("DownloadFileManager", " Destination: \(destinationURL.path)")
|
|
83
83
|
|
|
84
84
|
// Verify source file exists
|
|
85
85
|
guard fileManager.fileExists(atPath: temporaryLocation.path) else {
|
|
86
|
-
|
|
86
|
+
NitroPlayerLogger.log("DownloadFileManager", "❌ Source file does not exist at \(temporaryLocation.path)")
|
|
87
87
|
return nil
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
do {
|
|
91
91
|
// Remove existing file if present
|
|
92
92
|
if fileManager.fileExists(atPath: destinationURL.path) {
|
|
93
|
-
|
|
93
|
+
NitroPlayerLogger.log("DownloadFileManager", " Removing existing file at destination")
|
|
94
94
|
try fileManager.removeItem(at: destinationURL)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// Move from temporary location to permanent location
|
|
98
98
|
try fileManager.moveItem(at: temporaryLocation, to: destinationURL)
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
NitroPlayerLogger.log("DownloadFileManager", "✅ File saved successfully")
|
|
101
101
|
return destinationURL.path
|
|
102
102
|
} catch {
|
|
103
|
-
|
|
103
|
+
NitroPlayerLogger.log("DownloadFileManager", "❌ Failed to save file: \(error)")
|
|
104
104
|
return nil
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -133,7 +133,7 @@ final class DownloadFileManager {
|
|
|
133
133
|
// 1. Content-Disposition: attachment; filename="track.mp3"
|
|
134
134
|
if let disposition = httpResponse?.value(forHTTPHeaderField: "Content-Disposition") {
|
|
135
135
|
if let ext = extensionFromContentDisposition(disposition), !ext.isEmpty {
|
|
136
|
-
|
|
136
|
+
NitroPlayerLogger.log("DownloadFileManager", " [ExtResolve] Content-Disposition → .\(ext)")
|
|
137
137
|
return ext
|
|
138
138
|
}
|
|
139
139
|
}
|
|
@@ -142,7 +142,7 @@ final class DownloadFileManager {
|
|
|
142
142
|
if let contentType = httpResponse?.value(forHTTPHeaderField: "Content-Type") {
|
|
143
143
|
let mime = contentType.split(separator: ";").first.map(String.init)?.trimmingCharacters(in: .whitespaces) ?? contentType
|
|
144
144
|
if let ext = mimeTypeToExtension[mime.lowercased()] {
|
|
145
|
-
|
|
145
|
+
NitroPlayerLogger.log("DownloadFileManager", " [ExtResolve] Content-Type '\(mime)' → .\(ext)")
|
|
146
146
|
return ext
|
|
147
147
|
}
|
|
148
148
|
}
|
|
@@ -151,7 +151,7 @@ final class DownloadFileManager {
|
|
|
151
151
|
if let name = suggestedFilename, !name.isEmpty {
|
|
152
152
|
let ext = URL(fileURLWithPath: name).pathExtension.lowercased()
|
|
153
153
|
if !ext.isEmpty && isAudioExtension(ext) {
|
|
154
|
-
|
|
154
|
+
NitroPlayerLogger.log("DownloadFileManager", " [ExtResolve] suggestedFilename → .\(ext)")
|
|
155
155
|
return ext
|
|
156
156
|
}
|
|
157
157
|
}
|
|
@@ -160,12 +160,12 @@ final class DownloadFileManager {
|
|
|
160
160
|
if let urlString = originalURL, let url = URL(string: urlString) {
|
|
161
161
|
let ext = url.pathExtension.lowercased()
|
|
162
162
|
if !ext.isEmpty && isAudioExtension(ext) {
|
|
163
|
-
|
|
163
|
+
NitroPlayerLogger.log("DownloadFileManager", " [ExtResolve] URL path ext → .\(ext)")
|
|
164
164
|
return ext
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
|
|
168
|
+
NitroPlayerLogger.log("DownloadFileManager", " [ExtResolve] fallback → .mp3")
|
|
169
169
|
return "mp3"
|
|
170
170
|
}
|
|
171
171
|
|
|
@@ -200,7 +200,7 @@ final class DownloadFileManager {
|
|
|
200
200
|
try fileManager.removeItem(atPath: path)
|
|
201
201
|
}
|
|
202
202
|
} catch {
|
|
203
|
-
|
|
203
|
+
NitroPlayerLogger.log("DownloadFileManager", "Failed to delete file: \(error)")
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
@@ -372,12 +372,12 @@ final class DownloadManagerCore: NSObject {
|
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
func getLocalPath(trackId: String) -> String? {
|
|
375
|
-
|
|
375
|
+
NitroPlayerLogger.log("DownloadManagerCore", "🔍 getLocalPath() called for trackId: \(trackId)")
|
|
376
376
|
if let downloadedTrack = DownloadDatabase.shared.getDownloadedTrack(trackId: trackId) {
|
|
377
|
-
|
|
377
|
+
NitroPlayerLogger.log("DownloadManagerCore", " ✅ Found downloaded track, localPath: \(downloadedTrack.localPath)")
|
|
378
378
|
return downloadedTrack.localPath
|
|
379
379
|
} else {
|
|
380
|
-
|
|
380
|
+
NitroPlayerLogger.log("DownloadManagerCore", " ❌ No downloaded track found for trackId: \(trackId)")
|
|
381
381
|
return nil
|
|
382
382
|
}
|
|
383
383
|
}
|
|
@@ -406,9 +406,7 @@ final class DownloadManagerCore: NSObject {
|
|
|
406
406
|
func syncDownloads() -> Int {
|
|
407
407
|
let removedFromDb = DownloadDatabase.shared.syncDownloads()
|
|
408
408
|
let bytesFreed = DownloadFileManager.shared.cleanupOrphanedFiles()
|
|
409
|
-
|
|
410
|
-
"🔄 DownloadManagerCore: syncDownloads completed - removed \(removedFromDb) orphaned records, freed \(bytesFreed) bytes"
|
|
411
|
-
)
|
|
409
|
+
NitroPlayerLogger.log("DownloadManagerCore", "🔄 syncDownloads completed - removed \(removedFromDb) orphaned records, freed \(bytesFreed) bytes")
|
|
412
410
|
return removedFromDb
|
|
413
411
|
}
|
|
414
412
|
|
|
@@ -426,27 +424,27 @@ final class DownloadManagerCore: NSObject {
|
|
|
426
424
|
|
|
427
425
|
func getEffectiveUrl(track: TrackItem) -> String {
|
|
428
426
|
let preference = getPlaybackSourcePreference()
|
|
429
|
-
|
|
430
|
-
|
|
427
|
+
NitroPlayerLogger.log("DownloadManagerCore", "🔍 getEffectiveUrl() for track: \(track.id)")
|
|
428
|
+
NitroPlayerLogger.log("DownloadManagerCore", " Playback preference: \(preference)")
|
|
431
429
|
|
|
432
430
|
switch preference {
|
|
433
431
|
case .network:
|
|
434
|
-
|
|
432
|
+
NitroPlayerLogger.log("DownloadManagerCore", " → Using network URL (preference=network)")
|
|
435
433
|
return track.url
|
|
436
434
|
case .download:
|
|
437
435
|
if let localPath = getLocalPath(trackId: track.id) {
|
|
438
|
-
|
|
436
|
+
NitroPlayerLogger.log("DownloadManagerCore", " → Using local path: \(localPath)")
|
|
439
437
|
return localPath
|
|
440
438
|
} else {
|
|
441
|
-
|
|
439
|
+
NitroPlayerLogger.log("DownloadManagerCore", " → Local path not found, falling back to network URL")
|
|
442
440
|
return track.url
|
|
443
441
|
}
|
|
444
442
|
case .auto:
|
|
445
443
|
if let localPath = getLocalPath(trackId: track.id) {
|
|
446
|
-
|
|
444
|
+
NitroPlayerLogger.log("DownloadManagerCore", " → Using local path: \(localPath)")
|
|
447
445
|
return localPath
|
|
448
446
|
} else {
|
|
449
|
-
|
|
447
|
+
NitroPlayerLogger.log("DownloadManagerCore", " → Local path not found, using network URL")
|
|
450
448
|
return track.url
|
|
451
449
|
}
|
|
452
450
|
}
|
|
@@ -507,7 +505,7 @@ final class DownloadManagerCore: NSObject {
|
|
|
507
505
|
|
|
508
506
|
/// Load persisted track metadata and playlist associations (survives app restart)
|
|
509
507
|
private func loadPersistedMetadata() {
|
|
510
|
-
|
|
508
|
+
NitroPlayerLogger.log("DownloadManagerCore", "📦 Loading persisted metadata...")
|
|
511
509
|
|
|
512
510
|
// Load track metadata
|
|
513
511
|
if let data = UserDefaults.standard.data(forKey: Self.trackMetadataKey) {
|
|
@@ -516,24 +514,24 @@ final class DownloadManagerCore: NSObject {
|
|
|
516
514
|
for (trackId, record) in records {
|
|
517
515
|
trackMetadata[trackId] = recordToTrackItem(record)
|
|
518
516
|
}
|
|
519
|
-
|
|
517
|
+
NitroPlayerLogger.log("DownloadManagerCore", " ✅ Loaded \(trackMetadata.count) track metadata entries")
|
|
520
518
|
} catch {
|
|
521
|
-
|
|
519
|
+
NitroPlayerLogger.log("DownloadManagerCore", " ❌ Failed to load track metadata: \(error)")
|
|
522
520
|
}
|
|
523
521
|
} else {
|
|
524
|
-
|
|
522
|
+
NitroPlayerLogger.log("DownloadManagerCore", " ⚠️ No persisted track metadata found")
|
|
525
523
|
}
|
|
526
524
|
|
|
527
525
|
// Load playlist associations
|
|
528
526
|
if let data = UserDefaults.standard.data(forKey: Self.playlistAssociationsKey) {
|
|
529
527
|
do {
|
|
530
528
|
playlistAssociations = try JSONDecoder().decode([String: String].self, from: data)
|
|
531
|
-
|
|
529
|
+
NitroPlayerLogger.log("DownloadManagerCore", " ✅ Loaded \(playlistAssociations.count) playlist associations")
|
|
532
530
|
} catch {
|
|
533
|
-
|
|
531
|
+
NitroPlayerLogger.log("DownloadManagerCore", " ❌ Failed to load playlist associations: \(error)")
|
|
534
532
|
}
|
|
535
533
|
} else {
|
|
536
|
-
|
|
534
|
+
NitroPlayerLogger.log("DownloadManagerCore", " ⚠️ No persisted playlist associations found")
|
|
537
535
|
}
|
|
538
536
|
}
|
|
539
537
|
|
|
@@ -552,7 +550,7 @@ final class DownloadManagerCore: NSObject {
|
|
|
552
550
|
let playlistData = try JSONEncoder().encode(playlistAssociations)
|
|
553
551
|
UserDefaults.standard.set(playlistData, forKey: Self.playlistAssociationsKey)
|
|
554
552
|
} catch {
|
|
555
|
-
|
|
553
|
+
NitroPlayerLogger.log("DownloadManagerCore", "❌ Failed to save metadata: \(error)")
|
|
556
554
|
}
|
|
557
555
|
}
|
|
558
556
|
|
|
@@ -657,24 +655,22 @@ extension DownloadManagerCore: URLSessionDownloadDelegate {
|
|
|
657
655
|
_ session: URLSession, downloadTask: URLSessionDownloadTask,
|
|
658
656
|
didFinishDownloadingTo location: URL
|
|
659
657
|
) {
|
|
660
|
-
|
|
658
|
+
NitroPlayerLogger.log("DownloadManagerCore", "🎯 didFinishDownloadingTo called")
|
|
661
659
|
|
|
662
660
|
guard let description = downloadTask.taskDescription else {
|
|
663
|
-
|
|
661
|
+
NitroPlayerLogger.log("DownloadManagerCore", "❌ No task description")
|
|
664
662
|
return
|
|
665
663
|
}
|
|
666
664
|
let parts = description.split(separator: "|")
|
|
667
665
|
guard parts.count == 2 else {
|
|
668
|
-
|
|
666
|
+
NitroPlayerLogger.log("DownloadManagerCore", "❌ Invalid task description format: \(description)")
|
|
669
667
|
return
|
|
670
668
|
}
|
|
671
669
|
|
|
672
670
|
let downloadId = String(parts[0])
|
|
673
671
|
let trackId = String(parts[1])
|
|
674
672
|
|
|
675
|
-
|
|
676
|
-
"🎯 DownloadManagerCore: Processing completion for downloadId=\(downloadId), trackId=\(trackId)"
|
|
677
|
-
)
|
|
673
|
+
NitroPlayerLogger.log("DownloadManagerCore", "🎯 Processing completion for downloadId=\(downloadId), trackId=\(trackId)")
|
|
678
674
|
|
|
679
675
|
// IMPORTANT: Move file SYNCHRONOUSLY - the temp file is deleted after this method returns!
|
|
680
676
|
// Get storage location and original URL from track metadata
|
|
@@ -698,7 +694,7 @@ extension DownloadManagerCore: URLSessionDownloadDelegate {
|
|
|
698
694
|
// Now handle the rest asynchronously
|
|
699
695
|
queue.async(flags: .barrier) {
|
|
700
696
|
guard let destinationPath = destinationPath else {
|
|
701
|
-
|
|
697
|
+
NitroPlayerLogger.log("DownloadManagerCore", "❌ Failed to save file for trackId=\(trackId)")
|
|
702
698
|
self.taskMetadata[downloadId]?.state = .failed
|
|
703
699
|
self.taskMetadata[downloadId]?.error = DownloadError(
|
|
704
700
|
code: "FILE_MOVE_FAILED",
|
|
@@ -712,11 +708,11 @@ extension DownloadManagerCore: URLSessionDownloadDelegate {
|
|
|
712
708
|
return
|
|
713
709
|
}
|
|
714
710
|
|
|
715
|
-
|
|
711
|
+
NitroPlayerLogger.log("DownloadManagerCore", "✅ File saved to \(destinationPath)")
|
|
716
712
|
|
|
717
713
|
guard let track = self.trackMetadata[trackId] else {
|
|
718
|
-
|
|
719
|
-
|
|
714
|
+
NitroPlayerLogger.log("DownloadManagerCore", "❌ No track metadata for trackId=\(trackId)")
|
|
715
|
+
NitroPlayerLogger.log("DownloadManagerCore", " Available trackIds: \(Array(self.trackMetadata.keys))")
|
|
720
716
|
|
|
721
717
|
// Still mark as completed even if we don't have metadata
|
|
722
718
|
self.taskMetadata[downloadId]?.state = .completed
|
|
@@ -747,7 +743,7 @@ extension DownloadManagerCore: URLSessionDownloadDelegate {
|
|
|
747
743
|
// Save to database
|
|
748
744
|
DownloadDatabase.shared.saveDownloadedTrack(downloadedTrack, playlistId: playlistId)
|
|
749
745
|
|
|
750
|
-
|
|
746
|
+
NitroPlayerLogger.log("DownloadManagerCore", "✅ Track saved to database")
|
|
751
747
|
|
|
752
748
|
// Clean up persisted metadata (no longer needed after completion)
|
|
753
749
|
self.cleanupPersistedMetadata(trackId: trackId, downloadId: downloadId)
|
|
@@ -760,7 +756,7 @@ extension DownloadManagerCore: URLSessionDownloadDelegate {
|
|
|
760
756
|
self.activeTasks.removeValue(forKey: downloadId)
|
|
761
757
|
|
|
762
758
|
// Notify
|
|
763
|
-
|
|
759
|
+
NitroPlayerLogger.log("DownloadManagerCore", "✅ Notifying completion for trackId=\(trackId)")
|
|
764
760
|
self.notifyStateChange(
|
|
765
761
|
downloadId: downloadId, trackId: trackId, state: .completed, error: nil)
|
|
766
762
|
self.notifyComplete(downloadedTrack)
|
|
@@ -29,6 +29,9 @@ class EqualizerCore {
|
|
|
29
29
|
// Current gains storage - internal so TapContext can access
|
|
30
30
|
private(set) var currentGains: [Double] = [0, 0, 0, 0, 0]
|
|
31
31
|
|
|
32
|
+
// Dirty flag: set when gains change so TapContext only recalculates when needed
|
|
33
|
+
var gainsDirty: Bool = true
|
|
34
|
+
|
|
32
35
|
// UserDefaults keys
|
|
33
36
|
private let enabledKey = "eq_enabled"
|
|
34
37
|
private let bandGainsKey = "eq_band_gains"
|
|
@@ -81,7 +84,7 @@ class EqualizerCore {
|
|
|
81
84
|
|
|
82
85
|
private init() {
|
|
83
86
|
restoreSettings()
|
|
84
|
-
|
|
87
|
+
NitroPlayerLogger.log("EqualizerCore", "✅ Initialized with MTAudioProcessingTap support")
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
// MARK: - Audio Mix Creation for AVPlayerItem
|
|
@@ -98,19 +101,18 @@ class EqualizerCore {
|
|
|
98
101
|
let status = asset.statusOfValue(forKey: "tracks", error: &error)
|
|
99
102
|
|
|
100
103
|
if status == .failed {
|
|
101
|
-
|
|
102
|
-
"⚠️ EqualizerCore: Failed to load tracks key: \(error?.localizedDescription ?? "unknown")")
|
|
104
|
+
NitroPlayerLogger.log("EqualizerCore", "⚠️ Failed to load tracks key: \(error?.localizedDescription ?? "unknown")")
|
|
103
105
|
return
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
// Proceed only if loaded successfully
|
|
107
109
|
guard status == .loaded else {
|
|
108
|
-
|
|
110
|
+
NitroPlayerLogger.log("EqualizerCore", "⚠️ Tracks not loaded, status: \(status.rawValue)")
|
|
109
111
|
return
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
guard let audioTrack = asset.tracks(withMediaType: .audio).first else {
|
|
113
|
-
|
|
115
|
+
NitroPlayerLogger.log("EqualizerCore", "⚠️ No audio track found in asset")
|
|
114
116
|
return
|
|
115
117
|
}
|
|
116
118
|
|
|
@@ -137,7 +139,7 @@ class EqualizerCore {
|
|
|
137
139
|
)
|
|
138
140
|
|
|
139
141
|
guard createStatus == noErr, let audioTap = tap else {
|
|
140
|
-
|
|
142
|
+
NitroPlayerLogger.log("EqualizerCore", "❌ Failed to create audio processing tap, status: \(createStatus)")
|
|
141
143
|
return
|
|
142
144
|
}
|
|
143
145
|
|
|
@@ -150,7 +152,7 @@ class EqualizerCore {
|
|
|
150
152
|
// Apply to player item on main thread (AVPlayerItem properties should be accessed/modified on main thread or serial queue usually, but audioMix is thread safe - safely done on main to be sure)
|
|
151
153
|
DispatchQueue.main.async {
|
|
152
154
|
playerItem.audioMix = audioMix
|
|
153
|
-
|
|
155
|
+
NitroPlayerLogger.log("EqualizerCore", "✅ Applied audio mix with EQ tap to player item (async)")
|
|
154
156
|
}
|
|
155
157
|
}
|
|
156
158
|
}
|
|
@@ -163,7 +165,7 @@ class EqualizerCore {
|
|
|
163
165
|
notifyEnabledChange(enabled)
|
|
164
166
|
saveEnabled(enabled)
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
NitroPlayerLogger.log("EqualizerCore", "🎚️ Equalizer \(enabled ? "enabled" : "disabled")")
|
|
167
169
|
return true
|
|
168
170
|
}
|
|
169
171
|
|
|
@@ -187,6 +189,7 @@ class EqualizerCore {
|
|
|
187
189
|
|
|
188
190
|
let clampedGain = max(-12.0, min(12.0, gainDb))
|
|
189
191
|
currentGains[bandIndex] = clampedGain
|
|
192
|
+
gainsDirty = true
|
|
190
193
|
|
|
191
194
|
currentPresetName = nil
|
|
192
195
|
notifyBandChange(getBands())
|
|
@@ -194,7 +197,7 @@ class EqualizerCore {
|
|
|
194
197
|
saveBandGains(currentGains)
|
|
195
198
|
saveCurrentPreset(nil)
|
|
196
199
|
|
|
197
|
-
|
|
200
|
+
NitroPlayerLogger.log("EqualizerCore", "🎚️ Band \(bandIndex) gain set to \(clampedGain) dB")
|
|
198
201
|
return true
|
|
199
202
|
}
|
|
200
203
|
|
|
@@ -204,11 +207,12 @@ class EqualizerCore {
|
|
|
204
207
|
for i in 0..<5 {
|
|
205
208
|
currentGains[i] = max(-12.0, min(12.0, gains[i]))
|
|
206
209
|
}
|
|
210
|
+
gainsDirty = true
|
|
207
211
|
|
|
208
212
|
notifyBandChange(getBands())
|
|
209
213
|
saveBandGains(currentGains)
|
|
210
214
|
|
|
211
|
-
|
|
215
|
+
NitroPlayerLogger.log("EqualizerCore", "🎚️ All band gains updated")
|
|
212
216
|
return true
|
|
213
217
|
}
|
|
214
218
|
|
|
@@ -380,7 +384,7 @@ class EqualizerCore {
|
|
|
380
384
|
currentPresetName = UserDefaults.standard.string(forKey: currentPresetKey)
|
|
381
385
|
isEqualizerEnabled = enabled
|
|
382
386
|
|
|
383
|
-
|
|
387
|
+
NitroPlayerLogger.log("EqualizerCore", "✅ Restored settings - enabled: \(enabled), gains: \(currentGains)")
|
|
384
388
|
}
|
|
385
389
|
|
|
386
390
|
// MARK: - Callback Management
|
|
@@ -505,6 +509,7 @@ private class TapContext {
|
|
|
505
509
|
sampleRate: Double(sampleRate)
|
|
506
510
|
)
|
|
507
511
|
}
|
|
512
|
+
eqCore.gainsDirty = false
|
|
508
513
|
}
|
|
509
514
|
|
|
510
515
|
/// Calculate biquad coefficients for a peaking EQ filter
|
|
@@ -556,13 +561,13 @@ private func tapInitCallback(
|
|
|
556
561
|
let context = TapContext(eqCore: eqCore)
|
|
557
562
|
tapStorageOut.pointee = Unmanaged.passRetained(context).toOpaque()
|
|
558
563
|
|
|
559
|
-
|
|
564
|
+
NitroPlayerLogger.log("EqualizerCore", "🎛️ Tap initialized")
|
|
560
565
|
}
|
|
561
566
|
|
|
562
567
|
private func tapFinalizeCallback(tap: MTAudioProcessingTap) {
|
|
563
568
|
let storage = MTAudioProcessingTapGetStorage(tap)
|
|
564
569
|
Unmanaged<TapContext>.fromOpaque(storage).release()
|
|
565
|
-
|
|
570
|
+
NitroPlayerLogger.log("EqualizerCore", "🎛️ Tap finalized")
|
|
566
571
|
}
|
|
567
572
|
|
|
568
573
|
private func tapPrepareCallback(
|
|
@@ -578,13 +583,11 @@ private func tapPrepareCallback(
|
|
|
578
583
|
context.updateCoefficients()
|
|
579
584
|
context.resetFilterStates()
|
|
580
585
|
|
|
581
|
-
|
|
582
|
-
"🎛️ EqualizerCore: Tap prepared - sampleRate: \(context.sampleRate), channels: \(context.channelCount)"
|
|
583
|
-
)
|
|
586
|
+
NitroPlayerLogger.log("EqualizerCore", "🎛️ Tap prepared - sampleRate: \(context.sampleRate), channels: \(context.channelCount)")
|
|
584
587
|
}
|
|
585
588
|
|
|
586
589
|
private func tapUnprepareCallback(tap: MTAudioProcessingTap) {
|
|
587
|
-
|
|
590
|
+
NitroPlayerLogger.log("EqualizerCore", "🎛️ Tap unprepared")
|
|
588
591
|
}
|
|
589
592
|
|
|
590
593
|
private func tapProcessCallback(
|
|
@@ -610,7 +613,7 @@ private func tapProcessCallback(
|
|
|
610
613
|
)
|
|
611
614
|
|
|
612
615
|
guard status == noErr else {
|
|
613
|
-
|
|
616
|
+
NitroPlayerLogger.log("EqualizerCore", "❌ Failed to get source audio: \(status)")
|
|
614
617
|
return
|
|
615
618
|
}
|
|
616
619
|
|
|
@@ -620,8 +623,10 @@ private func tapProcessCallback(
|
|
|
620
623
|
return
|
|
621
624
|
}
|
|
622
625
|
|
|
623
|
-
// Update coefficients
|
|
624
|
-
context.
|
|
626
|
+
// Update coefficients only when gains have changed
|
|
627
|
+
if context.eqCore?.gainsDirty == true {
|
|
628
|
+
context.updateCoefficients()
|
|
629
|
+
}
|
|
625
630
|
|
|
626
631
|
// Process each buffer (channel)
|
|
627
632
|
let bufferList = UnsafeMutableAudioBufferListPointer(bufferListInOut)
|