react-native-nitro-player 0.4.0 ā 0.5.0
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/core/TrackPlayerCore.kt +89 -115
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadFileManager.kt +10 -7
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadWorker.kt +30 -3
- package/ios/core/TrackPlayerCore.swift +101 -113
- package/ios/download/DownloadFileManager.swift +12 -4
- package/ios/download/DownloadManagerCore.swift +6 -1
- package/ios/media/MediaSessionManager.swift +181 -108
- package/lib/hooks/callbackManager.d.ts +18 -0
- package/lib/hooks/callbackManager.js +66 -0
- package/lib/hooks/useNowPlaying.js +30 -18
- package/lib/hooks/useOnPlaybackProgressChange.js +2 -2
- package/package.json +2 -2
- package/src/hooks/callbackManager.ts +87 -0
- package/src/hooks/useNowPlaying.ts +31 -19
- package/src/hooks/useOnPlaybackProgressChange.ts +2 -2
|
@@ -625,10 +625,32 @@ class TrackPlayerCore private constructor(
|
|
|
625
625
|
|
|
626
626
|
fun skipToPrevious() {
|
|
627
627
|
handler.post {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
628
|
+
val currentPosition = player.currentPosition // milliseconds
|
|
629
|
+
|
|
630
|
+
if (currentPosition > 2000) {
|
|
631
|
+
// More than 2 seconds in, restart current track
|
|
632
|
+
println("š TrackPlayerCore: Past threshold, restarting current track")
|
|
631
633
|
player.seekTo(0)
|
|
634
|
+
} else if (currentTemporaryType != TemporaryType.NONE) {
|
|
635
|
+
// Playing temporary track within threshold ā remove from its list, go back to original
|
|
636
|
+
println("š TrackPlayerCore: Removing temp track, going back to original")
|
|
637
|
+
val currentMediaItem = player.currentMediaItem
|
|
638
|
+
if (currentMediaItem != null) {
|
|
639
|
+
val trackId = extractTrackId(currentMediaItem.mediaId)
|
|
640
|
+
when (currentTemporaryType) {
|
|
641
|
+
TemporaryType.PLAY_NEXT -> {
|
|
642
|
+
val idx = playNextStack.indexOfFirst { it.id == trackId }
|
|
643
|
+
if (idx >= 0) playNextStack.removeAt(idx)
|
|
644
|
+
}
|
|
645
|
+
TemporaryType.UP_NEXT -> {
|
|
646
|
+
val idx = upNextQueue.indexOfFirst { it.id == trackId }
|
|
647
|
+
if (idx >= 0) upNextQueue.removeAt(idx)
|
|
648
|
+
}
|
|
649
|
+
else -> {}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
currentTemporaryType = TemporaryType.NONE
|
|
653
|
+
playFromIndexInternal(currentTrackIndex)
|
|
632
654
|
} else if (currentTrackIndex > 0) {
|
|
633
655
|
// Go to previous track in original playlist
|
|
634
656
|
println("š TrackPlayerCore: Going to previous track, currentTrackIndex: $currentTrackIndex -> ${currentTrackIndex - 1}")
|
|
@@ -859,50 +881,40 @@ class TrackPlayerCore private constructor(
|
|
|
859
881
|
}
|
|
860
882
|
|
|
861
883
|
private fun skipToIndexInternal(index: Int): Boolean {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (!::player.isInitialized) {
|
|
865
|
-
println(" ā Player not initialized")
|
|
866
|
-
return false
|
|
867
|
-
}
|
|
884
|
+
if (!::player.isInitialized) return false
|
|
868
885
|
|
|
869
886
|
// Get actual queue to validate index and determine position
|
|
870
887
|
val actualQueue = getActualQueueInternal()
|
|
871
888
|
val totalQueueSize = actualQueue.size
|
|
872
889
|
|
|
873
890
|
// Validate index
|
|
874
|
-
if (index < 0 || index >= totalQueueSize)
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
891
|
+
if (index < 0 || index >= totalQueueSize) return false
|
|
892
|
+
|
|
893
|
+
// Calculate queue section boundaries using effective sizes
|
|
894
|
+
// (reduced by 1 when current track is from that temp list, matching getActualQueueInternal)
|
|
895
|
+
// When temp is playing, the original track at currentTrackIndex is included in "before",
|
|
896
|
+
// so the current playing position shifts by 1
|
|
897
|
+
val currentPos = if (currentTemporaryType != TemporaryType.NONE)
|
|
898
|
+
currentTrackIndex + 1 else currentTrackIndex
|
|
899
|
+
val effectivePlayNextSize = if (currentTemporaryType == TemporaryType.PLAY_NEXT)
|
|
900
|
+
maxOf(0, playNextStack.size - 1) else playNextStack.size
|
|
901
|
+
val effectiveUpNextSize = if (currentTemporaryType == TemporaryType.UP_NEXT)
|
|
902
|
+
maxOf(0, upNextQueue.size - 1) else upNextQueue.size
|
|
878
903
|
|
|
879
|
-
// Calculate queue section boundaries
|
|
880
|
-
// ActualQueue structure: [before_current] + [current] + [playNext] + [upNext] + [remaining_original]
|
|
881
|
-
// Use our internal tracking instead of player.currentMediaItemIndex (which is relative to ExoPlayer's subset queue)
|
|
882
|
-
val currentPos = currentTrackIndex
|
|
883
904
|
val playNextStart = currentPos + 1
|
|
884
|
-
val playNextEnd = playNextStart +
|
|
905
|
+
val playNextEnd = playNextStart + effectivePlayNextSize
|
|
885
906
|
val upNextStart = playNextEnd
|
|
886
|
-
val upNextEnd = upNextStart +
|
|
907
|
+
val upNextEnd = upNextStart + effectiveUpNextSize
|
|
887
908
|
val originalRemainingStart = upNextEnd
|
|
888
909
|
|
|
889
|
-
println(" Queue structure:")
|
|
890
|
-
println(" currentPos: $currentPos")
|
|
891
|
-
println(" playNextStart: $playNextStart, playNextEnd: $playNextEnd")
|
|
892
|
-
println(" upNextStart: $upNextStart, upNextEnd: $upNextEnd")
|
|
893
|
-
println(" originalRemainingStart: $originalRemainingStart")
|
|
894
|
-
println(" totalQueueSize: $totalQueueSize")
|
|
895
|
-
|
|
896
910
|
// Case 1: Target is before current - use playFromIndex on original
|
|
897
911
|
if (index < currentPos) {
|
|
898
|
-
println(" š Target is before current, jumping to original playlist index $index")
|
|
899
912
|
playFromIndexInternal(index)
|
|
900
913
|
return true
|
|
901
914
|
}
|
|
902
915
|
|
|
903
916
|
// Case 2: Target is current - seek to beginning
|
|
904
917
|
if (index == currentPos) {
|
|
905
|
-
println(" š Target is current track, seeking to beginning")
|
|
906
918
|
player.seekTo(0)
|
|
907
919
|
return true
|
|
908
920
|
}
|
|
@@ -910,12 +922,13 @@ class TrackPlayerCore private constructor(
|
|
|
910
922
|
// Case 3: Target is in playNext section
|
|
911
923
|
if (index >= playNextStart && index < playNextEnd) {
|
|
912
924
|
val playNextIndex = index - playNextStart
|
|
913
|
-
|
|
925
|
+
// Offset by 1 if current is from playNext (index 0 is already playing)
|
|
926
|
+
val actualListIndex = if (currentTemporaryType == TemporaryType.PLAY_NEXT)
|
|
927
|
+
playNextIndex + 1 else playNextIndex
|
|
914
928
|
|
|
915
929
|
// Remove tracks before the target from playNext (they're being skipped)
|
|
916
|
-
if (
|
|
917
|
-
repeat(
|
|
918
|
-
println(" Removed $playNextIndex tracks from playNext stack")
|
|
930
|
+
if (actualListIndex > 0) {
|
|
931
|
+
repeat(actualListIndex) { playNextStack.removeAt(0) }
|
|
919
932
|
}
|
|
920
933
|
|
|
921
934
|
// Rebuild queue and advance
|
|
@@ -927,16 +940,16 @@ class TrackPlayerCore private constructor(
|
|
|
927
940
|
// Case 4: Target is in upNext section
|
|
928
941
|
if (index >= upNextStart && index < upNextEnd) {
|
|
929
942
|
val upNextIndex = index - upNextStart
|
|
930
|
-
|
|
943
|
+
// Offset by 1 if current is from upNext (index 0 is already playing)
|
|
944
|
+
val actualListIndex = if (currentTemporaryType == TemporaryType.UP_NEXT)
|
|
945
|
+
upNextIndex + 1 else upNextIndex
|
|
931
946
|
|
|
932
947
|
// Clear all playNext tracks (they're being skipped)
|
|
933
948
|
playNextStack.clear()
|
|
934
|
-
println(" Cleared all playNext tracks")
|
|
935
949
|
|
|
936
950
|
// Remove tracks before target from upNext
|
|
937
|
-
if (
|
|
938
|
-
repeat(
|
|
939
|
-
println(" Removed $upNextIndex tracks from upNext queue")
|
|
951
|
+
if (actualListIndex > 0) {
|
|
952
|
+
repeat(actualListIndex) { upNextQueue.removeAt(0) }
|
|
940
953
|
}
|
|
941
954
|
|
|
942
955
|
// Rebuild queue and advance
|
|
@@ -947,37 +960,21 @@ class TrackPlayerCore private constructor(
|
|
|
947
960
|
|
|
948
961
|
// Case 5: Target is in remaining original tracks
|
|
949
962
|
if (index >= originalRemainingStart) {
|
|
950
|
-
// Get the target track directly from actualQueue
|
|
951
963
|
val targetTrack = actualQueue[index]
|
|
952
964
|
|
|
953
|
-
println(" š Case 5: Target is in remaining original tracks")
|
|
954
|
-
println(" targetTrack.id: ${targetTrack.id}")
|
|
955
|
-
println(" currentTracks.count: ${currentTracks.size}")
|
|
956
|
-
println(" currentTracks IDs: ${currentTracks.map { it.id }}")
|
|
957
|
-
|
|
958
965
|
// Find this track's index in the original playlist
|
|
959
966
|
val originalIndex = currentTracks.indexOfFirst { it.id == targetTrack.id }
|
|
960
|
-
if (originalIndex == -1)
|
|
961
|
-
println(" ā Could not find track ${targetTrack.id} in original playlist")
|
|
962
|
-
println(" Available tracks: ${currentTracks.map { it.id }}")
|
|
963
|
-
return false
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
println(" originalIndex found: $originalIndex")
|
|
967
|
+
if (originalIndex == -1) return false
|
|
967
968
|
|
|
968
969
|
// Clear all temporary tracks (they're being skipped)
|
|
969
970
|
playNextStack.clear()
|
|
970
971
|
upNextQueue.clear()
|
|
971
972
|
currentTemporaryType = TemporaryType.NONE
|
|
972
|
-
println(" Cleared all temporary tracks")
|
|
973
973
|
|
|
974
|
-
// IMPORTANT: Rebuild the ExoPlayer queue without temporary tracks, then seek
|
|
975
|
-
// We need to rebuild from the target index, not just seek
|
|
976
974
|
rebuildQueueAndPlayFromIndex(originalIndex)
|
|
977
975
|
return true
|
|
978
976
|
}
|
|
979
977
|
|
|
980
|
-
println(" ā Unexpected case, index $index not handled")
|
|
981
978
|
return false
|
|
982
979
|
}
|
|
983
980
|
|
|
@@ -986,7 +983,6 @@ class TrackPlayerCore private constructor(
|
|
|
986
983
|
playNextStack.clear()
|
|
987
984
|
upNextQueue.clear()
|
|
988
985
|
currentTemporaryType = TemporaryType.NONE
|
|
989
|
-
println(" š§¹ Cleared temporary tracks")
|
|
990
986
|
|
|
991
987
|
rebuildQueueAndPlayFromIndex(index)
|
|
992
988
|
}
|
|
@@ -1104,59 +1100,48 @@ class TrackPlayerCore private constructor(
|
|
|
1104
1100
|
private fun rebuildQueueFromCurrentPosition() {
|
|
1105
1101
|
if (!::player.isInitialized) return
|
|
1106
1102
|
|
|
1107
|
-
println("\nš TrackPlayerCore: REBUILDING QUEUE FROM CURRENT POSITION")
|
|
1108
|
-
println(" currentIndex: ${player.currentMediaItemIndex}")
|
|
1109
|
-
println(" currentMediaItem: ${player.currentMediaItem?.mediaId}")
|
|
1110
|
-
println(" playNextStack (${playNextStack.size}): ${playNextStack.map { "${it.id}:${it.title}" }}")
|
|
1111
|
-
println(" upNextQueue (${upNextQueue.size}): ${upNextQueue.map { "${it.id}:${it.title}" }}")
|
|
1112
|
-
|
|
1113
1103
|
val currentIndex = player.currentMediaItemIndex
|
|
1114
1104
|
if (currentIndex < 0) return
|
|
1115
1105
|
|
|
1116
|
-
// Build new queue order:
|
|
1117
|
-
// [playNext stack] + [upNext queue] + [remaining original tracks]
|
|
1118
1106
|
val newQueueTracks = mutableListOf<TrackItem>()
|
|
1119
1107
|
|
|
1120
1108
|
// Add playNext stack (LIFO - most recently added plays first)
|
|
1121
|
-
//
|
|
1122
|
-
|
|
1109
|
+
// Skip index 0 if current track is from playNext (it's already playing)
|
|
1110
|
+
if (currentTemporaryType == TemporaryType.PLAY_NEXT && playNextStack.size > 1) {
|
|
1111
|
+
newQueueTracks.addAll(playNextStack.subList(1, playNextStack.size))
|
|
1112
|
+
} else if (currentTemporaryType != TemporaryType.PLAY_NEXT) {
|
|
1113
|
+
newQueueTracks.addAll(playNextStack)
|
|
1114
|
+
}
|
|
1123
1115
|
|
|
1124
1116
|
// Add upNext queue (in order, FIFO)
|
|
1125
|
-
|
|
1117
|
+
// Skip index 0 if current track is from upNext (it's already playing)
|
|
1118
|
+
if (currentTemporaryType == TemporaryType.UP_NEXT && upNextQueue.size > 1) {
|
|
1119
|
+
newQueueTracks.addAll(upNextQueue.subList(1, upNextQueue.size))
|
|
1120
|
+
} else if (currentTemporaryType != TemporaryType.UP_NEXT) {
|
|
1121
|
+
newQueueTracks.addAll(upNextQueue)
|
|
1122
|
+
}
|
|
1126
1123
|
|
|
1127
|
-
// Add remaining original tracks
|
|
1128
|
-
if (
|
|
1129
|
-
val remaining = currentTracks.subList(
|
|
1130
|
-
println(" remaining original (${remaining.size}): ${remaining.map { it.id }}")
|
|
1124
|
+
// Add remaining original tracks ā use currentTrackIndex (original playlist position)
|
|
1125
|
+
if (currentTrackIndex + 1 < currentTracks.size) {
|
|
1126
|
+
val remaining = currentTracks.subList(currentTrackIndex + 1, currentTracks.size)
|
|
1131
1127
|
newQueueTracks.addAll(remaining)
|
|
1132
1128
|
}
|
|
1133
1129
|
|
|
1134
|
-
println(" New queue total: ${newQueueTracks.size} tracks")
|
|
1135
|
-
println(" Queue order: ${newQueueTracks.map { it.id }}")
|
|
1136
|
-
|
|
1137
1130
|
// Create MediaItems for new tracks
|
|
1138
1131
|
val playlistId = currentPlaylistId ?: ""
|
|
1139
1132
|
val newMediaItems =
|
|
1140
1133
|
newQueueTracks.map { track ->
|
|
1141
1134
|
val mediaId = if (playlistId.isNotEmpty()) "$playlistId:${track.id}" else track.id
|
|
1142
|
-
println(" Creating MediaItem: mediaId=$mediaId, title=${track.title}")
|
|
1143
1135
|
track.toMediaItem(mediaId)
|
|
1144
1136
|
}
|
|
1145
1137
|
|
|
1146
1138
|
// Remove all items after current
|
|
1147
|
-
val removedCount = player.mediaItemCount - currentIndex - 1
|
|
1148
|
-
println(" Removing $removedCount items after current")
|
|
1149
1139
|
while (player.mediaItemCount > currentIndex + 1) {
|
|
1150
1140
|
player.removeMediaItem(currentIndex + 1)
|
|
1151
1141
|
}
|
|
1152
1142
|
|
|
1153
1143
|
// Add new items
|
|
1154
1144
|
player.addMediaItems(newMediaItems)
|
|
1155
|
-
|
|
1156
|
-
println(" ā
Queue rebuilt. Player now has ${player.mediaItemCount} items")
|
|
1157
|
-
for (i in 0 until player.mediaItemCount) {
|
|
1158
|
-
println(" [$i]: ${player.getMediaItemAt(i).mediaId}")
|
|
1159
|
-
}
|
|
1160
1145
|
}
|
|
1161
1146
|
|
|
1162
1147
|
/**
|
|
@@ -1393,57 +1378,46 @@ class TrackPlayerCore private constructor(
|
|
|
1393
1378
|
}
|
|
1394
1379
|
|
|
1395
1380
|
private fun getActualQueueInternal(): List<TrackItem> {
|
|
1396
|
-
println("\nš TrackPlayerCore: getActualQueueInternal() called")
|
|
1397
|
-
println(" playNextStack size: ${playNextStack.size}, tracks: ${playNextStack.map { it.id }}")
|
|
1398
|
-
println(" upNextQueue size: ${upNextQueue.size}, tracks: ${upNextQueue.map { it.id }}")
|
|
1399
|
-
println(" currentTracks size: ${currentTracks.size}, tracks: ${currentTracks.map { it.id }}")
|
|
1400
|
-
println(" currentTrackIndex: $currentTrackIndex")
|
|
1401
|
-
|
|
1402
1381
|
val queue = mutableListOf<TrackItem>()
|
|
1403
1382
|
|
|
1404
|
-
if (!::player.isInitialized)
|
|
1405
|
-
println(" ā Player not initialized, returning empty")
|
|
1406
|
-
return emptyList()
|
|
1407
|
-
}
|
|
1383
|
+
if (!::player.isInitialized) return emptyList()
|
|
1408
1384
|
|
|
1409
|
-
// Use our internal tracking of position in original playlist
|
|
1410
1385
|
val currentIndex = currentTrackIndex
|
|
1411
|
-
|
|
1412
|
-
if (currentIndex < 0) {
|
|
1413
|
-
println(" ā currentIndex < 0, returning empty")
|
|
1414
|
-
return emptyList()
|
|
1415
|
-
}
|
|
1386
|
+
if (currentIndex < 0) return emptyList()
|
|
1416
1387
|
|
|
1417
1388
|
// Add tracks before current (original playlist)
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1389
|
+
// When a temp track is playing, include the original track at currentTrackIndex
|
|
1390
|
+
// (it already played before the temp track started)
|
|
1391
|
+
val beforeEnd = if (currentTemporaryType != TemporaryType.NONE)
|
|
1392
|
+
minOf(currentIndex + 1, currentTracks.size) else currentIndex
|
|
1393
|
+
if (beforeEnd > 0) {
|
|
1394
|
+
queue.addAll(currentTracks.subList(0, beforeEnd))
|
|
1422
1395
|
}
|
|
1423
1396
|
|
|
1424
|
-
// Add current track
|
|
1425
|
-
getCurrentTrack()?.let {
|
|
1426
|
-
println(" Adding current track: ${it.id}")
|
|
1427
|
-
queue.add(it)
|
|
1428
|
-
}
|
|
1397
|
+
// Add current track (temp or original)
|
|
1398
|
+
getCurrentTrack()?.let { queue.add(it) }
|
|
1429
1399
|
|
|
1430
1400
|
// Add playNext stack (LIFO - most recently added plays first)
|
|
1431
|
-
//
|
|
1432
|
-
|
|
1433
|
-
|
|
1401
|
+
// Skip index 0 if current track is from playNext (it's already added as current)
|
|
1402
|
+
if (currentTemporaryType == TemporaryType.PLAY_NEXT && playNextStack.size > 1) {
|
|
1403
|
+
queue.addAll(playNextStack.subList(1, playNextStack.size))
|
|
1404
|
+
} else if (currentTemporaryType != TemporaryType.PLAY_NEXT) {
|
|
1405
|
+
queue.addAll(playNextStack)
|
|
1406
|
+
}
|
|
1434
1407
|
|
|
1435
1408
|
// Add upNext queue (in order, FIFO)
|
|
1436
|
-
|
|
1437
|
-
|
|
1409
|
+
// Skip index 0 if current track is from upNext (it's already added as current)
|
|
1410
|
+
if (currentTemporaryType == TemporaryType.UP_NEXT && upNextQueue.size > 1) {
|
|
1411
|
+
queue.addAll(upNextQueue.subList(1, upNextQueue.size))
|
|
1412
|
+
} else if (currentTemporaryType != TemporaryType.UP_NEXT) {
|
|
1413
|
+
queue.addAll(upNextQueue)
|
|
1414
|
+
}
|
|
1438
1415
|
|
|
1439
1416
|
// Add remaining original tracks
|
|
1440
1417
|
if (currentIndex + 1 < currentTracks.size) {
|
|
1441
|
-
|
|
1442
|
-
println(" Adding ${remaining.size} remaining tracks")
|
|
1443
|
-
queue.addAll(remaining)
|
|
1418
|
+
queue.addAll(currentTracks.subList(currentIndex + 1, currentTracks.size))
|
|
1444
1419
|
}
|
|
1445
1420
|
|
|
1446
|
-
println(" ā
Final queue size: ${queue.size}, tracks: ${queue.map { it.id }}")
|
|
1447
1421
|
return queue
|
|
1448
1422
|
}
|
|
1449
1423
|
}
|
|
@@ -50,6 +50,7 @@ class DownloadFileManager private constructor(
|
|
|
50
50
|
fun createDownloadFile(
|
|
51
51
|
trackId: String,
|
|
52
52
|
storageLocation: StorageLocation,
|
|
53
|
+
extension: String = "mp3",
|
|
53
54
|
): File {
|
|
54
55
|
val destinationDir =
|
|
55
56
|
when (storageLocation) {
|
|
@@ -58,7 +59,7 @@ class DownloadFileManager private constructor(
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
// Create unique filename based on trackId
|
|
61
|
-
val fileName = "$trackId
|
|
62
|
+
val fileName = "$trackId.$extension"
|
|
62
63
|
return File(destinationDir, fileName)
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -119,15 +120,17 @@ class DownloadFileManager private constructor(
|
|
|
119
120
|
|
|
120
121
|
fun getLocalPath(trackId: String): String? {
|
|
121
122
|
// Check private directory first
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
privateDownloadsDir.listFiles()?.forEach { file ->
|
|
124
|
+
if (file.nameWithoutExtension == trackId) {
|
|
125
|
+
return file.absolutePath
|
|
126
|
+
}
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
// Check public directory
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
publicDownloadsDir.listFiles()?.forEach { file ->
|
|
131
|
+
if (file.nameWithoutExtension == trackId) {
|
|
132
|
+
return file.absolutePath
|
|
133
|
+
}
|
|
131
134
|
}
|
|
132
135
|
|
|
133
136
|
return null
|
|
@@ -8,6 +8,7 @@ import androidx.core.app.NotificationCompat
|
|
|
8
8
|
import androidx.work.CoroutineWorker
|
|
9
9
|
import androidx.work.ForegroundInfo
|
|
10
10
|
import androidx.work.WorkerParameters
|
|
11
|
+
import android.webkit.MimeTypeMap
|
|
11
12
|
import com.margelo.nitro.nitroplayer.*
|
|
12
13
|
import kotlinx.coroutines.Dispatchers
|
|
13
14
|
import kotlinx.coroutines.withContext
|
|
@@ -122,16 +123,42 @@ class DownloadWorker(
|
|
|
122
123
|
if (responseCode != HttpURLConnection.HTTP_OK) {
|
|
123
124
|
throw Exception("Server returned HTTP $responseCode")
|
|
124
125
|
}
|
|
126
|
+
// Determine extension
|
|
127
|
+
var extension = MimeTypeMap.getFileExtensionFromUrl(urlString)
|
|
128
|
+
|
|
129
|
+
// 1. Try Content-Disposition
|
|
130
|
+
if (extension.isNullOrEmpty()) {
|
|
131
|
+
val contentDisposition = connection.getHeaderField("Content-Disposition")
|
|
132
|
+
if (contentDisposition != null) {
|
|
133
|
+
val match = Regex("filename=\"?([^\";]+)\"?").find(contentDisposition)
|
|
134
|
+
if (match != null) {
|
|
135
|
+
val filename = match.groupValues[1]
|
|
136
|
+
extension = MimeTypeMap.getFileExtensionFromUrl(filename)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. Try Content-Type
|
|
142
|
+
if (extension.isNullOrEmpty()) {
|
|
143
|
+
val contentType = connection.contentType
|
|
144
|
+
if (contentType != null) {
|
|
145
|
+
val mimeType = contentType.split(";")[0].trim()
|
|
146
|
+
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
val finalExtension = if (extension.isNullOrEmpty()) "mp3" else extension
|
|
125
151
|
|
|
126
|
-
val totalBytes = connection.contentLengthLong
|
|
127
|
-
var bytesDownloaded: Long = 0
|
|
128
152
|
|
|
129
153
|
// Create destination file
|
|
130
|
-
val destinationFile = fileManager.createDownloadFile(trackId, storageLocation)
|
|
154
|
+
val destinationFile = fileManager.createDownloadFile(trackId, storageLocation, finalExtension)
|
|
131
155
|
|
|
132
156
|
inputStream = BufferedInputStream(connection.inputStream)
|
|
133
157
|
outputStream = FileOutputStream(destinationFile)
|
|
134
158
|
|
|
159
|
+
val totalBytes = connection.contentLengthLong
|
|
160
|
+
var bytesDownloaded: Long = 0
|
|
161
|
+
|
|
135
162
|
val buffer = ByteArray(BUFFER_SIZE)
|
|
136
163
|
var bytesRead: Int
|
|
137
164
|
var lastProgressUpdate = System.currentTimeMillis()
|