react-native-mp3-player 1.0.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.
Files changed (328) hide show
  1. package/LICENSE +205 -0
  2. package/README.md +88 -0
  3. package/android/build.gradle +114 -0
  4. package/android/proguard-rules.txt +0 -0
  5. package/android/src/main/AndroidManifest.xml +27 -0
  6. package/android/src/main/java/com/doublesymmetry/trackplayer/HeadlessJsMediaService.kt +199 -0
  7. package/android/src/main/java/com/doublesymmetry/trackplayer/TrackPlayer.kt +30 -0
  8. package/android/src/main/java/com/doublesymmetry/trackplayer/extensions/AudioPlayerStateExt.kt +19 -0
  9. package/android/src/main/java/com/doublesymmetry/trackplayer/extensions/EnumExtensions.kt +5 -0
  10. package/android/src/main/java/com/doublesymmetry/trackplayer/extensions/NumberExt.kt +13 -0
  11. package/android/src/main/java/com/doublesymmetry/trackplayer/model/MetadataAdapter.kt +227 -0
  12. package/android/src/main/java/com/doublesymmetry/trackplayer/model/NowPlayingMetadata.kt +16 -0
  13. package/android/src/main/java/com/doublesymmetry/trackplayer/model/PlaybackMetadata.kt +203 -0
  14. package/android/src/main/java/com/doublesymmetry/trackplayer/model/State.kt +13 -0
  15. package/android/src/main/java/com/doublesymmetry/trackplayer/model/Track.kt +67 -0
  16. package/android/src/main/java/com/doublesymmetry/trackplayer/model/TrackAudioItem.kt +18 -0
  17. package/android/src/main/java/com/doublesymmetry/trackplayer/model/TrackMetadata.kt +38 -0
  18. package/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicEvents.kt +65 -0
  19. package/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt +775 -0
  20. package/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt +1251 -0
  21. package/android/src/main/java/com/doublesymmetry/trackplayer/utils/AppForegroundTracker.kt +35 -0
  22. package/android/src/main/java/com/doublesymmetry/trackplayer/utils/BundleUtils.kt +147 -0
  23. package/android/src/main/java/com/doublesymmetry/trackplayer/utils/CoilBitmapLoader.kt +64 -0
  24. package/android/src/main/java/com/doublesymmetry/trackplayer/utils/MediaItemBuilder.kt +41 -0
  25. package/android/src/main/java/com/doublesymmetry/trackplayer/utils/RejectionException.kt +11 -0
  26. package/android/src/main/java/com/lovegaoshi/kotlinaudio/event/EventHolder.kt +30 -0
  27. package/android/src/main/java/com/lovegaoshi/kotlinaudio/event/PlayerEventHolder.kt +124 -0
  28. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/AudioContentType.kt +10 -0
  29. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/AudioItem.kt +133 -0
  30. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/AudioItemTransitionReason.kt +33 -0
  31. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/AudioPlayerState.kt +30 -0
  32. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/BufferConfig.kt +8 -0
  33. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/CacheConfig.kt +17 -0
  34. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/Capability.kt +19 -0
  35. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/CustomButton.kt +19 -0
  36. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/FocusChangeData.kt +3 -0
  37. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/MediaSessionCallback.kt +17 -0
  38. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PlayWhenReadyChangeData.kt +3 -0
  39. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PlaybackEndedReason.kt +5 -0
  40. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PlaybackError.kt +6 -0
  41. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PlayerConfig.kt +36 -0
  42. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PlayerOptions.kt +39 -0
  43. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PositionChangedReason.kt +39 -0
  44. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/RepeatMode.kt +16 -0
  45. package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/WakeMode.kt +7 -0
  46. package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/AudioPlayer.kt +689 -0
  47. package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/ForwardingPlayer.java +1124 -0
  48. package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/QueuedAudioPlayer.kt +295 -0
  49. package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/Buffer.kt +34 -0
  50. package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/Cache.kt +47 -0
  51. package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/FocusManager.kt +59 -0
  52. package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/MediaFactory.kt +165 -0
  53. package/android/src/main/java/com/lovegaoshi/kotlinaudio/service/MusicService.kt +127 -0
  54. package/android/src/main/java/com/lovegaoshi/kotlinaudio/utils/Utils.kt +113 -0
  55. package/android/src/main/res/drawable/baseline_repeat_24.xml +5 -0
  56. package/android/src/main/res/drawable/baseline_repeat_one_24.xml +5 -0
  57. package/android/src/main/res/drawable/forward.xml +5 -0
  58. package/android/src/main/res/drawable/heart_24px.xml +5 -0
  59. package/android/src/main/res/drawable/hearte_24px.xml +5 -0
  60. package/android/src/main/res/drawable/ifl_24px.xml +5 -0
  61. package/android/src/main/res/drawable/rewind.xml +5 -0
  62. package/android/src/main/res/drawable/shuffle_24px.xml +5 -0
  63. package/android/src/main/res/values/strings.xml +5 -0
  64. package/android/src/main/res/xml/automotive_app_desc.xml +3 -0
  65. package/ios/Example/SwiftAudio/Assets.xcassets/22AMI.imageset/22AMillion.jpg +0 -0
  66. package/ios/Example/SwiftAudio/Assets.xcassets/22AMI.imageset/Contents.json +21 -0
  67. package/ios/Example/SwiftAudio/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  68. package/ios/Example/SwiftAudio/Assets.xcassets/AppIcon.appiconset/Contents.json +58 -0
  69. package/ios/Example/SwiftAudio/Assets.xcassets/Contents.json +6 -0
  70. package/ios/Example/SwiftAudio/Assets.xcassets/cover.imageset/Contents.json +21 -0
  71. package/ios/Example/SwiftAudio/Assets.xcassets/cover.imageset/cover.jpg +0 -0
  72. package/ios/Example/SwiftAudio/AudioController.swift +46 -0
  73. package/ios/Example/SwiftAudio/Extensions.swift +22 -0
  74. package/ios/Example/SwiftAudio/PlayerView.swift +172 -0
  75. package/ios/Example/SwiftAudio/PlayerViewModel.swift +120 -0
  76. package/ios/Example/SwiftAudio/Preview Content/Preview Assets.xcassets/Contents.json +6 -0
  77. package/ios/Example/SwiftAudio/QueueView.swift +65 -0
  78. package/ios/Example/SwiftAudio/SwiftAudio.entitlements +12 -0
  79. package/ios/Example/SwiftAudio/SwiftAudioApp.swift +17 -0
  80. package/ios/Example/SwiftAudio.xcodeproj/project.pbxproj +412 -0
  81. package/ios/Example/SwiftAudio.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  82. package/ios/Example/SwiftAudio.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  83. package/ios/RNTrackPlayer/Models/Capabilities.swift +52 -0
  84. package/ios/RNTrackPlayer/Models/MediaURL.swift +38 -0
  85. package/ios/RNTrackPlayer/Models/MetadataAdapter.swift +147 -0
  86. package/ios/RNTrackPlayer/Models/PitchAlgorithms.swift +13 -0
  87. package/ios/RNTrackPlayer/Models/SessionCategories.swift +106 -0
  88. package/ios/RNTrackPlayer/Models/State.swift +26 -0
  89. package/ios/RNTrackPlayer/Models/Track.swift +140 -0
  90. package/ios/RNTrackPlayer/RNTrackPlayer.swift +941 -0
  91. package/ios/RNTrackPlayer/Support/RNTrackPlayer-Bridging-Header.h +7 -0
  92. package/ios/RNTrackPlayer/TrackPlayer.h +14 -0
  93. package/ios/RNTrackPlayer/TrackPlayer.mm +246 -0
  94. package/ios/RNTrackPlayer/Utils/EventType.swift +44 -0
  95. package/ios/RNTrackPlayer/Utils/Metadata.swift +60 -0
  96. package/ios/SwiftAudioEx/Package.swift +20 -0
  97. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AVPlayerWrapper/AVPlayerWrapper.swift +521 -0
  98. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AVPlayerWrapper/AVPlayerWrapperDelegate.swift +27 -0
  99. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AVPlayerWrapper/AVPlayerWrapperProtocol.swift +69 -0
  100. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AVPlayerWrapper/AVPlayerWrapperState.swift +43 -0
  101. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioItem.swift +158 -0
  102. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioPlayer.swift +459 -0
  103. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioPlayerError.swift +26 -0
  104. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioSessionController/AudioSession.swift +33 -0
  105. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioSessionController/AudioSessionController.swift +135 -0
  106. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioTap.swift +99 -0
  107. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Event.swift +155 -0
  108. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/MediaItemProperty.swift +95 -0
  109. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/NowPlayingInfoCenter.swift +17 -0
  110. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/NowPlayingInfoController.swift +73 -0
  111. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/NowPlayingInfoControllerProtocol.swift +26 -0
  112. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/NowPlayingInfoKeyValue.swift +14 -0
  113. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/NowPlayingInfoProperty.swift +234 -0
  114. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Observer/AVPlayerItemNotificationObserver.swift +102 -0
  115. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Observer/AVPlayerItemObserver.swift +136 -0
  116. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Observer/AVPlayerObserver.swift +120 -0
  117. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Observer/AVPlayerTimeObserver.swift +112 -0
  118. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/QueueManager.swift +356 -0
  119. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/QueuedAudioPlayer.swift +236 -0
  120. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/RemoteCommandController/RemoteCommand.swift +170 -0
  121. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/RemoteCommandController/RemoteCommandController.swift +206 -0
  122. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/RepeatMode.swift +15 -0
  123. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/TimeEventFrequency.swift +26 -0
  124. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Utils/DispatchQueueType.swift +18 -0
  125. package/ios/SwiftAudioEx/Sources/SwiftAudioEx/WaveformAudioTap.swift +159 -0
  126. package/lib/specs/NativeTrackPlayer.d.ts +129 -0
  127. package/lib/specs/NativeTrackPlayer.js +4 -0
  128. package/lib/src/constants/AndroidAudioContentType.d.ts +35 -0
  129. package/lib/src/constants/AndroidAudioContentType.js +36 -0
  130. package/lib/src/constants/AndroidAutoContentStyle.d.ts +10 -0
  131. package/lib/src/constants/AndroidAutoContentStyle.js +11 -0
  132. package/lib/src/constants/AppKilledPlaybackBehavior.d.ts +17 -0
  133. package/lib/src/constants/AppKilledPlaybackBehavior.js +18 -0
  134. package/lib/src/constants/Capability.d.ts +17 -0
  135. package/lib/src/constants/Capability.js +19 -0
  136. package/lib/src/constants/Event.d.ts +163 -0
  137. package/lib/src/constants/Event.js +164 -0
  138. package/lib/src/constants/IOSCategory.d.ts +36 -0
  139. package/lib/src/constants/IOSCategory.js +37 -0
  140. package/lib/src/constants/IOSCategoryMode.d.ts +47 -0
  141. package/lib/src/constants/IOSCategoryMode.js +48 -0
  142. package/lib/src/constants/IOSCategoryOptions.d.ts +44 -0
  143. package/lib/src/constants/IOSCategoryOptions.js +45 -0
  144. package/lib/src/constants/MediaItemPlayable.d.ts +4 -0
  145. package/lib/src/constants/MediaItemPlayable.js +5 -0
  146. package/lib/src/constants/PitchAlgorithm.d.ts +14 -0
  147. package/lib/src/constants/PitchAlgorithm.js +16 -0
  148. package/lib/src/constants/RatingType.d.ts +8 -0
  149. package/lib/src/constants/RatingType.js +10 -0
  150. package/lib/src/constants/RepeatMode.d.ts +8 -0
  151. package/lib/src/constants/RepeatMode.js +10 -0
  152. package/lib/src/constants/State.d.ts +34 -0
  153. package/lib/src/constants/State.js +35 -0
  154. package/lib/src/constants/TrackType.d.ts +6 -0
  155. package/lib/src/constants/TrackType.js +7 -0
  156. package/lib/src/constants/index.d.ts +14 -0
  157. package/lib/src/constants/index.js +14 -0
  158. package/lib/src/hooks/index.d.ts +6 -0
  159. package/lib/src/hooks/index.js +6 -0
  160. package/lib/src/hooks/useActiveTrack.d.ts +2 -0
  161. package/lib/src/hooks/useActiveTrack.js +28 -0
  162. package/lib/src/hooks/useAppIsInBackground.d.ts +1 -0
  163. package/lib/src/hooks/useAppIsInBackground.js +16 -0
  164. package/lib/src/hooks/useIsPlaying.d.ts +35 -0
  165. package/lib/src/hooks/useIsPlaying.js +50 -0
  166. package/lib/src/hooks/usePlayWhenReady.d.ts +1 -0
  167. package/lib/src/hooks/usePlayWhenReady.js +27 -0
  168. package/lib/src/hooks/usePlaybackState.d.ts +10 -0
  169. package/lib/src/hooks/usePlaybackState.js +35 -0
  170. package/lib/src/hooks/useProgress.d.ts +7 -0
  171. package/lib/src/hooks/useProgress.js +55 -0
  172. package/lib/src/hooks/useTrackPlayerEvents.d.ts +8 -0
  173. package/lib/src/hooks/useTrackPlayerEvents.js +30 -0
  174. package/lib/src/index.d.ts +5 -0
  175. package/lib/src/index.js +5 -0
  176. package/lib/src/interfaces/AndroidAutoBrowseTree.d.ts +5 -0
  177. package/lib/src/interfaces/AndroidAutoBrowseTree.js +1 -0
  178. package/lib/src/interfaces/AndroidOptions.d.ts +41 -0
  179. package/lib/src/interfaces/AndroidOptions.js +1 -0
  180. package/lib/src/interfaces/CustomButtons.d.ts +5 -0
  181. package/lib/src/interfaces/CustomButtons.js +1 -0
  182. package/lib/src/interfaces/FeedbackOptions.d.ts +6 -0
  183. package/lib/src/interfaces/FeedbackOptions.js +1 -0
  184. package/lib/src/interfaces/MediaItem.d.ts +18 -0
  185. package/lib/src/interfaces/MediaItem.js +1 -0
  186. package/lib/src/interfaces/MetadataOptions.d.ts +3 -0
  187. package/lib/src/interfaces/MetadataOptions.js +1 -0
  188. package/lib/src/interfaces/NowPlayingMetadata.d.ts +4 -0
  189. package/lib/src/interfaces/NowPlayingMetadata.js +1 -0
  190. package/lib/src/interfaces/PlaybackState.d.ts +8 -0
  191. package/lib/src/interfaces/PlaybackState.js +1 -0
  192. package/lib/src/interfaces/PlayerOptions.d.ts +127 -0
  193. package/lib/src/interfaces/PlayerOptions.js +1 -0
  194. package/lib/src/interfaces/Progress.d.ts +15 -0
  195. package/lib/src/interfaces/Progress.js +1 -0
  196. package/lib/src/interfaces/ResourceObject.d.ts +1 -0
  197. package/lib/src/interfaces/ResourceObject.js +1 -0
  198. package/lib/src/interfaces/ServiceHandler.d.ts +1 -0
  199. package/lib/src/interfaces/ServiceHandler.js +1 -0
  200. package/lib/src/interfaces/Track.d.ts +21 -0
  201. package/lib/src/interfaces/Track.js +1 -0
  202. package/lib/src/interfaces/TrackMetadataBase.d.ts +28 -0
  203. package/lib/src/interfaces/TrackMetadataBase.js +1 -0
  204. package/lib/src/interfaces/UpdateOptions.d.ts +52 -0
  205. package/lib/src/interfaces/UpdateOptions.js +1 -0
  206. package/lib/src/interfaces/events/AudioMetadataReceivedEvent.d.ts +33 -0
  207. package/lib/src/interfaces/events/AudioMetadataReceivedEvent.js +1 -0
  208. package/lib/src/interfaces/events/ControllerConnectedEvent.d.ts +8 -0
  209. package/lib/src/interfaces/events/ControllerConnectedEvent.js +1 -0
  210. package/lib/src/interfaces/events/EventPayloadByEvent.d.ts +73 -0
  211. package/lib/src/interfaces/events/EventPayloadByEvent.js +1 -0
  212. package/lib/src/interfaces/events/PlaybackActiveTrackChangedEvent.d.ts +24 -0
  213. package/lib/src/interfaces/events/PlaybackActiveTrackChangedEvent.js +1 -0
  214. package/lib/src/interfaces/events/PlaybackAnimatedVolumeChangedEvent.d.ts +4 -0
  215. package/lib/src/interfaces/events/PlaybackAnimatedVolumeChangedEvent.js +1 -0
  216. package/lib/src/interfaces/events/PlaybackErrorEvent.d.ts +6 -0
  217. package/lib/src/interfaces/events/PlaybackErrorEvent.js +1 -0
  218. package/lib/src/interfaces/events/PlaybackMetadataReceivedEvent.d.ts +16 -0
  219. package/lib/src/interfaces/events/PlaybackMetadataReceivedEvent.js +1 -0
  220. package/lib/src/interfaces/events/PlaybackPlayWhenReadyChangedEvent.d.ts +4 -0
  221. package/lib/src/interfaces/events/PlaybackPlayWhenReadyChangedEvent.js +1 -0
  222. package/lib/src/interfaces/events/PlaybackProgressUpdatedEvent.d.ts +4 -0
  223. package/lib/src/interfaces/events/PlaybackProgressUpdatedEvent.js +1 -0
  224. package/lib/src/interfaces/events/PlaybackQueueEndedEvent.d.ts +9 -0
  225. package/lib/src/interfaces/events/PlaybackQueueEndedEvent.js +1 -0
  226. package/lib/src/interfaces/events/PlaybackResumeEvent.d.ts +3 -0
  227. package/lib/src/interfaces/events/PlaybackResumeEvent.js +1 -0
  228. package/lib/src/interfaces/events/PlaybackTrackChangedEvent.d.ts +11 -0
  229. package/lib/src/interfaces/events/PlaybackTrackChangedEvent.js +1 -0
  230. package/lib/src/interfaces/events/PlayerErrorEvent.d.ts +6 -0
  231. package/lib/src/interfaces/events/PlayerErrorEvent.js +1 -0
  232. package/lib/src/interfaces/events/RemoteBrowseEvent.d.ts +4 -0
  233. package/lib/src/interfaces/events/RemoteBrowseEvent.js +1 -0
  234. package/lib/src/interfaces/events/RemoteCustomActionEvent.d.ts +7 -0
  235. package/lib/src/interfaces/events/RemoteCustomActionEvent.js +1 -0
  236. package/lib/src/interfaces/events/RemoteDuckEvent.d.ts +13 -0
  237. package/lib/src/interfaces/events/RemoteDuckEvent.js +1 -0
  238. package/lib/src/interfaces/events/RemoteJumpBackwardEvent.d.ts +8 -0
  239. package/lib/src/interfaces/events/RemoteJumpBackwardEvent.js +1 -0
  240. package/lib/src/interfaces/events/RemoteJumpForwardEvent.d.ts +8 -0
  241. package/lib/src/interfaces/events/RemoteJumpForwardEvent.js +1 -0
  242. package/lib/src/interfaces/events/RemotePlayIdEvent.d.ts +4 -0
  243. package/lib/src/interfaces/events/RemotePlayIdEvent.js +1 -0
  244. package/lib/src/interfaces/events/RemotePlaySearchEvent.d.ts +9 -0
  245. package/lib/src/interfaces/events/RemotePlaySearchEvent.js +1 -0
  246. package/lib/src/interfaces/events/RemoteSeekEvent.d.ts +4 -0
  247. package/lib/src/interfaces/events/RemoteSeekEvent.js +1 -0
  248. package/lib/src/interfaces/events/RemoteSetRatingEvent.d.ts +4 -0
  249. package/lib/src/interfaces/events/RemoteSetRatingEvent.js +1 -0
  250. package/lib/src/interfaces/events/RemoteSkipEvent.d.ts +3 -0
  251. package/lib/src/interfaces/events/RemoteSkipEvent.js +1 -0
  252. package/lib/src/interfaces/events/index.d.ts +18 -0
  253. package/lib/src/interfaces/events/index.js +18 -0
  254. package/lib/src/interfaces/index.d.ts +15 -0
  255. package/lib/src/interfaces/index.js +15 -0
  256. package/lib/src/resolveAssetSource.d.ts +2 -0
  257. package/lib/src/resolveAssetSource.js +3 -0
  258. package/lib/src/trackPlayer.d.ts +347 -0
  259. package/lib/src/trackPlayer.js +592 -0
  260. package/package.json +94 -0
  261. package/react-native-mp3-player.podspec +22 -0
  262. package/specs/NativeTrackPlayer.ts +148 -0
  263. package/src/constants/AndroidAudioContentType.ts +35 -0
  264. package/src/constants/AndroidAutoContentStyle.ts +10 -0
  265. package/src/constants/AppKilledPlaybackBehavior.ts +19 -0
  266. package/src/constants/Capability.ts +19 -0
  267. package/src/constants/Event.ts +164 -0
  268. package/src/constants/IOSCategory.ts +36 -0
  269. package/src/constants/IOSCategoryMode.ts +47 -0
  270. package/src/constants/IOSCategoryOptions.ts +44 -0
  271. package/src/constants/MediaItemPlayable.ts +4 -0
  272. package/src/constants/PitchAlgorithm.ts +16 -0
  273. package/src/constants/RatingType.ts +10 -0
  274. package/src/constants/RepeatMode.ts +10 -0
  275. package/src/constants/State.ts +34 -0
  276. package/src/constants/TrackType.ts +6 -0
  277. package/src/constants/index.ts +14 -0
  278. package/src/hooks/index.ts +6 -0
  279. package/src/hooks/useActiveTrack.ts +36 -0
  280. package/src/hooks/useAppIsInBackground.ts +20 -0
  281. package/src/hooks/useIsPlaying.ts +56 -0
  282. package/src/hooks/usePlayWhenReady.ts +37 -0
  283. package/src/hooks/usePlaybackState.ts +45 -0
  284. package/src/hooks/useProgress.ts +64 -0
  285. package/src/hooks/useTrackPlayerEvents.ts +48 -0
  286. package/src/index.ts +7 -0
  287. package/src/interfaces/AndroidAutoBrowseTree.ts +6 -0
  288. package/src/interfaces/AndroidOptions.ts +48 -0
  289. package/src/interfaces/CustomButtons.ts +6 -0
  290. package/src/interfaces/FeedbackOptions.ts +7 -0
  291. package/src/interfaces/MediaItem.ts +19 -0
  292. package/src/interfaces/MetadataOptions.ts +4 -0
  293. package/src/interfaces/NowPlayingMetadata.ts +5 -0
  294. package/src/interfaces/PlaybackState.ts +11 -0
  295. package/src/interfaces/PlayerOptions.ts +133 -0
  296. package/src/interfaces/Progress.ts +15 -0
  297. package/src/interfaces/ResourceObject.ts +1 -0
  298. package/src/interfaces/ServiceHandler.ts +1 -0
  299. package/src/interfaces/Track.ts +23 -0
  300. package/src/interfaces/TrackMetadataBase.ts +29 -0
  301. package/src/interfaces/UpdateOptions.ts +59 -0
  302. package/src/interfaces/events/AudioMetadataReceivedEvent.ts +37 -0
  303. package/src/interfaces/events/ControllerConnectedEvent.ts +9 -0
  304. package/src/interfaces/events/EventPayloadByEvent.ts +76 -0
  305. package/src/interfaces/events/PlaybackActiveTrackChangedEvent.ts +29 -0
  306. package/src/interfaces/events/PlaybackAnimatedVolumeChangedEvent.ts +4 -0
  307. package/src/interfaces/events/PlaybackErrorEvent.ts +6 -0
  308. package/src/interfaces/events/PlaybackMetadataReceivedEvent.ts +16 -0
  309. package/src/interfaces/events/PlaybackPlayWhenReadyChangedEvent.ts +4 -0
  310. package/src/interfaces/events/PlaybackProgressUpdatedEvent.ts +5 -0
  311. package/src/interfaces/events/PlaybackQueueEndedEvent.ts +9 -0
  312. package/src/interfaces/events/PlaybackResumeEvent.ts +5 -0
  313. package/src/interfaces/events/PlaybackTrackChangedEvent.ts +11 -0
  314. package/src/interfaces/events/PlayerErrorEvent.ts +6 -0
  315. package/src/interfaces/events/RemoteBrowseEvent.ts +4 -0
  316. package/src/interfaces/events/RemoteCustomActionEvent.ts +7 -0
  317. package/src/interfaces/events/RemoteDuckEvent.ts +13 -0
  318. package/src/interfaces/events/RemoteJumpBackwardEvent.ts +8 -0
  319. package/src/interfaces/events/RemoteJumpForwardEvent.ts +8 -0
  320. package/src/interfaces/events/RemotePlayIdEvent.ts +4 -0
  321. package/src/interfaces/events/RemotePlaySearchEvent.ts +21 -0
  322. package/src/interfaces/events/RemoteSeekEvent.ts +4 -0
  323. package/src/interfaces/events/RemoteSetRatingEvent.ts +5 -0
  324. package/src/interfaces/events/RemoteSkipEvent.ts +3 -0
  325. package/src/interfaces/events/index.ts +18 -0
  326. package/src/interfaces/index.ts +15 -0
  327. package/src/resolveAssetSource.ts +3 -0
  328. package/src/trackPlayer.ts +768 -0
@@ -0,0 +1,1251 @@
1
+ package com.doublesymmetry.trackplayer.service
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.*
5
+ import android.content.Intent
6
+ import android.graphics.Bitmap
7
+ import android.os.Binder
8
+ import android.os.Build
9
+ import android.os.Bundle
10
+ import android.os.IBinder
11
+ import android.provider.Settings
12
+ import android.view.KeyEvent
13
+ import androidx.annotation.MainThread
14
+ import androidx.annotation.OptIn
15
+ import androidx.media.utils.MediaConstants
16
+ import androidx.media3.common.C
17
+ import androidx.media3.common.Player
18
+ import androidx.media3.common.util.UnstableApi
19
+ import androidx.media3.session.CacheBitmapLoader
20
+ import androidx.media3.session.LibraryResult
21
+ import androidx.media3.common.MediaItem
22
+ import androidx.media3.common.util.BitmapLoader
23
+ import androidx.media3.exoplayer.ExoPlayer
24
+ import androidx.media3.session.CommandButton
25
+ import androidx.media3.session.MediaSession
26
+ import androidx.media3.session.SessionCommand
27
+ import androidx.media3.session.SessionCommands
28
+ import androidx.media3.session.SessionResult
29
+ import com.lovegaoshi.kotlinaudio.models.*
30
+ import com.lovegaoshi.kotlinaudio.player.QueuedAudioPlayer
31
+ import com.doublesymmetry.trackplayer.HeadlessJsMediaService
32
+ import com.doublesymmetry.trackplayer.extensions.NumberExt.Companion.toMilliseconds
33
+ import com.doublesymmetry.trackplayer.extensions.NumberExt.Companion.toSeconds
34
+ import com.doublesymmetry.trackplayer.extensions.asLibState
35
+ import com.doublesymmetry.trackplayer.extensions.find
36
+ import com.doublesymmetry.trackplayer.model.MetadataAdapter
37
+ import com.doublesymmetry.trackplayer.model.PlaybackMetadata
38
+ import com.doublesymmetry.trackplayer.model.Track
39
+ import com.doublesymmetry.trackplayer.model.TrackAudioItem
40
+ import com.doublesymmetry.trackplayer.module.MusicEvents
41
+ import com.doublesymmetry.trackplayer.module.MusicEvents.Companion.METADATA_PAYLOAD_KEY
42
+ import com.doublesymmetry.trackplayer.R as TrackPlayerR
43
+ import com.doublesymmetry.trackplayer.utils.BundleUtils
44
+ import com.doublesymmetry.trackplayer.utils.BundleUtils.setRating
45
+ import com.doublesymmetry.trackplayer.utils.CoilBitmapLoader
46
+ import com.doublesymmetry.trackplayer.utils.buildMediaItem
47
+ import com.facebook.react.bridge.Arguments
48
+ import com.facebook.react.jstasks.HeadlessJsTaskConfig
49
+ import com.google.common.collect.ImmutableList
50
+ import com.google.common.util.concurrent.Futures
51
+ import com.google.common.util.concurrent.ListenableFuture
52
+ import kotlinx.coroutines.*
53
+ import kotlinx.coroutines.flow.flow
54
+ import timber.log.Timber
55
+ import java.util.concurrent.TimeUnit
56
+ import kotlin.system.exitProcess
57
+ import androidx.core.net.toUri
58
+
59
+ @OptIn(UnstableApi::class)
60
+ @MainThread
61
+ class MusicService : HeadlessJsMediaService() {
62
+ private lateinit var player: QueuedAudioPlayer
63
+ private val binder = MusicBinder()
64
+ private val scope = MainScope()
65
+ private lateinit var fakePlayer: ExoPlayer
66
+ private lateinit var mediaSession: MediaLibrarySession
67
+ private var progressUpdateJob: Job? = null
68
+ var mediaTree: Map<String, List<MediaItem>> = HashMap()
69
+ var mediaTreeStyle: List<Int> = listOf(
70
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM,
71
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
72
+ private var sessionCommands: SessionCommands? = null
73
+ private var playerCommands: Player.Commands? = null
74
+ private var customLayout: List<CommandButton> = listOf()
75
+ private var lastWake: Long = 0
76
+ var lastConnectedPackage: String = ""
77
+
78
+ fun crossFadePrepare(previous: Boolean = false, seekTo: Double = 0.0) {
79
+ player.crossFadePrepare(previous, seekTo)
80
+ }
81
+
82
+ fun switchExoPlayer(
83
+ fadeDuration: Long = 2500,
84
+ fadeInterval: Long = 20,
85
+ fadeToVolume: Float = 1f,
86
+ waitUntil: Long = 0
87
+ ) {
88
+ player.switchExoPlayer(
89
+ fadeDuration = fadeDuration,
90
+ fadeInterval = fadeInterval,
91
+ fadeToVolume = fadeToVolume,
92
+ waitUntil = waitUntil,
93
+ playerOperation = {
94
+ player.play()
95
+ emitPlaybackTrackChangedEvents(null, null, 0.0)
96
+ })
97
+
98
+ }
99
+
100
+ fun acquireWakeLock() { acquireWakeLockNow(this) }
101
+
102
+ fun abandonWakeLock() { wakeLock?.release() }
103
+
104
+ fun getBitmapLoader(): BitmapLoader {
105
+ return mediaSession.bitmapLoader
106
+ }
107
+
108
+ fun getCurrentBitmap(): ListenableFuture<Bitmap>? {
109
+ return player.exoPlayer.currentMediaItem?.mediaMetadata?.let {
110
+ mediaSession.bitmapLoader.loadBitmapFromMetadata(
111
+ it
112
+ )
113
+ }
114
+ }
115
+
116
+ @ExperimentalCoroutinesApi
117
+ override fun onCreate() {
118
+ Timber.plant(Timber.DebugTree())
119
+ Timber.tag("APM").d("RNTP musicservice created.")
120
+ fakePlayer = ExoPlayer.Builder(this).build()
121
+ val openAppIntent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
122
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
123
+ // Add the Uri data so apps can identify that it was a notification click
124
+ data = "trackplayer://notification.click".toUri()
125
+ action = Intent.ACTION_VIEW
126
+ }
127
+ // Unique session id per process to avoid "Session ID must be unique" crash after aggressive OS kill (e.g. Oppo/Xiaomi)
128
+ val sessionId = "rntp_media_session_${android.os.Process.myPid()}"
129
+ mediaSession = MediaLibrarySession.Builder(this, fakePlayer, APMMediaSessionCallback() )
130
+ .setId(sessionId)
131
+ .setBitmapLoader(CacheBitmapLoader(CoilBitmapLoader(this)))
132
+ // https://github.com/androidx/media/issues/1218
133
+ .setSessionActivity(PendingIntent.getActivity(this, 0, openAppIntent, getPendingIntentFlags()))
134
+ .build()
135
+ super.onCreate()
136
+ }
137
+
138
+ /**
139
+ * Use [appKilledPlaybackBehavior] instead.
140
+ */
141
+ @Deprecated("This will be removed soon")
142
+ var stoppingAppPausesPlayback = true
143
+ private set
144
+
145
+ enum class AppKilledPlaybackBehavior(val string: String) {
146
+ CONTINUE_PLAYBACK("continue-playback"),
147
+ PAUSE_PLAYBACK("pause-playback"),
148
+ STOP_PLAYBACK_AND_REMOVE_NOTIFICATION("stop-playback-and-remove-notification")
149
+ }
150
+
151
+ private var appKilledPlaybackBehavior = AppKilledPlaybackBehavior.STOP_PLAYBACK_AND_REMOVE_NOTIFICATION
152
+ private var stopForegroundGracePeriod: Int = DEFAULT_STOP_FOREGROUND_GRACE_PERIOD
153
+
154
+ val tracks: List<Track>
155
+ get() = player.items.map { (it as TrackAudioItem).track }
156
+
157
+ val currentTrack: Track
158
+ get() {
159
+ return try {
160
+ (player.currentItem as TrackAudioItem).track
161
+ } catch (e: Exception) {
162
+ Track(this, Bundle(), 0)
163
+ }
164
+ }
165
+
166
+ val state
167
+ get() = player.playerState
168
+
169
+
170
+ val playbackError
171
+ get() = player.playbackError
172
+
173
+ val event
174
+ get() = player.playerEventHolder
175
+
176
+ var playWhenReady: Boolean
177
+ get() = player.playWhenReady
178
+ set(value) {
179
+ player.playWhenReady = value
180
+ }
181
+
182
+ private var latestOptions: Bundle? = null
183
+ private var compactCapabilities: List<Capability> = emptyList()
184
+ private var commandStarted = false
185
+
186
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
187
+ Timber.tag("APM").d("onStartCommand: ${intent?.action}, ${intent?.`package`}")
188
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
189
+ // HACK: this is not supposed to be here. I definitely screwed up. but Why?
190
+ onMediaKeyEvent(intent)
191
+ }
192
+ // HACK: Why is onPlay triggering onStartCommand??
193
+ if (!commandStarted) {
194
+ commandStarted = true
195
+ super.onStartCommand(intent, flags, startId)
196
+ }
197
+ return START_STICKY
198
+ }
199
+
200
+ @MainThread
201
+ fun setupPlayer(playerOptions: Bundle?) {
202
+ if (this::player.isInitialized) {
203
+ print("Player was initialized. Prevent re-initializing again")
204
+ return
205
+ }
206
+ Timber.tag("APM").d("RNTP musicservice set up")
207
+ val mPlayerOptions = PlayerOptions(
208
+ crossfade = playerOptions?.getBoolean(CROSSFADE, false) ?: false,
209
+ cacheSize = playerOptions?.getDouble(MAX_CACHE_SIZE_KEY)?.toLong() ?: 0,
210
+ audioContentType = when(playerOptions?.getString(ANDROID_AUDIO_CONTENT_TYPE)) {
211
+ "music" -> C.AUDIO_CONTENT_TYPE_MUSIC
212
+ "speech" -> C.AUDIO_CONTENT_TYPE_SPEECH
213
+ "sonification" -> C.AUDIO_CONTENT_TYPE_SONIFICATION
214
+ "movie" -> C.AUDIO_CONTENT_TYPE_MOVIE
215
+ "unknown" -> C.AUDIO_CONTENT_TYPE_UNKNOWN
216
+ else -> C.AUDIO_CONTENT_TYPE_MUSIC
217
+ },
218
+ wakeMode = playerOptions?.getInt(WAKE_MODE, 0) ?: 0,
219
+ handleAudioBecomingNoisy = playerOptions?.getBoolean(HANDLE_NOISY, true) ?: true,
220
+ alwaysShowNext = playerOptions?.getBoolean(ALWAYS_SHOW_NEXT, true) ?: true,
221
+ handleAudioFocus = playerOptions?.getBoolean(AUTO_HANDLE_INTERRUPTIONS) ?: true,
222
+ bufferOptions = BufferOptions(
223
+ playerOptions?.getDouble(MIN_BUFFER_KEY)?.toMilliseconds()?.toInt(),
224
+ playerOptions?.getDouble(MAX_BUFFER_KEY)?.toMilliseconds()?.toInt(),
225
+ playerOptions?.getDouble(PLAY_BUFFER_KEY)?.toMilliseconds()?.toInt(),
226
+ playerOptions?.getDouble(BACK_BUFFER_KEY)?.toMilliseconds()?.toInt(),
227
+ ),
228
+
229
+ skipSilence = playerOptions?.getBoolean(SKIP_SILENCE) ?: false
230
+ )
231
+ player = QueuedAudioPlayer(this@MusicService, mPlayerOptions)
232
+ fakePlayer.release()
233
+ mediaSession.player = player.player
234
+ observeEvents()
235
+ }
236
+
237
+ @MainThread
238
+ fun updateOptions(options: Bundle) {
239
+ latestOptions = options
240
+ val androidOptions = options.getBundle(ANDROID_OPTIONS_KEY)
241
+
242
+ if (androidOptions?.containsKey(PARSE_EMBEDDED_ARTWORK) == true) {
243
+ player.parseEmbeddedArtwork = androidOptions.getBoolean(PARSE_EMBEDDED_ARTWORK)
244
+ }
245
+ if (androidOptions?.containsKey(AUDIO_OFFLOAD_KEY) == true) {
246
+ player.setAudioOffload(androidOptions.getBoolean(AUDIO_OFFLOAD_KEY))
247
+ }
248
+ if (androidOptions?.containsKey(SKIP_SILENCE) == true) {
249
+ player.skipSilence = androidOptions.getBoolean(SKIP_SILENCE)
250
+ }
251
+
252
+ appKilledPlaybackBehavior =
253
+ AppKilledPlaybackBehavior::string.find(androidOptions?.getString(APP_KILLED_PLAYBACK_BEHAVIOR_KEY)) ?:
254
+ AppKilledPlaybackBehavior.CONTINUE_PLAYBACK
255
+
256
+ BundleUtils.getIntOrNull(androidOptions, STOP_FOREGROUND_GRACE_PERIOD_KEY)?.let { stopForegroundGracePeriod = it }
257
+
258
+ // TODO: This handles a deprecated flag. Should be removed soon.
259
+ options.getBoolean(STOPPING_APP_PAUSES_PLAYBACK_KEY).let {
260
+ stoppingAppPausesPlayback = options.getBoolean(STOPPING_APP_PAUSES_PLAYBACK_KEY)
261
+ if (stoppingAppPausesPlayback) {
262
+ appKilledPlaybackBehavior = AppKilledPlaybackBehavior.PAUSE_PLAYBACK
263
+ }
264
+ }
265
+
266
+ player.alwaysPauseOnInterruption = androidOptions?.getBoolean(PAUSE_ON_INTERRUPTION_KEY) ?: false
267
+ player.shuffleMode = androidOptions?.getBoolean(SHUFFLE_KEY) ?: false
268
+
269
+ // setup progress update events if configured
270
+ progressUpdateJob?.cancel()
271
+ val updateInterval = BundleUtils.getDoubleOrNull(options, PROGRESS_UPDATE_EVENT_INTERVAL_KEY)
272
+ if (updateInterval != null && updateInterval > 0) {
273
+ progressUpdateJob = scope.launch {
274
+ progressUpdateEventFlow(updateInterval).collect { emit(MusicEvents.PLAYBACK_PROGRESS_UPDATED, it) }
275
+ }
276
+ }
277
+
278
+ val capabilities = options.getIntegerArrayList("capabilities")?.map { Capability.entries[it] } ?: emptyList()
279
+ var notificationCapabilities = options.getIntegerArrayList("notificationCapabilities")?.map { Capability.entries[it] } ?: emptyList()
280
+ compactCapabilities = options.getIntegerArrayList("compactCapabilities")?.map { Capability.entries[it] } ?: emptyList()
281
+ val customActions = options.getBundle(CUSTOM_ACTIONS_KEY)
282
+ val customActionsList = customActions?.getStringArrayList(CUSTOM_ACTIONS_LIST_KEY)
283
+ if (notificationCapabilities.isEmpty()) notificationCapabilities = capabilities
284
+
285
+ val playerCommandsBuilder = Player.Commands.Builder().addAll(
286
+ // HACK: without COMMAND_GET_CURRENT_MEDIA_ITEM, notification cannot be created
287
+ Player.COMMAND_GET_CURRENT_MEDIA_ITEM,
288
+ Player.COMMAND_GET_TRACKS,
289
+ Player.COMMAND_GET_TIMELINE,
290
+ Player.COMMAND_GET_METADATA,
291
+ Player.COMMAND_GET_AUDIO_ATTRIBUTES,
292
+ Player.COMMAND_GET_VOLUME,
293
+ Player.COMMAND_GET_DEVICE_VOLUME,
294
+ Player.COMMAND_GET_TEXT,
295
+ Player.COMMAND_SEEK_TO_MEDIA_ITEM,
296
+ Player.COMMAND_SET_MEDIA_ITEM,
297
+ Player.COMMAND_PREPARE,
298
+ Player.COMMAND_RELEASE,
299
+ Player.COMMAND_CHANGE_MEDIA_ITEMS,
300
+ )
301
+ notificationCapabilities.forEach {
302
+ when (it) {
303
+ Capability.PLAY, Capability.PAUSE -> {
304
+ playerCommandsBuilder.add(Player.COMMAND_PLAY_PAUSE)
305
+ }
306
+ Capability.STOP -> {
307
+ playerCommandsBuilder.add(Player.COMMAND_STOP)
308
+ }
309
+ Capability.SKIP_TO_NEXT -> {
310
+ playerCommandsBuilder.add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
311
+ playerCommandsBuilder.add(Player.COMMAND_SEEK_TO_NEXT)
312
+ }
313
+ Capability.SKIP_TO_PREVIOUS -> {
314
+ playerCommandsBuilder.add(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
315
+ playerCommandsBuilder.add(Player.COMMAND_SEEK_TO_PREVIOUS)
316
+ }
317
+ Capability.JUMP_FORWARD -> {
318
+ playerCommandsBuilder.add(Player.COMMAND_SEEK_FORWARD)
319
+ }
320
+ Capability.JUMP_BACKWARD -> {
321
+ playerCommandsBuilder.add(Player.COMMAND_SEEK_BACK)
322
+ }
323
+ Capability.SEEK_TO -> {
324
+ playerCommandsBuilder.add(Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)
325
+ }
326
+ else -> { }
327
+ }
328
+ }
329
+ customLayout = customActionsList?.map {
330
+ v -> CustomButton(
331
+ displayName = v,
332
+ sessionCommand = v,
333
+ iconRes = BundleUtils.getCustomIcon(
334
+ this,
335
+ customActions,
336
+ v,
337
+ TrackPlayerR.drawable.ifl_24px
338
+ )
339
+ ).commandButton
340
+ } ?: ImmutableList.of()
341
+
342
+ val sessionCommandsBuilder = MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
343
+ customLayout.forEach {
344
+ v ->
345
+ v.sessionCommand?.let { sessionCommandsBuilder.add(it) }
346
+ }
347
+
348
+ sessionCommands = sessionCommandsBuilder.build()
349
+ playerCommands = playerCommandsBuilder.build()
350
+
351
+ if (mediaSession.mediaNotificationControllerInfo != null) {
352
+ // https://github.com/androidx/media/blob/c35a9d62baec57118ea898e271ac66819399649b/demos/session_service/src/main/java/androidx/media3/demo/session/DemoMediaLibrarySessionCallback.kt#L107
353
+ mediaSession.setCustomLayout(
354
+ mediaSession.mediaNotificationControllerInfo!!,
355
+ customLayout
356
+ )
357
+ mediaSession.setAvailableCommands(
358
+ mediaSession.mediaNotificationControllerInfo!!,
359
+ sessionCommands!!,
360
+ playerCommands!!
361
+ )
362
+ }
363
+ }
364
+
365
+ @MainThread
366
+ private fun progressUpdateEventFlow(interval: Double) = flow {
367
+ while (true) {
368
+ if (player.isPlaying) {
369
+ val bundle = progressUpdateEvent()
370
+ emit(bundle)
371
+ }
372
+
373
+ delay((interval * 1000).toLong())
374
+ }
375
+ }
376
+
377
+ @MainThread
378
+ private suspend fun progressUpdateEvent(): Bundle {
379
+ return withContext(Dispatchers.Main) {
380
+ Bundle().apply {
381
+ putDouble(POSITION_KEY, player.position.toSeconds())
382
+ putDouble(DURATION_KEY, player.duration.toSeconds())
383
+ putDouble(BUFFERED_POSITION_KEY, player.bufferedPosition.toSeconds())
384
+ putInt(TRACK_KEY, player.currentIndex)
385
+ }
386
+ }
387
+ }
388
+
389
+ @MainThread
390
+ fun add(track: Track) {
391
+ add(listOf(track))
392
+ }
393
+
394
+ @MainThread
395
+ fun add(tracks: List<Track>) {
396
+ try {
397
+ Timber.tag("RNTP").d("MusicService.add: Converting ${tracks.size} tracks to AudioItems")
398
+ val items = tracks.map { it.toAudioItem() }
399
+
400
+ Timber.tag("RNTP").d("MusicService.add: Calling player.add with ${items.size} items")
401
+ player.add(items)
402
+
403
+ Timber.tag("RNTP").d("MusicService.add: Successfully added tracks to player")
404
+ } catch (exception: Exception) {
405
+ Timber.tag("RNTP").e(exception, "MusicService.add: Error adding tracks")
406
+ throw exception
407
+ }
408
+ }
409
+
410
+ @MainThread
411
+ fun add(tracks: List<Track>, atIndex: Int) {
412
+ val items = tracks.map { it.toAudioItem() }
413
+ player.add(items, atIndex)
414
+ }
415
+
416
+ @MainThread
417
+ fun load(track: Track) {
418
+ player.load(track.toAudioItem())
419
+ }
420
+
421
+ @MainThread
422
+ fun move(fromIndex: Int, toIndex: Int) {
423
+ player.move(fromIndex, toIndex)
424
+ }
425
+
426
+ @MainThread
427
+ fun remove(index: Int) {
428
+ remove(listOf(index))
429
+ }
430
+
431
+ @MainThread
432
+ fun remove(indexes: List<Int>) {
433
+ player.remove(indexes)
434
+ }
435
+
436
+ @MainThread
437
+ fun clear() {
438
+ player.clear()
439
+ }
440
+
441
+ @MainThread
442
+ fun play() {
443
+ player.play()
444
+ }
445
+
446
+ @MainThread
447
+ fun pause() {
448
+ player.pause()
449
+ }
450
+
451
+ @MainThread
452
+ fun stop() {
453
+ player.stop()
454
+ }
455
+
456
+ @MainThread
457
+ fun removeUpcomingTracks() {
458
+ player.removeUpcomingItems()
459
+ }
460
+
461
+ @MainThread
462
+ fun removePreviousTracks() {
463
+ player.removePreviousItems()
464
+ }
465
+
466
+ @MainThread
467
+ fun skip(index: Int) {
468
+ player.jumpToItem(index)
469
+ }
470
+
471
+ @MainThread
472
+ fun skipToNext() {
473
+ player.next()
474
+ }
475
+
476
+ @MainThread
477
+ fun skipToPrevious() {
478
+ player.previous()
479
+ }
480
+
481
+ @MainThread
482
+ fun seekTo(seconds: Float) {
483
+ player.seek((seconds * 1000).toLong(), TimeUnit.MILLISECONDS)
484
+ }
485
+
486
+ @MainThread
487
+ fun seekBy(offset: Float) {
488
+ player.seekBy((offset.toLong()), TimeUnit.SECONDS)
489
+ }
490
+
491
+ @MainThread
492
+ fun retry() {
493
+ player.prepare()
494
+ }
495
+
496
+ @MainThread
497
+ fun getCurrentTrackIndex(): Int = player.currentIndex
498
+
499
+ @MainThread
500
+ fun getRate(): Float = player.playbackSpeed
501
+
502
+ @MainThread
503
+ fun setRate(value: Float) {
504
+ player.playbackSpeed = value
505
+ }
506
+
507
+ @MainThread
508
+ fun getPitch(): Float = player.playbackPitch
509
+
510
+ @MainThread
511
+ fun setPitch(value: Float) {
512
+ player.playbackPitch = value
513
+ }
514
+
515
+ @MainThread
516
+ fun getRepeatMode(): RepeatMode = player.repeatMode
517
+
518
+ @MainThread
519
+ fun setRepeatMode(value: RepeatMode) {
520
+ player.repeatMode = value
521
+ }
522
+
523
+ @MainThread
524
+ fun getVolume(): Float = player.volume
525
+
526
+ @MainThread
527
+ fun setVolume(value: Float) {
528
+ player.volume = value
529
+ }
530
+
531
+ @MainThread
532
+ fun setAnimatedVolume(value: Float, duration: Long = 500L, interval: Long = 20L, emitEventMsg: String = ""): Deferred<Unit> {
533
+ val eventMsgBundle = Bundle()
534
+ eventMsgBundle.putString(DATA_KEY, emitEventMsg)
535
+ return player.fadeVolume(value, duration, interval) {
536
+ emit(
537
+ MusicEvents.PLAYBACK_ANIMATED_VOLUME_CHANGED,
538
+ eventMsgBundle
539
+ )
540
+ }
541
+ }
542
+
543
+ fun fadeOutPause (duration: Long = 500L, interval: Long = 20L) {
544
+ player.fadeVolume(0f, duration, interval) {
545
+ player.pause()
546
+ }
547
+ }
548
+
549
+ fun fadeOutNext (duration: Long = 500L, interval: Long = 20L, toVolume: Float = 1f) {
550
+ player.fadeVolume(0f, duration, interval) {
551
+ player.next()
552
+ player.fadeVolume(toVolume, duration, interval)
553
+ }
554
+ }
555
+
556
+ fun fadeOutPrevious (duration: Long = 500L, interval: Long = 20L, toVolume: Float = 1f) {
557
+ player.fadeVolume(0f, duration, interval) {
558
+ player.previous()
559
+ player.fadeVolume(toVolume, duration, interval)
560
+ }
561
+ }
562
+
563
+ fun fadeOutJump (index: Int, duration: Long = 500L, interval: Long = 20L, toVolume: Float = 1f) {
564
+ player.fadeVolume(0f, duration, interval) {
565
+ player.jumpToItem(index)
566
+ player.fadeVolume(toVolume, duration, interval)
567
+ }
568
+ }
569
+ @MainThread
570
+ fun getDurationInSeconds(): Double = player.duration.toSeconds()
571
+
572
+ @MainThread
573
+ fun getPositionInSeconds(): Double = player.position.toSeconds()
574
+
575
+ @MainThread
576
+ fun getBufferedPositionInSeconds(): Double = player.bufferedPosition.toSeconds()
577
+
578
+ @MainThread
579
+ fun getPlayerStateBundle(state: AudioPlayerState): Bundle {
580
+ val bundle = Bundle()
581
+ bundle.putString(STATE_KEY, state.asLibState.state)
582
+ if (state == AudioPlayerState.ERROR) {
583
+ bundle.putBundle(ERROR_KEY, getPlaybackErrorBundle())
584
+ }
585
+ return bundle
586
+ }
587
+
588
+ @MainThread
589
+ fun updateMetadataForTrack(index: Int, track: Track) {
590
+ player.replaceItem(index, track.toAudioItem())
591
+ }
592
+
593
+ @MainThread
594
+ fun updateNowPlayingMetadata(track: Track) {
595
+ updateMetadataForTrack(player.currentIndex, track)
596
+ }
597
+
598
+ @MainThread
599
+ fun clearNotificationMetadata() {
600
+ }
601
+
602
+ private fun emitPlaybackTrackChangedEvents(
603
+ index: Int?,
604
+ previousIndex: Int?,
605
+ oldPosition: Double
606
+ ) {
607
+ val a = Bundle()
608
+ a.putDouble(POSITION_KEY, oldPosition)
609
+ if (index != null) {
610
+ a.putInt(NEXT_TRACK_KEY, index)
611
+ }
612
+
613
+ if (previousIndex != null) {
614
+ a.putInt(TRACK_KEY, previousIndex)
615
+ }
616
+
617
+ emit(MusicEvents.PLAYBACK_TRACK_CHANGED, a)
618
+
619
+ val b = Bundle()
620
+ b.putDouble("lastPosition", oldPosition)
621
+ if (tracks.isNotEmpty()) {
622
+ b.putInt("index", player.currentIndex)
623
+ b.putBundle("track", tracks[player.currentIndex].originalItem)
624
+ if (previousIndex != null) {
625
+ b.putInt("lastIndex", previousIndex)
626
+ b.putBundle("lastTrack", tracks[previousIndex].originalItem)
627
+ }
628
+ }
629
+ emit(MusicEvents.PLAYBACK_ACTIVE_TRACK_CHANGED, b)
630
+ }
631
+
632
+ private fun emitQueueEndedEvent() {
633
+ val bundle = Bundle()
634
+ bundle.putInt(TRACK_KEY, player.currentIndex)
635
+ bundle.putDouble(POSITION_KEY, player.position.toSeconds())
636
+ emit(MusicEvents.PLAYBACK_QUEUE_ENDED, bundle)
637
+ }
638
+
639
+ @Suppress("DEPRECATION")
640
+ fun isForegroundService(): Boolean {
641
+ val manager = baseContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager
642
+ for (service in manager.getRunningServices(Int.MAX_VALUE)) {
643
+ if (MusicService::class.java.name == service.service.className) {
644
+ return service.foreground
645
+ }
646
+ }
647
+ Timber.e("isForegroundService found no matching service")
648
+ return false
649
+ }
650
+
651
+ @MainThread
652
+ private fun observeEvents() {
653
+ scope.launch {
654
+ event.stateChange.collect {
655
+ emit(MusicEvents.PLAYBACK_STATE, getPlayerStateBundle(it))
656
+
657
+ if (it == AudioPlayerState.ENDED && player.nextItem == null) {
658
+ emitQueueEndedEvent()
659
+ }
660
+ }
661
+ }
662
+
663
+ scope.launch {
664
+ event.audioItemTransition.collect {
665
+ if (it !is AudioItemTransitionReason.REPEAT) {
666
+ emitPlaybackTrackChangedEvents(
667
+ player.currentIndex,
668
+ player.previousIndex,
669
+ (it?.oldPosition ?: 0).toSeconds()
670
+ )
671
+ }
672
+ }
673
+ }
674
+
675
+ scope.launch {
676
+ event.onAudioFocusChanged.collect {
677
+ Bundle().apply {
678
+ putBoolean(IS_FOCUS_LOSS_PERMANENT_KEY, it.isFocusLostPermanently)
679
+ putBoolean(IS_PAUSED_KEY, it.isPaused)
680
+ emit(MusicEvents.BUTTON_DUCK, this)
681
+ }
682
+ }
683
+ }
684
+
685
+ scope.launch {
686
+ event.onPlayerActionTriggeredExternally.collect {
687
+ when (it) {
688
+ is MediaSessionCallback.RATING -> {
689
+ Bundle().apply {
690
+ setRating(this, "rating", it.rating)
691
+ emit(MusicEvents.BUTTON_SET_RATING, this)
692
+ }
693
+ }
694
+ is MediaSessionCallback.SEEK -> {
695
+ Bundle().apply {
696
+ putDouble("position", it.positionMs.toSeconds())
697
+ emit(MusicEvents.BUTTON_SEEK_TO, this)
698
+ }
699
+ }
700
+ MediaSessionCallback.PLAY -> emit(MusicEvents.BUTTON_PLAY)
701
+ MediaSessionCallback.PAUSE -> emit(MusicEvents.BUTTON_PAUSE)
702
+ MediaSessionCallback.NEXT -> emit(MusicEvents.BUTTON_SKIP_NEXT)
703
+ MediaSessionCallback.PREVIOUS -> emit(MusicEvents.BUTTON_SKIP_PREVIOUS)
704
+ MediaSessionCallback.STOP -> emit(MusicEvents.BUTTON_STOP)
705
+ MediaSessionCallback.FORWARD -> {
706
+ Bundle().apply {
707
+ val interval = latestOptions?.getDouble(FORWARD_JUMP_INTERVAL_KEY, DEFAULT_JUMP_INTERVAL) ?:
708
+ DEFAULT_JUMP_INTERVAL
709
+ putInt("interval", interval.toInt())
710
+ emit(MusicEvents.BUTTON_JUMP_FORWARD, this)
711
+ }
712
+ }
713
+ MediaSessionCallback.REWIND -> {
714
+ Bundle().apply {
715
+ val interval = latestOptions?.getDouble(BACKWARD_JUMP_INTERVAL_KEY, DEFAULT_JUMP_INTERVAL) ?:
716
+ DEFAULT_JUMP_INTERVAL
717
+ putInt("interval", interval.toInt())
718
+ emit(MusicEvents.BUTTON_JUMP_BACKWARD, this)
719
+ }
720
+ }
721
+
722
+ is MediaSessionCallback.CUSTOMACTION -> {
723
+ Bundle().apply {
724
+ putString("customAction", it.customAction)
725
+ emit(MusicEvents.BUTTON_CUSTOM_ACTION, this)
726
+ }
727
+ }
728
+ }
729
+ }
730
+ }
731
+
732
+ scope.launch {
733
+ event.onTimedMetadata.collect {
734
+ val data = MetadataAdapter.fromMetadata(it)
735
+ val bundle = Bundle().apply {
736
+ putParcelableArrayList(METADATA_PAYLOAD_KEY, ArrayList(data))
737
+ }
738
+ emit(MusicEvents.METADATA_TIMED_RECEIVED, bundle)
739
+
740
+ // TODO: Handle the different types of metadata and publish to new events
741
+ val metadata = PlaybackMetadata.fromId3Metadata(it)
742
+ ?: PlaybackMetadata.fromIcy(it)
743
+ ?: PlaybackMetadata.fromVorbisComment(it)
744
+ ?: PlaybackMetadata.fromQuickTime(it)
745
+
746
+ if (metadata != null) {
747
+ Bundle().apply {
748
+ putString("source", metadata.source)
749
+ putString("title", metadata.title)
750
+ putString("url", metadata.url)
751
+ putString("artist", metadata.artist)
752
+ putString("album", metadata.album)
753
+ putString("date", metadata.date)
754
+ putString("genre", metadata.genre)
755
+ emit(MusicEvents.PLAYBACK_METADATA, this)
756
+ }
757
+ }
758
+ }
759
+ }
760
+
761
+ scope.launch {
762
+ event.onCommonMetadata.collect {
763
+ val data = MetadataAdapter.fromMediaMetadata(it)
764
+ val bundle = Bundle().apply {
765
+ putBundle(METADATA_PAYLOAD_KEY, data)
766
+ }
767
+ emit(MusicEvents.METADATA_COMMON_RECEIVED, bundle)
768
+ }
769
+ }
770
+
771
+ scope.launch {
772
+ event.playWhenReadyChange.collect {
773
+ Bundle().apply {
774
+ putBoolean("playWhenReady", it.playWhenReady)
775
+ emit(MusicEvents.PLAYBACK_PLAY_WHEN_READY_CHANGED, this)
776
+ }
777
+ }
778
+ }
779
+
780
+ scope.launch {
781
+ event.playbackError.collect {
782
+ emit(MusicEvents.PLAYBACK_ERROR, getPlaybackErrorBundle())
783
+ }
784
+ }
785
+ }
786
+
787
+ private fun getPlaybackErrorBundle(): Bundle {
788
+ val bundle = Bundle()
789
+ val error = playbackError
790
+ if (error?.message != null) {
791
+ bundle.putString("message", error.message)
792
+ }
793
+ if (error?.code != null) {
794
+ bundle.putString("code", "android-" + error.code)
795
+ }
796
+ return bundle
797
+ }
798
+
799
+ @SuppressLint("VisibleForTests")
800
+ @MainThread
801
+ fun emit(event: String, data: Bundle? = null) {
802
+ reactContext?.emitDeviceEvent(event, data?.let { Arguments.fromBundle(it) })
803
+ }
804
+
805
+ @SuppressLint("VisibleForTests")
806
+ @MainThread
807
+ private fun emitList(event: String, data: List<Bundle> = emptyList()) {
808
+ val payload = Arguments.createArray()
809
+ data.forEach { payload.pushMap(Arguments.fromBundle(it)) }
810
+
811
+ reactContext?.emitDeviceEvent(event, payload)
812
+ }
813
+
814
+ override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig {
815
+ return HeadlessJsTaskConfig(TASK_KEY, Arguments.createMap(), 0, true)
816
+ }
817
+
818
+ @MainThread
819
+ override fun onBind(intent: Intent?): IBinder? {
820
+ val intentAction = intent?.action
821
+ Timber.tag("APM").d("onbind: $intentAction")
822
+ return if (intentAction != null) {
823
+ super.onBind(intent)
824
+ } else {
825
+ binder
826
+ }
827
+ }
828
+
829
+ override fun onUnbind(intent: Intent?): Boolean {
830
+ Timber.tag("APM").d("unbind: ${intent?.action}")
831
+ return super.onUnbind(intent)
832
+ }
833
+
834
+ override fun onUpdateNotification(session: MediaSession, startInForegroundRequired: Boolean) {
835
+ // https://github.com/androidx/media/issues/843#issuecomment-1860555950
836
+ super.onUpdateNotification(session, true)
837
+ }
838
+
839
+ @MainThread
840
+ override fun onTaskRemoved(rootIntent: Intent?) {
841
+ onUnbind(rootIntent)
842
+ Timber
843
+ .tag("APM")
844
+ .d("onTaskRemoved: ${::player.isInitialized}, $appKilledPlaybackBehavior")
845
+ if (!::player.isInitialized) {
846
+ mediaSession.release()
847
+ return
848
+ }
849
+
850
+ when (appKilledPlaybackBehavior) {
851
+ AppKilledPlaybackBehavior.PAUSE_PLAYBACK -> player.pause()
852
+ AppKilledPlaybackBehavior.STOP_PLAYBACK_AND_REMOVE_NOTIFICATION -> {
853
+ Timber.tag("APM").d("onTaskRemoved: Killing service")
854
+ mediaSession.release()
855
+ player.clear()
856
+ player.stop()
857
+ // HACK: the service first stops, then starts, then call onTaskRemove. Why system
858
+ // registers the service being restarted?
859
+ player.destroy()
860
+ scope.cancel()
861
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
862
+ stopForeground(STOP_FOREGROUND_REMOVE)
863
+ } else {
864
+ @Suppress("DEPRECATION")
865
+ stopForeground(true)
866
+ }
867
+ onDestroy()
868
+ // https://github.com/androidx/media/issues/27#issuecomment-1456042326
869
+ stopSelf()
870
+ exitProcess(0)
871
+ }
872
+ else -> {}
873
+ }
874
+ }
875
+
876
+ @SuppressLint("VisibleForTests")
877
+ private fun selfWake(clientPackageName: String): Boolean {
878
+ val reactActivity = reactContext?.currentActivity
879
+ if (
880
+ // HACK: validate reactActivity is present; if not, send wake intent
881
+ (reactActivity == null || reactActivity.isDestroyed)
882
+ && Settings.canDrawOverlays(this)
883
+ ) {
884
+ val currentTime = System.currentTimeMillis()
885
+ if (currentTime - lastWake < 100000) {
886
+ return false
887
+ }
888
+ lastWake = currentTime
889
+ val activityIntent = packageManager.getLaunchIntentForPackage(packageName)
890
+ activityIntent!!.data = "trackplayer://service-bound".toUri()
891
+ activityIntent.action = Intent.ACTION_VIEW
892
+ activityIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
893
+ var activityOptions = ActivityOptions.makeBasic()
894
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
895
+ activityOptions = activityOptions.setPendingIntentBackgroundActivityStartMode(
896
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
897
+ }
898
+ this.startActivity(activityIntent, activityOptions.toBundle())
899
+ return true
900
+ }
901
+ return false
902
+ }
903
+
904
+ override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession {
905
+ Timber.tag("APM").d("onGetSession: ${controllerInfo.packageName}")
906
+ return mediaSession
907
+ }
908
+
909
+ fun notifyChildrenChanged() {
910
+ mediaSession.connectedControllers.forEach {
911
+ controller ->
912
+ mediaTree.forEach {
913
+ it -> mediaSession.notifyChildrenChanged(controller, it.key, it.value.size, null)
914
+ }
915
+
916
+ }
917
+ }
918
+
919
+ @MainThread
920
+ override fun onHeadlessJsTaskFinish(taskId: Int) {
921
+ // This is empty so ReactNative doesn't kill this service
922
+ }
923
+
924
+ @MainThread
925
+ override fun onDestroy() {
926
+ Timber.tag("APM").d("RNTP service is destroyed.")
927
+ if (::mediaSession.isInitialized) {
928
+ mediaSession.release()
929
+ }
930
+ if (::fakePlayer.isInitialized) {
931
+ fakePlayer.release()
932
+ }
933
+ if (::player.isInitialized) {
934
+ player.destroy()
935
+ }
936
+ progressUpdateJob?.cancel()
937
+ super.onDestroy()
938
+ }
939
+
940
+ fun onMediaKeyEvent(intent: Intent?): Boolean? {
941
+ val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
942
+ intent?.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
943
+ } else {
944
+ intent?.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
945
+ }
946
+
947
+ if (keyEvent?.action == KeyEvent.ACTION_DOWN) {
948
+ return when (keyEvent.keyCode) {
949
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
950
+ emit(MusicEvents.BUTTON_PLAY_PAUSE)
951
+ true
952
+ }
953
+ KeyEvent.KEYCODE_MEDIA_STOP -> {
954
+ emit(MusicEvents.BUTTON_STOP)
955
+ true
956
+ }
957
+ KeyEvent.KEYCODE_MEDIA_PAUSE -> {
958
+ emit(MusicEvents.BUTTON_PAUSE)
959
+ true
960
+ }
961
+ KeyEvent.KEYCODE_MEDIA_PLAY -> {
962
+ emit(MusicEvents.BUTTON_PLAY)
963
+ true
964
+ }
965
+ KeyEvent.KEYCODE_MEDIA_NEXT -> {
966
+ emit(MusicEvents.BUTTON_SKIP_NEXT)
967
+ true
968
+ }
969
+ KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
970
+ emit(MusicEvents.BUTTON_SKIP_PREVIOUS)
971
+ true
972
+ }
973
+ KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD, KeyEvent.KEYCODE_MEDIA_STEP_FORWARD -> {
974
+ emit(MusicEvents.BUTTON_JUMP_FORWARD)
975
+ true
976
+ }
977
+ KeyEvent.KEYCODE_MEDIA_REWIND, KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD, KeyEvent.KEYCODE_MEDIA_STEP_BACKWARD -> {
978
+ emit(MusicEvents.BUTTON_JUMP_BACKWARD)
979
+ true
980
+ }
981
+ else -> null
982
+ }
983
+ }
984
+ return null
985
+ }
986
+
987
+ @MainThread
988
+ inner class MusicBinder : Binder() {
989
+ val service = this@MusicService
990
+ }
991
+
992
+ private inner class APMMediaSessionCallback: MediaLibrarySession.Callback {
993
+ // HACK: I'm sure most of the callbacks were not implemented correctly.
994
+ // ATM I only care that andorid auto still functions.
995
+
996
+ private val rootItem = buildMediaItem(title = "root", mediaId = AA_ROOT_KEY, isPlayable = false)
997
+ private val forYouItem = buildMediaItem(title = "For You", mediaId = AA_FOR_YOU_KEY, isPlayable = false)
998
+
999
+ override fun onDisconnected(
1000
+ session: MediaSession,
1001
+ controller: MediaSession.ControllerInfo
1002
+ ) {
1003
+ emit(MusicEvents.CONNECTOR_DISCONNECTED, Bundle().apply {
1004
+ putString("package", controller.packageName)
1005
+ })
1006
+ super.onDisconnected(session, controller)
1007
+ }
1008
+ // Configure commands available to the controller in onConnect()
1009
+ @OptIn(UnstableApi::class)
1010
+ override fun onConnect(
1011
+ session: MediaSession,
1012
+ controller: MediaSession.ControllerInfo
1013
+ ): MediaSession.ConnectionResult {
1014
+ Timber.tag("APM").d("connection via: ${controller.packageName}")
1015
+ val isMediaNotificationController = session.isMediaNotificationController(controller)
1016
+ val isAutomotiveController = session.isAutomotiveController(controller)
1017
+ val isAutoCompanionController = session.isAutoCompanionController(controller)
1018
+ emit(MusicEvents.CONNECTOR_CONNECTED, Bundle().apply {
1019
+ putString("package", controller.packageName)
1020
+ putBoolean("isMediaNotificationController", isMediaNotificationController)
1021
+ putBoolean("isAutomotiveController", isAutomotiveController)
1022
+ putBoolean("isAutoCompanionController", isAutoCompanionController)
1023
+ })
1024
+ if (controller.packageName in arrayOf(
1025
+ "com.android.systemui",
1026
+ // https://github.com/googlesamples/android-media-controller
1027
+ "com.example.android.mediacontroller",
1028
+ // Android Auto
1029
+ "com.google.android.projection.gearhead"
1030
+ )) {
1031
+ lastConnectedPackage = controller.packageName
1032
+ // HACK: attempt to wake up activity (for legacy APM). if not, start headless.
1033
+ if (!selfWake(controller.packageName)) {
1034
+ onStartCommand(null, 0, 0)
1035
+ }
1036
+ }
1037
+ return if (
1038
+ isMediaNotificationController ||
1039
+ isAutomotiveController ||
1040
+ isAutoCompanionController
1041
+ ) {
1042
+ MediaSession.ConnectionResult.AcceptedResultBuilder(session)
1043
+ .setCustomLayout(customLayout)
1044
+ .setAvailableSessionCommands(sessionCommands ?: MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS)
1045
+ .setAvailablePlayerCommands(playerCommands ?: MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS)
1046
+ .build()
1047
+ } else {
1048
+ super.onConnect(session, controller)
1049
+ }
1050
+ }
1051
+
1052
+ override fun onCustomCommand(
1053
+ session: MediaSession,
1054
+ controller: MediaSession.ControllerInfo,
1055
+ customCommand: SessionCommand,
1056
+ args: Bundle
1057
+ ): ListenableFuture<SessionResult> {
1058
+ emit(MusicEvents.BUTTON_CUSTOM_ACTION, Bundle().apply { putString("customAction", customCommand.customAction) })
1059
+ return super.onCustomCommand(session, controller, customCommand, args)
1060
+ }
1061
+
1062
+ override fun onGetLibraryRoot(
1063
+ session: MediaLibrarySession,
1064
+ browser: MediaSession.ControllerInfo,
1065
+ params: LibraryParams?
1066
+ ): ListenableFuture<LibraryResult<MediaItem>> {
1067
+ val rootExtras = Bundle().apply {
1068
+ putBoolean("android.media.browse.CONTENT_STYLE_SUPPORTED", true)
1069
+ putInt("android.media.browse.CONTENT_STYLE_BROWSABLE_HINT", mediaTreeStyle[0])
1070
+ putInt("android.media.browse.CONTENT_STYLE_PLAYABLE_HINT", mediaTreeStyle[1])
1071
+ }
1072
+ val libraryParams = LibraryParams.Builder().setExtras(rootExtras).build()
1073
+ Timber.tag("APM").d("acquiring root: ${browser.packageName}")
1074
+ // https://github.com/androidx/media/issues/1731#issuecomment-2411109462
1075
+ val mRootItem = when (browser.packageName) {
1076
+ "com.google.android.googlequicksearchbox" -> {
1077
+ if (mediaTree[AA_FOR_YOU_KEY] == null) rootItem else forYouItem
1078
+ }
1079
+ else -> rootItem
1080
+ }
1081
+ return Futures.immediateFuture(LibraryResult.ofItem(mRootItem, libraryParams))
1082
+ }
1083
+
1084
+ override fun onGetChildren(
1085
+ session: MediaLibrarySession,
1086
+ browser: MediaSession.ControllerInfo,
1087
+ parentId: String,
1088
+ page: Int,
1089
+ pageSize: Int,
1090
+ params: LibraryParams?
1091
+ ): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
1092
+ emit(MusicEvents.BUTTON_BROWSE, Bundle().apply { putString("mediaId", parentId) })
1093
+ return Futures.immediateFuture(LibraryResult.ofItemList(mediaTree[parentId] ?: listOf(), null))
1094
+ }
1095
+
1096
+ override fun onGetItem(
1097
+ session: MediaLibrarySession,
1098
+ browser: MediaSession.ControllerInfo,
1099
+ mediaId: String
1100
+ ): ListenableFuture<LibraryResult<MediaItem>> {
1101
+ Timber.tag("APM").d("acquiring item: ${browser.packageName}, $mediaId")
1102
+ // emit(MusicEvents.BUTTON_PLAY_FROM_ID, Bundle().apply { putString("id", mediaId) })
1103
+ return Futures.immediateFuture(LibraryResult.ofItem(rootItem, null))
1104
+ }
1105
+
1106
+ override fun onSearch(
1107
+ session: MediaLibrarySession,
1108
+ browser: MediaSession.ControllerInfo,
1109
+ query: String,
1110
+ params: LibraryParams?
1111
+ ): ListenableFuture<LibraryResult<Void>> {
1112
+ Timber.tag("APM").d("searching: ${browser.packageName}, $query")
1113
+ return super.onSearch(session, browser, query, params)
1114
+ }
1115
+
1116
+ override fun onAddMediaItems(
1117
+ mediaSession: MediaSession,
1118
+ controller: MediaSession.ControllerInfo,
1119
+ mediaItems: MutableList<MediaItem>
1120
+ ): ListenableFuture<MutableList<MediaItem>> {
1121
+ Timber.tag("APM")
1122
+ .d("addMediaItem: ${controller.packageName}, ${mediaItems[0].mediaId}, ${mediaItems.size}")
1123
+ return super.onAddMediaItems(mediaSession, controller, mediaItems)
1124
+ }
1125
+
1126
+ override fun onSetMediaItems(
1127
+ mediaSession: MediaSession,
1128
+ controller: MediaSession.ControllerInfo,
1129
+ mediaItems: MutableList<MediaItem>,
1130
+ startIndex: Int,
1131
+ startPositionMs: Long
1132
+ ): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
1133
+ Timber.tag("APM").d("setMediaItem: ${controller.packageName}, ${mediaItems[0].toBundle()}")
1134
+ if (mediaItems[0].requestMetadata.searchQuery == null) {
1135
+ emit(MusicEvents.BUTTON_PLAY_FROM_ID, Bundle().apply {
1136
+ putString("id", mediaItems[0].mediaId)
1137
+ })
1138
+ } else {
1139
+ emit(MusicEvents.BUTTON_PLAY_FROM_SEARCH, Bundle().apply {
1140
+ putString("query", mediaItems[0].requestMetadata.searchQuery)
1141
+ })
1142
+ }
1143
+ return super.onSetMediaItems(
1144
+ mediaSession,
1145
+ controller,
1146
+ mediaItems,
1147
+ startIndex,
1148
+ startPositionMs
1149
+ )
1150
+ }
1151
+
1152
+ override fun onMediaButtonEvent(
1153
+ session: MediaSession,
1154
+ controllerInfo: MediaSession.ControllerInfo,
1155
+ intent: Intent
1156
+ ): Boolean {
1157
+ return onMediaKeyEvent(intent) ?: super.onMediaButtonEvent(session, controllerInfo, intent)
1158
+ }
1159
+
1160
+ override fun onGetSearchResult(
1161
+ session: MediaLibrarySession,
1162
+ browser: MediaSession.ControllerInfo,
1163
+ query: String,
1164
+ page: Int,
1165
+ pageSize: Int,
1166
+ params: LibraryParams?
1167
+ ): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
1168
+ Timber.tag("APM").d("searching2: ${browser.packageName}, $query")
1169
+ return super.onGetSearchResult(session, browser, query, page, pageSize, params)
1170
+ }
1171
+
1172
+ override fun onPlaybackResumption(
1173
+ mediaSession: MediaSession,
1174
+ controller: MediaSession.ControllerInfo
1175
+ ): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
1176
+ Timber.tag("APM").d("triggered onPlaybackResumption")
1177
+ try {
1178
+ this@MusicService.player
1179
+ emit(MusicEvents.PLAYBACK_RESUME, Bundle().apply {
1180
+ putString("package", controller.packageName)
1181
+ })
1182
+ } catch (e: Exception) {
1183
+ // player has not been initialized; forcefully trigger onStartCommand
1184
+ // TODO: emit event after the player is initialized?
1185
+ this@MusicService.onStartCommand(null, 0, 0)
1186
+ }
1187
+ return super.onPlaybackResumption(mediaSession, controller)
1188
+ }
1189
+ }
1190
+
1191
+ private fun getPendingIntentFlags(): Int {
1192
+ return PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
1193
+ }
1194
+
1195
+ companion object {
1196
+ const val EMPTY_NOTIFICATION_ID = 1
1197
+ const val STATE_KEY = "state"
1198
+ const val ERROR_KEY = "error"
1199
+ const val EVENT_KEY = "event"
1200
+ const val DATA_KEY = "data"
1201
+ const val TRACK_KEY = "track"
1202
+ const val NEXT_TRACK_KEY = "nextTrack"
1203
+ const val POSITION_KEY = "position"
1204
+ const val DURATION_KEY = "duration"
1205
+ const val BUFFERED_POSITION_KEY = "buffered"
1206
+
1207
+ const val TASK_KEY = "TrackPlayer"
1208
+
1209
+ const val MIN_BUFFER_KEY = "minBuffer"
1210
+ const val MAX_BUFFER_KEY = "maxBuffer"
1211
+ const val PLAY_BUFFER_KEY = "playBuffer"
1212
+ const val BACK_BUFFER_KEY = "backBuffer"
1213
+
1214
+ const val FORWARD_JUMP_INTERVAL_KEY = "forwardJumpInterval"
1215
+ const val BACKWARD_JUMP_INTERVAL_KEY = "backwardJumpInterval"
1216
+ const val PROGRESS_UPDATE_EVENT_INTERVAL_KEY = "progressUpdateEventInterval"
1217
+
1218
+ const val MAX_CACHE_SIZE_KEY = "maxCacheSize"
1219
+
1220
+ const val ANDROID_OPTIONS_KEY = "android"
1221
+
1222
+ const val CUSTOM_ACTIONS_KEY = "customActions"
1223
+ const val CUSTOM_ACTIONS_LIST_KEY = "customActionsList"
1224
+
1225
+ const val STOPPING_APP_PAUSES_PLAYBACK_KEY = "stoppingAppPausesPlayback"
1226
+ const val APP_KILLED_PLAYBACK_BEHAVIOR_KEY = "appKilledPlaybackBehavior"
1227
+ const val AUDIO_OFFLOAD_KEY = "audioOffload"
1228
+ const val SHUFFLE_KEY = "shuffle"
1229
+ const val STOP_FOREGROUND_GRACE_PERIOD_KEY = "stopForegroundGracePeriod"
1230
+ const val PAUSE_ON_INTERRUPTION_KEY = "alwaysPauseOnInterruption"
1231
+ const val AUTO_UPDATE_METADATA = "autoUpdateMetadata"
1232
+ const val AUTO_HANDLE_INTERRUPTIONS = "autoHandleInterruptions"
1233
+ const val ANDROID_AUDIO_CONTENT_TYPE = "androidAudioContentType"
1234
+ const val IS_FOCUS_LOSS_PERMANENT_KEY = "permanent"
1235
+ const val IS_PAUSED_KEY = "paused"
1236
+
1237
+ const val PARSE_EMBEDDED_ARTWORK = "androidParseEmbeddedArtwork"
1238
+ const val HANDLE_NOISY = "androidHandleAudioBecomingNoisy"
1239
+ const val CROSSFADE = "crossfade"
1240
+ const val ALWAYS_SHOW_NEXT = "androidAlwaysShowNext"
1241
+ const val SKIP_SILENCE = "androidSkipSilence"
1242
+ const val WAKE_MODE = "androidWakeMode"
1243
+
1244
+ const val AA_FOR_YOU_KEY = "for-you"
1245
+ const val AA_ROOT_KEY = "/"
1246
+
1247
+ const val DEFAULT_JUMP_INTERVAL = 15.0
1248
+ const val DEFAULT_STOP_FOREGROUND_GRACE_PERIOD = 5
1249
+ }
1250
+ }
1251
+