react-native-nitro-player 0.0.1
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/NitroPlayer.podspec +31 -0
- package/README.md +610 -0
- package/android/CMakeLists.txt +29 -0
- package/android/build.gradle +147 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +7 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrary.kt +29 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +116 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridPlayerQueue.kt +167 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +93 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/NitroPlayerPackage.kt +21 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/connection/AndroidAutoConnectionDetector.kt +171 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +639 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaBrowserService.kt +352 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaLibrary.kt +58 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaLibraryManager.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaLibraryParser.kt +73 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +506 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/Playlist.kt +21 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +454 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/queue/Queue.kt +94 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/queue/QueueManager.kt +143 -0
- package/ios/HybridAudioRoutePicker.swift +53 -0
- package/ios/HybridTrackPlayer.swift +100 -0
- package/ios/core/TrackPlayerCore.swift +1040 -0
- package/ios/media/MediaSessionManager.swift +230 -0
- package/ios/playlist/PlaylistManager.swift +446 -0
- package/ios/playlist/PlaylistModel.swift +49 -0
- package/ios/queue/HybridPlayerQueue.swift +95 -0
- package/ios/queue/Queue.swift +126 -0
- package/ios/queue/QueueManager.swift +157 -0
- package/lib/hooks/index.d.ts +6 -0
- package/lib/hooks/index.js +6 -0
- package/lib/hooks/useAndroidAutoConnection.d.ts +13 -0
- package/lib/hooks/useAndroidAutoConnection.js +26 -0
- package/lib/hooks/useAudioDevices.d.ts +26 -0
- package/lib/hooks/useAudioDevices.js +55 -0
- package/lib/hooks/useOnChangeTrack.d.ts +9 -0
- package/lib/hooks/useOnChangeTrack.js +17 -0
- package/lib/hooks/useOnPlaybackProgressChange.d.ts +9 -0
- package/lib/hooks/useOnPlaybackProgressChange.js +19 -0
- package/lib/hooks/useOnPlaybackStateChange.d.ts +9 -0
- package/lib/hooks/useOnPlaybackStateChange.js +17 -0
- package/lib/hooks/useOnSeek.d.ts +8 -0
- package/lib/hooks/useOnSeek.js +17 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.js +24 -0
- package/lib/specs/AndroidAutoMediaLibrary.nitro.d.ts +21 -0
- package/lib/specs/AndroidAutoMediaLibrary.nitro.js +1 -0
- package/lib/specs/AudioDevices.nitro.d.ts +24 -0
- package/lib/specs/AudioDevices.nitro.js +1 -0
- package/lib/specs/AudioRoutePicker.nitro.d.ts +10 -0
- package/lib/specs/AudioRoutePicker.nitro.js +1 -0
- package/lib/specs/TrackPlayer.nitro.d.ts +39 -0
- package/lib/specs/TrackPlayer.nitro.js +1 -0
- package/lib/types/AndroidAutoMediaLibrary.d.ts +44 -0
- package/lib/types/AndroidAutoMediaLibrary.js +1 -0
- package/lib/types/PlayerQueue.d.ts +32 -0
- package/lib/types/PlayerQueue.js +1 -0
- package/lib/utils/androidAutoMediaLibrary.d.ts +47 -0
- package/lib/utils/androidAutoMediaLibrary.js +62 -0
- package/nitro.json +31 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroPlayer+autolinking.cmake +91 -0
- package/nitrogen/generated/android/NitroPlayer+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroPlayerOnLoad.cpp +88 -0
- package/nitrogen/generated/android/NitroPlayerOnLoad.hpp +25 -0
- package/nitrogen/generated/android/c++/JFunc_void_TrackItem_std__optional_Reason_.hpp +85 -0
- package/nitrogen/generated/android/c++/JFunc_void_TrackPlayerState_std__optional_Reason_.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_double_double_std__optional_bool_.hpp +76 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string_Playlist_std__optional_QueueOperation_.hpp +88 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Playlist__std__optional_QueueOperation_.hpp +106 -0
- package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.cpp +55 -0
- package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.hpp +66 -0
- package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.cpp +70 -0
- package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.hpp +66 -0
- package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.cpp +143 -0
- package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.hpp +77 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +137 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +78 -0
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +65 -0
- package/nitrogen/generated/android/c++/JPlayerState.hpp +87 -0
- package/nitrogen/generated/android/c++/JPlaylist.hpp +99 -0
- package/nitrogen/generated/android/c++/JQueueOperation.hpp +65 -0
- package/nitrogen/generated/android/c++/JReason.hpp +65 -0
- package/nitrogen/generated/android/c++/JTAudioDevice.hpp +69 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +86 -0
- package/nitrogen/generated/android/c++/JTrackPlayerState.hpp +62 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.hpp +77 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_String.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_String.hpp +70 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.hpp +74 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_TrackItem_std__optional_Reason_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_TrackPlayerState_std__optional_Reason_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_bool.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_double_double.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_double_double_std__optional_bool_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__string_Playlist_std__optional_QueueOperation_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_Playlist__std__optional_QueueOperation_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrarySpec.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAudioDevicesSpec.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridPlayerQueueSpec.kt +116 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +134 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/NitroPlayerOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerConfig.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerState.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Playlist.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/QueueOperation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Reason.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TAudioDevice.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackItem.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackPlayerState.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_Playlist.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_String.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_TrackItem.kt +59 -0
- package/nitrogen/generated/ios/NitroPlayer+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +123 -0
- package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +531 -0
- package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Umbrella.hpp +80 -0
- package/nitrogen/generated/ios/NitroPlayerAutolinking.mm +49 -0
- package/nitrogen/generated/ios/NitroPlayerAutolinking.swift +55 -0
- package/nitrogen/generated/ios/c++/HybridAudioRoutePickerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridAudioRoutePickerSpecSwift.hpp +74 -0
- package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.hpp +167 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +174 -0
- package/nitrogen/generated/ios/swift/Func_void_TrackItem_std__optional_Reason_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_TrackPlayerState_std__optional_Reason_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_double_double.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_double_double_std__optional_bool_.swift +54 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string_Playlist_std__optional_QueueOperation_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_Playlist__std__optional_QueueOperation_.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridAudioRoutePickerSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridAudioRoutePickerSpec_cxx.swift +130 -0
- package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec.swift +68 -0
- package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec_cxx.swift +349 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +69 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +325 -0
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +115 -0
- package/nitrogen/generated/ios/swift/PlayerState.swift +181 -0
- package/nitrogen/generated/ios/swift/Playlist.swift +182 -0
- package/nitrogen/generated/ios/swift/QueueOperation.swift +48 -0
- package/nitrogen/generated/ios/swift/Reason.swift +48 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +147 -0
- package/nitrogen/generated/ios/swift/TrackPlayerState.swift +44 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_Playlist.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_String.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_TrackItem.swift +18 -0
- package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.hpp +63 -0
- package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.hpp +65 -0
- package/nitrogen/generated/shared/c++/HybridAudioRoutePickerSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridAudioRoutePickerSpec.hpp +62 -0
- package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.cpp +33 -0
- package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.hpp +87 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +34 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +91 -0
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +83 -0
- package/nitrogen/generated/shared/c++/PlayerState.hpp +103 -0
- package/nitrogen/generated/shared/c++/Playlist.hpp +97 -0
- package/nitrogen/generated/shared/c++/QueueOperation.hpp +84 -0
- package/nitrogen/generated/shared/c++/Reason.hpp +84 -0
- package/nitrogen/generated/shared/c++/TAudioDevice.hpp +87 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +102 -0
- package/nitrogen/generated/shared/c++/TrackPlayerState.hpp +80 -0
- package/package.json +172 -0
- package/react-native.config.js +16 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useAndroidAutoConnection.ts +30 -0
- package/src/hooks/useAudioDevices.ts +64 -0
- package/src/hooks/useOnChangeTrack.ts +24 -0
- package/src/hooks/useOnPlaybackProgressChange.ts +30 -0
- package/src/hooks/useOnPlaybackStateChange.ts +24 -0
- package/src/hooks/useOnSeek.ts +25 -0
- package/src/index.ts +47 -0
- package/src/specs/AndroidAutoMediaLibrary.nitro.ts +22 -0
- package/src/specs/AudioDevices.nitro.ts +25 -0
- package/src/specs/AudioRoutePicker.nitro.ts +9 -0
- package/src/specs/TrackPlayer.nitro.ts +81 -0
- package/src/types/AndroidAutoMediaLibrary.ts +58 -0
- package/src/types/PlayerQueue.ts +38 -0
- package/src/utils/androidAutoMediaLibrary.ts +66 -0
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
@file:Suppress("ktlint:standard:max-line-length", "ktlint:standard:if-else-wrapping")
|
|
2
|
+
|
|
3
|
+
package com.margelo.nitro.nitroplayer.core
|
|
4
|
+
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.net.Uri
|
|
7
|
+
import androidx.media3.common.MediaItem
|
|
8
|
+
import androidx.media3.common.MediaMetadata
|
|
9
|
+
import androidx.media3.common.Player
|
|
10
|
+
import androidx.media3.exoplayer.DefaultLoadControl
|
|
11
|
+
import androidx.media3.exoplayer.ExoPlayer
|
|
12
|
+
import com.margelo.nitro.core.NullType
|
|
13
|
+
import com.margelo.nitro.nitroplayer.NitroPlayerPackage
|
|
14
|
+
import com.margelo.nitro.nitroplayer.PlayerState
|
|
15
|
+
import com.margelo.nitro.nitroplayer.Reason
|
|
16
|
+
import com.margelo.nitro.nitroplayer.TrackItem
|
|
17
|
+
import com.margelo.nitro.nitroplayer.TrackPlayerState
|
|
18
|
+
import com.margelo.nitro.nitroplayer.Variant_NullType_String
|
|
19
|
+
import com.margelo.nitro.nitroplayer.Variant_NullType_TrackItem
|
|
20
|
+
import com.margelo.nitro.nitroplayer.connection.AndroidAutoConnectionDetector
|
|
21
|
+
import com.margelo.nitro.nitroplayer.media.MediaLibrary
|
|
22
|
+
import com.margelo.nitro.nitroplayer.media.MediaLibraryManager
|
|
23
|
+
import com.margelo.nitro.nitroplayer.media.MediaLibraryParser
|
|
24
|
+
import com.margelo.nitro.nitroplayer.media.MediaSessionManager
|
|
25
|
+
import com.margelo.nitro.nitroplayer.media.NitroPlayerMediaBrowserService
|
|
26
|
+
import com.margelo.nitro.nitroplayer.playlist.PlaylistManager
|
|
27
|
+
import java.util.concurrent.CountDownLatch
|
|
28
|
+
import java.util.concurrent.TimeUnit
|
|
29
|
+
|
|
30
|
+
class TrackPlayerCore private constructor(
|
|
31
|
+
private val context: Context,
|
|
32
|
+
) {
|
|
33
|
+
private val handler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
34
|
+
private lateinit var player: ExoPlayer
|
|
35
|
+
private val playlistManager = PlaylistManager.getInstance(context)
|
|
36
|
+
private val mediaLibraryManager = MediaLibraryManager.getInstance(context)
|
|
37
|
+
private var mediaSessionManager: MediaSessionManager? = null
|
|
38
|
+
private var currentPlaylistId: String? = null
|
|
39
|
+
private var isManuallySeeked = false
|
|
40
|
+
private var isAndroidAutoConnected: Boolean = false
|
|
41
|
+
private var androidAutoConnectionDetector: AndroidAutoConnectionDetector? = null
|
|
42
|
+
var onAndroidAutoConnectionChange: ((Boolean) -> Unit)? = null
|
|
43
|
+
private val progressUpdateRunnable =
|
|
44
|
+
object : Runnable {
|
|
45
|
+
override fun run() {
|
|
46
|
+
if (::player.isInitialized && player.playbackState != Player.STATE_IDLE) {
|
|
47
|
+
val position = player.currentPosition / 1000.0
|
|
48
|
+
val duration = if (player.duration > 0) player.duration / 1000.0 else 0.0
|
|
49
|
+
onPlaybackProgressChange?.invoke(position, duration, if (isManuallySeeked) true else null)
|
|
50
|
+
isManuallySeeked = false
|
|
51
|
+
}
|
|
52
|
+
handler.postDelayed(this, 250) // Update every 250ms
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var onChangeTrack: ((TrackItem, Reason?) -> Unit)? = null
|
|
57
|
+
var onPlaybackStateChange: ((TrackPlayerState, Reason?) -> Unit)? = null
|
|
58
|
+
var onSeek: ((Double, Double) -> Unit)? = null
|
|
59
|
+
var onPlaybackProgressChange: ((Double, Double, Boolean?) -> Unit)? = null
|
|
60
|
+
|
|
61
|
+
companion object {
|
|
62
|
+
@Volatile
|
|
63
|
+
@Suppress("ktlint:standard:property-naming")
|
|
64
|
+
private var INSTANCE: TrackPlayerCore? = null
|
|
65
|
+
|
|
66
|
+
fun getInstance(context: Context): TrackPlayerCore =
|
|
67
|
+
INSTANCE ?: synchronized(this) {
|
|
68
|
+
INSTANCE ?: TrackPlayerCore(context).also { INSTANCE = it }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
init {
|
|
73
|
+
handler.post {
|
|
74
|
+
// Configure LoadControl for gapless playback
|
|
75
|
+
// This enables pre-buffering of the next track for seamless transitions
|
|
76
|
+
val loadControl =
|
|
77
|
+
DefaultLoadControl
|
|
78
|
+
.Builder()
|
|
79
|
+
.setBufferDurationsMs(
|
|
80
|
+
DefaultLoadControl.DEFAULT_MIN_BUFFER_MS, // Minimum buffer: 1.5s
|
|
81
|
+
DefaultLoadControl.DEFAULT_MAX_BUFFER_MS, // Maximum buffer: 5s
|
|
82
|
+
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS, // Buffer for playback: 2.5s
|
|
83
|
+
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS, // Buffer after rebuffer: 5s
|
|
84
|
+
).setBackBuffer(DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS, true) // Keep back buffer for seamless transitions
|
|
85
|
+
.setPrioritizeTimeOverSizeThresholds(true) // Prioritize time-based buffering
|
|
86
|
+
.build()
|
|
87
|
+
|
|
88
|
+
player =
|
|
89
|
+
ExoPlayer
|
|
90
|
+
.Builder(context)
|
|
91
|
+
.setLoadControl(loadControl)
|
|
92
|
+
.build()
|
|
93
|
+
mediaSessionManager =
|
|
94
|
+
MediaSessionManager(context, player, playlistManager).apply {
|
|
95
|
+
setTrackPlayerCore(this@TrackPlayerCore)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Set references for MediaBrowserService
|
|
99
|
+
NitroPlayerMediaBrowserService.trackPlayerCore = this
|
|
100
|
+
NitroPlayerMediaBrowserService.mediaSessionManager = mediaSessionManager
|
|
101
|
+
|
|
102
|
+
// Initialize Android Auto connection detector
|
|
103
|
+
androidAutoConnectionDetector =
|
|
104
|
+
AndroidAutoConnectionDetector(context).apply {
|
|
105
|
+
onConnectionChanged = { connected, connectionType ->
|
|
106
|
+
handler.post {
|
|
107
|
+
isAndroidAutoConnected = connected
|
|
108
|
+
NitroPlayerMediaBrowserService.isAndroidAutoConnected = connected
|
|
109
|
+
|
|
110
|
+
// Notify JavaScript
|
|
111
|
+
onAndroidAutoConnectionChange?.invoke(connected)
|
|
112
|
+
|
|
113
|
+
println("🚗 Android Auto connection changed: connected=$connected, type=$connectionType")
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
registerCarConnectionReceiver()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
player.addListener(
|
|
120
|
+
object : Player.Listener {
|
|
121
|
+
override fun onMediaItemTransition(
|
|
122
|
+
mediaItem: MediaItem?,
|
|
123
|
+
reason: Int,
|
|
124
|
+
) {
|
|
125
|
+
// Handle playlist switching if needed
|
|
126
|
+
mediaItem?.mediaId?.let { mediaId ->
|
|
127
|
+
if (mediaId.contains(':')) {
|
|
128
|
+
val colonIndex = mediaId.indexOf(':')
|
|
129
|
+
val playlistId = mediaId.substring(0, colonIndex)
|
|
130
|
+
if (playlistId != currentPlaylistId) {
|
|
131
|
+
// Track from different playlist - ensure playlist is loaded
|
|
132
|
+
val playlist = playlistManager.getPlaylist(playlistId)
|
|
133
|
+
if (playlist != null && currentPlaylistId != playlistId) {
|
|
134
|
+
// This shouldn't happen if playlists are loaded correctly,
|
|
135
|
+
// but handle it as a safety measure
|
|
136
|
+
println(
|
|
137
|
+
"⚠️ TrackPlayerCore: Detected track from different playlist, updating...",
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
val track = findTrack(mediaItem)
|
|
145
|
+
if (track != null) {
|
|
146
|
+
val r =
|
|
147
|
+
when (reason) {
|
|
148
|
+
Player.MEDIA_ITEM_TRANSITION_REASON_AUTO -> Reason.END
|
|
149
|
+
Player.MEDIA_ITEM_TRANSITION_REASON_SEEK -> Reason.USER_ACTION
|
|
150
|
+
Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED -> Reason.USER_ACTION
|
|
151
|
+
else -> null
|
|
152
|
+
}
|
|
153
|
+
onChangeTrack?.invoke(track, r)
|
|
154
|
+
mediaSessionManager?.onTrackChanged()
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
override fun onTimelineChanged(
|
|
159
|
+
timeline: androidx.media3.common.Timeline,
|
|
160
|
+
reason: Int,
|
|
161
|
+
) {
|
|
162
|
+
if (reason == Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) {
|
|
163
|
+
// Playlist changed - update MediaBrowserService
|
|
164
|
+
NitroPlayerMediaBrowserService.getInstance()?.onPlaylistsUpdated()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
override fun onPlayWhenReadyChanged(
|
|
169
|
+
playWhenReady: Boolean,
|
|
170
|
+
reason: Int,
|
|
171
|
+
) {
|
|
172
|
+
val r =
|
|
173
|
+
when (reason) {
|
|
174
|
+
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST -> Reason.USER_ACTION
|
|
175
|
+
else -> null
|
|
176
|
+
}
|
|
177
|
+
emitStateChange(r)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
override fun onPlaybackStateChanged(playbackState: Int) {
|
|
181
|
+
emitStateChange()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
185
|
+
emitStateChange()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
override fun onPositionDiscontinuity(
|
|
189
|
+
oldPosition: Player.PositionInfo,
|
|
190
|
+
newPosition: Player.PositionInfo,
|
|
191
|
+
reason: Int,
|
|
192
|
+
) {
|
|
193
|
+
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
|
|
194
|
+
isManuallySeeked = true
|
|
195
|
+
onSeek?.invoke(newPosition.positionMs / 1000.0, player.duration / 1000.0)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
// Start progress updates
|
|
202
|
+
handler.post(progressUpdateRunnable)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Load a playlist for playback using ExoPlayer's native playlist API
|
|
208
|
+
* Based on: https://developer.android.com/media/media3/exoplayer/playlists
|
|
209
|
+
*/
|
|
210
|
+
fun loadPlaylist(playlistId: String) {
|
|
211
|
+
handler.post {
|
|
212
|
+
val playlist = playlistManager.getPlaylist(playlistId)
|
|
213
|
+
if (playlist != null) {
|
|
214
|
+
currentPlaylistId = playlistId
|
|
215
|
+
updatePlayerQueue(playlist.tracks)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Play a specific track from a playlist (for Android Auto)
|
|
222
|
+
* MediaId format: "playlistId:trackId"
|
|
223
|
+
*/
|
|
224
|
+
fun playFromPlaylistTrack(mediaId: String) {
|
|
225
|
+
handler.post {
|
|
226
|
+
try {
|
|
227
|
+
// Parse mediaId: "playlistId:trackId"
|
|
228
|
+
val colonIndex = mediaId.indexOf(':')
|
|
229
|
+
if (colonIndex > 0 && colonIndex < mediaId.length - 1) {
|
|
230
|
+
val playlistId = mediaId.substring(0, colonIndex)
|
|
231
|
+
val trackId = mediaId.substring(colonIndex + 1)
|
|
232
|
+
|
|
233
|
+
val playlist = playlistManager.getPlaylist(playlistId)
|
|
234
|
+
if (playlist != null) {
|
|
235
|
+
val trackIndex = playlist.tracks.indexOfFirst { it.id == trackId }
|
|
236
|
+
if (trackIndex >= 0) {
|
|
237
|
+
// Load playlist if not already loaded
|
|
238
|
+
if (currentPlaylistId != playlistId) {
|
|
239
|
+
loadPlaylist(playlistId)
|
|
240
|
+
// Wait a bit for playlist to load, then seek
|
|
241
|
+
handler.postDelayed({
|
|
242
|
+
playFromIndex(trackIndex)
|
|
243
|
+
}, 100)
|
|
244
|
+
} else {
|
|
245
|
+
// Playlist already loaded, just seek to track
|
|
246
|
+
playFromIndex(trackIndex)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch (e: Exception) {
|
|
252
|
+
println("❌ TrackPlayerCore: Error playing from playlist track - ${e.message}")
|
|
253
|
+
e.printStackTrace()
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Update the player queue when playlist changes
|
|
260
|
+
*/
|
|
261
|
+
fun updatePlaylist(playlistId: String) {
|
|
262
|
+
handler.post {
|
|
263
|
+
if (currentPlaylistId == playlistId) {
|
|
264
|
+
val playlist = playlistManager.getPlaylist(playlistId)
|
|
265
|
+
if (playlist != null) {
|
|
266
|
+
updatePlayerQueue(playlist.tracks)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get current playlist ID
|
|
274
|
+
*/
|
|
275
|
+
fun getCurrentPlaylistId(): String? = currentPlaylistId
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get playlist manager (for access from other classes like Google Cast)
|
|
279
|
+
*/
|
|
280
|
+
fun getPlaylistManager(): PlaylistManager = playlistManager
|
|
281
|
+
|
|
282
|
+
private fun emitStateChange(reason: Reason? = null) {
|
|
283
|
+
val state =
|
|
284
|
+
when (player.playbackState) {
|
|
285
|
+
Player.STATE_IDLE -> TrackPlayerState.STOPPED
|
|
286
|
+
Player.STATE_BUFFERING -> if (player.playWhenReady) TrackPlayerState.PLAYING else TrackPlayerState.PAUSED
|
|
287
|
+
Player.STATE_READY -> if (player.isPlaying) TrackPlayerState.PLAYING else TrackPlayerState.PAUSED
|
|
288
|
+
Player.STATE_ENDED -> TrackPlayerState.STOPPED
|
|
289
|
+
else -> TrackPlayerState.STOPPED
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
val actualReason = reason ?: if (player.playbackState == Player.STATE_ENDED) Reason.END else null
|
|
293
|
+
onPlaybackStateChange?.invoke(state, actualReason)
|
|
294
|
+
mediaSessionManager?.onPlaybackStateChanged()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private fun updatePlayerQueue(tracks: List<TrackItem>) {
|
|
298
|
+
// Create MediaItems with playlist info in mediaId for Android Auto
|
|
299
|
+
val mediaItems =
|
|
300
|
+
tracks.mapIndexed { index, track ->
|
|
301
|
+
val playlistId = currentPlaylistId ?: ""
|
|
302
|
+
// Format: "playlistId:trackId" so we can identify playlist and track
|
|
303
|
+
val mediaId = if (playlistId.isNotEmpty()) "$playlistId:${track.id}" else track.id
|
|
304
|
+
track.toMediaItem(mediaId)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
player.setMediaItems(mediaItems, false)
|
|
308
|
+
if (player.playbackState == Player.STATE_IDLE && mediaItems.isNotEmpty()) {
|
|
309
|
+
player.prepare()
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private fun TrackItem.toMediaItem(customMediaId: String? = null): MediaItem {
|
|
314
|
+
val metadataBuilder =
|
|
315
|
+
MediaMetadata
|
|
316
|
+
.Builder()
|
|
317
|
+
.setTitle(title)
|
|
318
|
+
.setArtist(artist)
|
|
319
|
+
.setAlbumTitle(album)
|
|
320
|
+
|
|
321
|
+
artwork?.asSecondOrNull()?.let { artworkUrl ->
|
|
322
|
+
try {
|
|
323
|
+
metadataBuilder.setArtworkUri(Uri.parse(artworkUrl))
|
|
324
|
+
} catch (e: Exception) {
|
|
325
|
+
// Ignore invalid artwork URI
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return MediaItem
|
|
330
|
+
.Builder()
|
|
331
|
+
.setMediaId(customMediaId ?: id)
|
|
332
|
+
.setUri(url)
|
|
333
|
+
.setMediaMetadata(metadataBuilder.build())
|
|
334
|
+
.build()
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private fun findTrack(mediaItem: MediaItem?): TrackItem? {
|
|
338
|
+
if (mediaItem == null) return null
|
|
339
|
+
|
|
340
|
+
val mediaId = mediaItem.mediaId
|
|
341
|
+
val trackId =
|
|
342
|
+
if (mediaId.contains(':')) {
|
|
343
|
+
// Format: "playlistId:trackId"
|
|
344
|
+
mediaId.substring(mediaId.indexOf(':') + 1)
|
|
345
|
+
} else {
|
|
346
|
+
mediaId
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
val playlist = currentPlaylistId?.let { playlistManager.getPlaylist(it) }
|
|
350
|
+
return playlist?.tracks?.find { it.id == trackId }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
fun play() {
|
|
354
|
+
handler.post { player.play() }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fun pause() {
|
|
358
|
+
handler.post { player.pause() }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
fun playSong(
|
|
362
|
+
songId: String,
|
|
363
|
+
fromPlaylist: String?,
|
|
364
|
+
) {
|
|
365
|
+
println("🎵 TrackPlayerCore: playSong() called - songId: $songId, fromPlaylist: $fromPlaylist")
|
|
366
|
+
|
|
367
|
+
handler.post {
|
|
368
|
+
var targetPlaylistId: String? = null
|
|
369
|
+
var songIndex: Int = -1
|
|
370
|
+
|
|
371
|
+
// Case 1: If fromPlaylist is provided, use that playlist
|
|
372
|
+
if (fromPlaylist != null) {
|
|
373
|
+
println("🎵 TrackPlayerCore: Looking for song in specified playlist: $fromPlaylist")
|
|
374
|
+
val playlist = playlistManager.getPlaylist(fromPlaylist)
|
|
375
|
+
if (playlist != null) {
|
|
376
|
+
songIndex = playlist.tracks.indexOfFirst { it.id == songId }
|
|
377
|
+
if (songIndex >= 0) {
|
|
378
|
+
targetPlaylistId = fromPlaylist
|
|
379
|
+
println("✅ Found song at index $songIndex in playlist $fromPlaylist")
|
|
380
|
+
} else {
|
|
381
|
+
println("⚠️ Song $songId not found in specified playlist $fromPlaylist")
|
|
382
|
+
return@post
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
println("⚠️ Playlist $fromPlaylist not found")
|
|
386
|
+
return@post
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// Case 2: If fromPlaylist is not provided, search in current/loaded playlist first
|
|
390
|
+
else {
|
|
391
|
+
println("🎵 TrackPlayerCore: No playlist specified, checking current playlist")
|
|
392
|
+
|
|
393
|
+
// Check if song exists in currently loaded playlist
|
|
394
|
+
if (currentPlaylistId != null) {
|
|
395
|
+
val currentPlaylist = playlistManager.getPlaylist(currentPlaylistId!!)
|
|
396
|
+
if (currentPlaylist != null) {
|
|
397
|
+
songIndex = currentPlaylist.tracks.indexOfFirst { it.id == songId }
|
|
398
|
+
if (songIndex >= 0) {
|
|
399
|
+
targetPlaylistId = currentPlaylistId
|
|
400
|
+
println("✅ Found song at index $songIndex in current playlist $currentPlaylistId")
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// If not found in current playlist, search in all playlists
|
|
406
|
+
if (songIndex == -1) {
|
|
407
|
+
println("🔍 Song not found in current playlist, searching all playlists...")
|
|
408
|
+
val allPlaylists = playlistManager.getAllPlaylists()
|
|
409
|
+
|
|
410
|
+
for (playlist in allPlaylists) {
|
|
411
|
+
songIndex = playlist.tracks.indexOfFirst { it.id == songId }
|
|
412
|
+
if (songIndex >= 0) {
|
|
413
|
+
targetPlaylistId = playlist.id
|
|
414
|
+
println("✅ Found song at index $songIndex in playlist ${playlist.id}")
|
|
415
|
+
break
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// If still not found, just use the first playlist if available
|
|
420
|
+
if (songIndex == -1 && allPlaylists.isNotEmpty()) {
|
|
421
|
+
targetPlaylistId = allPlaylists[0].id
|
|
422
|
+
songIndex = 0
|
|
423
|
+
println("⚠️ Song not found in any playlist, using first playlist and starting at index 0")
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Now play the song
|
|
429
|
+
if (targetPlaylistId == null || songIndex < 0) {
|
|
430
|
+
println("❌ Could not determine playlist or song index")
|
|
431
|
+
return@post
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Load playlist if it's different from current
|
|
435
|
+
if (currentPlaylistId != targetPlaylistId) {
|
|
436
|
+
println("🔄 Loading new playlist: $targetPlaylistId")
|
|
437
|
+
val playlist = playlistManager.getPlaylist(targetPlaylistId)
|
|
438
|
+
if (playlist != null) {
|
|
439
|
+
currentPlaylistId = targetPlaylistId
|
|
440
|
+
updatePlayerQueue(playlist.tracks)
|
|
441
|
+
|
|
442
|
+
// Wait a bit for playlist to load, then play from index
|
|
443
|
+
handler.postDelayed({
|
|
444
|
+
println("▶️ Playing from index: $songIndex")
|
|
445
|
+
playFromIndex(songIndex)
|
|
446
|
+
}, 100)
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
// Playlist already loaded, just play from index
|
|
450
|
+
println("▶️ Playing from index: $songIndex")
|
|
451
|
+
playFromIndex(songIndex)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
fun skipToNext() {
|
|
457
|
+
handler.post {
|
|
458
|
+
if (player.hasNextMediaItem()) {
|
|
459
|
+
player.seekToNextMediaItem()
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
fun skipToPrevious() {
|
|
465
|
+
handler.post {
|
|
466
|
+
if (player.hasPreviousMediaItem()) {
|
|
467
|
+
player.seekToPreviousMediaItem()
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
fun seek(position: Double) {
|
|
473
|
+
handler.post {
|
|
474
|
+
isManuallySeeked = true
|
|
475
|
+
player.seekTo((position * 1000).toLong())
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
fun getState(): PlayerState {
|
|
480
|
+
// Check if we're already on the main thread
|
|
481
|
+
if (android.os.Looper.myLooper() == handler.looper) {
|
|
482
|
+
return getStateInternal()
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Use CountDownLatch to wait for the result on the main thread
|
|
486
|
+
val latch = CountDownLatch(1)
|
|
487
|
+
var result: PlayerState? = null
|
|
488
|
+
|
|
489
|
+
handler.post {
|
|
490
|
+
try {
|
|
491
|
+
result = getStateInternal()
|
|
492
|
+
} finally {
|
|
493
|
+
latch.countDown()
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
// Wait up to 5 seconds for the result
|
|
499
|
+
latch.await(5, TimeUnit.SECONDS)
|
|
500
|
+
} catch (e: InterruptedException) {
|
|
501
|
+
Thread.currentThread().interrupt()
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return result ?: getStateInternal()
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
private fun getStateInternal(): PlayerState =
|
|
508
|
+
if (::player.isInitialized) {
|
|
509
|
+
val currentMediaItem = player.currentMediaItem
|
|
510
|
+
val track =
|
|
511
|
+
if (currentMediaItem != null) {
|
|
512
|
+
findTrack(currentMediaItem)
|
|
513
|
+
} else {
|
|
514
|
+
null
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Convert nullable TrackItem to Variant_NullType_TrackItem
|
|
518
|
+
val currentTrack: Variant_NullType_TrackItem? =
|
|
519
|
+
if (track != null) {
|
|
520
|
+
Variant_NullType_TrackItem.create(track)
|
|
521
|
+
} else {
|
|
522
|
+
null
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
val currentPosition = player.currentPosition / 1000.0
|
|
526
|
+
val totalDuration = if (player.duration > 0) player.duration / 1000.0 else 0.0
|
|
527
|
+
|
|
528
|
+
val currentState =
|
|
529
|
+
when (player.playbackState) {
|
|
530
|
+
Player.STATE_IDLE -> TrackPlayerState.STOPPED
|
|
531
|
+
Player.STATE_BUFFERING -> if (player.playWhenReady) TrackPlayerState.PLAYING else TrackPlayerState.PAUSED
|
|
532
|
+
Player.STATE_READY -> if (player.isPlaying) TrackPlayerState.PLAYING else TrackPlayerState.PAUSED
|
|
533
|
+
Player.STATE_ENDED -> TrackPlayerState.STOPPED
|
|
534
|
+
else -> TrackPlayerState.STOPPED
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Get current playlist
|
|
538
|
+
val currentPlaylist = currentPlaylistId?.let { playlistManager.getPlaylist(it) }
|
|
539
|
+
|
|
540
|
+
// Use ExoPlayer's currentMediaItemIndex
|
|
541
|
+
val currentIndex =
|
|
542
|
+
if (player.currentMediaItemIndex >= 0) {
|
|
543
|
+
player.currentMediaItemIndex.toDouble()
|
|
544
|
+
} else {
|
|
545
|
+
-1.0
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
PlayerState(
|
|
549
|
+
currentTrack = currentTrack,
|
|
550
|
+
currentPosition = currentPosition,
|
|
551
|
+
totalDuration = totalDuration,
|
|
552
|
+
currentState = currentState,
|
|
553
|
+
currentPlaylistId = currentPlaylistId?.let { Variant_NullType_String.create(it) },
|
|
554
|
+
currentIndex = currentIndex,
|
|
555
|
+
)
|
|
556
|
+
} else {
|
|
557
|
+
// Return default state if player is not initialized
|
|
558
|
+
PlayerState(
|
|
559
|
+
currentTrack = null,
|
|
560
|
+
currentPosition = 0.0,
|
|
561
|
+
totalDuration = 0.0,
|
|
562
|
+
currentState = TrackPlayerState.STOPPED,
|
|
563
|
+
currentPlaylistId = currentPlaylistId?.let { Variant_NullType_String.create(it) },
|
|
564
|
+
currentIndex = -1.0,
|
|
565
|
+
)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
fun configure(
|
|
569
|
+
androidAutoEnabled: Boolean?,
|
|
570
|
+
carPlayEnabled: Boolean?,
|
|
571
|
+
showInNotification: Boolean?,
|
|
572
|
+
) {
|
|
573
|
+
handler.post {
|
|
574
|
+
androidAutoEnabled?.let {
|
|
575
|
+
NitroPlayerMediaBrowserService.isAndroidAutoEnabled = it
|
|
576
|
+
}
|
|
577
|
+
mediaSessionManager?.configure(
|
|
578
|
+
androidAutoEnabled,
|
|
579
|
+
carPlayEnabled,
|
|
580
|
+
showInNotification,
|
|
581
|
+
)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Public method to get all playlists (for MediaBrowserService and other classes)
|
|
586
|
+
fun getAllPlaylists(): List<com.margelo.nitro.nitroplayer.playlist.Playlist> = playlistManager.getAllPlaylists()
|
|
587
|
+
|
|
588
|
+
// Public method to get current track for MediaBrowserService
|
|
589
|
+
fun getCurrentTrack(): TrackItem? {
|
|
590
|
+
if (!::player.isInitialized) return null
|
|
591
|
+
val currentMediaItem = player.currentMediaItem ?: return null
|
|
592
|
+
return findTrack(currentMediaItem)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Public method to play from a specific index (for Android Auto)
|
|
596
|
+
fun playFromIndex(index: Int) {
|
|
597
|
+
handler.post {
|
|
598
|
+
if (::player.isInitialized && index >= 0 && index < player.mediaItemCount) {
|
|
599
|
+
player.seekToDefaultPosition(index)
|
|
600
|
+
player.playWhenReady = true
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Clean up resources
|
|
606
|
+
fun destroy() {
|
|
607
|
+
handler.post {
|
|
608
|
+
androidAutoConnectionDetector?.unregisterCarConnectionReceiver()
|
|
609
|
+
handler.removeCallbacks(progressUpdateRunnable)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Check if Android Auto is connected
|
|
614
|
+
fun isAndroidAutoConnected(): Boolean = isAndroidAutoConnected
|
|
615
|
+
|
|
616
|
+
// Set the Android Auto media library structure from JSON
|
|
617
|
+
fun setAndroidAutoMediaLibrary(libraryJson: String) {
|
|
618
|
+
handler.post {
|
|
619
|
+
try {
|
|
620
|
+
val library = MediaLibraryParser.fromJson(libraryJson)
|
|
621
|
+
mediaLibraryManager.setMediaLibrary(library)
|
|
622
|
+
// Notify Android Auto to refresh
|
|
623
|
+
NitroPlayerMediaBrowserService.getInstance()?.onPlaylistsUpdated()
|
|
624
|
+
println("✅ TrackPlayerCore: Android Auto media library set successfully")
|
|
625
|
+
} catch (e: Exception) {
|
|
626
|
+
println("❌ TrackPlayerCore: Error setting media library - ${e.message}")
|
|
627
|
+
e.printStackTrace()
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Clear the Android Auto media library
|
|
633
|
+
fun clearAndroidAutoMediaLibrary() {
|
|
634
|
+
handler.post {
|
|
635
|
+
mediaLibraryManager.clear()
|
|
636
|
+
NitroPlayerMediaBrowserService.getInstance()?.onPlaylistsUpdated()
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|