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.
Files changed (191) hide show
  1. package/NitroPlayer.podspec +31 -0
  2. package/README.md +610 -0
  3. package/android/CMakeLists.txt +29 -0
  4. package/android/build.gradle +147 -0
  5. package/android/fix-prefab.gradle +51 -0
  6. package/android/gradle.properties +5 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/cpp/cpp-adapter.cpp +7 -0
  9. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrary.kt +29 -0
  10. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +116 -0
  11. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridPlayerQueue.kt +167 -0
  12. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +93 -0
  13. package/android/src/main/java/com/margelo/nitro/nitroplayer/NitroPlayerPackage.kt +21 -0
  14. package/android/src/main/java/com/margelo/nitro/nitroplayer/connection/AndroidAutoConnectionDetector.kt +171 -0
  15. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +639 -0
  16. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaBrowserService.kt +352 -0
  17. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaLibrary.kt +58 -0
  18. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaLibraryManager.kt +77 -0
  19. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaLibraryParser.kt +73 -0
  20. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +506 -0
  21. package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/Playlist.kt +21 -0
  22. package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +454 -0
  23. package/android/src/main/java/com/margelo/nitro/nitroplayer/queue/Queue.kt +94 -0
  24. package/android/src/main/java/com/margelo/nitro/nitroplayer/queue/QueueManager.kt +143 -0
  25. package/ios/HybridAudioRoutePicker.swift +53 -0
  26. package/ios/HybridTrackPlayer.swift +100 -0
  27. package/ios/core/TrackPlayerCore.swift +1040 -0
  28. package/ios/media/MediaSessionManager.swift +230 -0
  29. package/ios/playlist/PlaylistManager.swift +446 -0
  30. package/ios/playlist/PlaylistModel.swift +49 -0
  31. package/ios/queue/HybridPlayerQueue.swift +95 -0
  32. package/ios/queue/Queue.swift +126 -0
  33. package/ios/queue/QueueManager.swift +157 -0
  34. package/lib/hooks/index.d.ts +6 -0
  35. package/lib/hooks/index.js +6 -0
  36. package/lib/hooks/useAndroidAutoConnection.d.ts +13 -0
  37. package/lib/hooks/useAndroidAutoConnection.js +26 -0
  38. package/lib/hooks/useAudioDevices.d.ts +26 -0
  39. package/lib/hooks/useAudioDevices.js +55 -0
  40. package/lib/hooks/useOnChangeTrack.d.ts +9 -0
  41. package/lib/hooks/useOnChangeTrack.js +17 -0
  42. package/lib/hooks/useOnPlaybackProgressChange.d.ts +9 -0
  43. package/lib/hooks/useOnPlaybackProgressChange.js +19 -0
  44. package/lib/hooks/useOnPlaybackStateChange.d.ts +9 -0
  45. package/lib/hooks/useOnPlaybackStateChange.js +17 -0
  46. package/lib/hooks/useOnSeek.d.ts +8 -0
  47. package/lib/hooks/useOnSeek.js +17 -0
  48. package/lib/index.d.ts +14 -0
  49. package/lib/index.js +24 -0
  50. package/lib/specs/AndroidAutoMediaLibrary.nitro.d.ts +21 -0
  51. package/lib/specs/AndroidAutoMediaLibrary.nitro.js +1 -0
  52. package/lib/specs/AudioDevices.nitro.d.ts +24 -0
  53. package/lib/specs/AudioDevices.nitro.js +1 -0
  54. package/lib/specs/AudioRoutePicker.nitro.d.ts +10 -0
  55. package/lib/specs/AudioRoutePicker.nitro.js +1 -0
  56. package/lib/specs/TrackPlayer.nitro.d.ts +39 -0
  57. package/lib/specs/TrackPlayer.nitro.js +1 -0
  58. package/lib/types/AndroidAutoMediaLibrary.d.ts +44 -0
  59. package/lib/types/AndroidAutoMediaLibrary.js +1 -0
  60. package/lib/types/PlayerQueue.d.ts +32 -0
  61. package/lib/types/PlayerQueue.js +1 -0
  62. package/lib/utils/androidAutoMediaLibrary.d.ts +47 -0
  63. package/lib/utils/androidAutoMediaLibrary.js +62 -0
  64. package/nitro.json +31 -0
  65. package/nitrogen/generated/.gitattributes +1 -0
  66. package/nitrogen/generated/android/NitroPlayer+autolinking.cmake +91 -0
  67. package/nitrogen/generated/android/NitroPlayer+autolinking.gradle +27 -0
  68. package/nitrogen/generated/android/NitroPlayerOnLoad.cpp +88 -0
  69. package/nitrogen/generated/android/NitroPlayerOnLoad.hpp +25 -0
  70. package/nitrogen/generated/android/c++/JFunc_void_TrackItem_std__optional_Reason_.hpp +85 -0
  71. package/nitrogen/generated/android/c++/JFunc_void_TrackPlayerState_std__optional_Reason_.hpp +80 -0
  72. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
  73. package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +75 -0
  74. package/nitrogen/generated/android/c++/JFunc_void_double_double_std__optional_bool_.hpp +76 -0
  75. package/nitrogen/generated/android/c++/JFunc_void_std__string_Playlist_std__optional_QueueOperation_.hpp +88 -0
  76. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Playlist__std__optional_QueueOperation_.hpp +106 -0
  77. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.cpp +55 -0
  78. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.hpp +66 -0
  79. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.cpp +70 -0
  80. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.hpp +66 -0
  81. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.cpp +143 -0
  82. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.hpp +77 -0
  83. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +137 -0
  84. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +78 -0
  85. package/nitrogen/generated/android/c++/JPlayerConfig.hpp +65 -0
  86. package/nitrogen/generated/android/c++/JPlayerState.hpp +87 -0
  87. package/nitrogen/generated/android/c++/JPlaylist.hpp +99 -0
  88. package/nitrogen/generated/android/c++/JQueueOperation.hpp +65 -0
  89. package/nitrogen/generated/android/c++/JReason.hpp +65 -0
  90. package/nitrogen/generated/android/c++/JTAudioDevice.hpp +69 -0
  91. package/nitrogen/generated/android/c++/JTrackItem.hpp +86 -0
  92. package/nitrogen/generated/android/c++/JTrackPlayerState.hpp +62 -0
  93. package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.cpp +26 -0
  94. package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.hpp +77 -0
  95. package/nitrogen/generated/android/c++/JVariant_NullType_String.cpp +26 -0
  96. package/nitrogen/generated/android/c++/JVariant_NullType_String.hpp +70 -0
  97. package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.cpp +26 -0
  98. package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.hpp +74 -0
  99. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_TrackItem_std__optional_Reason_.kt +80 -0
  100. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_TrackPlayerState_std__optional_Reason_.kt +80 -0
  101. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_bool.kt +80 -0
  102. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_double_double.kt +80 -0
  103. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_double_double_std__optional_bool_.kt +80 -0
  104. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__string_Playlist_std__optional_QueueOperation_.kt +80 -0
  105. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_Playlist__std__optional_QueueOperation_.kt +80 -0
  106. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrarySpec.kt +61 -0
  107. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAudioDevicesSpec.kt +61 -0
  108. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridPlayerQueueSpec.kt +116 -0
  109. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +134 -0
  110. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/NitroPlayerOnLoad.kt +35 -0
  111. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerConfig.kt +44 -0
  112. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerState.kt +53 -0
  113. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Playlist.kt +50 -0
  114. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/QueueOperation.kt +23 -0
  115. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Reason.kt +23 -0
  116. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TAudioDevice.kt +47 -0
  117. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackItem.kt +56 -0
  118. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackPlayerState.kt +22 -0
  119. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_Playlist.kt +59 -0
  120. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_String.kt +59 -0
  121. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_TrackItem.kt +59 -0
  122. package/nitrogen/generated/ios/NitroPlayer+autolinking.rb +60 -0
  123. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +123 -0
  124. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +531 -0
  125. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Umbrella.hpp +80 -0
  126. package/nitrogen/generated/ios/NitroPlayerAutolinking.mm +49 -0
  127. package/nitrogen/generated/ios/NitroPlayerAutolinking.swift +55 -0
  128. package/nitrogen/generated/ios/c++/HybridAudioRoutePickerSpecSwift.cpp +11 -0
  129. package/nitrogen/generated/ios/c++/HybridAudioRoutePickerSpecSwift.hpp +74 -0
  130. package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.cpp +11 -0
  131. package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.hpp +167 -0
  132. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.cpp +11 -0
  133. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +174 -0
  134. package/nitrogen/generated/ios/swift/Func_void_TrackItem_std__optional_Reason_.swift +47 -0
  135. package/nitrogen/generated/ios/swift/Func_void_TrackPlayerState_std__optional_Reason_.swift +47 -0
  136. package/nitrogen/generated/ios/swift/Func_void_bool.swift +47 -0
  137. package/nitrogen/generated/ios/swift/Func_void_double_double.swift +47 -0
  138. package/nitrogen/generated/ios/swift/Func_void_double_double_std__optional_bool_.swift +54 -0
  139. package/nitrogen/generated/ios/swift/Func_void_std__string_Playlist_std__optional_QueueOperation_.swift +47 -0
  140. package/nitrogen/generated/ios/swift/Func_void_std__vector_Playlist__std__optional_QueueOperation_.swift +47 -0
  141. package/nitrogen/generated/ios/swift/HybridAudioRoutePickerSpec.swift +56 -0
  142. package/nitrogen/generated/ios/swift/HybridAudioRoutePickerSpec_cxx.swift +130 -0
  143. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec.swift +68 -0
  144. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec_cxx.swift +349 -0
  145. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +69 -0
  146. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +325 -0
  147. package/nitrogen/generated/ios/swift/PlayerConfig.swift +115 -0
  148. package/nitrogen/generated/ios/swift/PlayerState.swift +181 -0
  149. package/nitrogen/generated/ios/swift/Playlist.swift +182 -0
  150. package/nitrogen/generated/ios/swift/QueueOperation.swift +48 -0
  151. package/nitrogen/generated/ios/swift/Reason.swift +48 -0
  152. package/nitrogen/generated/ios/swift/TrackItem.swift +147 -0
  153. package/nitrogen/generated/ios/swift/TrackPlayerState.swift +44 -0
  154. package/nitrogen/generated/ios/swift/Variant_NullType_Playlist.swift +18 -0
  155. package/nitrogen/generated/ios/swift/Variant_NullType_String.swift +18 -0
  156. package/nitrogen/generated/ios/swift/Variant_NullType_TrackItem.swift +18 -0
  157. package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.cpp +22 -0
  158. package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.hpp +63 -0
  159. package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.cpp +22 -0
  160. package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.hpp +65 -0
  161. package/nitrogen/generated/shared/c++/HybridAudioRoutePickerSpec.cpp +21 -0
  162. package/nitrogen/generated/shared/c++/HybridAudioRoutePickerSpec.hpp +62 -0
  163. package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.cpp +33 -0
  164. package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.hpp +87 -0
  165. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +34 -0
  166. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +91 -0
  167. package/nitrogen/generated/shared/c++/PlayerConfig.hpp +83 -0
  168. package/nitrogen/generated/shared/c++/PlayerState.hpp +103 -0
  169. package/nitrogen/generated/shared/c++/Playlist.hpp +97 -0
  170. package/nitrogen/generated/shared/c++/QueueOperation.hpp +84 -0
  171. package/nitrogen/generated/shared/c++/Reason.hpp +84 -0
  172. package/nitrogen/generated/shared/c++/TAudioDevice.hpp +87 -0
  173. package/nitrogen/generated/shared/c++/TrackItem.hpp +102 -0
  174. package/nitrogen/generated/shared/c++/TrackPlayerState.hpp +80 -0
  175. package/package.json +172 -0
  176. package/react-native.config.js +16 -0
  177. package/src/hooks/index.ts +6 -0
  178. package/src/hooks/useAndroidAutoConnection.ts +30 -0
  179. package/src/hooks/useAudioDevices.ts +64 -0
  180. package/src/hooks/useOnChangeTrack.ts +24 -0
  181. package/src/hooks/useOnPlaybackProgressChange.ts +30 -0
  182. package/src/hooks/useOnPlaybackStateChange.ts +24 -0
  183. package/src/hooks/useOnSeek.ts +25 -0
  184. package/src/index.ts +47 -0
  185. package/src/specs/AndroidAutoMediaLibrary.nitro.ts +22 -0
  186. package/src/specs/AudioDevices.nitro.ts +25 -0
  187. package/src/specs/AudioRoutePicker.nitro.ts +9 -0
  188. package/src/specs/TrackPlayer.nitro.ts +81 -0
  189. package/src/types/AndroidAutoMediaLibrary.ts +58 -0
  190. package/src/types/PlayerQueue.ts +38 -0
  191. package/src/utils/androidAutoMediaLibrary.ts +66 -0
@@ -0,0 +1,352 @@
1
+ @file:Suppress("ktlint:standard:filename")
2
+
3
+ package com.margelo.nitro.nitroplayer.media
4
+
5
+ import android.content.Context
6
+ import android.net.Uri
7
+ import android.os.Bundle
8
+ import android.support.v4.media.MediaBrowserCompat
9
+ import android.support.v4.media.MediaDescriptionCompat
10
+ import androidx.media.MediaBrowserServiceCompat
11
+ import androidx.media.utils.MediaConstants
12
+ import com.margelo.nitro.nitroplayer.TrackItem
13
+ import com.margelo.nitro.nitroplayer.core.TrackPlayerCore
14
+ import com.margelo.nitro.nitroplayer.playlist.Playlist
15
+ import kotlinx.coroutines.CoroutineScope
16
+ import kotlinx.coroutines.Dispatchers
17
+ import kotlinx.coroutines.SupervisorJob
18
+ import kotlinx.coroutines.cancel
19
+ import kotlinx.coroutines.launch
20
+
21
+ class NitroPlayerMediaBrowserService : MediaBrowserServiceCompat() {
22
+ companion object {
23
+ private const val ROOT_ID = "root"
24
+ private const val EMPTY_ROOT_ID = "empty_root"
25
+ private const val PLAYLIST_PREFIX = "playlist_"
26
+
27
+ var trackPlayerCore: TrackPlayerCore? = null
28
+ var mediaSessionManager: MediaSessionManager? = null
29
+ var isAndroidAutoEnabled: Boolean = false
30
+ var isAndroidAutoConnected: Boolean = false
31
+
32
+ @Volatile
33
+ private var instance: NitroPlayerMediaBrowserService? = null
34
+
35
+ fun getInstance(): NitroPlayerMediaBrowserService? = instance
36
+ }
37
+
38
+ private val serviceScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
39
+ private lateinit var mediaLibraryManager: MediaLibraryManager
40
+
41
+ override fun onCreate() {
42
+ super.onCreate()
43
+
44
+ instance = this
45
+ mediaLibraryManager = MediaLibraryManager.getInstance(applicationContext)
46
+
47
+ // Use the existing MediaSession from MediaSessionManager
48
+ // This ensures the session is already connected to the ExoPlayer
49
+ try {
50
+ val session = mediaSessionManager?.mediaSession
51
+ if (session != null) {
52
+ // Convert Media3 MediaSession to MediaSessionCompat for MediaBrowserService
53
+ sessionToken = session.sessionCompatToken
54
+ println("🎵 NitroPlayerMediaBrowserService: MediaSession token set successfully")
55
+ } else {
56
+ println("⚠️ NitroPlayerMediaBrowserService: MediaSession not available yet")
57
+ }
58
+ } catch (e: Exception) {
59
+ println("❌ NitroPlayerMediaBrowserService: Error setting session token - ${e.message}")
60
+ e.printStackTrace()
61
+ }
62
+
63
+ println("🚀 NitroPlayerMediaBrowserService: Service created")
64
+ }
65
+
66
+ override fun onDestroy() {
67
+ super.onDestroy()
68
+ instance = null
69
+ serviceScope.cancel()
70
+ println("🛑 NitroPlayerMediaBrowserService: Service destroyed")
71
+ }
72
+
73
+ override fun onGetRoot(
74
+ clientPackageName: String,
75
+ clientUid: Int,
76
+ rootHints: Bundle?,
77
+ ): BrowserRoot? {
78
+ println("📂 NitroPlayerMediaBrowserService: onGetRoot called from $clientPackageName")
79
+
80
+ // Check if Android Auto is enabled
81
+ if (!isAndroidAutoEnabled) {
82
+ println("⚠️ NitroPlayerMediaBrowserService: Android Auto not enabled")
83
+ return BrowserRoot(EMPTY_ROOT_ID, null)
84
+ }
85
+
86
+ // Allow Android Auto and other media browsers to connect
87
+ // Enable grid layout for playlists at root level
88
+ val extras =
89
+ Bundle().apply {
90
+ putInt(
91
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
92
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM,
93
+ )
94
+ }
95
+ println("✅ NitroPlayerMediaBrowserService: Allowing connection from $clientPackageName with grid layout")
96
+ return BrowserRoot(ROOT_ID, extras)
97
+ }
98
+
99
+ override fun onLoadChildren(
100
+ parentId: String,
101
+ result: Result<MutableList<MediaBrowserCompat.MediaItem>>,
102
+ ) {
103
+ println("📂 NitroPlayerMediaBrowserService: onLoadChildren called for parentId: $parentId")
104
+
105
+ if (!isAndroidAutoEnabled) {
106
+ println("⚠️ NitroPlayerMediaBrowserService: Android Auto not enabled, returning empty")
107
+ result.sendResult(mutableListOf())
108
+ return
109
+ }
110
+
111
+ when {
112
+ parentId == ROOT_ID -> {
113
+ // Return root items from media library
114
+ result.detach()
115
+
116
+ serviceScope.launch {
117
+ try {
118
+ val library = mediaLibraryManager.getMediaLibrary()
119
+
120
+ if (library == null) {
121
+ // Fallback: show playlists if no media library is set
122
+ println("⚠️ NitroPlayerMediaBrowserService: No media library set, using fallback playlists")
123
+ val mediaItems = loadFallbackPlaylists()
124
+ result.sendResult(mediaItems)
125
+ return@launch
126
+ }
127
+
128
+ val mediaItems = mutableListOf<MediaBrowserCompat.MediaItem>()
129
+
130
+ library.rootItems.forEach { item ->
131
+ mediaItems.add(convertToMediaBrowserItem(item, library.layoutType))
132
+ }
133
+
134
+ println("✅ NitroPlayerMediaBrowserService: Returning ${mediaItems.size} root items")
135
+ result.sendResult(mediaItems)
136
+ } catch (e: Exception) {
137
+ println("❌ NitroPlayerMediaBrowserService: Error loading root items - ${e.message}")
138
+ e.printStackTrace()
139
+ result.sendResult(mutableListOf())
140
+ }
141
+ }
142
+ }
143
+
144
+ parentId.startsWith(PLAYLIST_PREFIX) -> {
145
+ // Return tracks in a specific playlist
146
+ result.detach()
147
+
148
+ val playlistId = parentId.removePrefix(PLAYLIST_PREFIX)
149
+
150
+ serviceScope.launch {
151
+ try {
152
+ val playlist = trackPlayerCore?.getPlaylistManager()?.getPlaylist(playlistId)
153
+
154
+ if (playlist == null) {
155
+ println("⚠️ NitroPlayerMediaBrowserService: Playlist '$playlistId' not found")
156
+ result.sendResult(mutableListOf())
157
+ return@launch
158
+ }
159
+
160
+ val mediaItems = mutableListOf<MediaBrowserCompat.MediaItem>()
161
+
162
+ playlist.tracks.forEachIndexed { index, track ->
163
+ val extras =
164
+ Bundle().apply {
165
+ putString("playlistId", playlistId)
166
+ putInt("trackIndex", index)
167
+ putString("trackId", track.id)
168
+ }
169
+
170
+ val description =
171
+ MediaDescriptionCompat
172
+ .Builder()
173
+ .setMediaId("$playlistId:${track.id}")
174
+ .setTitle(track.title)
175
+ .setSubtitle(track.artist)
176
+ .setDescription(track.album)
177
+ .setIconUri(track.artwork?.asSecondOrNull()?.let { Uri.parse(it) })
178
+ .setExtras(extras)
179
+ .build()
180
+
181
+ mediaItems.add(
182
+ MediaBrowserCompat.MediaItem(
183
+ description,
184
+ MediaBrowserCompat.MediaItem.FLAG_PLAYABLE,
185
+ ),
186
+ )
187
+ }
188
+
189
+ println("✅ NitroPlayerMediaBrowserService: Returning ${mediaItems.size} tracks from playlist '$playlistId'")
190
+ result.sendResult(mediaItems)
191
+ } catch (e: Exception) {
192
+ println("❌ NitroPlayerMediaBrowserService: Error loading playlist tracks - ${e.message}")
193
+ e.printStackTrace()
194
+ result.sendResult(mutableListOf())
195
+ }
196
+ }
197
+ }
198
+
199
+ parentId == EMPTY_ROOT_ID -> {
200
+ result.sendResult(mutableListOf())
201
+ }
202
+
203
+ else -> {
204
+ // Handle custom folder IDs from media library
205
+ result.detach()
206
+
207
+ serviceScope.launch {
208
+ try {
209
+ val children = mediaLibraryManager.getChildrenById(parentId)
210
+
211
+ if (children == null) {
212
+ println("⚠️ NitroPlayerMediaBrowserService: No children found for parentId: $parentId")
213
+ result.sendResult(mutableListOf())
214
+ return@launch
215
+ }
216
+
217
+ val library = mediaLibraryManager.getMediaLibrary()
218
+ val defaultLayout = library?.layoutType ?: LayoutType.LIST
219
+ val mediaItems =
220
+ children
221
+ .map { item ->
222
+ convertToMediaBrowserItem(item, defaultLayout)
223
+ }.toMutableList()
224
+
225
+ println("✅ NitroPlayerMediaBrowserService: Returning ${mediaItems.size} items for parentId: $parentId")
226
+ result.sendResult(mediaItems)
227
+ } catch (e: Exception) {
228
+ println("❌ NitroPlayerMediaBrowserService: Error loading children - ${e.message}")
229
+ e.printStackTrace()
230
+ result.sendResult(mutableListOf())
231
+ }
232
+ }
233
+ }
234
+ }
235
+ }
236
+
237
+ fun onPlaylistsUpdated() {
238
+ try {
239
+ notifyChildrenChanged(ROOT_ID)
240
+ println("📢 NitroPlayerMediaBrowserService: Notified Android Auto of playlist update")
241
+ } catch (e: Exception) {
242
+ println("⚠️ NitroPlayerMediaBrowserService: Error notifying children changed: ${e.message}")
243
+ }
244
+ }
245
+
246
+ fun onPlaylistUpdated(playlistId: String) {
247
+ try {
248
+ notifyChildrenChanged("$PLAYLIST_PREFIX$playlistId")
249
+ println("📢 NitroPlayerMediaBrowserService: Notified Android Auto of playlist '$playlistId' update")
250
+ } catch (e: Exception) {
251
+ println("⚠️ NitroPlayerMediaBrowserService: Error notifying playlist changed: ${e.message}")
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Convert MediaLibrary MediaItem to Android Auto MediaBrowserCompat.MediaItem
257
+ */
258
+ private fun convertToMediaBrowserItem(
259
+ item: MediaItem,
260
+ defaultLayout: LayoutType,
261
+ ): MediaBrowserCompat.MediaItem {
262
+ val layoutType = item.layoutType ?: defaultLayout
263
+ val contentStyle =
264
+ when (layoutType) {
265
+ LayoutType.GRID -> MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
266
+ LayoutType.LIST -> MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
267
+ }
268
+
269
+ val extras =
270
+ Bundle().apply {
271
+ putInt(
272
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
273
+ contentStyle,
274
+ )
275
+ }
276
+
277
+ // Determine the media ID based on item type
278
+ val mediaId =
279
+ when (item.mediaType) {
280
+ MediaType.PLAYLIST -> {
281
+ // For playlist items, use the playlist reference
282
+ if (item.playlistId != null) {
283
+ "$PLAYLIST_PREFIX${item.playlistId}"
284
+ } else {
285
+ item.id
286
+ }
287
+ }
288
+
289
+ else -> {
290
+ item.id
291
+ }
292
+ }
293
+
294
+ val description =
295
+ MediaDescriptionCompat
296
+ .Builder()
297
+ .setMediaId(mediaId)
298
+ .setTitle(item.title)
299
+ .setSubtitle(item.subtitle)
300
+ .setIconUri(item.iconUrl?.let { Uri.parse(it) })
301
+ .setExtras(extras)
302
+ .build()
303
+
304
+ // Determine if browsable or playable
305
+ val flag =
306
+ if (item.isPlayable && item.mediaType == MediaType.AUDIO) {
307
+ MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
308
+ } else {
309
+ MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
310
+ }
311
+
312
+ return MediaBrowserCompat.MediaItem(description, flag)
313
+ }
314
+
315
+ /**
316
+ * Fallback: Load playlists when no media library is set
317
+ */
318
+ private suspend fun loadFallbackPlaylists(): MutableList<MediaBrowserCompat.MediaItem> {
319
+ val playlists = trackPlayerCore?.getAllPlaylists() ?: emptyList()
320
+ val mediaItems = mutableListOf<MediaBrowserCompat.MediaItem>()
321
+
322
+ playlists.forEach { playlist ->
323
+ val extras =
324
+ Bundle().apply {
325
+ putInt(
326
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
327
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM,
328
+ )
329
+ }
330
+
331
+ val description =
332
+ MediaDescriptionCompat
333
+ .Builder()
334
+ .setMediaId("$PLAYLIST_PREFIX${playlist.id}")
335
+ .setTitle(playlist.name)
336
+ .setSubtitle(playlist.description ?: "${playlist.tracks.size} tracks")
337
+ .setIconUri(playlist.artwork?.let { Uri.parse(it) })
338
+ .setExtras(extras)
339
+ .build()
340
+
341
+ mediaItems.add(
342
+ MediaBrowserCompat.MediaItem(
343
+ description,
344
+ MediaBrowserCompat.MediaItem.FLAG_BROWSABLE,
345
+ ),
346
+ )
347
+ }
348
+
349
+ println("✅ NitroPlayerMediaBrowserService: Loaded ${mediaItems.size} playlists as fallback")
350
+ return mediaItems
351
+ }
352
+ }
@@ -0,0 +1,58 @@
1
+ package com.margelo.nitro.nitroplayer.media
2
+
3
+ import com.margelo.nitro.nitroplayer.Variant_NullType_String
4
+
5
+ /**
6
+ * Layout type for Android Auto media browser
7
+ */
8
+ enum class LayoutType {
9
+ GRID,
10
+ LIST,
11
+ }
12
+
13
+ /**
14
+ * Media type for different kinds of content
15
+ */
16
+ enum class MediaType {
17
+ FOLDER,
18
+ AUDIO,
19
+ PLAYLIST,
20
+ }
21
+
22
+ /**
23
+ * Media item that can be displayed in Android Auto
24
+ */
25
+ data class MediaItem(
26
+ /** Unique identifier for the media item */
27
+ val id: String,
28
+ /** Display title */
29
+ val title: String,
30
+ /** Optional subtitle/description */
31
+ val subtitle: String? = null,
32
+ /** Optional icon/artwork URL */
33
+ val iconUrl: String? = null,
34
+ /** Whether this item can be played directly */
35
+ val isPlayable: Boolean = false,
36
+ /** Media type */
37
+ val mediaType: MediaType = MediaType.FOLDER,
38
+ /** Reference to playlist ID (for playlist items) */
39
+ val playlistId: String? = null,
40
+ /** Child items for browsable folders */
41
+ val children: List<MediaItem>? = null,
42
+ /** Layout type for folder items (overrides library default) */
43
+ val layoutType: LayoutType? = null,
44
+ )
45
+
46
+ /**
47
+ * Media library structure for Android Auto
48
+ */
49
+ data class MediaLibrary(
50
+ /** Layout type for the media browser (applies to all folders by default) */
51
+ val layoutType: LayoutType = LayoutType.LIST,
52
+ /** Root level media items */
53
+ val rootItems: List<MediaItem> = emptyList(),
54
+ /** Optional app name to display */
55
+ val appName: String? = null,
56
+ /** Optional app icon URL */
57
+ val appIconUrl: String? = null,
58
+ )
@@ -0,0 +1,77 @@
1
+ package com.margelo.nitro.nitroplayer.media
2
+
3
+ import android.content.Context
4
+
5
+ /**
6
+ * Manages the Android Auto media library structure
7
+ */
8
+ class MediaLibraryManager private constructor(
9
+ context: Context,
10
+ ) {
11
+ @Volatile
12
+ private var mediaLibrary: MediaLibrary? = null
13
+
14
+ companion object {
15
+ @Volatile
16
+ @Suppress("ktlint:standard:property-naming")
17
+ private var INSTANCE: MediaLibraryManager? = null
18
+
19
+ fun getInstance(context: Context): MediaLibraryManager =
20
+ INSTANCE ?: synchronized(this) {
21
+ INSTANCE ?: MediaLibraryManager(context).also { INSTANCE = it }
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Set the media library structure
27
+ */
28
+ fun setMediaLibrary(library: MediaLibrary) {
29
+ mediaLibrary = library
30
+ println("📚 MediaLibraryManager: Media library set with ${library.rootItems.size} root items")
31
+ }
32
+
33
+ /**
34
+ * Get the current media library
35
+ */
36
+ fun getMediaLibrary(): MediaLibrary? = mediaLibrary
37
+
38
+ /**
39
+ * Get media item by ID (searches recursively)
40
+ */
41
+ fun getMediaItemById(itemId: String): MediaItem? {
42
+ val library = mediaLibrary ?: return null
43
+ return findMediaItemRecursive(library.rootItems, itemId)
44
+ }
45
+
46
+ private fun findMediaItemRecursive(
47
+ items: List<MediaItem>,
48
+ targetId: String,
49
+ ): MediaItem? {
50
+ for (item in items) {
51
+ if (item.id == targetId) {
52
+ return item
53
+ }
54
+ item.children?.let { children ->
55
+ val found = findMediaItemRecursive(children, targetId)
56
+ if (found != null) return found
57
+ }
58
+ }
59
+ return null
60
+ }
61
+
62
+ /**
63
+ * Get children of a media item by ID
64
+ */
65
+ fun getChildrenById(parentId: String): List<MediaItem>? {
66
+ val item = getMediaItemById(parentId)
67
+ return item?.children
68
+ }
69
+
70
+ /**
71
+ * Clear the media library
72
+ */
73
+ fun clear() {
74
+ mediaLibrary = null
75
+ println("📚 MediaLibraryManager: Media library cleared")
76
+ }
77
+ }
@@ -0,0 +1,73 @@
1
+ package com.margelo.nitro.nitroplayer.media
2
+
3
+ import org.json.JSONArray
4
+ import org.json.JSONObject
5
+
6
+ /**
7
+ * Parser for MediaLibrary JSON structure
8
+ */
9
+ object MediaLibraryParser {
10
+ fun fromJson(json: String): MediaLibrary {
11
+ val jsonObject = JSONObject(json)
12
+
13
+ val layoutType =
14
+ when (jsonObject.optString("layoutType", "list").lowercase()) {
15
+ "grid" -> LayoutType.GRID
16
+ else -> LayoutType.LIST
17
+ }
18
+
19
+ val rootItems = parseMediaItems(jsonObject.optJSONArray("rootItems"))
20
+
21
+ return MediaLibrary(
22
+ layoutType = layoutType,
23
+ rootItems = rootItems,
24
+ appName = jsonObject.optString("appName").takeIf { it.isNotEmpty() },
25
+ appIconUrl = jsonObject.optString("appIconUrl").takeIf { it.isNotEmpty() },
26
+ )
27
+ }
28
+
29
+ private fun parseMediaItems(jsonArray: JSONArray?): List<MediaItem> {
30
+ if (jsonArray == null) return emptyList()
31
+
32
+ val items = mutableListOf<MediaItem>()
33
+
34
+ for (i in 0 until jsonArray.length()) {
35
+ val itemJson = jsonArray.optJSONObject(i) ?: continue
36
+ items.add(parseMediaItem(itemJson))
37
+ }
38
+
39
+ return items
40
+ }
41
+
42
+ private fun parseMediaItem(jsonObject: JSONObject): MediaItem {
43
+ val mediaTypeStr = jsonObject.optString("mediaType", "folder").lowercase()
44
+ val mediaType =
45
+ when (mediaTypeStr) {
46
+ "audio" -> MediaType.AUDIO
47
+ "playlist" -> MediaType.PLAYLIST
48
+ else -> MediaType.FOLDER
49
+ }
50
+
51
+ val layoutTypeStr = jsonObject.optString("layoutType", "").lowercase()
52
+ val layoutType =
53
+ when (layoutTypeStr) {
54
+ "grid" -> LayoutType.GRID
55
+ "list" -> LayoutType.LIST
56
+ else -> null
57
+ }
58
+
59
+ val children = parseMediaItems(jsonObject.optJSONArray("children"))
60
+
61
+ return MediaItem(
62
+ id = jsonObject.getString("id"),
63
+ title = jsonObject.getString("title"),
64
+ subtitle = jsonObject.optString("subtitle").takeIf { it.isNotEmpty() },
65
+ iconUrl = jsonObject.optString("iconUrl").takeIf { it.isNotEmpty() },
66
+ isPlayable = jsonObject.optBoolean("isPlayable", false),
67
+ mediaType = mediaType,
68
+ playlistId = jsonObject.optString("playlistId").takeIf { it.isNotEmpty() },
69
+ children = children.takeIf { it.isNotEmpty() },
70
+ layoutType = layoutType,
71
+ )
72
+ }
73
+ }