@wwdrew/expo-apple-music 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 (452) hide show
  1. package/ATTRIBUTION.md +24 -0
  2. package/LICENSE +190 -0
  3. package/NOTICE +7 -0
  4. package/README.md +81 -0
  5. package/android/build.gradle +26 -0
  6. package/android/libs/mediaplayback-release-1.1.1.aar +0 -0
  7. package/android/libs/musickitauth-release-1.1.2.aar +0 -0
  8. package/android/src/main/AndroidManifest.xml +16 -0
  9. package/android/src/main/java/expo/modules/applemusic/AndroidCatalogService.kt +86 -0
  10. package/android/src/main/java/expo/modules/applemusic/AndroidDeveloperToken.kt +39 -0
  11. package/android/src/main/java/expo/modules/applemusic/AndroidHistoryService.kt +24 -0
  12. package/android/src/main/java/expo/modules/applemusic/AndroidLibraryMutationsService.kt +30 -0
  13. package/android/src/main/java/expo/modules/applemusic/AndroidLibraryService.kt +61 -0
  14. package/android/src/main/java/expo/modules/applemusic/AndroidPlaybackController.kt +484 -0
  15. package/android/src/main/java/expo/modules/applemusic/AndroidPlaybackObserver.kt +173 -0
  16. package/android/src/main/java/expo/modules/applemusic/AndroidQueueService.kt +78 -0
  17. package/android/src/main/java/expo/modules/applemusic/AndroidRatingsService.kt +27 -0
  18. package/android/src/main/java/expo/modules/applemusic/AndroidRecommendationsService.kt +15 -0
  19. package/android/src/main/java/expo/modules/applemusic/AndroidSubscriptionService.kt +24 -0
  20. package/android/src/main/java/expo/modules/applemusic/AppleMusicErrorCodes.kt +13 -0
  21. package/android/src/main/java/expo/modules/applemusic/AppleMusicErrors.kt +46 -0
  22. package/android/src/main/java/expo/modules/applemusic/AppleMusicHttpMethod.kt +8 -0
  23. package/android/src/main/java/expo/modules/applemusic/AppleMusicJsonMapper.kt +258 -0
  24. package/android/src/main/java/expo/modules/applemusic/AppleMusicNativeLoader.kt +32 -0
  25. package/android/src/main/java/expo/modules/applemusic/AppleMusicRestJson.kt +40 -0
  26. package/android/src/main/java/expo/modules/applemusic/AppleMusicRestQuery.kt +12 -0
  27. package/android/src/main/java/expo/modules/applemusic/AppleMusicRestStack.kt +19 -0
  28. package/android/src/main/java/expo/modules/applemusic/AppleMusicRestTransport.kt +118 -0
  29. package/android/src/main/java/expo/modules/applemusic/AuthenticatedSession.kt +57 -0
  30. package/android/src/main/java/expo/modules/applemusic/BridgeResponses.kt +55 -0
  31. package/android/src/main/java/expo/modules/applemusic/CatalogRestClient.kt +306 -0
  32. package/android/src/main/java/expo/modules/applemusic/ExpoAppleMusicModule.kt +152 -0
  33. package/android/src/main/java/expo/modules/applemusic/HistoryRestClient.kt +60 -0
  34. package/android/src/main/java/expo/modules/applemusic/LibraryIds.kt +6 -0
  35. package/android/src/main/java/expo/modules/applemusic/LibraryMutationsRestClient.kt +95 -0
  36. package/android/src/main/java/expo/modules/applemusic/LibraryRestClient.kt +195 -0
  37. package/android/src/main/java/expo/modules/applemusic/MusicKitAuthContract.kt +78 -0
  38. package/android/src/main/java/expo/modules/applemusic/MusicKitAuthStorage.kt +76 -0
  39. package/android/src/main/java/expo/modules/applemusic/MusicKitTokenProvider.kt +13 -0
  40. package/android/src/main/java/expo/modules/applemusic/PaginationOptions.kt +14 -0
  41. package/android/src/main/java/expo/modules/applemusic/RatingsRestClient.kt +72 -0
  42. package/android/src/main/java/expo/modules/applemusic/RecommendationsRestClient.kt +37 -0
  43. package/android/src/main/java/expo/modules/applemusic/StorefrontRestClient.kt +44 -0
  44. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeAuth.kt +69 -0
  45. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeCatalog.kt +76 -0
  46. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeHistory.kt +35 -0
  47. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeLibrary.kt +54 -0
  48. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeLibraryMutations.kt +30 -0
  49. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgePlayer.kt +89 -0
  50. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeRatings.kt +29 -0
  51. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeRecommendations.kt +18 -0
  52. package/app.plugin.js +1 -0
  53. package/build/ExpoAppleMusicModule.web.d.ts +15 -0
  54. package/build/ExpoAppleMusicModule.web.d.ts.map +1 -0
  55. package/build/ExpoAppleMusicModule.web.js +33 -0
  56. package/build/ExpoAppleMusicModule.web.js.map +1 -0
  57. package/build/api/call-native.d.ts +6 -0
  58. package/build/api/call-native.d.ts.map +1 -0
  59. package/build/api/call-native.js +35 -0
  60. package/build/api/call-native.js.map +1 -0
  61. package/build/api/decode-jwt-exp.d.ts +5 -0
  62. package/build/api/decode-jwt-exp.d.ts.map +1 -0
  63. package/build/api/decode-jwt-exp.js +28 -0
  64. package/build/api/decode-jwt-exp.js.map +1 -0
  65. package/build/api/library-ids.d.ts +4 -0
  66. package/build/api/library-ids.d.ts.map +1 -0
  67. package/build/api/library-ids.js +11 -0
  68. package/build/api/library-ids.js.map +1 -0
  69. package/build/api/pagination.d.ts +12 -0
  70. package/build/api/pagination.d.ts.map +1 -0
  71. package/build/api/pagination.js +13 -0
  72. package/build/api/pagination.js.map +1 -0
  73. package/build/api/parse-authorize-result.d.ts +3 -0
  74. package/build/api/parse-authorize-result.d.ts.map +1 -0
  75. package/build/api/parse-authorize-result.js +27 -0
  76. package/build/api/parse-authorize-result.js.map +1 -0
  77. package/build/api/require-music-user-token.d.ts +2 -0
  78. package/build/api/require-music-user-token.d.ts.map +1 -0
  79. package/build/api/require-music-user-token.js +14 -0
  80. package/build/api/require-music-user-token.js.map +1 -0
  81. package/build/api/sync-developer-token.d.ts +4 -0
  82. package/build/api/sync-developer-token.d.ts.map +1 -0
  83. package/build/api/sync-developer-token.js +27 -0
  84. package/build/api/sync-developer-token.js.map +1 -0
  85. package/build/bridge/bridge-methods.d.ts +17 -0
  86. package/build/bridge/bridge-methods.d.ts.map +1 -0
  87. package/build/bridge/bridge-methods.js +71 -0
  88. package/build/bridge/bridge-methods.js.map +1 -0
  89. package/build/bridge/bridge-responses.d.ts +64 -0
  90. package/build/bridge/bridge-responses.d.ts.map +1 -0
  91. package/build/bridge/bridge-responses.js +67 -0
  92. package/build/bridge/bridge-responses.js.map +1 -0
  93. package/build/bridge/handlers/auth-bridge.d.ts +11 -0
  94. package/build/bridge/handlers/auth-bridge.d.ts.map +1 -0
  95. package/build/bridge/handlers/auth-bridge.js +49 -0
  96. package/build/bridge/handlers/auth-bridge.js.map +1 -0
  97. package/build/bridge/handlers/catalog-bridge.d.ts +34 -0
  98. package/build/bridge/handlers/catalog-bridge.d.ts.map +1 -0
  99. package/build/bridge/handlers/catalog-bridge.js +58 -0
  100. package/build/bridge/handlers/catalog-bridge.js.map +1 -0
  101. package/build/bridge/handlers/history-bridge.d.ts +19 -0
  102. package/build/bridge/handlers/history-bridge.d.ts.map +1 -0
  103. package/build/bridge/handlers/history-bridge.js +31 -0
  104. package/build/bridge/handlers/history-bridge.js.map +1 -0
  105. package/build/bridge/handlers/index.d.ts +124 -0
  106. package/build/bridge/handlers/index.d.ts.map +1 -0
  107. package/build/bridge/handlers/index.js +21 -0
  108. package/build/bridge/handlers/index.js.map +1 -0
  109. package/build/bridge/handlers/library-bridge.d.ts +23 -0
  110. package/build/bridge/handlers/library-bridge.d.ts.map +1 -0
  111. package/build/bridge/handlers/library-bridge.js +41 -0
  112. package/build/bridge/handlers/library-bridge.js.map +1 -0
  113. package/build/bridge/handlers/library-mutations-bridge.d.ts +16 -0
  114. package/build/bridge/handlers/library-mutations-bridge.d.ts.map +1 -0
  115. package/build/bridge/handlers/library-mutations-bridge.js +20 -0
  116. package/build/bridge/handlers/library-mutations-bridge.js.map +1 -0
  117. package/build/bridge/handlers/player-bridge.d.ts +18 -0
  118. package/build/bridge/handlers/player-bridge.d.ts.map +1 -0
  119. package/build/bridge/handlers/player-bridge.js +42 -0
  120. package/build/bridge/handlers/player-bridge.js.map +1 -0
  121. package/build/bridge/handlers/ratings-bridge.d.ts +15 -0
  122. package/build/bridge/handlers/ratings-bridge.d.ts.map +1 -0
  123. package/build/bridge/handlers/ratings-bridge.js +16 -0
  124. package/build/bridge/handlers/ratings-bridge.js.map +1 -0
  125. package/build/bridge/handlers/recommendations-bridge.d.ts +10 -0
  126. package/build/bridge/handlers/recommendations-bridge.d.ts.map +1 -0
  127. package/build/bridge/handlers/recommendations-bridge.js +14 -0
  128. package/build/bridge/handlers/recommendations-bridge.js.map +1 -0
  129. package/build/constants/apple-music-error-codes.d.ts +22 -0
  130. package/build/constants/apple-music-error-codes.d.ts.map +1 -0
  131. package/build/constants/apple-music-error-codes.js +21 -0
  132. package/build/constants/apple-music-error-codes.js.map +1 -0
  133. package/build/hooks/use-current-song.d.ts +10 -0
  134. package/build/hooks/use-current-song.d.ts.map +1 -0
  135. package/build/hooks/use-current-song.js +35 -0
  136. package/build/hooks/use-current-song.js.map +1 -0
  137. package/build/hooks/use-is-playing.d.ts +6 -0
  138. package/build/hooks/use-is-playing.d.ts.map +1 -0
  139. package/build/hooks/use-is-playing.js +21 -0
  140. package/build/hooks/use-is-playing.js.map +1 -0
  141. package/build/hooks/use-playback-state.d.ts +12 -0
  142. package/build/hooks/use-playback-state.d.ts.map +1 -0
  143. package/build/hooks/use-playback-state.js +41 -0
  144. package/build/hooks/use-playback-state.js.map +1 -0
  145. package/build/index.d.ts +52 -0
  146. package/build/index.d.ts.map +1 -0
  147. package/build/index.js +44 -0
  148. package/build/index.js.map +1 -0
  149. package/build/mappers/apple-music-json-mapper.d.ts +94 -0
  150. package/build/mappers/apple-music-json-mapper.d.ts.map +1 -0
  151. package/build/mappers/apple-music-json-mapper.js +212 -0
  152. package/build/mappers/apple-music-json-mapper.js.map +1 -0
  153. package/build/modules/auth.d.ts +32 -0
  154. package/build/modules/auth.d.ts.map +1 -0
  155. package/build/modules/auth.js +60 -0
  156. package/build/modules/auth.js.map +1 -0
  157. package/build/modules/catalog.d.ts +46 -0
  158. package/build/modules/catalog.d.ts.map +1 -0
  159. package/build/modules/catalog.js +47 -0
  160. package/build/modules/catalog.js.map +1 -0
  161. package/build/modules/history.d.ts +17 -0
  162. package/build/modules/history.d.ts.map +1 -0
  163. package/build/modules/history.js +28 -0
  164. package/build/modules/history.js.map +1 -0
  165. package/build/modules/library-mutations.d.ts +9 -0
  166. package/build/modules/library-mutations.d.ts.map +1 -0
  167. package/build/modules/library-mutations.js +41 -0
  168. package/build/modules/library-mutations.js.map +1 -0
  169. package/build/modules/library.d.ts +21 -0
  170. package/build/modules/library.d.ts.map +1 -0
  171. package/build/modules/library.js +38 -0
  172. package/build/modules/library.js.map +1 -0
  173. package/build/modules/player.d.ts +53 -0
  174. package/build/modules/player.d.ts.map +1 -0
  175. package/build/modules/player.js +68 -0
  176. package/build/modules/player.js.map +1 -0
  177. package/build/modules/ratings.d.ts +10 -0
  178. package/build/modules/ratings.d.ts.map +1 -0
  179. package/build/modules/ratings.js +37 -0
  180. package/build/modules/ratings.js.map +1 -0
  181. package/build/modules/recommendations.d.ts +7 -0
  182. package/build/modules/recommendations.d.ts.map +1 -0
  183. package/build/modules/recommendations.js +15 -0
  184. package/build/modules/recommendations.js.map +1 -0
  185. package/build/native-module.d.ts +5 -0
  186. package/build/native-module.d.ts.map +1 -0
  187. package/build/native-module.js +5 -0
  188. package/build/native-module.js.map +1 -0
  189. package/build/native-module.web.d.ts +11 -0
  190. package/build/native-module.web.d.ts.map +1 -0
  191. package/build/native-module.web.js +11 -0
  192. package/build/native-module.web.js.map +1 -0
  193. package/build/rest/apple-music-rest-stack.d.ts +21 -0
  194. package/build/rest/apple-music-rest-stack.d.ts.map +1 -0
  195. package/build/rest/apple-music-rest-stack.js +21 -0
  196. package/build/rest/apple-music-rest-stack.js.map +1 -0
  197. package/build/rest/apple-music-rest-transport.d.ts +8 -0
  198. package/build/rest/apple-music-rest-transport.d.ts.map +1 -0
  199. package/build/rest/apple-music-rest-transport.js +2 -0
  200. package/build/rest/apple-music-rest-transport.js.map +1 -0
  201. package/build/rest/catalog-rest-client.d.ts +38 -0
  202. package/build/rest/catalog-rest-client.d.ts.map +1 -0
  203. package/build/rest/catalog-rest-client.js +189 -0
  204. package/build/rest/catalog-rest-client.js.map +1 -0
  205. package/build/rest/history-rest-client.d.ts +37 -0
  206. package/build/rest/history-rest-client.d.ts.map +1 -0
  207. package/build/rest/history-rest-client.js +30 -0
  208. package/build/rest/history-rest-client.js.map +1 -0
  209. package/build/rest/library-ids.d.ts +2 -0
  210. package/build/rest/library-ids.d.ts.map +1 -0
  211. package/build/rest/library-ids.js +2 -0
  212. package/build/rest/library-ids.js.map +1 -0
  213. package/build/rest/library-mutations-rest-client.d.ts +22 -0
  214. package/build/rest/library-mutations-rest-client.d.ts.map +1 -0
  215. package/build/rest/library-mutations-rest-client.js +37 -0
  216. package/build/rest/library-mutations-rest-client.js.map +1 -0
  217. package/build/rest/library-rest-client.d.ts +59 -0
  218. package/build/rest/library-rest-client.d.ts.map +1 -0
  219. package/build/rest/library-rest-client.js +141 -0
  220. package/build/rest/library-rest-client.js.map +1 -0
  221. package/build/rest/ratings-rest-client.d.ts +18 -0
  222. package/build/rest/ratings-rest-client.d.ts.map +1 -0
  223. package/build/rest/ratings-rest-client.js +37 -0
  224. package/build/rest/ratings-rest-client.js.map +1 -0
  225. package/build/rest/recommendations-rest-client.d.ts +32 -0
  226. package/build/rest/recommendations-rest-client.d.ts.map +1 -0
  227. package/build/rest/recommendations-rest-client.js +26 -0
  228. package/build/rest/recommendations-rest-client.js.map +1 -0
  229. package/build/rest/resource-ids-query.d.ts +2 -0
  230. package/build/rest/resource-ids-query.d.ts.map +1 -0
  231. package/build/rest/resource-ids-query.js +10 -0
  232. package/build/rest/resource-ids-query.js.map +1 -0
  233. package/build/rest/rest-json.d.ts +11 -0
  234. package/build/rest/rest-json.d.ts.map +1 -0
  235. package/build/rest/rest-json.js +29 -0
  236. package/build/rest/rest-json.js.map +1 -0
  237. package/build/rest/storefront-rest-client.d.ts +14 -0
  238. package/build/rest/storefront-rest-client.d.ts.map +1 -0
  239. package/build/rest/storefront-rest-client.js +36 -0
  240. package/build/rest/storefront-rest-client.js.map +1 -0
  241. package/build/types/album.d.ts +8 -0
  242. package/build/types/album.d.ts.map +1 -0
  243. package/build/types/album.js +2 -0
  244. package/build/types/album.js.map +1 -0
  245. package/build/types/albums-response.d.ts +5 -0
  246. package/build/types/albums-response.d.ts.map +1 -0
  247. package/build/types/albums-response.js +2 -0
  248. package/build/types/albums-response.js.map +1 -0
  249. package/build/types/android-authorize-options.d.ts +18 -0
  250. package/build/types/android-authorize-options.d.ts.map +1 -0
  251. package/build/types/android-authorize-options.js +2 -0
  252. package/build/types/android-authorize-options.js.map +1 -0
  253. package/build/types/artist.d.ts +9 -0
  254. package/build/types/artist.d.ts.map +1 -0
  255. package/build/types/artist.js +2 -0
  256. package/build/types/artist.js.map +1 -0
  257. package/build/types/auth-status.d.ts +19 -0
  258. package/build/types/auth-status.d.ts.map +1 -0
  259. package/build/types/auth-status.js +18 -0
  260. package/build/types/auth-status.js.map +1 -0
  261. package/build/types/authorize-result.d.ts +8 -0
  262. package/build/types/authorize-result.d.ts.map +1 -0
  263. package/build/types/authorize-result.js +2 -0
  264. package/build/types/authorize-result.js.map +1 -0
  265. package/build/types/catalog-album-tracks.d.ts +5 -0
  266. package/build/types/catalog-album-tracks.d.ts.map +1 -0
  267. package/build/types/catalog-album-tracks.js +2 -0
  268. package/build/types/catalog-album-tracks.js.map +1 -0
  269. package/build/types/catalog-charts.d.ts +25 -0
  270. package/build/types/catalog-charts.d.ts.map +1 -0
  271. package/build/types/catalog-charts.js +7 -0
  272. package/build/types/catalog-charts.js.map +1 -0
  273. package/build/types/catalog-resource-type.d.ts +11 -0
  274. package/build/types/catalog-resource-type.d.ts.map +1 -0
  275. package/build/types/catalog-resource-type.js +10 -0
  276. package/build/types/catalog-resource-type.js.map +1 -0
  277. package/build/types/catalog-search.d.ts +24 -0
  278. package/build/types/catalog-search.d.ts.map +1 -0
  279. package/build/types/catalog-search.js +9 -0
  280. package/build/types/catalog-search.js.map +1 -0
  281. package/build/types/check-subscription.d.ts +31 -0
  282. package/build/types/check-subscription.d.ts.map +1 -0
  283. package/build/types/check-subscription.js +5 -0
  284. package/build/types/check-subscription.js.map +1 -0
  285. package/build/types/library-music-videos.d.ts +5 -0
  286. package/build/types/library-music-videos.d.ts.map +1 -0
  287. package/build/types/library-music-videos.js +2 -0
  288. package/build/types/library-music-videos.js.map +1 -0
  289. package/build/types/library-mutations.d.ts +21 -0
  290. package/build/types/library-mutations.d.ts.map +1 -0
  291. package/build/types/library-mutations.js +8 -0
  292. package/build/types/library-mutations.js.map +1 -0
  293. package/build/types/library-search.d.ts +22 -0
  294. package/build/types/library-search.d.ts.map +1 -0
  295. package/build/types/library-search.js +9 -0
  296. package/build/types/library-search.js.map +1 -0
  297. package/build/types/music-item.d.ts +8 -0
  298. package/build/types/music-item.d.ts.map +1 -0
  299. package/build/types/music-item.js +7 -0
  300. package/build/types/music-item.js.map +1 -0
  301. package/build/types/music-video.d.ts +8 -0
  302. package/build/types/music-video.d.ts.map +1 -0
  303. package/build/types/music-video.js +2 -0
  304. package/build/types/music-video.js.map +1 -0
  305. package/build/types/pagination.d.ts +11 -0
  306. package/build/types/pagination.d.ts.map +1 -0
  307. package/build/types/pagination.js +2 -0
  308. package/build/types/pagination.js.map +1 -0
  309. package/build/types/playback-state.d.ts +9 -0
  310. package/build/types/playback-state.d.ts.map +1 -0
  311. package/build/types/playback-state.js +2 -0
  312. package/build/types/playback-state.js.map +1 -0
  313. package/build/types/playback-status.d.ts +10 -0
  314. package/build/types/playback-status.d.ts.map +1 -0
  315. package/build/types/playback-status.js +9 -0
  316. package/build/types/playback-status.js.map +1 -0
  317. package/build/types/playlist.d.ts +15 -0
  318. package/build/types/playlist.d.ts.map +1 -0
  319. package/build/types/playlist.js +2 -0
  320. package/build/types/playlist.js.map +1 -0
  321. package/build/types/rating.d.ts +34 -0
  322. package/build/types/rating.d.ts.map +1 -0
  323. package/build/types/rating.js +26 -0
  324. package/build/types/rating.js.map +1 -0
  325. package/build/types/recent-resource.d.ts +11 -0
  326. package/build/types/recent-resource.d.ts.map +1 -0
  327. package/build/types/recent-resource.js +2 -0
  328. package/build/types/recent-resource.js.map +1 -0
  329. package/build/types/recommendation.d.ts +37 -0
  330. package/build/types/recommendation.d.ts.map +1 -0
  331. package/build/types/recommendation.js +2 -0
  332. package/build/types/recommendation.js.map +1 -0
  333. package/build/types/song.d.ts +8 -0
  334. package/build/types/song.d.ts.map +1 -0
  335. package/build/types/song.js +2 -0
  336. package/build/types/song.js.map +1 -0
  337. package/build/types/station.d.ts +9 -0
  338. package/build/types/station.d.ts.map +1 -0
  339. package/build/types/station.js +2 -0
  340. package/build/types/station.js.map +1 -0
  341. package/build/types/storefront.d.ts +4 -0
  342. package/build/types/storefront.d.ts.map +1 -0
  343. package/build/types/storefront.js +2 -0
  344. package/build/types/storefront.js.map +1 -0
  345. package/build/types/tracks-from-library.d.ts +11 -0
  346. package/build/types/tracks-from-library.d.ts.map +1 -0
  347. package/build/types/tracks-from-library.js +2 -0
  348. package/build/types/tracks-from-library.js.map +1 -0
  349. package/build/utils/apple-music-error.d.ts +10 -0
  350. package/build/utils/apple-music-error.d.ts.map +1 -0
  351. package/build/utils/apple-music-error.js +13 -0
  352. package/build/utils/apple-music-error.js.map +1 -0
  353. package/build/utils/get-error-message.d.ts +2 -0
  354. package/build/utils/get-error-message.d.ts.map +1 -0
  355. package/build/utils/get-error-message.js +21 -0
  356. package/build/utils/get-error-message.js.map +1 -0
  357. package/build/utils/is-library-item.d.ts +2 -0
  358. package/build/utils/is-library-item.d.ts.map +1 -0
  359. package/build/utils/is-library-item.js +4 -0
  360. package/build/utils/is-library-item.js.map +1 -0
  361. package/build/utils/normalize-resource-ids.d.ts +4 -0
  362. package/build/utils/normalize-resource-ids.d.ts.map +1 -0
  363. package/build/utils/normalize-resource-ids.js +12 -0
  364. package/build/utils/normalize-resource-ids.js.map +1 -0
  365. package/build/web/MusicKitLoader.d.ts +11 -0
  366. package/build/web/MusicKitLoader.d.ts.map +1 -0
  367. package/build/web/MusicKitLoader.js +135 -0
  368. package/build/web/MusicKitLoader.js.map +1 -0
  369. package/build/web/WebAppleMusicApiClient.d.ts +151 -0
  370. package/build/web/WebAppleMusicApiClient.d.ts.map +1 -0
  371. package/build/web/WebAppleMusicApiClient.js +139 -0
  372. package/build/web/WebAppleMusicApiClient.js.map +1 -0
  373. package/build/web/WebAppleMusicRestTransport.d.ts +9 -0
  374. package/build/web/WebAppleMusicRestTransport.d.ts.map +1 -0
  375. package/build/web/WebAppleMusicRestTransport.js +31 -0
  376. package/build/web/WebAppleMusicRestTransport.js.map +1 -0
  377. package/build/web/WebPlaybackController.d.ts +12 -0
  378. package/build/web/WebPlaybackController.d.ts.map +1 -0
  379. package/build/web/WebPlaybackController.js +90 -0
  380. package/build/web/WebPlaybackController.js.map +1 -0
  381. package/build/web/WebPlaybackObserver.d.ts +22 -0
  382. package/build/web/WebPlaybackObserver.d.ts.map +1 -0
  383. package/build/web/WebPlaybackObserver.js +106 -0
  384. package/build/web/WebPlaybackObserver.js.map +1 -0
  385. package/build/web/WebQueueService.d.ts +10 -0
  386. package/build/web/WebQueueService.d.ts.map +1 -0
  387. package/build/web/WebQueueService.js +53 -0
  388. package/build/web/WebQueueService.js.map +1 -0
  389. package/build/web/WebSubscriptionService.d.ts +7 -0
  390. package/build/web/WebSubscriptionService.d.ts.map +1 -0
  391. package/build/web/WebSubscriptionService.js +24 -0
  392. package/build/web/WebSubscriptionService.js.map +1 -0
  393. package/build/web/apple-music-errors.d.ts +11 -0
  394. package/build/web/apple-music-errors.d.ts.map +1 -0
  395. package/build/web/apple-music-errors.js +31 -0
  396. package/build/web/apple-music-errors.js.map +1 -0
  397. package/build/web/extract-music-user-token.d.ts +4 -0
  398. package/build/web/extract-music-user-token.d.ts.map +1 -0
  399. package/build/web/extract-music-user-token.js +15 -0
  400. package/build/web/extract-music-user-token.js.map +1 -0
  401. package/build/web/map-auth-status.d.ts +17 -0
  402. package/build/web/map-auth-status.d.ts.map +1 -0
  403. package/build/web/map-auth-status.js +85 -0
  404. package/build/web/map-auth-status.js.map +1 -0
  405. package/build/web/music-kit-api.d.ts +18 -0
  406. package/build/web/music-kit-api.d.ts.map +1 -0
  407. package/build/web/music-kit-api.js +120 -0
  408. package/build/web/music-kit-api.js.map +1 -0
  409. package/build/web/musickit-types.d.ts +70 -0
  410. package/build/web/musickit-types.d.ts.map +1 -0
  411. package/build/web/musickit-types.js +3 -0
  412. package/build/web/musickit-types.js.map +1 -0
  413. package/build/web/pagination.d.ts +2 -0
  414. package/build/web/pagination.d.ts.map +1 -0
  415. package/build/web/pagination.js +2 -0
  416. package/build/web/pagination.js.map +1 -0
  417. package/expo-module.config.json +19 -0
  418. package/ios/AppleMusicBridgeError.swift +60 -0
  419. package/ios/AppleMusicErrorCodes.swift +11 -0
  420. package/ios/AppleMusicRestClient.swift +213 -0
  421. package/ios/AuthenticatedSession.swift +64 -0
  422. package/ios/BridgePagination.swift +17 -0
  423. package/ios/BridgeResponses.swift +82 -0
  424. package/ios/CatalogSearchStore.swift +13 -0
  425. package/ios/CatalogSearchStoreFactory.swift +31 -0
  426. package/ios/CatalogService.swift +307 -0
  427. package/ios/ExpoAppleMusic.podspec +29 -0
  428. package/ios/ExpoAppleMusicModule.swift +505 -0
  429. package/ios/HistoryService.swift +53 -0
  430. package/ios/LibraryMutationsService.swift +63 -0
  431. package/ios/LibraryService.swift +313 -0
  432. package/ios/MusicItemMapper.swift +171 -0
  433. package/ios/MusicKitAuthStorage.swift +38 -0
  434. package/ios/MusicKitCatalogSearchStore.swift +62 -0
  435. package/ios/PlaybackController.swift +201 -0
  436. package/ios/PlaybackObserver.swift +225 -0
  437. package/ios/QueueService.swift +166 -0
  438. package/ios/RatingsService.swift +66 -0
  439. package/ios/RecommendationsService.swift +34 -0
  440. package/ios/RestCatalogSearchStore.swift +62 -0
  441. package/ios/RestJsonMapper.swift +268 -0
  442. package/ios/StorefrontService.swift +55 -0
  443. package/ios/SubscriptionService.swift +119 -0
  444. package/ios/bridge/ExpoBridgeCatalog.swift +98 -0
  445. package/ios/bridge/ExpoBridgeHistory.swift +71 -0
  446. package/ios/bridge/ExpoBridgeLibrary.swift +93 -0
  447. package/ios/bridge/ExpoBridgeRecommendations.swift +28 -0
  448. package/package.json +89 -0
  449. package/plugin/build/index.d.ts +5 -0
  450. package/plugin/build/index.js +10 -0
  451. package/plugin/build/with-expo-apple-music.d.ts +10 -0
  452. package/plugin/build/with-expo-apple-music.js +50 -0
@@ -0,0 +1,484 @@
1
+ package expo.modules.applemusic
2
+
3
+ import android.content.Context
4
+ import android.os.Handler
5
+ import android.os.Looper
6
+ import android.util.Log
7
+ import com.apple.android.music.playback.controller.MediaPlayerController
8
+ import com.apple.android.music.playback.controller.MediaPlayerControllerFactory
9
+ import com.apple.android.music.playback.model.MediaContainerType
10
+ import com.apple.android.music.playback.model.MediaItemType
11
+ import com.apple.android.music.playback.model.MediaPlayerException
12
+ import com.apple.android.music.playback.model.PlayerMediaItem
13
+ import com.apple.android.music.playback.model.PlayerQueueItem
14
+ import com.apple.android.music.playback.queue.CatalogPlaybackQueueItemProvider
15
+ import com.apple.android.music.playback.queue.PlaybackQueueInsertionType
16
+ import com.apple.android.music.playback.queue.PlaybackQueueItemProvider
17
+ import expo.modules.kotlin.exception.CodedException
18
+ import kotlinx.coroutines.Dispatchers
19
+ import kotlinx.coroutines.suspendCancellableCoroutine
20
+ import kotlinx.coroutines.withContext
21
+ import kotlin.coroutines.resume
22
+ import kotlin.coroutines.resumeWithException
23
+
24
+ internal class AndroidPlaybackController private constructor(
25
+ context: Context,
26
+ ) {
27
+ private val appContext = context.applicationContext
28
+ private val mainHandler = Handler(Looper.getMainLooper())
29
+
30
+ @Volatile
31
+ private var controller: MediaPlayerController? = null
32
+
33
+ private val externalListeners =
34
+ mutableSetOf<MediaPlayerController.Listener>()
35
+
36
+ @Volatile
37
+ private var boundMusicUserToken: String? = null
38
+
39
+ /** Drops the native player when the music user token changes (SDK caches credentials). */
40
+ internal fun applyMusicUserToken(token: String?) {
41
+ val trimmed = token?.trim()?.takeIf { it.isNotEmpty() } ?: return
42
+ MusicKitAuthStorage.saveMusicUserToken(appContext, trimmed)
43
+ if (trimmed != boundMusicUserToken) {
44
+ releaseControllerSync()
45
+ }
46
+ }
47
+
48
+ private fun ensureController(): MediaPlayerController {
49
+ AndroidDeveloperToken.requireStored(appContext)
50
+ val token = MusicKitAuthStorage.getMusicUserToken(appContext)
51
+ if (!token.isNullOrEmpty()) {
52
+ return ensurePlaybackController(token)
53
+ }
54
+ val existing = controller
55
+ if (existing != null) {
56
+ return existing
57
+ }
58
+ return createController(null)
59
+ }
60
+
61
+ private fun ensurePlaybackController(musicUserToken: String): MediaPlayerController {
62
+ val existing = controller
63
+ if (existing != null && boundMusicUserToken == musicUserToken) {
64
+ return existing
65
+ }
66
+ releaseControllerSync()
67
+ return createController(musicUserToken)
68
+ }
69
+
70
+ private fun createController(musicUserToken: String?): MediaPlayerController {
71
+ AppleMusicNativeLoader.ensureLoaded()
72
+ return MediaPlayerControllerFactory.createLocalController(
73
+ appContext,
74
+ MusicKitTokenProvider(appContext),
75
+ ).also { player ->
76
+ player.addListener(globalErrorListener)
77
+ externalListeners.forEach { player.addListener(it) }
78
+ controller = player
79
+ boundMusicUserToken = musicUserToken
80
+ }
81
+ }
82
+
83
+ private fun releaseControllerSync() {
84
+ clearSongCache()
85
+ val player = controller ?: return
86
+ controller = null
87
+ boundMusicUserToken = null
88
+ try {
89
+ player.removeListener(globalErrorListener)
90
+ externalListeners.forEach { player.removeListener(it) }
91
+ player.release()
92
+ } catch (error: Exception) {
93
+ Log.w(TAG, "release playback controller failed", error)
94
+ }
95
+ }
96
+
97
+ private var cachedSongId: String? = null
98
+ private var cachedSongInfo: Map<String, Any?>? = null
99
+
100
+ var playbackErrorHandler: ((MediaPlayerException, String) -> Unit)? = null
101
+
102
+ private val globalErrorListener =
103
+ object : MediaPlayerController.Listener {
104
+ override fun onPlaybackError(
105
+ player: MediaPlayerController,
106
+ error: MediaPlayerException,
107
+ ) {
108
+ Log.e(TAG, "playback error type=${error.type} code=${error.errorCode}", error)
109
+ playbackErrorHandler?.invoke(error, "playback")
110
+ }
111
+
112
+ override fun onPlayerStateRestored(player: MediaPlayerController) {}
113
+ override fun onPlaybackStateChanged(
114
+ player: MediaPlayerController,
115
+ previousState: Int,
116
+ newState: Int,
117
+ ) {
118
+ }
119
+ override fun onPlaybackStateUpdated(player: MediaPlayerController) {}
120
+ override fun onBufferingStateChanged(player: MediaPlayerController, buffering: Boolean) {}
121
+ override fun onCurrentItemChanged(
122
+ player: MediaPlayerController,
123
+ previous: PlayerQueueItem?,
124
+ current: PlayerQueueItem?,
125
+ ) {
126
+ }
127
+ override fun onItemEnded(
128
+ player: MediaPlayerController,
129
+ item: PlayerQueueItem,
130
+ endPosition: Long,
131
+ ) {
132
+ }
133
+ override fun onMetadataUpdated(player: MediaPlayerController, item: PlayerQueueItem) {}
134
+ override fun onPlaybackQueueChanged(
135
+ player: MediaPlayerController,
136
+ items: MutableList<PlayerQueueItem>,
137
+ ) {
138
+ }
139
+ override fun onPlaybackQueueItemsAdded(
140
+ player: MediaPlayerController,
141
+ queueInsertionType: Int,
142
+ containerIndex: Int,
143
+ itemCount: Int,
144
+ ) {
145
+ }
146
+ override fun onPlaybackRepeatModeChanged(player: MediaPlayerController, mode: Int) {}
147
+ override fun onPlaybackShuffleModeChanged(player: MediaPlayerController, mode: Int) {}
148
+ }
149
+
150
+ fun addListener(listener: MediaPlayerController.Listener) {
151
+ externalListeners.add(listener)
152
+ val player = controller
153
+ if (player != null) {
154
+ player.addListener(listener)
155
+ return
156
+ }
157
+ if (hasStoredDeveloperToken()) {
158
+ ensureController().addListener(listener)
159
+ }
160
+ }
161
+
162
+ /**
163
+ * After [MusicKitAuthStorage.saveDeveloperToken], attach listeners registered before a JWT existed.
164
+ */
165
+ internal fun attachPendingListenersAfterDeveloperTokenStored() {
166
+ if (!hasStoredDeveloperToken() || externalListeners.isEmpty()) return
167
+ ensureController()
168
+ }
169
+
170
+ private fun hasStoredDeveloperToken(): Boolean =
171
+ !MusicKitAuthStorage.getDeveloperToken(appContext).isNullOrBlank()
172
+
173
+ private fun idlePlaybackState(): Map<String, Any?> =
174
+ mapOf(
175
+ "playbackRate" to 1.0,
176
+ "playbackStatus" to "stopped",
177
+ "playbackTime" to 0.0,
178
+ )
179
+
180
+ fun removeListener(listener: MediaPlayerController.Listener) {
181
+ externalListeners.remove(listener)
182
+ controller?.removeListener(listener)
183
+ }
184
+
185
+ /** API parity with iOS `configureAudioSession`; playback focus is handled by [MediaPlayerController]. */
186
+ fun configurePlayer(mixWithOthers: Boolean): Map<String, Any?> =
187
+ mapOf("mixWithOthers" to mixWithOthers)
188
+
189
+ /**
190
+ * [MediaPlayerController.prepare] loads queue items asynchronously — wait for
191
+ * [onPlaybackQueueItemsAdded] before returning to JS.
192
+ */
193
+ suspend fun prepareQueue(provider: PlaybackQueueItemProvider, musicUserToken: String? = null) {
194
+ AndroidDeveloperToken.requireStored(appContext)
195
+ val stack = AppleMusicRestStack.create(appContext)
196
+ val effectiveToken = resolvePlaybackMusicUserToken(appContext, musicUserToken)
197
+ stack.storefront.requireUserStorefront(effectiveToken)
198
+ withContext(Dispatchers.Main) {
199
+ val player = ensurePlaybackController(effectiveToken)
200
+ suspendCancellableCoroutine { continuation ->
201
+ lateinit var timeoutRunnable: Runnable
202
+
203
+ val prepareListener =
204
+ object : MediaPlayerController.Listener {
205
+ private fun cleanup() {
206
+ player.removeListener(this)
207
+ mainHandler.removeCallbacks(timeoutRunnable)
208
+ }
209
+
210
+ private fun finishSuccess() {
211
+ if (!continuation.isActive) return
212
+ cleanup()
213
+ continuation.resume(Unit)
214
+ }
215
+
216
+ private fun finishError(error: Exception) {
217
+ if (!continuation.isActive) return
218
+ cleanup()
219
+ continuation.resumeWithException(mapPlaybackException(error))
220
+ }
221
+
222
+ override fun onPlaybackQueueItemsAdded(
223
+ player: MediaPlayerController,
224
+ queueInsertionType: Int,
225
+ containerIndex: Int,
226
+ itemCount: Int,
227
+ ) {
228
+ if (itemCount > 0) finishSuccess()
229
+ }
230
+
231
+ override fun onPlaybackError(
232
+ player: MediaPlayerController,
233
+ error: MediaPlayerException,
234
+ ) {
235
+ finishError(error)
236
+ }
237
+
238
+ override fun onPlayerStateRestored(player: MediaPlayerController) {}
239
+ override fun onPlaybackStateChanged(
240
+ player: MediaPlayerController,
241
+ previousState: Int,
242
+ newState: Int,
243
+ ) {
244
+ }
245
+ override fun onPlaybackStateUpdated(player: MediaPlayerController) {}
246
+ override fun onBufferingStateChanged(player: MediaPlayerController, buffering: Boolean) {}
247
+ override fun onCurrentItemChanged(
248
+ player: MediaPlayerController,
249
+ previous: PlayerQueueItem?,
250
+ current: PlayerQueueItem?,
251
+ ) {
252
+ }
253
+ override fun onItemEnded(
254
+ player: MediaPlayerController,
255
+ item: PlayerQueueItem,
256
+ endPosition: Long,
257
+ ) {
258
+ }
259
+ override fun onMetadataUpdated(player: MediaPlayerController, item: PlayerQueueItem) {}
260
+ override fun onPlaybackQueueChanged(
261
+ player: MediaPlayerController,
262
+ items: MutableList<PlayerQueueItem>,
263
+ ) {
264
+ }
265
+ override fun onPlaybackRepeatModeChanged(player: MediaPlayerController, mode: Int) {}
266
+ override fun onPlaybackShuffleModeChanged(player: MediaPlayerController, mode: Int) {}
267
+ }
268
+
269
+ timeoutRunnable =
270
+ Runnable {
271
+ if (!continuation.isActive) return@Runnable
272
+ player.removeListener(prepareListener)
273
+ if (player.playbackQueueItemCount > 0) {
274
+ continuation.resume(Unit)
275
+ } else {
276
+ continuation.resumeWithException(
277
+ CodedException(
278
+ AppleMusicErrorCodes.PLAYBACK_ERROR,
279
+ "Playback queue stayed empty after prepare",
280
+ null,
281
+ ),
282
+ )
283
+ }
284
+ }
285
+
286
+ continuation.invokeOnCancellation {
287
+ player.removeListener(prepareListener)
288
+ mainHandler.removeCallbacks(timeoutRunnable)
289
+ }
290
+
291
+ player.addListener(prepareListener)
292
+ mainHandler.postDelayed(timeoutRunnable, PREPARE_TIMEOUT_MS)
293
+
294
+ try {
295
+ player.prepare(
296
+ provider,
297
+ PlaybackQueueInsertionType.INSERTION_TYPE_CLEAR_AND_REPLACE,
298
+ true,
299
+ )
300
+ } catch (error: Exception) {
301
+ player.removeListener(prepareListener)
302
+ mainHandler.removeCallbacks(timeoutRunnable)
303
+ if (continuation.isActive) {
304
+ continuation.resumeWithException(mapPlaybackException(error))
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+
311
+ fun play() {
312
+ mainHandler.post { ensureController().play() }
313
+ }
314
+
315
+ fun pause() {
316
+ mainHandler.post { ensureController().pause() }
317
+ }
318
+
319
+ fun togglePlayback() {
320
+ mainHandler.post {
321
+ val player = ensureController()
322
+ when (player.playbackState) {
323
+ com.apple.android.music.playback.model.PlaybackState.PLAYING -> player.pause()
324
+ else -> player.play()
325
+ }
326
+ }
327
+ }
328
+
329
+ fun skipToNext() {
330
+ mainHandler.post { ensureController().skipToNextItem() }
331
+ }
332
+
333
+ fun skipToPrevious() {
334
+ mainHandler.post { ensureController().skipToPreviousItem() }
335
+ }
336
+
337
+ fun restartCurrentEntry(onComplete: ((Double) -> Unit)? = null) {
338
+ mainHandler.post {
339
+ ensureController().seekToPosition(0)
340
+ onComplete?.invoke(0.0)
341
+ }
342
+ }
343
+
344
+ fun seekToTime(seconds: Double, onComplete: ((Double) -> Unit)? = null) {
345
+ mainHandler.post {
346
+ val player = ensureController()
347
+ player.seekToPosition((seconds * 1000).toLong())
348
+ val actual = player.currentPosition.coerceAtLeast(0) / 1000.0
349
+ onComplete?.invoke(actual)
350
+ }
351
+ }
352
+
353
+ fun buildSongProvider(vararg catalogIds: String, startIndex: Int = 0): PlaybackQueueItemProvider {
354
+ val ids = catalogIds.map { it.trim() }.filter { it.isNotEmpty() }.toTypedArray()
355
+ if (ids.isEmpty()) {
356
+ throw AppleMusicErrors.apiError("No catalog song ids for playback queue")
357
+ }
358
+ return CatalogPlaybackQueueItemProvider.Builder()
359
+ .items(MediaItemType.SONG, *ids)
360
+ .apply {
361
+ if (startIndex > 0) startItemIndex(startIndex)
362
+ }
363
+ .build()
364
+ }
365
+
366
+ fun buildAlbumProvider(catalogId: String): PlaybackQueueItemProvider =
367
+ CatalogPlaybackQueueItemProvider.Builder()
368
+ .containers(MediaContainerType.ALBUM, catalogId.trim())
369
+ .build()
370
+
371
+ fun buildPlaylistProvider(catalogId: String): PlaybackQueueItemProvider =
372
+ CatalogPlaybackQueueItemProvider.Builder()
373
+ .containers(MediaContainerType.PLAYLIST, catalogId.trim())
374
+ .build()
375
+
376
+ fun warmUp() {
377
+ AppleMusicNativeLoader.ensureLoaded()
378
+ }
379
+
380
+ /** Releases the native player and clears caches; keeps this singleton for observer re-attach. */
381
+ internal fun releaseMediaPlayer() {
382
+ if (Looper.myLooper() == Looper.getMainLooper()) {
383
+ releaseControllerSync()
384
+ } else {
385
+ mainHandler.post { releaseControllerSync() }
386
+ }
387
+ }
388
+
389
+ fun clearSongCache() {
390
+ cachedSongId = null
391
+ cachedSongInfo = null
392
+ }
393
+
394
+ fun currentState(): Map<String, Any?> {
395
+ if (!hasStoredDeveloperToken()) {
396
+ return idlePlaybackState()
397
+ }
398
+ val player = controller ?: ensureController()
399
+ val playbackStatus = AppleMusicJsonMapper.describePlaybackStatus(player.playbackState)
400
+ val playbackTime = player.currentPosition.coerceAtLeast(0) / 1000.0
401
+ return buildMap {
402
+ put("playbackRate", player.playbackRate)
403
+ put("playbackStatus", playbackStatus)
404
+ put("playbackTime", playbackTime)
405
+ fetchCurrentSongInfo()?.let { put("currentSong", it) }
406
+ }
407
+ }
408
+
409
+ fun fetchCurrentSongInfo(): Map<String, Any?>? {
410
+ val player = controller ?: return null
411
+ val item: PlayerMediaItem = player.currentItem?.item ?: return run {
412
+ clearSongCache()
413
+ null
414
+ }
415
+
416
+ val currentId =
417
+ item.subscriptionStoreId?.takeIf { it.isNotEmpty() }
418
+ ?: item.a()?.takeIf { !it.isNullOrEmpty() }
419
+
420
+ if (currentId == null) {
421
+ val fallback = AppleMusicJsonMapper.mapPlayerMediaItem(item)
422
+ return fallback.takeIf { (it["title"] as? String)?.isNotEmpty() == true } ?: cachedSongInfo
423
+ }
424
+
425
+ if (currentId == cachedSongId && cachedSongInfo != null) {
426
+ return cachedSongInfo
427
+ }
428
+
429
+ val songInfo = AppleMusicJsonMapper.mapPlayerMediaItem(item)
430
+ cachedSongId = currentId
431
+ cachedSongInfo = songInfo
432
+ return songInfo
433
+ }
434
+
435
+
436
+ companion object {
437
+ private const val TAG = "ExpoAppleMusic"
438
+ private const val PREPARE_TIMEOUT_MS = 20_000L
439
+
440
+ @Volatile
441
+ private var instance: AndroidPlaybackController? = null
442
+
443
+ fun getInstance(context: Context): AndroidPlaybackController =
444
+ instance
445
+ ?: synchronized(this) {
446
+ instance ?: AndroidPlaybackController(context.applicationContext).also { instance = it }
447
+ }
448
+
449
+ fun warmUp(context: Context) {
450
+ try {
451
+ getInstance(context).warmUp()
452
+ } catch (error: Exception) {
453
+ Log.w(TAG, "playback warmUp failed", error)
454
+ }
455
+ }
456
+
457
+ fun resetInstance() {
458
+ synchronized(this) {
459
+ instance?.releaseMediaPlayer()
460
+ }
461
+ }
462
+
463
+ fun mapPlaybackException(error: Exception): CodedException =
464
+ when (error) {
465
+ is CodedException -> error
466
+ is MediaPlayerException ->
467
+ CodedException(
468
+ AppleMusicErrorCodes.PLAYBACK_ERROR,
469
+ error.message ?: "Media playback failed (type=${error.type}, code=${error.errorCode})",
470
+ null,
471
+ )
472
+ else -> {
473
+ val message = error.message.orEmpty()
474
+ val hint =
475
+ if (error is java.io.FileNotFoundException && message.contains("api.music.apple.com")) {
476
+ "Apple Music API rejected the request (often an expired session). Call Auth.authorize(developerToken) again."
477
+ } else {
478
+ error.message ?: "Playback failed"
479
+ }
480
+ CodedException(AppleMusicErrorCodes.PLAYBACK_ERROR, hint, null)
481
+ }
482
+ }
483
+ }
484
+ }
@@ -0,0 +1,173 @@
1
+ package expo.modules.applemusic
2
+
3
+ import android.content.Context
4
+ import android.os.Handler
5
+ import android.os.Looper
6
+ import com.apple.android.music.playback.controller.MediaPlayerController
7
+ import com.apple.android.music.playback.model.MediaPlayerException
8
+ import com.apple.android.music.playback.model.PlayerQueueItem
9
+ import com.apple.android.music.playback.model.PlaybackState
10
+
11
+ internal interface AndroidPlaybackObserverDelegate {
12
+ fun onPlaybackStateChange(body: Map<String, Any?>)
13
+ fun onCurrentSongChange(body: Map<String, Any?>)
14
+ fun onPlaybackTimeUpdate(playbackTime: Double)
15
+ fun onPlaybackError(body: Map<String, Any?>)
16
+ }
17
+
18
+ internal class AndroidPlaybackObserver(
19
+ context: Context,
20
+ ) {
21
+ private val appContext = context.applicationContext
22
+ private val playback: AndroidPlaybackController
23
+ get() = AndroidPlaybackController.getInstance(appContext)
24
+
25
+ private val mainHandler = Handler(Looper.getMainLooper())
26
+ private var observing = false
27
+ private var lastReportedStatus: String? = null
28
+ private var timeRunnable: Runnable? = null
29
+ private var pendingSongEmitRunnable: Runnable? = null
30
+
31
+ var delegate: AndroidPlaybackObserverDelegate? = null
32
+
33
+ private val listener =
34
+ object : MediaPlayerController.Listener {
35
+ override fun onPlaybackStateChanged(
36
+ player: MediaPlayerController,
37
+ previousState: Int,
38
+ newState: Int,
39
+ ) {
40
+ emitStateIfChanged()
41
+ manageTimeUpdates(newState == PlaybackState.PLAYING)
42
+ }
43
+
44
+ override fun onPlaybackStateUpdated(player: MediaPlayerController) {
45
+ emitStateIfChanged()
46
+ }
47
+
48
+ override fun onCurrentItemChanged(
49
+ player: MediaPlayerController,
50
+ previous: PlayerQueueItem?,
51
+ current: PlayerQueueItem?,
52
+ ) {
53
+ scheduleEmitCurrentSong()
54
+ }
55
+
56
+ override fun onMetadataUpdated(player: MediaPlayerController, item: PlayerQueueItem) {
57
+ scheduleEmitCurrentSong()
58
+ }
59
+
60
+ override fun onPlaybackQueueItemsAdded(
61
+ player: MediaPlayerController,
62
+ queueInsertionType: Int,
63
+ containerIndex: Int,
64
+ itemCount: Int,
65
+ ) {
66
+ if (itemCount > 0) scheduleEmitCurrentSong()
67
+ }
68
+
69
+ override fun onPlaybackError(player: MediaPlayerController, error: MediaPlayerException) {
70
+ delegate?.onPlaybackError(
71
+ mapOf(
72
+ "message" to (error.message ?: "Playback error"),
73
+ "code" to error.errorCode,
74
+ "domain" to "MediaPlayer",
75
+ "operation" to "playback",
76
+ ),
77
+ )
78
+ }
79
+
80
+ override fun onPlayerStateRestored(player: MediaPlayerController) {}
81
+ override fun onBufferingStateChanged(player: MediaPlayerController, buffering: Boolean) {}
82
+ override fun onItemEnded(player: MediaPlayerController, item: PlayerQueueItem, endPosition: Long) {}
83
+ override fun onPlaybackQueueChanged(
84
+ player: MediaPlayerController,
85
+ items: MutableList<PlayerQueueItem>,
86
+ ) {
87
+ }
88
+ override fun onPlaybackRepeatModeChanged(player: MediaPlayerController, mode: Int) {}
89
+ override fun onPlaybackShuffleModeChanged(player: MediaPlayerController, mode: Int) {}
90
+ }
91
+
92
+ fun startObserving() {
93
+ if (observing) return
94
+ observing = true
95
+ playback.addListener(listener)
96
+ emitStateIfChanged()
97
+ scheduleEmitCurrentSong()
98
+ }
99
+
100
+ fun stopObserving() {
101
+ if (!observing) return
102
+ observing = false
103
+ playback.removeListener(listener)
104
+ cancelPendingSongEmit()
105
+ stopTimeUpdates()
106
+ lastReportedStatus = null
107
+ }
108
+
109
+ private fun emitStateIfChanged() {
110
+ val state = playback.currentState()
111
+ val status = state["playbackStatus"] as? String ?: return
112
+ if (status == lastReportedStatus) return
113
+ lastReportedStatus = status
114
+ delegate?.onPlaybackStateChange(state)
115
+ }
116
+
117
+ private fun scheduleEmitCurrentSong() {
118
+ cancelPendingSongEmit()
119
+ emitCurrentSong(attempt = 0)
120
+ }
121
+
122
+ private fun cancelPendingSongEmit() {
123
+ pendingSongEmitRunnable?.let { mainHandler.removeCallbacks(it) }
124
+ pendingSongEmitRunnable = null
125
+ }
126
+
127
+ private fun emitCurrentSong(attempt: Int) {
128
+ val song = playback.fetchCurrentSongInfo()
129
+ if (song != null) {
130
+ cancelPendingSongEmit()
131
+ delegate?.onCurrentSongChange(mapOf("currentSong" to song))
132
+ return
133
+ }
134
+ if (!observing || attempt >= 4) return
135
+ val runnable =
136
+ Runnable {
137
+ pendingSongEmitRunnable = null
138
+ emitCurrentSong(attempt + 1)
139
+ }
140
+ pendingSongEmitRunnable = runnable
141
+ mainHandler.postDelayed(runnable, 150L * (attempt + 1))
142
+ }
143
+
144
+ private fun manageTimeUpdates(playing: Boolean) {
145
+ if (playing) {
146
+ startTimeUpdates()
147
+ } else {
148
+ stopTimeUpdates()
149
+ val time = playback.currentState()["playbackTime"] as? Double ?: 0.0
150
+ delegate?.onPlaybackTimeUpdate(time)
151
+ }
152
+ }
153
+
154
+ private fun startTimeUpdates() {
155
+ if (timeRunnable != null) return
156
+ val runnable =
157
+ object : Runnable {
158
+ override fun run() {
159
+ if (!observing) return
160
+ val time = playback.currentState()["playbackTime"] as? Double ?: 0.0
161
+ delegate?.onPlaybackTimeUpdate(time)
162
+ mainHandler.postDelayed(this, 1000)
163
+ }
164
+ }
165
+ timeRunnable = runnable
166
+ mainHandler.post(runnable)
167
+ }
168
+
169
+ private fun stopTimeUpdates() {
170
+ timeRunnable?.let { mainHandler.removeCallbacks(it) }
171
+ timeRunnable = null
172
+ }
173
+ }