@javascriptcommon/react-native-track-player 1.2.9 → 4.1.4

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