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
|
@@ -49,7 +49,7 @@ class TrackPlayerCore: NSObject {
|
|
|
49
49
|
private var currentTrackIndex: Int = -1
|
|
50
50
|
private var currentTracks: [TrackItem] = []
|
|
51
51
|
private var isManuallySeeked = false
|
|
52
|
-
private var
|
|
52
|
+
private var currentRepeatMode: RepeatMode = .off
|
|
53
53
|
private var boundaryTimeObserver: Any?
|
|
54
54
|
private var currentItemObservers: [NSKeyValueObservation] = []
|
|
55
55
|
|
|
@@ -116,7 +116,7 @@ class TrackPlayerCore: NSObject {
|
|
|
116
116
|
try audioSession.setCategory(.playback, mode: .default, options: [])
|
|
117
117
|
try audioSession.setActive(true)
|
|
118
118
|
} catch {
|
|
119
|
-
|
|
119
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Failed to setup audio session - \(error)")
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -138,8 +138,7 @@ class TrackPlayerCore: NSObject {
|
|
|
138
138
|
player?.audiovisualBackgroundPlaybackPolicy = .continuesIfPossible
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
"🎵 TrackPlayerCore: Gapless playback configured - automaticallyWaitsToMinimizeStalling=true (flipped to false on first readyToPlay)")
|
|
141
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎵 Gapless playback configured - automaticallyWaitsToMinimizeStalling=true (flipped to false on first readyToPlay)")
|
|
143
142
|
|
|
144
143
|
setupPlayerObservers()
|
|
145
144
|
}
|
|
@@ -201,19 +200,19 @@ class TrackPlayerCore: NSObject {
|
|
|
201
200
|
guard let player = player,
|
|
202
201
|
let currentItem = player.currentItem
|
|
203
202
|
else {
|
|
204
|
-
|
|
203
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Cannot setup boundary observer - no player or item")
|
|
205
204
|
return
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
// Wait for duration to be available
|
|
209
208
|
guard currentItem.status == .readyToPlay else {
|
|
210
|
-
|
|
209
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Item not ready, will setup boundaries when ready")
|
|
211
210
|
return
|
|
212
211
|
}
|
|
213
212
|
|
|
214
213
|
let duration = currentItem.duration.seconds
|
|
215
214
|
guard duration > 0 && !duration.isNaN && !duration.isInfinite else {
|
|
216
|
-
|
|
215
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Invalid duration: \(duration), cannot setup boundaries")
|
|
217
216
|
return
|
|
218
217
|
}
|
|
219
218
|
|
|
@@ -236,9 +235,7 @@ class TrackPlayerCore: NSObject {
|
|
|
236
235
|
time += interval
|
|
237
236
|
}
|
|
238
237
|
|
|
239
|
-
|
|
240
|
-
"⏱️ TrackPlayerCore: Setting up \(boundaryTimes.count) boundary observers (interval: \(interval)s, duration: \(Int(duration))s)"
|
|
241
|
-
)
|
|
238
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⏱️ Setting up \(boundaryTimes.count) boundary observers (interval: \(interval)s, duration: \(Int(duration))s)")
|
|
242
239
|
|
|
243
240
|
// Add boundary time observer
|
|
244
241
|
boundaryTimeObserver = player.addBoundaryTimeObserver(forTimes: boundaryTimes, queue: .main) {
|
|
@@ -247,7 +244,7 @@ class TrackPlayerCore: NSObject {
|
|
|
247
244
|
self.handleBoundaryTimeCrossed()
|
|
248
245
|
}
|
|
249
246
|
|
|
250
|
-
|
|
247
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⏱️ Boundary time observer setup complete")
|
|
251
248
|
}
|
|
252
249
|
|
|
253
250
|
private func handleBoundaryTimeCrossed() {
|
|
@@ -263,9 +260,7 @@ class TrackPlayerCore: NSObject {
|
|
|
263
260
|
|
|
264
261
|
guard duration > 0 && !duration.isNaN && !duration.isInfinite else { return }
|
|
265
262
|
|
|
266
|
-
|
|
267
|
-
"⏱️ TrackPlayerCore: Boundary crossed - position: \(Int(position))s / \(Int(duration))s, callback exists: \(!onPlaybackProgressChangeListeners.isEmpty)"
|
|
268
|
-
)
|
|
263
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⏱️ Boundary crossed - position: \(Int(position))s / \(Int(duration))s, callback exists: \(!onPlaybackProgressChangeListeners.isEmpty)")
|
|
269
264
|
|
|
270
265
|
notifyPlaybackProgress(
|
|
271
266
|
position,
|
|
@@ -278,100 +273,48 @@ class TrackPlayerCore: NSObject {
|
|
|
278
273
|
// MARK: - Notification Handlers
|
|
279
274
|
|
|
280
275
|
@objc private func playerItemDidPlayToEndTime(notification: Notification) {
|
|
281
|
-
|
|
276
|
+
NitroPlayerLogger.log("TrackPlayerCore", "\n🏁 Track finished playing")
|
|
282
277
|
|
|
283
278
|
guard let finishedItem = notification.object as? AVPlayerItem else {
|
|
284
|
-
// Don't call skipToNext — AVQueuePlayer with actionAtItemEnd = .advance already auto-advances
|
|
285
279
|
return
|
|
286
280
|
}
|
|
287
281
|
|
|
288
|
-
//
|
|
282
|
+
// 1. TRACK repeat — handle FIRST, before any temp-track removal
|
|
283
|
+
if currentRepeatMode == .track {
|
|
284
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔁 TRACK repeat — seeking to zero and replaying")
|
|
285
|
+
player?.seek(to: .zero)
|
|
286
|
+
player?.play()
|
|
287
|
+
return // do not remove temp tracks, do not notify track change (same track looping)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 2. Remove finished temp track from its list
|
|
289
291
|
if let trackId = finishedItem.trackId {
|
|
290
292
|
// Check if it was a playNext track
|
|
291
293
|
if let index = playNextStack.firstIndex(where: { $0.id == trackId }) {
|
|
292
294
|
let track = playNextStack.remove(at: index)
|
|
293
|
-
|
|
295
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🏁 Finished playNext track: \(track.title) - removed from stack")
|
|
294
296
|
}
|
|
295
297
|
// Check if it was an upNext track
|
|
296
298
|
else if let index = upNextQueue.firstIndex(where: { $0.id == trackId }) {
|
|
297
299
|
let track = upNextQueue.remove(at: index)
|
|
298
|
-
|
|
300
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🏁 Finished upNext track: \(track.title) - removed from queue")
|
|
299
301
|
}
|
|
300
302
|
// Otherwise it was from original playlist
|
|
301
303
|
else if let track = currentTracks.first(where: { $0.id == trackId }) {
|
|
302
|
-
|
|
304
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🏁 Finished original track: \(track.title)")
|
|
303
305
|
}
|
|
304
306
|
}
|
|
305
307
|
|
|
306
|
-
//
|
|
308
|
+
// 3. Normal next-track advance happens via actionAtItemEnd = .advance
|
|
309
|
+
// The KVO observer (currentItemDidChange) will handle the track change notification
|
|
307
310
|
if let player = player {
|
|
308
|
-
|
|
311
|
+
NitroPlayerLogger.log("TrackPlayerCore", "📋 Remaining items in queue: \(player.items().count)")
|
|
309
312
|
}
|
|
310
|
-
|
|
311
|
-
// Handle repeat modes
|
|
312
|
-
switch repeatMode {
|
|
313
|
-
case .track:
|
|
314
|
-
// Repeat current track - seek to beginning and play
|
|
315
|
-
print("🔁 TrackPlayerCore: Repeat mode is TRACK - replaying current track")
|
|
316
|
-
DispatchQueue.main.async { [weak self] in
|
|
317
|
-
guard let self = self, let player = self.player else { return }
|
|
318
|
-
// For temporary tracks, just seek to beginning
|
|
319
|
-
if self.currentTemporaryType != .none {
|
|
320
|
-
player.seek(to: .zero)
|
|
321
|
-
player.play()
|
|
322
|
-
} else {
|
|
323
|
-
// For original tracks, recreate via playFromIndex
|
|
324
|
-
self.playFromIndex(index: self.currentTrackIndex)
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return
|
|
328
|
-
|
|
329
|
-
case .playlist:
|
|
330
|
-
// Check if we're at the end of the ORIGINAL playlist (ignore temps)
|
|
331
|
-
if currentTemporaryType == .none && currentTrackIndex >= currentTracks.count - 1 {
|
|
332
|
-
// Check if there are still temporary tracks
|
|
333
|
-
if !playNextStack.isEmpty || !upNextQueue.isEmpty {
|
|
334
|
-
print("🔁 TrackPlayerCore: Temporary tracks remaining, continuing...")
|
|
335
|
-
} else {
|
|
336
|
-
print("🔁 TrackPlayerCore: Repeat mode is PLAYLIST - restarting from beginning")
|
|
337
|
-
// Clear temps and restart
|
|
338
|
-
playNextStack.removeAll()
|
|
339
|
-
upNextQueue.removeAll()
|
|
340
|
-
DispatchQueue.main.async { [weak self] in
|
|
341
|
-
guard let self = self else { return }
|
|
342
|
-
self.playFromIndex(index: 0)
|
|
343
|
-
}
|
|
344
|
-
return
|
|
345
|
-
}
|
|
346
|
-
} else {
|
|
347
|
-
print("🔁 TrackPlayerCore: Repeat mode is PLAYLIST - continuing to next track")
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
case .off:
|
|
351
|
-
// Default behavior - stop at end of playlist
|
|
352
|
-
print("🔁 TrackPlayerCore: Repeat mode is OFF")
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Track ended naturally — notify with .end reason
|
|
356
|
-
// AVQueuePlayer with actionAtItemEnd = .advance auto-advances to next item
|
|
357
|
-
// The KVO observer (currentItemDidChange) will handle the track change notification
|
|
358
|
-
notifyTrackChange(
|
|
359
|
-
getCurrentTrack()
|
|
360
|
-
?? TrackItem(
|
|
361
|
-
id: "",
|
|
362
|
-
title: "",
|
|
363
|
-
artist: "",
|
|
364
|
-
album: "",
|
|
365
|
-
duration: 0,
|
|
366
|
-
url: "",
|
|
367
|
-
artwork: nil,
|
|
368
|
-
extraPayload: nil
|
|
369
|
-
), .end)
|
|
370
313
|
}
|
|
371
314
|
|
|
372
315
|
@objc private func playerItemFailedToPlayToEndTime(notification: Notification) {
|
|
373
316
|
if let error = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? Error {
|
|
374
|
-
|
|
317
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Playback failed - \(error)")
|
|
375
318
|
notifyPlaybackStateChange(.stopped, .error)
|
|
376
319
|
}
|
|
377
320
|
}
|
|
@@ -382,14 +325,12 @@ class TrackPlayerCore: NSObject {
|
|
|
382
325
|
else { return }
|
|
383
326
|
|
|
384
327
|
for event in errorLog.events ?? [] {
|
|
385
|
-
|
|
386
|
-
"❌ TrackPlayerCore: Error log - \(event.errorComment ?? "Unknown error") - Code: \(event.errorStatusCode)"
|
|
387
|
-
)
|
|
328
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Error log - \(event.errorComment ?? "Unknown error") - Code: \(event.errorStatusCode)")
|
|
388
329
|
}
|
|
389
330
|
|
|
390
331
|
// Also check item error
|
|
391
332
|
if let error = item.error {
|
|
392
|
-
|
|
333
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Item error - \(error.localizedDescription)")
|
|
393
334
|
}
|
|
394
335
|
}
|
|
395
336
|
|
|
@@ -401,7 +342,7 @@ class TrackPlayerCore: NSObject {
|
|
|
401
342
|
let position = currentItem.currentTime().seconds
|
|
402
343
|
let duration = currentItem.duration.seconds
|
|
403
344
|
|
|
404
|
-
|
|
345
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎯 Time jumped (seek detected) - position: \(Int(position))s")
|
|
405
346
|
|
|
406
347
|
// Call onSeek callback immediately
|
|
407
348
|
notifySeek(position, duration)
|
|
@@ -421,24 +362,24 @@ class TrackPlayerCore: NSObject {
|
|
|
421
362
|
) {
|
|
422
363
|
guard let player = player else { return }
|
|
423
364
|
|
|
424
|
-
|
|
365
|
+
NitroPlayerLogger.log("TrackPlayerCore", "👀 KVO - keyPath: \(keyPath ?? "nil")")
|
|
425
366
|
|
|
426
367
|
if keyPath == "status" {
|
|
427
|
-
|
|
368
|
+
NitroPlayerLogger.log("TrackPlayerCore", "👀 Player status changed to: \(player.status.rawValue)")
|
|
428
369
|
if player.status == .readyToPlay {
|
|
429
370
|
emitStateChange()
|
|
430
371
|
} else if player.status == .failed {
|
|
431
|
-
|
|
372
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Player failed")
|
|
432
373
|
notifyPlaybackStateChange(.stopped, .error)
|
|
433
374
|
}
|
|
434
375
|
} else if keyPath == "rate" {
|
|
435
|
-
|
|
376
|
+
NitroPlayerLogger.log("TrackPlayerCore", "👀 Rate changed to: \(player.rate)")
|
|
436
377
|
emitStateChange()
|
|
437
378
|
} else if keyPath == "timeControlStatus" {
|
|
438
|
-
|
|
379
|
+
NitroPlayerLogger.log("TrackPlayerCore", "👀 TimeControlStatus changed to: \(player.timeControlStatus.rawValue)")
|
|
439
380
|
emitStateChange()
|
|
440
381
|
} else if keyPath == "currentItem" {
|
|
441
|
-
|
|
382
|
+
NitroPlayerLogger.log("TrackPlayerCore", "👀 Current item changed")
|
|
442
383
|
currentItemDidChange()
|
|
443
384
|
}
|
|
444
385
|
}
|
|
@@ -453,43 +394,64 @@ class TrackPlayerCore: NSObject {
|
|
|
453
394
|
guard let player = player,
|
|
454
395
|
let currentItem = player.currentItem
|
|
455
396
|
else {
|
|
456
|
-
|
|
397
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Current item changed to nil")
|
|
398
|
+
// Queue exhausted — handle PLAYLIST repeat
|
|
399
|
+
if currentRepeatMode == .playlist && !currentTracks.isEmpty, let player = player {
|
|
400
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔁 PLAYLIST repeat — rebuilding original queue and restarting")
|
|
401
|
+
playNextStack.removeAll()
|
|
402
|
+
upNextQueue.removeAll()
|
|
403
|
+
currentTemporaryType = .none
|
|
404
|
+
|
|
405
|
+
let allItems = currentTracks.compactMap { createGaplessPlayerItem(for: $0, isPreload: false) }
|
|
406
|
+
var lastItem: AVPlayerItem? = nil
|
|
407
|
+
for item in allItems {
|
|
408
|
+
player.insert(item, after: lastItem)
|
|
409
|
+
lastItem = item
|
|
410
|
+
}
|
|
411
|
+
currentTrackIndex = 0
|
|
412
|
+
player.play()
|
|
413
|
+
|
|
414
|
+
if let firstTrack = currentTracks.first {
|
|
415
|
+
notifyTrackChange(firstTrack, .repeat)
|
|
416
|
+
mediaSessionManager?.onTrackChanged()
|
|
417
|
+
}
|
|
418
|
+
}
|
|
457
419
|
return
|
|
458
420
|
}
|
|
459
421
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
422
|
+
NitroPlayerLogger.log("TrackPlayerCore", "\n" + String(repeating: "▶", count: Constants.separatorLineLength))
|
|
423
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔄 CURRENT ITEM CHANGED")
|
|
424
|
+
NitroPlayerLogger.log("TrackPlayerCore", String(repeating: "▶", count: Constants.separatorLineLength))
|
|
463
425
|
|
|
464
426
|
// Log current item details
|
|
465
427
|
if let trackId = currentItem.trackId,
|
|
466
428
|
let track = currentTracks.first(where: { $0.id == trackId })
|
|
467
429
|
{
|
|
468
|
-
|
|
430
|
+
NitroPlayerLogger.log("TrackPlayerCore", "▶️ NOW PLAYING: \(track.title) - \(track.artist) (ID: \(track.id))")
|
|
469
431
|
} else {
|
|
470
|
-
|
|
432
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⚠️ NOW PLAYING: Unknown track (trackId: \(currentItem.trackId ?? "nil"))")
|
|
471
433
|
}
|
|
472
434
|
|
|
473
435
|
// Show remaining items in queue
|
|
474
436
|
let remainingItems = player.items()
|
|
475
|
-
|
|
437
|
+
NitroPlayerLogger.log("TrackPlayerCore", "\n📋 REMAINING ITEMS IN QUEUE: \(remainingItems.count)")
|
|
476
438
|
for (index, item) in remainingItems.enumerated() {
|
|
477
439
|
if let trackId = item.trackId, let track = currentTracks.first(where: { $0.id == trackId }) {
|
|
478
440
|
let marker = item == currentItem ? "▶️" : " "
|
|
479
|
-
|
|
441
|
+
NitroPlayerLogger.log("TrackPlayerCore", "\(marker) [\(index + 1)] \(track.title) - \(track.artist)")
|
|
480
442
|
} else {
|
|
481
|
-
|
|
443
|
+
NitroPlayerLogger.log("TrackPlayerCore", " [\(index + 1)] ⚠️ Unknown track")
|
|
482
444
|
}
|
|
483
445
|
}
|
|
484
446
|
|
|
485
|
-
|
|
447
|
+
NitroPlayerLogger.log("TrackPlayerCore", String(repeating: "▶", count: Constants.separatorLineLength) + "\n")
|
|
486
448
|
|
|
487
449
|
// Log item status
|
|
488
|
-
|
|
450
|
+
NitroPlayerLogger.log("TrackPlayerCore", "📱 Item status: \(currentItem.status.rawValue)")
|
|
489
451
|
|
|
490
452
|
// Check for errors
|
|
491
453
|
if let error = currentItem.error {
|
|
492
|
-
|
|
454
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Current item has error - \(error.localizedDescription)")
|
|
493
455
|
}
|
|
494
456
|
|
|
495
457
|
// Setup KVO observers for current item
|
|
@@ -497,12 +459,12 @@ class TrackPlayerCore: NSObject {
|
|
|
497
459
|
|
|
498
460
|
// Update track index and determine temporary type
|
|
499
461
|
if let trackId = currentItem.trackId {
|
|
500
|
-
|
|
501
|
-
|
|
462
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔍 Looking up trackId '\(trackId)' in currentTracks...")
|
|
463
|
+
NitroPlayerLogger.log("TrackPlayerCore", " Current index BEFORE lookup: \(currentTrackIndex)")
|
|
502
464
|
|
|
503
465
|
// Update temporary type
|
|
504
466
|
currentTemporaryType = determineCurrentTemporaryType()
|
|
505
|
-
|
|
467
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 🎯 Track type: \(currentTemporaryType)")
|
|
506
468
|
|
|
507
469
|
// If it's a temporary track, don't update currentTrackIndex
|
|
508
470
|
if currentTemporaryType != .none {
|
|
@@ -515,38 +477,38 @@ class TrackPlayerCore: NSObject {
|
|
|
515
477
|
}
|
|
516
478
|
|
|
517
479
|
if let track = tempTrack {
|
|
518
|
-
|
|
519
|
-
|
|
480
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 🎵 Temporary track: \(track.title) - \(track.artist)")
|
|
481
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 📢 Emitting onChangeTrack for temporary track")
|
|
520
482
|
notifyTrackChange(track, .skip)
|
|
521
483
|
mediaSessionManager?.onTrackChanged()
|
|
522
484
|
}
|
|
523
485
|
}
|
|
524
486
|
// It's an original playlist track
|
|
525
487
|
else if let index = currentTracks.firstIndex(where: { $0.id == trackId }) {
|
|
526
|
-
|
|
527
|
-
|
|
488
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ✅ Found track at index: \(index)")
|
|
489
|
+
NitroPlayerLogger.log("TrackPlayerCore", " Setting currentTrackIndex from \(currentTrackIndex) to \(index)")
|
|
528
490
|
|
|
529
491
|
let oldIndex = currentTrackIndex
|
|
530
492
|
currentTrackIndex = index
|
|
531
493
|
|
|
532
494
|
if let track = currentTracks[safe: index] {
|
|
533
|
-
|
|
495
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 🎵 Track: \(track.title) - \(track.artist)")
|
|
534
496
|
|
|
535
497
|
// Only emit onChangeTrack if index actually changed
|
|
536
498
|
// This prevents duplicate emissions
|
|
537
499
|
if oldIndex != index {
|
|
538
|
-
|
|
500
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 📢 Emitting onChangeTrack (index changed from \(oldIndex) to \(index))")
|
|
539
501
|
notifyTrackChange(track, .skip)
|
|
540
502
|
mediaSessionManager?.onTrackChanged()
|
|
541
503
|
} else {
|
|
542
|
-
|
|
504
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ⏭️ Skipping onChangeTrack emission (index unchanged)")
|
|
543
505
|
}
|
|
544
506
|
}
|
|
545
507
|
} else {
|
|
546
|
-
|
|
547
|
-
|
|
508
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ⚠️ Track ID '\(trackId)' NOT FOUND in currentTracks!")
|
|
509
|
+
NitroPlayerLogger.log("TrackPlayerCore", " Current tracks:")
|
|
548
510
|
for (idx, track) in currentTracks.enumerated() {
|
|
549
|
-
|
|
511
|
+
NitroPlayerLogger.log("TrackPlayerCore", " [\(idx)] \(track.id) - \(track.title)")
|
|
550
512
|
}
|
|
551
513
|
}
|
|
552
514
|
}
|
|
@@ -563,19 +525,19 @@ class TrackPlayerCore: NSObject {
|
|
|
563
525
|
}
|
|
564
526
|
|
|
565
527
|
private func setupCurrentItemObservers(item: AVPlayerItem) {
|
|
566
|
-
|
|
528
|
+
NitroPlayerLogger.log("TrackPlayerCore", "📱 Setting up item observers")
|
|
567
529
|
|
|
568
530
|
// Observe status - recreate boundaries when ready and update now playing info
|
|
569
531
|
let statusObserver = item.observe(\.status, options: [.new]) { [weak self] item, _ in
|
|
570
532
|
if item.status == .readyToPlay {
|
|
571
|
-
|
|
533
|
+
NitroPlayerLogger.log("TrackPlayerCore", "✅ Item ready, setting up boundaries")
|
|
572
534
|
self?.setupBoundaryTimeObserver()
|
|
573
535
|
// First item is buffered and ready — disable stall waiting for gapless inter-track transitions
|
|
574
536
|
self?.player?.automaticallyWaitsToMinimizeStalling = false
|
|
575
537
|
// Update now playing info now that duration is available
|
|
576
538
|
self?.mediaSessionManager?.updateNowPlayingInfo()
|
|
577
539
|
} else if item.status == .failed {
|
|
578
|
-
|
|
540
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Item failed")
|
|
579
541
|
self?.notifyPlaybackStateChange(.stopped, .error)
|
|
580
542
|
}
|
|
581
543
|
}
|
|
@@ -584,7 +546,7 @@ class TrackPlayerCore: NSObject {
|
|
|
584
546
|
// Observe playback buffer
|
|
585
547
|
let bufferEmptyObserver = item.observe(\.isPlaybackBufferEmpty, options: [.new]) { item, _ in
|
|
586
548
|
if item.isPlaybackBufferEmpty {
|
|
587
|
-
|
|
549
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⏸️ Buffer empty (buffering)")
|
|
588
550
|
}
|
|
589
551
|
}
|
|
590
552
|
currentItemObservers.append(bufferEmptyObserver)
|
|
@@ -592,7 +554,7 @@ class TrackPlayerCore: NSObject {
|
|
|
592
554
|
let bufferKeepUpObserver = item.observe(\.isPlaybackLikelyToKeepUp, options: [.new]) {
|
|
593
555
|
item, _ in
|
|
594
556
|
if item.isPlaybackLikelyToKeepUp {
|
|
595
|
-
|
|
557
|
+
NitroPlayerLogger.log("TrackPlayerCore", "▶️ Buffer likely to keep up")
|
|
596
558
|
}
|
|
597
559
|
}
|
|
598
560
|
currentItemObservers.append(bufferKeepUpObserver)
|
|
@@ -611,32 +573,32 @@ class TrackPlayerCore: NSObject {
|
|
|
611
573
|
}
|
|
612
574
|
|
|
613
575
|
private func loadPlaylistInternal(playlistId: String) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
576
|
+
NitroPlayerLogger.log("TrackPlayerCore", "\n" + String(repeating: "🎼", count: Constants.playlistSeparatorLength))
|
|
577
|
+
NitroPlayerLogger.log("TrackPlayerCore", "📂 LOAD PLAYLIST REQUEST")
|
|
578
|
+
NitroPlayerLogger.log("TrackPlayerCore", " Playlist ID: \(playlistId)")
|
|
617
579
|
|
|
618
580
|
// Clear temporary tracks when loading new playlist
|
|
619
581
|
self.playNextStack.removeAll()
|
|
620
582
|
self.upNextQueue.removeAll()
|
|
621
583
|
self.currentTemporaryType = .none
|
|
622
|
-
|
|
584
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 🧹 Cleared temporary tracks")
|
|
623
585
|
|
|
624
586
|
let playlist = self.playlistManager.getPlaylist(playlistId: playlistId)
|
|
625
587
|
if let playlist = playlist {
|
|
626
|
-
|
|
627
|
-
|
|
588
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ✅ Found playlist: \(playlist.name)")
|
|
589
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 📋 Contains \(playlist.tracks.count) tracks:")
|
|
628
590
|
for (index, track) in playlist.tracks.enumerated() {
|
|
629
|
-
|
|
591
|
+
NitroPlayerLogger.log("TrackPlayerCore", " [\(index + 1)] \(track.title) - \(track.artist)")
|
|
630
592
|
}
|
|
631
|
-
|
|
593
|
+
NitroPlayerLogger.log("TrackPlayerCore", String(repeating: "🎼", count: Constants.playlistSeparatorLength) + "\n")
|
|
632
594
|
|
|
633
595
|
self.currentPlaylistId = playlistId
|
|
634
596
|
self.updatePlayerQueue(tracks: playlist.tracks)
|
|
635
597
|
// Emit initial state (paused/stopped before play)
|
|
636
598
|
self.emitStateChange()
|
|
637
599
|
} else {
|
|
638
|
-
|
|
639
|
-
|
|
600
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ❌ Playlist NOT FOUND")
|
|
601
|
+
NitroPlayerLogger.log("TrackPlayerCore", String(repeating: "🎼", count: Constants.playlistSeparatorLength) + "\n")
|
|
640
602
|
}
|
|
641
603
|
}
|
|
642
604
|
|
|
@@ -685,8 +647,8 @@ class TrackPlayerCore: NSObject {
|
|
|
685
647
|
state = .stopped
|
|
686
648
|
}
|
|
687
649
|
|
|
688
|
-
|
|
689
|
-
|
|
650
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔔 Emitting state change: \(state)")
|
|
651
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔔 Callback exists: \(!onPlaybackStateChangeListeners.isEmpty)")
|
|
690
652
|
notifyPlaybackStateChange(state, reason)
|
|
691
653
|
mediaSessionManager?.onPlaybackStateChanged()
|
|
692
654
|
}
|
|
@@ -706,19 +668,19 @@ class TrackPlayerCore: NSObject {
|
|
|
706
668
|
|
|
707
669
|
if isLocal {
|
|
708
670
|
// Local file - use fileURLWithPath
|
|
709
|
-
|
|
710
|
-
|
|
671
|
+
NitroPlayerLogger.log("TrackPlayerCore", "📥 Using DOWNLOADED version for \(track.title)")
|
|
672
|
+
NitroPlayerLogger.log("TrackPlayerCore", " Local path: \(effectiveUrlString)")
|
|
711
673
|
|
|
712
674
|
// Verify file exists
|
|
713
675
|
if FileManager.default.fileExists(atPath: effectiveUrlString) {
|
|
714
676
|
url = URL(fileURLWithPath: effectiveUrlString)
|
|
715
|
-
|
|
716
|
-
|
|
677
|
+
NitroPlayerLogger.log("TrackPlayerCore", " File URL: \(url.absoluteString)")
|
|
678
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ✅ File verified to exist")
|
|
717
679
|
} else {
|
|
718
|
-
|
|
719
|
-
|
|
680
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ❌ Downloaded file does NOT exist at path!")
|
|
681
|
+
NitroPlayerLogger.log("TrackPlayerCore", " Falling back to remote URL: \(track.url)")
|
|
720
682
|
guard let remoteUrl = URL(string: track.url) else {
|
|
721
|
-
|
|
683
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Invalid remote URL: \(track.url)")
|
|
722
684
|
return nil
|
|
723
685
|
}
|
|
724
686
|
url = remoteUrl
|
|
@@ -726,18 +688,18 @@ class TrackPlayerCore: NSObject {
|
|
|
726
688
|
} else {
|
|
727
689
|
// Remote URL
|
|
728
690
|
guard let remoteUrl = URL(string: effectiveUrlString) else {
|
|
729
|
-
|
|
691
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Invalid URL for track: \(track.title) - \(effectiveUrlString)")
|
|
730
692
|
return nil
|
|
731
693
|
}
|
|
732
694
|
url = remoteUrl
|
|
733
|
-
|
|
695
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🌐 Using REMOTE version for \(track.title)")
|
|
734
696
|
}
|
|
735
697
|
|
|
736
698
|
// Check if we have a preloaded asset for this track
|
|
737
699
|
let asset: AVURLAsset
|
|
738
700
|
if let preloadedAsset = preloadedAssets[track.id] {
|
|
739
701
|
asset = preloadedAsset
|
|
740
|
-
|
|
702
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🚀 Using preloaded asset for \(track.title)")
|
|
741
703
|
} else {
|
|
742
704
|
// No AVURLAssetPreferPreciseDurationAndTimingKey — gapless playback is achieved via
|
|
743
705
|
// AVQueuePlayer's internal audio buffer pre-roll, not timing metadata.
|
|
@@ -763,7 +725,7 @@ class TrackPlayerCore: NSObject {
|
|
|
763
725
|
// Apply equalizer audio mix to the player item
|
|
764
726
|
// This enables real-time EQ processing via MTAudioProcessingTap
|
|
765
727
|
EqualizerCore.shared.applyAudioMix(to: item)
|
|
766
|
-
|
|
728
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎛️ Requesting EQ audio mix application for \(track.title)")
|
|
767
729
|
|
|
768
730
|
// If this is a preload request, start loading asset keys asynchronously
|
|
769
731
|
if isPreload {
|
|
@@ -774,14 +736,12 @@ class TrackPlayerCore: NSObject {
|
|
|
774
736
|
var error: NSError?
|
|
775
737
|
let status = asset.statusOfValue(forKey: key, error: &error)
|
|
776
738
|
if status == .failed {
|
|
777
|
-
|
|
778
|
-
"⚠️ TrackPlayerCore: Failed to load key '\(key)' for \(track.title): \(error?.localizedDescription ?? "unknown")"
|
|
779
|
-
)
|
|
739
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Failed to load key '\(key)' for \(track.title): \(error?.localizedDescription ?? "unknown")")
|
|
780
740
|
allKeysLoaded = false
|
|
781
741
|
}
|
|
782
742
|
}
|
|
783
743
|
if allKeysLoaded {
|
|
784
|
-
|
|
744
|
+
NitroPlayerLogger.log("TrackPlayerCore", "✅ All asset keys preloaded for \(track.title)")
|
|
785
745
|
}
|
|
786
746
|
}
|
|
787
747
|
}
|
|
@@ -826,7 +786,7 @@ class TrackPlayerCore: NSObject {
|
|
|
826
786
|
if allKeysLoaded {
|
|
827
787
|
DispatchQueue.main.async {
|
|
828
788
|
self?.preloadedAssets[track.id] = asset
|
|
829
|
-
|
|
789
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎯 Preloaded asset for upcoming track: \(track.title)")
|
|
830
790
|
}
|
|
831
791
|
}
|
|
832
792
|
}
|
|
@@ -851,7 +811,7 @@ class TrackPlayerCore: NSObject {
|
|
|
851
811
|
}
|
|
852
812
|
|
|
853
813
|
if !assetsToRemove.isEmpty {
|
|
854
|
-
|
|
814
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🧹 Cleaned up \(assetsToRemove.count) preloaded assets")
|
|
855
815
|
}
|
|
856
816
|
}
|
|
857
817
|
}
|
|
@@ -864,9 +824,7 @@ class TrackPlayerCore: NSObject {
|
|
|
864
824
|
let box = WeakCallbackBox(owner: owner, callback: listener)
|
|
865
825
|
listenersQueue.async(flags: .barrier) { [weak self] in
|
|
866
826
|
self?.onChangeTrackListeners.append(box)
|
|
867
|
-
|
|
868
|
-
"🎯 TrackPlayerCore: Added onChangeTrack listener (total: \(self?.onChangeTrackListeners.count ?? 0))"
|
|
869
|
-
)
|
|
827
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎯 Added onChangeTrack listener (total: \(self?.onChangeTrackListeners.count ?? 0))")
|
|
870
828
|
}
|
|
871
829
|
}
|
|
872
830
|
|
|
@@ -877,9 +835,7 @@ class TrackPlayerCore: NSObject {
|
|
|
877
835
|
let box = WeakCallbackBox(owner: owner, callback: listener)
|
|
878
836
|
listenersQueue.async(flags: .barrier) { [weak self] in
|
|
879
837
|
self?.onPlaybackStateChangeListeners.append(box)
|
|
880
|
-
|
|
881
|
-
"🎯 TrackPlayerCore: Added onPlaybackStateChange listener (total: \(self?.onPlaybackStateChangeListeners.count ?? 0))"
|
|
882
|
-
)
|
|
838
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎯 Added onPlaybackStateChange listener (total: \(self?.onPlaybackStateChangeListeners.count ?? 0))")
|
|
883
839
|
}
|
|
884
840
|
}
|
|
885
841
|
|
|
@@ -887,7 +843,7 @@ class TrackPlayerCore: NSObject {
|
|
|
887
843
|
let box = WeakCallbackBox(owner: owner, callback: listener)
|
|
888
844
|
listenersQueue.async(flags: .barrier) { [weak self] in
|
|
889
845
|
self?.onSeekListeners.append(box)
|
|
890
|
-
|
|
846
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎯 Added onSeek listener (total: \(self?.onSeekListeners.count ?? 0))")
|
|
891
847
|
}
|
|
892
848
|
}
|
|
893
849
|
|
|
@@ -898,9 +854,7 @@ class TrackPlayerCore: NSObject {
|
|
|
898
854
|
let box = WeakCallbackBox(owner: owner, callback: listener)
|
|
899
855
|
listenersQueue.async(flags: .barrier) { [weak self] in
|
|
900
856
|
self?.onPlaybackProgressChangeListeners.append(box)
|
|
901
|
-
|
|
902
|
-
"🎯 TrackPlayerCore: Added onPlaybackProgressChange listener (total: \(self?.onPlaybackProgressChangeListeners.count ?? 0))"
|
|
903
|
-
)
|
|
857
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎯 Added onPlaybackProgressChange listener (total: \(self?.onPlaybackProgressChangeListeners.count ?? 0))")
|
|
904
858
|
}
|
|
905
859
|
}
|
|
906
860
|
|
|
@@ -913,10 +867,8 @@ class TrackPlayerCore: NSObject {
|
|
|
913
867
|
// Remove dead listeners
|
|
914
868
|
self.onChangeTrackListeners.removeAll { !$0.isAlive }
|
|
915
869
|
|
|
916
|
-
// Get live callbacks
|
|
917
|
-
let liveCallbacks = self.onChangeTrackListeners.
|
|
918
|
-
$0.isAlive ? $0.callback : nil
|
|
919
|
-
}
|
|
870
|
+
// Get live callbacks (all remaining are alive after removeAll)
|
|
871
|
+
let liveCallbacks = self.onChangeTrackListeners.map { $0.callback }
|
|
920
872
|
|
|
921
873
|
// Call on main thread
|
|
922
874
|
if !liveCallbacks.isEmpty {
|
|
@@ -935,9 +887,7 @@ class TrackPlayerCore: NSObject {
|
|
|
935
887
|
|
|
936
888
|
self.onPlaybackStateChangeListeners.removeAll { !$0.isAlive }
|
|
937
889
|
|
|
938
|
-
let liveCallbacks = self.onPlaybackStateChangeListeners.
|
|
939
|
-
$0.isAlive ? $0.callback : nil
|
|
940
|
-
}
|
|
890
|
+
let liveCallbacks = self.onPlaybackStateChangeListeners.map { $0.callback }
|
|
941
891
|
|
|
942
892
|
if !liveCallbacks.isEmpty {
|
|
943
893
|
DispatchQueue.main.async {
|
|
@@ -955,9 +905,7 @@ class TrackPlayerCore: NSObject {
|
|
|
955
905
|
|
|
956
906
|
self.onSeekListeners.removeAll { !$0.isAlive }
|
|
957
907
|
|
|
958
|
-
let liveCallbacks = self.onSeekListeners.
|
|
959
|
-
$0.isAlive ? $0.callback : nil
|
|
960
|
-
}
|
|
908
|
+
let liveCallbacks = self.onSeekListeners.map { $0.callback }
|
|
961
909
|
|
|
962
910
|
if !liveCallbacks.isEmpty {
|
|
963
911
|
DispatchQueue.main.async {
|
|
@@ -975,9 +923,7 @@ class TrackPlayerCore: NSObject {
|
|
|
975
923
|
|
|
976
924
|
self.onPlaybackProgressChangeListeners.removeAll { !$0.isAlive }
|
|
977
925
|
|
|
978
|
-
let liveCallbacks = self.onPlaybackProgressChangeListeners.
|
|
979
|
-
$0.isAlive ? $0.callback : nil
|
|
980
|
-
}
|
|
926
|
+
let liveCallbacks = self.onPlaybackProgressChangeListeners.map { $0.callback }
|
|
981
927
|
|
|
982
928
|
if !liveCallbacks.isEmpty {
|
|
983
929
|
DispatchQueue.main.async {
|
|
@@ -994,29 +940,28 @@ class TrackPlayerCore: NSObject {
|
|
|
994
940
|
// MARK: - Queue Management
|
|
995
941
|
|
|
996
942
|
private func updatePlayerQueue(tracks: [TrackItem]) {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
943
|
+
NitroPlayerLogger.log("TrackPlayerCore", "\n" + String(repeating: "=", count: Constants.separatorLineLength))
|
|
944
|
+
NitroPlayerLogger.log("TrackPlayerCore", "📋 UPDATE PLAYER QUEUE - Received \(tracks.count) tracks")
|
|
945
|
+
NitroPlayerLogger.log("TrackPlayerCore", String(repeating: "=", count: Constants.separatorLineLength))
|
|
1000
946
|
|
|
1001
947
|
#if DEBUG
|
|
1002
948
|
for (index, track) in tracks.enumerated() {
|
|
1003
949
|
let isDownloaded = DownloadManagerCore.shared.isTrackDownloaded(trackId: track.id)
|
|
1004
950
|
let downloadStatus = isDownloaded ? "📥 DOWNLOADED" : "🌐 REMOTE"
|
|
1005
|
-
|
|
1006
|
-
" [\(index + 1)] 🎵 \(track.title) - \(track.artist) (ID: \(track.id)) - \(downloadStatus)")
|
|
951
|
+
NitroPlayerLogger.log("TrackPlayerCore", " [\(index + 1)] 🎵 \(track.title) - \(track.artist) (ID: \(track.id)) - \(downloadStatus)")
|
|
1007
952
|
if isDownloaded {
|
|
1008
953
|
if let localPath = DownloadManagerCore.shared.getLocalPath(trackId: track.id) {
|
|
1009
|
-
|
|
954
|
+
NitroPlayerLogger.log("TrackPlayerCore", " Local path: \(localPath)")
|
|
1010
955
|
}
|
|
1011
956
|
}
|
|
1012
957
|
}
|
|
1013
|
-
|
|
958
|
+
NitroPlayerLogger.log("TrackPlayerCore", String(repeating: "=", count: Constants.separatorLineLength) + "\n")
|
|
1014
959
|
#endif
|
|
1015
960
|
|
|
1016
961
|
// Store tracks for index tracking
|
|
1017
962
|
currentTracks = tracks
|
|
1018
963
|
currentTrackIndex = 0
|
|
1019
|
-
|
|
964
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔢 Reset currentTrackIndex to 0 (will be updated by KVO observer)")
|
|
1020
965
|
|
|
1021
966
|
// Remove old boundary observer if exists (this is safe)
|
|
1022
967
|
if let boundaryObserver = boundaryTimeObserver, let currentPlayer = player {
|
|
@@ -1037,22 +982,20 @@ class TrackPlayerCore: NSObject {
|
|
|
1037
982
|
return createGaplessPlayerItem(for: track, isPreload: isPreload)
|
|
1038
983
|
}
|
|
1039
984
|
|
|
1040
|
-
|
|
985
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎵 Created \(items.count) gapless-optimized player items")
|
|
1041
986
|
|
|
1042
987
|
guard !items.isEmpty else {
|
|
1043
|
-
|
|
988
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ No valid items to play")
|
|
1044
989
|
return
|
|
1045
990
|
}
|
|
1046
991
|
|
|
1047
992
|
// Replace current queue (player should always exist after setupPlayer)
|
|
1048
993
|
guard let existingPlayer = self.player else {
|
|
1049
|
-
|
|
994
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ No player available - this should never happen!")
|
|
1050
995
|
return
|
|
1051
996
|
}
|
|
1052
997
|
|
|
1053
|
-
|
|
1054
|
-
"🔄 TrackPlayerCore: Updating queue - removing \(existingPlayer.items().count) items, adding \(items.count) new items"
|
|
1055
|
-
)
|
|
998
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔄 Updating queue - removing \(existingPlayer.items().count) items, adding \(items.count) new items")
|
|
1056
999
|
|
|
1057
1000
|
// Remove all existing items
|
|
1058
1001
|
existingPlayer.removeAllItems()
|
|
@@ -1066,28 +1009,27 @@ class TrackPlayerCore: NSObject {
|
|
|
1066
1009
|
lastItem = item
|
|
1067
1010
|
|
|
1068
1011
|
if let trackId = item.trackId, let track = tracks.first(where: { $0.id == trackId }) {
|
|
1069
|
-
|
|
1012
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ➕ Added to player queue [\(index + 1)]: \(track.title)")
|
|
1070
1013
|
}
|
|
1071
1014
|
}
|
|
1072
1015
|
|
|
1073
1016
|
#if DEBUG
|
|
1074
1017
|
let trackById = Dictionary(uniqueKeysWithValues: tracks.map { ($0.id, $0) })
|
|
1075
|
-
|
|
1076
|
-
"\n🔍 TrackPlayerCore: VERIFICATION - Player now has \(existingPlayer.items().count) items:")
|
|
1018
|
+
NitroPlayerLogger.log("TrackPlayerCore", "\n🔍 VERIFICATION - Player now has \(existingPlayer.items().count) items:")
|
|
1077
1019
|
for (index, item) in existingPlayer.items().enumerated() {
|
|
1078
1020
|
if let trackId = item.trackId, let track = trackById[trackId] {
|
|
1079
|
-
|
|
1021
|
+
NitroPlayerLogger.log("TrackPlayerCore", " [\(index + 1)] ✓ \(track.title) - \(track.artist) (ID: \(track.id))")
|
|
1080
1022
|
} else {
|
|
1081
|
-
|
|
1023
|
+
NitroPlayerLogger.log("TrackPlayerCore", " [\(index + 1)] ⚠️ Unknown item (no trackId)")
|
|
1082
1024
|
}
|
|
1083
1025
|
}
|
|
1084
1026
|
if let currentItem = existingPlayer.currentItem,
|
|
1085
1027
|
let trackId = currentItem.trackId,
|
|
1086
1028
|
let track = trackById[trackId]
|
|
1087
1029
|
{
|
|
1088
|
-
|
|
1030
|
+
NitroPlayerLogger.log("TrackPlayerCore", "▶️ Current item: \(track.title)")
|
|
1089
1031
|
}
|
|
1090
|
-
|
|
1032
|
+
NitroPlayerLogger.log("TrackPlayerCore", String(repeating: "=", count: Constants.separatorLineLength) + "\n")
|
|
1091
1033
|
#endif
|
|
1092
1034
|
|
|
1093
1035
|
// Note: Boundary time observers will be set up automatically when item becomes ready
|
|
@@ -1095,8 +1037,8 @@ class TrackPlayerCore: NSObject {
|
|
|
1095
1037
|
|
|
1096
1038
|
// Notify track change
|
|
1097
1039
|
if let firstTrack = tracks.first {
|
|
1098
|
-
|
|
1099
|
-
|
|
1040
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎵 Emitting track change: \(firstTrack.title)")
|
|
1041
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎵 onChangeTrack callbacks count: \(onChangeTrackListeners.count)")
|
|
1100
1042
|
notifyTrackChange(firstTrack, nil)
|
|
1101
1043
|
mediaSessionManager?.onTrackChanged()
|
|
1102
1044
|
}
|
|
@@ -1104,7 +1046,7 @@ class TrackPlayerCore: NSObject {
|
|
|
1104
1046
|
// Start preloading upcoming tracks for gapless playback
|
|
1105
1047
|
preloadUpcomingTracks(from: 1)
|
|
1106
1048
|
|
|
1107
|
-
|
|
1049
|
+
NitroPlayerLogger.log("TrackPlayerCore", "✅ Queue updated with \(items.count) gapless-optimized tracks")
|
|
1108
1050
|
}
|
|
1109
1051
|
|
|
1110
1052
|
func getCurrentTrack() -> TrackItem? {
|
|
@@ -1143,6 +1085,7 @@ class TrackPlayerCore: NSObject {
|
|
|
1143
1085
|
|
|
1144
1086
|
private func getActualQueueInternal() -> [TrackItem] {
|
|
1145
1087
|
var queue: [TrackItem] = []
|
|
1088
|
+
queue.reserveCapacity(currentTracks.count + playNextStack.count + upNextQueue.count)
|
|
1146
1089
|
|
|
1147
1090
|
// Add tracks before current (original playlist)
|
|
1148
1091
|
// When a temp track is playing, include the original track at currentTrackIndex
|
|
@@ -1150,7 +1093,7 @@ class TrackPlayerCore: NSObject {
|
|
|
1150
1093
|
let beforeEnd = currentTemporaryType != .none
|
|
1151
1094
|
? min(currentTrackIndex + 1, currentTracks.count) : currentTrackIndex
|
|
1152
1095
|
if beforeEnd > 0 {
|
|
1153
|
-
queue.append(contentsOf:
|
|
1096
|
+
queue.append(contentsOf: currentTracks[0..<beforeEnd])
|
|
1154
1097
|
}
|
|
1155
1098
|
|
|
1156
1099
|
// Add current track (temp or original)
|
|
@@ -1161,7 +1104,7 @@ class TrackPlayerCore: NSObject {
|
|
|
1161
1104
|
// Add playNext stack (LIFO - most recently added plays first)
|
|
1162
1105
|
// Skip index 0 if current track is from playNext (it's already added as current)
|
|
1163
1106
|
if currentTemporaryType == .playNext && playNextStack.count > 1 {
|
|
1164
|
-
queue.append(contentsOf:
|
|
1107
|
+
queue.append(contentsOf: playNextStack.dropFirst())
|
|
1165
1108
|
} else if currentTemporaryType != .playNext {
|
|
1166
1109
|
queue.append(contentsOf: playNextStack)
|
|
1167
1110
|
}
|
|
@@ -1169,21 +1112,21 @@ class TrackPlayerCore: NSObject {
|
|
|
1169
1112
|
// Add upNext queue (in order, FIFO)
|
|
1170
1113
|
// Skip index 0 if current track is from upNext (it's already added as current)
|
|
1171
1114
|
if currentTemporaryType == .upNext && upNextQueue.count > 1 {
|
|
1172
|
-
queue.append(contentsOf:
|
|
1115
|
+
queue.append(contentsOf: upNextQueue.dropFirst())
|
|
1173
1116
|
} else if currentTemporaryType != .upNext {
|
|
1174
1117
|
queue.append(contentsOf: upNextQueue)
|
|
1175
1118
|
}
|
|
1176
1119
|
|
|
1177
1120
|
// Add remaining original tracks
|
|
1178
1121
|
if currentTrackIndex + 1 < currentTracks.count {
|
|
1179
|
-
queue.append(contentsOf:
|
|
1122
|
+
queue.append(contentsOf: currentTracks[(currentTrackIndex + 1)...])
|
|
1180
1123
|
}
|
|
1181
1124
|
|
|
1182
1125
|
return queue
|
|
1183
1126
|
}
|
|
1184
1127
|
|
|
1185
1128
|
func play() {
|
|
1186
|
-
|
|
1129
|
+
NitroPlayerLogger.log("TrackPlayerCore", "▶️ play() called")
|
|
1187
1130
|
if Thread.isMainThread {
|
|
1188
1131
|
playInternal()
|
|
1189
1132
|
} else {
|
|
@@ -1194,13 +1137,13 @@ class TrackPlayerCore: NSObject {
|
|
|
1194
1137
|
}
|
|
1195
1138
|
|
|
1196
1139
|
private func playInternal() {
|
|
1197
|
-
|
|
1140
|
+
NitroPlayerLogger.log("TrackPlayerCore", "▶️ Calling player.play()")
|
|
1198
1141
|
if let player = self.player {
|
|
1199
|
-
|
|
1142
|
+
NitroPlayerLogger.log("TrackPlayerCore", "▶️ Player status: \(player.status.rawValue)")
|
|
1200
1143
|
if let currentItem = player.currentItem {
|
|
1201
|
-
|
|
1144
|
+
NitroPlayerLogger.log("TrackPlayerCore", "▶️ Current item status: \(currentItem.status.rawValue)")
|
|
1202
1145
|
if let error = currentItem.error {
|
|
1203
|
-
|
|
1146
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Current item error: \(error.localizedDescription)")
|
|
1204
1147
|
}
|
|
1205
1148
|
}
|
|
1206
1149
|
player.play()
|
|
@@ -1211,12 +1154,12 @@ class TrackPlayerCore: NSObject {
|
|
|
1211
1154
|
self?.emitStateChange()
|
|
1212
1155
|
}
|
|
1213
1156
|
} else {
|
|
1214
|
-
|
|
1157
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ No player available")
|
|
1215
1158
|
}
|
|
1216
1159
|
}
|
|
1217
1160
|
|
|
1218
1161
|
func pause() {
|
|
1219
|
-
|
|
1162
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⏸️ pause() called")
|
|
1220
1163
|
if Thread.isMainThread {
|
|
1221
1164
|
pauseInternal()
|
|
1222
1165
|
} else {
|
|
@@ -1245,31 +1188,31 @@ class TrackPlayerCore: NSObject {
|
|
|
1245
1188
|
self.playNextStack.removeAll()
|
|
1246
1189
|
self.upNextQueue.removeAll()
|
|
1247
1190
|
self.currentTemporaryType = .none
|
|
1248
|
-
|
|
1191
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 🧹 Cleared temporary tracks")
|
|
1249
1192
|
|
|
1250
1193
|
var targetPlaylistId: String?
|
|
1251
1194
|
var songIndex: Int = -1
|
|
1252
1195
|
|
|
1253
1196
|
// Case 1: If fromPlaylist is provided, use that playlist
|
|
1254
1197
|
if let playlistId = fromPlaylist {
|
|
1255
|
-
|
|
1198
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎵 Looking for song in specified playlist: \(playlistId)")
|
|
1256
1199
|
if let playlist = self.playlistManager.getPlaylist(playlistId: playlistId) {
|
|
1257
1200
|
if let index = playlist.tracks.firstIndex(where: { $0.id == songId }) {
|
|
1258
1201
|
targetPlaylistId = playlistId
|
|
1259
1202
|
songIndex = index
|
|
1260
|
-
|
|
1203
|
+
NitroPlayerLogger.log("TrackPlayerCore", "✅ Found song at index \(index) in playlist \(playlistId)")
|
|
1261
1204
|
} else {
|
|
1262
|
-
|
|
1205
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Song \(songId) not found in specified playlist \(playlistId)")
|
|
1263
1206
|
return
|
|
1264
1207
|
}
|
|
1265
1208
|
} else {
|
|
1266
|
-
|
|
1209
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Playlist \(playlistId) not found")
|
|
1267
1210
|
return
|
|
1268
1211
|
}
|
|
1269
1212
|
}
|
|
1270
1213
|
// Case 2: If fromPlaylist is not provided, search in current/loaded playlist first
|
|
1271
1214
|
else {
|
|
1272
|
-
|
|
1215
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🎵 No playlist specified, checking current playlist")
|
|
1273
1216
|
|
|
1274
1217
|
// Check if song exists in currently loaded playlist
|
|
1275
1218
|
if let currentId = self.currentPlaylistId,
|
|
@@ -1278,20 +1221,20 @@ class TrackPlayerCore: NSObject {
|
|
|
1278
1221
|
if let index = currentPlaylist.tracks.firstIndex(where: { $0.id == songId }) {
|
|
1279
1222
|
targetPlaylistId = currentId
|
|
1280
1223
|
songIndex = index
|
|
1281
|
-
|
|
1224
|
+
NitroPlayerLogger.log("TrackPlayerCore", "✅ Found song at index \(index) in current playlist \(currentId)")
|
|
1282
1225
|
}
|
|
1283
1226
|
}
|
|
1284
1227
|
|
|
1285
1228
|
// If not found in current playlist, search in all playlists
|
|
1286
1229
|
if songIndex == -1 {
|
|
1287
|
-
|
|
1230
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔍 Song not found in current playlist, searching all playlists...")
|
|
1288
1231
|
let allPlaylists = self.playlistManager.getAllPlaylists()
|
|
1289
1232
|
|
|
1290
1233
|
for playlist in allPlaylists {
|
|
1291
1234
|
if let index = playlist.tracks.firstIndex(where: { $0.id == songId }) {
|
|
1292
1235
|
targetPlaylistId = playlist.id
|
|
1293
1236
|
songIndex = index
|
|
1294
|
-
|
|
1237
|
+
NitroPlayerLogger.log("TrackPlayerCore", "✅ Found song at index \(index) in playlist \(playlist.id)")
|
|
1295
1238
|
break
|
|
1296
1239
|
}
|
|
1297
1240
|
}
|
|
@@ -1300,20 +1243,20 @@ class TrackPlayerCore: NSObject {
|
|
|
1300
1243
|
if songIndex == -1 && !allPlaylists.isEmpty {
|
|
1301
1244
|
targetPlaylistId = allPlaylists[0].id
|
|
1302
1245
|
songIndex = 0
|
|
1303
|
-
|
|
1246
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Song not found in any playlist, using first playlist and starting at index 0")
|
|
1304
1247
|
}
|
|
1305
1248
|
}
|
|
1306
1249
|
}
|
|
1307
1250
|
|
|
1308
1251
|
// Now play the song
|
|
1309
1252
|
guard let playlistId = targetPlaylistId, songIndex >= 0 else {
|
|
1310
|
-
|
|
1253
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Could not determine playlist or song index")
|
|
1311
1254
|
return
|
|
1312
1255
|
}
|
|
1313
1256
|
|
|
1314
1257
|
// Load playlist if it's different from current
|
|
1315
1258
|
if self.currentPlaylistId != playlistId {
|
|
1316
|
-
|
|
1259
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔄 Loading new playlist: \(playlistId)")
|
|
1317
1260
|
if let playlist = self.playlistManager.getPlaylist(playlistId: playlistId) {
|
|
1318
1261
|
self.currentPlaylistId = playlistId
|
|
1319
1262
|
self.updatePlayerQueue(tracks: playlist.tracks)
|
|
@@ -1321,7 +1264,7 @@ class TrackPlayerCore: NSObject {
|
|
|
1321
1264
|
}
|
|
1322
1265
|
|
|
1323
1266
|
// Play from the found index
|
|
1324
|
-
|
|
1267
|
+
NitroPlayerLogger.log("TrackPlayerCore", "▶️ Playing from index: \(songIndex)")
|
|
1325
1268
|
self.playFromIndex(index: songIndex)
|
|
1326
1269
|
}
|
|
1327
1270
|
|
|
@@ -1432,17 +1375,18 @@ class TrackPlayerCore: NSObject {
|
|
|
1432
1375
|
// MARK: - Repeat Mode
|
|
1433
1376
|
|
|
1434
1377
|
func setRepeatMode(mode: RepeatMode) -> Bool {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
self
|
|
1438
|
-
} else {
|
|
1439
|
-
DispatchQueue.main.sync { [weak self] in
|
|
1440
|
-
self?.repeatMode = mode
|
|
1441
|
-
}
|
|
1378
|
+
currentRepeatMode = mode
|
|
1379
|
+
DispatchQueue.main.async { [weak self] in
|
|
1380
|
+
self?.player?.actionAtItemEnd = (mode == .track) ? .none : .advance
|
|
1442
1381
|
}
|
|
1382
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔁 setRepeatMode: \(mode)")
|
|
1443
1383
|
return true
|
|
1444
1384
|
}
|
|
1445
1385
|
|
|
1386
|
+
func getRepeatMode() -> RepeatMode {
|
|
1387
|
+
return currentRepeatMode
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1446
1390
|
func getState() -> PlayerState {
|
|
1447
1391
|
// Called from Promise.async background thread
|
|
1448
1392
|
// Schedule on main thread and wait for result
|
|
@@ -1544,7 +1488,7 @@ class TrackPlayerCore: NSObject {
|
|
|
1544
1488
|
|
|
1545
1489
|
func setVolume(volume: Double) -> Bool {
|
|
1546
1490
|
guard let player = player else {
|
|
1547
|
-
|
|
1491
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Cannot set volume - no player available")
|
|
1548
1492
|
return false
|
|
1549
1493
|
}
|
|
1550
1494
|
DispatchQueue.main.async { [weak self] in
|
|
@@ -1556,8 +1500,7 @@ class TrackPlayerCore: NSObject {
|
|
|
1556
1500
|
// Convert to 0.0-1.0 range for AVQueuePlayer
|
|
1557
1501
|
let normalizedVolume = Float(clampedVolume / 100.0)
|
|
1558
1502
|
currentPlayer.volume = normalizedVolume
|
|
1559
|
-
|
|
1560
|
-
"🔊 TrackPlayerCore: Volume set to \(Int(clampedVolume))% (normalized: \(normalizedVolume))")
|
|
1503
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🔊 Volume set to \(Int(clampedVolume))% (normalized: \(normalizedVolume))")
|
|
1561
1504
|
}
|
|
1562
1505
|
return true
|
|
1563
1506
|
}
|
|
@@ -1688,21 +1631,19 @@ class TrackPlayerCore: NSObject {
|
|
|
1688
1631
|
|
|
1689
1632
|
private func playFromIndexInternalWithResult(index: Int) -> Bool {
|
|
1690
1633
|
guard index >= 0 && index < self.currentTracks.count else {
|
|
1691
|
-
|
|
1692
|
-
"❌ TrackPlayerCore: playFromIndex - invalid index \(index), currentTracks.count = \(self.currentTracks.count)"
|
|
1693
|
-
)
|
|
1634
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ playFromIndex - invalid index \(index), currentTracks.count = \(self.currentTracks.count)")
|
|
1694
1635
|
return false
|
|
1695
1636
|
}
|
|
1696
1637
|
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1638
|
+
NitroPlayerLogger.log("TrackPlayerCore", "\n🎯 PLAY FROM INDEX \(index)")
|
|
1639
|
+
NitroPlayerLogger.log("TrackPlayerCore", " Total tracks in playlist: \(self.currentTracks.count)")
|
|
1640
|
+
NitroPlayerLogger.log("TrackPlayerCore", " Current index: \(self.currentTrackIndex), target index: \(index)")
|
|
1700
1641
|
|
|
1701
1642
|
// Clear temporary tracks when jumping to specific index
|
|
1702
1643
|
self.playNextStack.removeAll()
|
|
1703
1644
|
self.upNextQueue.removeAll()
|
|
1704
1645
|
self.currentTemporaryType = .none
|
|
1705
|
-
|
|
1646
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 🧹 Cleared temporary tracks")
|
|
1706
1647
|
|
|
1707
1648
|
// Store the full playlist
|
|
1708
1649
|
let fullPlaylist = self.currentTracks
|
|
@@ -1713,9 +1654,7 @@ class TrackPlayerCore: NSObject {
|
|
|
1713
1654
|
// Recreate the queue starting from the target index
|
|
1714
1655
|
// This ensures all remaining tracks are in the queue
|
|
1715
1656
|
let tracksToPlay = Array(fullPlaylist[index...])
|
|
1716
|
-
|
|
1717
|
-
" 🔄 Creating gapless queue with \(tracksToPlay.count) tracks starting from index \(index)"
|
|
1718
|
-
)
|
|
1657
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 🔄 Creating gapless queue with \(tracksToPlay.count) tracks starting from index \(index)")
|
|
1719
1658
|
|
|
1720
1659
|
// Create gapless-optimized player items
|
|
1721
1660
|
let items = tracksToPlay.enumerated().compactMap { (offset, track) -> AVPlayerItem? in
|
|
@@ -1724,7 +1663,7 @@ class TrackPlayerCore: NSObject {
|
|
|
1724
1663
|
}
|
|
1725
1664
|
|
|
1726
1665
|
guard let player = self.player, !items.isEmpty else {
|
|
1727
|
-
|
|
1666
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ No player or no items to play")
|
|
1728
1667
|
return false
|
|
1729
1668
|
}
|
|
1730
1669
|
|
|
@@ -1749,9 +1688,9 @@ class TrackPlayerCore: NSObject {
|
|
|
1749
1688
|
// Restore the full playlist reference (don't slice it!)
|
|
1750
1689
|
self.currentTracks = fullPlaylist
|
|
1751
1690
|
|
|
1752
|
-
|
|
1691
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ✅ Gapless queue recreated. Now at index: \(self.currentTrackIndex)")
|
|
1753
1692
|
if let track = self.getCurrentTrack() {
|
|
1754
|
-
|
|
1693
|
+
NitroPlayerLogger.log("TrackPlayerCore", " 🎵 Playing: \(track.title)")
|
|
1755
1694
|
notifyTrackChange(track, .skip)
|
|
1756
1695
|
self.mediaSessionManager?.onTrackChanged()
|
|
1757
1696
|
}
|
|
@@ -1776,17 +1715,17 @@ class TrackPlayerCore: NSObject {
|
|
|
1776
1715
|
}
|
|
1777
1716
|
|
|
1778
1717
|
private func addToUpNextInternal(trackId: String) {
|
|
1779
|
-
|
|
1718
|
+
NitroPlayerLogger.log("TrackPlayerCore", "📋 addToUpNext(\(trackId))")
|
|
1780
1719
|
|
|
1781
1720
|
// Find the track from current playlist or all playlists
|
|
1782
1721
|
guard let track = self.findTrackById(trackId) else {
|
|
1783
|
-
|
|
1722
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Track \(trackId) not found")
|
|
1784
1723
|
return
|
|
1785
1724
|
}
|
|
1786
1725
|
|
|
1787
1726
|
// Add to end of upNext queue (FIFO)
|
|
1788
1727
|
self.upNextQueue.append(track)
|
|
1789
|
-
|
|
1728
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ✅ Added '\(track.title)' to upNext queue (position: \(self.upNextQueue.count))")
|
|
1790
1729
|
|
|
1791
1730
|
// Rebuild the player queue if actively playing
|
|
1792
1731
|
if self.player?.currentItem != nil {
|
|
@@ -1806,17 +1745,17 @@ class TrackPlayerCore: NSObject {
|
|
|
1806
1745
|
}
|
|
1807
1746
|
|
|
1808
1747
|
private func playNextInternal(trackId: String) {
|
|
1809
|
-
|
|
1748
|
+
NitroPlayerLogger.log("TrackPlayerCore", "⏭️ playNext(\(trackId))")
|
|
1810
1749
|
|
|
1811
1750
|
// Find the track from current playlist or all playlists
|
|
1812
1751
|
guard let track = self.findTrackById(trackId) else {
|
|
1813
|
-
|
|
1752
|
+
NitroPlayerLogger.log("TrackPlayerCore", "❌ Track \(trackId) not found")
|
|
1814
1753
|
return
|
|
1815
1754
|
}
|
|
1816
1755
|
|
|
1817
1756
|
// Insert at beginning of playNext stack (LIFO)
|
|
1818
1757
|
self.playNextStack.insert(track, at: 0)
|
|
1819
|
-
|
|
1758
|
+
NitroPlayerLogger.log("TrackPlayerCore", " ✅ Added '\(track.title)' to playNext stack (position: 1)")
|
|
1820
1759
|
|
|
1821
1760
|
// Rebuild the player queue if actively playing
|
|
1822
1761
|
if self.player?.currentItem != nil {
|
|
@@ -1921,7 +1860,7 @@ class TrackPlayerCore: NSObject {
|
|
|
1921
1860
|
// MARK: - Cleanup
|
|
1922
1861
|
|
|
1923
1862
|
deinit {
|
|
1924
|
-
|
|
1863
|
+
NitroPlayerLogger.log("TrackPlayerCore", "🧹 Cleaning up...")
|
|
1925
1864
|
|
|
1926
1865
|
// Clear preloaded assets for gapless playback
|
|
1927
1866
|
preloadedAssets.removeAll()
|
|
@@ -1940,12 +1879,12 @@ class TrackPlayerCore: NSObject {
|
|
|
1940
1879
|
currentPlayer.removeObserver(self, forKeyPath: "rate")
|
|
1941
1880
|
currentPlayer.removeObserver(self, forKeyPath: "timeControlStatus")
|
|
1942
1881
|
currentPlayer.removeObserver(self, forKeyPath: "currentItem")
|
|
1943
|
-
|
|
1882
|
+
NitroPlayerLogger.log("TrackPlayerCore", "✅ Player observers removed")
|
|
1944
1883
|
}
|
|
1945
1884
|
|
|
1946
1885
|
// Remove all notification observers
|
|
1947
1886
|
NotificationCenter.default.removeObserver(self)
|
|
1948
|
-
|
|
1887
|
+
NitroPlayerLogger.log("TrackPlayerCore", "✅ Cleanup complete")
|
|
1949
1888
|
}
|
|
1950
1889
|
}
|
|
1951
1890
|
|