@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,118 @@
1
+ package expo.modules.applemusic
2
+
3
+ import android.content.Context
4
+ import kotlinx.coroutines.Dispatchers
5
+ import kotlinx.coroutines.withContext
6
+ import okhttp3.HttpUrl
7
+ import okhttp3.MediaType.Companion.toMediaType
8
+ import okhttp3.OkHttpClient
9
+ import okhttp3.Request
10
+ import okhttp3.RequestBody.Companion.toRequestBody
11
+ import org.json.JSONObject
12
+ import java.util.concurrent.TimeUnit
13
+
14
+ /** Shared HTTP adapter for Apple Music REST (see docs/ANDROID_IMPLEMENTATION.md). */
15
+ internal interface AppleMusicRestTransport {
16
+ suspend fun request(
17
+ musicUserToken: String?,
18
+ method: AppleMusicHttpMethod,
19
+ path: String,
20
+ query: Map<String, String> = emptyMap(),
21
+ body: JSONObject? = null,
22
+ ): JSONObject
23
+
24
+ suspend fun getJson(
25
+ musicUserToken: String?,
26
+ path: String,
27
+ query: Map<String, String> = emptyMap(),
28
+ ): JSONObject
29
+ }
30
+
31
+ internal class OkHttpAppleMusicRestTransport(
32
+ private val context: Context,
33
+ ) : AppleMusicRestTransport {
34
+ private val http =
35
+ OkHttpClient.Builder()
36
+ .connectTimeout(30, TimeUnit.SECONDS)
37
+ .readTimeout(30, TimeUnit.SECONDS)
38
+ .build()
39
+
40
+ private fun pathRequiresMusicUserToken(path: String): Boolean = path.startsWith("/v1/me/")
41
+
42
+ override suspend fun request(
43
+ musicUserToken: String?,
44
+ method: AppleMusicHttpMethod,
45
+ path: String,
46
+ query: Map<String, String>,
47
+ body: JSONObject?,
48
+ ): JSONObject =
49
+ withContext(Dispatchers.IO) {
50
+ val developerToken = AndroidDeveloperToken.requireStored(context)
51
+ val userToken =
52
+ if (pathRequiresMusicUserToken(path)) {
53
+ requireMusicUserToken(musicUserToken)
54
+ } else {
55
+ musicUserToken?.trim()?.takeIf { it.isNotEmpty() }
56
+ }
57
+
58
+ val urlBuilder =
59
+ HttpUrl.Builder()
60
+ .scheme("https")
61
+ .host("api.music.apple.com")
62
+ .encodedPath(path)
63
+
64
+ query.forEach { (key, value) -> urlBuilder.addQueryParameter(key, value) }
65
+
66
+ val jsonMediaType = "application/json".toMediaType()
67
+ val requestBody = body?.toString()?.toRequestBody(jsonMediaType)
68
+
69
+ val requestBuilder =
70
+ Request.Builder()
71
+ .url(urlBuilder.build())
72
+ .header("Authorization", "Bearer $developerToken")
73
+
74
+ if (!userToken.isNullOrEmpty()) {
75
+ requestBuilder.header("Music-User-Token", userToken)
76
+ }
77
+
78
+ when (method) {
79
+ AppleMusicHttpMethod.GET -> requestBuilder.get()
80
+ AppleMusicHttpMethod.POST ->
81
+ requestBuilder.post(requestBody ?: "{}".toRequestBody(jsonMediaType))
82
+ AppleMusicHttpMethod.PUT ->
83
+ requestBuilder.put(requestBody ?: "{}".toRequestBody(jsonMediaType))
84
+ AppleMusicHttpMethod.DELETE ->
85
+ if (requestBody != null) {
86
+ requestBuilder.delete(requestBody)
87
+ } else {
88
+ requestBuilder.delete()
89
+ }
90
+ }
91
+
92
+ http.newCall(requestBuilder.build()).execute().use { response ->
93
+ val responseBody = response.body?.string().orEmpty()
94
+ if (!response.isSuccessful) {
95
+ if (response.code == 403) {
96
+ throw AppleMusicErrors.permissionDenied()
97
+ }
98
+ val message =
99
+ try {
100
+ JSONObject(responseBody).optJSONArray("errors")?.optJSONObject(0)?.optString("detail")
101
+ } catch (_: Exception) {
102
+ null
103
+ } ?: "Apple Music API error (${response.code})"
104
+ throw AppleMusicErrors.apiError(message)
105
+ }
106
+ if (responseBody.isEmpty()) {
107
+ return@withContext JSONObject()
108
+ }
109
+ return@withContext JSONObject(responseBody)
110
+ }
111
+ }
112
+
113
+ override suspend fun getJson(
114
+ musicUserToken: String?,
115
+ path: String,
116
+ query: Map<String, String>,
117
+ ): JSONObject = request(musicUserToken, AppleMusicHttpMethod.GET, path, query)
118
+ }
@@ -0,0 +1,57 @@
1
+ package expo.modules.applemusic
2
+
3
+ import android.content.Context
4
+
5
+ /**
6
+ * Snapshot of stored developer JWT (see docs/AUTH.md).
7
+ * Music user tokens are app-owned in JS; native may cache the last token for playback SDK (see [MusicKitAuthStorage]).
8
+ */
9
+ internal data class AuthenticatedSession(
10
+ val developerToken: String?,
11
+ ) {
12
+ val hasDeveloperToken: Boolean
13
+ get() = !developerToken.isNullOrBlank()
14
+
15
+ companion object {
16
+ fun load(context: Context): AuthenticatedSession =
17
+ AuthenticatedSession(
18
+ developerToken = MusicKitAuthStorage.getDeveloperToken(context),
19
+ )
20
+ }
21
+ }
22
+
23
+ /**
24
+ * In-memory session snapshot for native playback and REST storefront cache.
25
+ * Music user tokens are app-owned in JS; native keeps the last token from authorize
26
+ * or bridge calls for playback SDK + storefront (see [MusicKitAuthStorage]).
27
+ */
28
+ internal object AuthenticatedSessionCache {
29
+ @Volatile
30
+ var storefrontId: String? = null
31
+
32
+ @Volatile
33
+ var musicUserToken: String? = null
34
+
35
+ fun rememberMusicUserToken(token: String?) {
36
+ val trimmed = token?.trim()?.takeIf { it.isNotEmpty() }
37
+ if (trimmed != musicUserToken) {
38
+ musicUserToken = trimmed
39
+ storefrontId = null
40
+ }
41
+ }
42
+
43
+ fun invalidate() {
44
+ storefrontId = null
45
+ musicUserToken = null
46
+ }
47
+ }
48
+
49
+ /** Token for playback SDK + `/v1/me/storefront`; updates session cache when explicit. */
50
+ internal fun resolvePlaybackMusicUserToken(context: Context, explicit: String?): String {
51
+ explicit?.trim()?.takeIf { it.isNotEmpty() }?.let {
52
+ MusicKitAuthStorage.saveMusicUserToken(context, it)
53
+ return it
54
+ }
55
+ MusicKitAuthStorage.getMusicUserToken(context)?.let { return it }
56
+ throw AppleMusicErrors.missingMusicUserToken()
57
+ }
@@ -0,0 +1,55 @@
1
+ package expo.modules.applemusic
2
+
3
+ /** Standard bridge response envelopes (keys match iOS and TypeScript). */
4
+ internal object BridgeResponses {
5
+ fun storefront(id: String): Map<String, Any?> = mapOf("id" to id)
6
+
7
+ fun catalogSearch(result: AndroidCatalogService.SearchResult): Map<String, Any?> =
8
+ mapOf(
9
+ "songs" to result.songs,
10
+ "albums" to result.albums,
11
+ "artists" to result.artists,
12
+ "playlists" to result.playlists,
13
+ "stations" to result.stations,
14
+ "musicVideos" to result.musicVideos,
15
+ )
16
+
17
+ fun catalogCharts(result: AndroidCatalogService.ChartsResult): Map<String, Any?> =
18
+ mapOf(
19
+ "songs" to result.songs,
20
+ "albums" to result.albums,
21
+ "playlists" to result.playlists,
22
+ "musicVideos" to result.musicVideos,
23
+ )
24
+
25
+ fun songs(items: List<Map<String, Any?>>): Map<String, Any?> = mapOf("songs" to items)
26
+
27
+ fun albums(items: List<Map<String, Any?>>): Map<String, Any?> = mapOf("albums" to items)
28
+
29
+ fun artists(items: List<Map<String, Any?>>): Map<String, Any?> = mapOf("artists" to items)
30
+
31
+ fun playlists(items: List<Map<String, Any?>>): Map<String, Any?> = mapOf("playlists" to items)
32
+
33
+ fun musicVideos(items: List<Map<String, Any?>>): Map<String, Any?> = mapOf("musicVideos" to items)
34
+
35
+ fun librarySearch(result: AndroidLibraryService.LibrarySearchResult): Map<String, Any?> =
36
+ mapOf(
37
+ "songs" to result.songs,
38
+ "albums" to result.albums,
39
+ "artists" to result.artists,
40
+ "playlists" to result.playlists,
41
+ "musicVideos" to result.musicVideos,
42
+ )
43
+
44
+ fun stations(items: List<Map<String, Any?>>): Map<String, Any?> = mapOf("stations" to items)
45
+
46
+ fun recentlyPlayedResources(items: List<Map<String, Any?>>): Map<String, Any?> =
47
+ mapOf("recentlyPlayedItems" to items)
48
+
49
+ fun recentItems(items: List<Map<String, Any?>>): Map<String, Any?> = mapOf("items" to items)
50
+
51
+ fun recommendations(items: List<Map<String, Any?>>): Map<String, Any?> =
52
+ mapOf("recommendations" to items)
53
+
54
+ fun replaySummaries(items: List<Map<String, Any?>>): Map<String, Any?> = mapOf("summaries" to items)
55
+ }
@@ -0,0 +1,306 @@
1
+ package expo.modules.applemusic
2
+
3
+ import kotlinx.coroutines.Dispatchers
4
+ import kotlinx.coroutines.withContext
5
+ import org.json.JSONArray
6
+ import org.json.JSONObject
7
+
8
+ /** Catalog-domain Apple Music REST (search, resources, charts). */
9
+ internal class CatalogRestClient(
10
+ private val transport: AppleMusicRestTransport,
11
+ private val storefront: StorefrontRestClient,
12
+ ) {
13
+ data class CatalogSearchResult(
14
+ val songs: List<Map<String, Any?>>,
15
+ val albums: List<Map<String, Any?>>,
16
+ val artists: List<Map<String, Any?>>,
17
+ val playlists: List<Map<String, Any?>>,
18
+ val stations: List<Map<String, Any?>>,
19
+ val musicVideos: List<Map<String, Any?>>,
20
+ )
21
+
22
+ suspend fun catalogSearch(
23
+ term: String,
24
+ types: List<String>,
25
+ limit: Int,
26
+ offset: Int,
27
+ ): CatalogSearchResult =
28
+ withContext(Dispatchers.IO) {
29
+ val storefrontId = storefront.getCatalogStorefront()
30
+ val typeParam =
31
+ types
32
+ .mapNotNull { catalogSearchTypeParam(it) }
33
+ .distinct()
34
+ .joinToString(",")
35
+ .ifEmpty { "songs,albums" }
36
+
37
+ val json =
38
+ transport.getJson(
39
+ null,
40
+ "/v1/catalog/$storefrontId/search",
41
+ mapOf(
42
+ "term" to term,
43
+ "types" to typeParam,
44
+ "limit" to limit.toString(),
45
+ "offset" to offset.toString(),
46
+ ),
47
+ )
48
+
49
+ val results = json.optJSONObject("results") ?: JSONObject()
50
+ CatalogSearchResult(
51
+ songs =
52
+ mapResourceArray(results.optJSONObject("songs")?.optJSONArray("data")) {
53
+ AppleMusicJsonMapper.mapSong(it)
54
+ },
55
+ albums =
56
+ mapResourceArray(results.optJSONObject("albums")?.optJSONArray("data")) {
57
+ AppleMusicJsonMapper.mapAlbum(it)
58
+ },
59
+ artists =
60
+ mapResourceArray(results.optJSONObject("artists")?.optJSONArray("data")) {
61
+ AppleMusicJsonMapper.mapArtist(it)
62
+ },
63
+ playlists =
64
+ mapResourceArray(results.optJSONObject("playlists")?.optJSONArray("data")) {
65
+ AppleMusicJsonMapper.mapPlaylist(it)
66
+ },
67
+ stations =
68
+ mapResourceArray(results.optJSONObject("stations")?.optJSONArray("data")) {
69
+ AppleMusicJsonMapper.mapStation(it)
70
+ },
71
+ musicVideos =
72
+ mapResourceArray(results.optJSONObject("music-videos")?.optJSONArray("data")) {
73
+ AppleMusicJsonMapper.mapMusicVideo(it)
74
+ },
75
+ )
76
+ }
77
+
78
+ suspend fun getCatalogSong(id: String): Map<String, Any?> =
79
+ getCatalogResource("/songs/$id") { AppleMusicJsonMapper.mapSong(it) }
80
+
81
+ suspend fun getCatalogAlbum(id: String): Map<String, Any?> =
82
+ getCatalogResource("/albums/$id") { AppleMusicJsonMapper.mapAlbum(it) }
83
+
84
+ suspend fun getCatalogArtist(id: String): Map<String, Any?> =
85
+ getCatalogResource("/artists/$id") { AppleMusicJsonMapper.mapArtist(it) }
86
+
87
+ suspend fun getCatalogPlaylist(id: String): Map<String, Any?> =
88
+ getCatalogResource("/playlists/$id") { AppleMusicJsonMapper.mapPlaylist(it) }
89
+
90
+ suspend fun getCatalogStation(id: String): Map<String, Any?> =
91
+ getCatalogResource("/stations/$id") { AppleMusicJsonMapper.mapStation(it) }
92
+
93
+ suspend fun getCatalogMusicVideo(id: String): Map<String, Any?> =
94
+ getCatalogResource("/music-videos/$id") { AppleMusicJsonMapper.mapMusicVideo(it) }
95
+
96
+ suspend fun getCatalogResources(type: String, ids: List<String>): List<Map<String, Any?>> =
97
+ withContext(Dispatchers.IO) {
98
+ val trimmed = ids.map { it.trim() }.filter { it.isNotEmpty() }
99
+ if (trimmed.isEmpty()) {
100
+ return@withContext emptyList()
101
+ }
102
+ val storefrontId = storefront.getCatalogStorefront()
103
+ val json =
104
+ transport.getJson(
105
+ null,
106
+ "/v1/catalog/$storefrontId/$type",
107
+ mapOf("ids" to trimmed.joinToString(",")),
108
+ )
109
+ val data = requireDataArray(json)
110
+ buildList {
111
+ for (i in 0 until data.length()) {
112
+ val resource = data.getJSONObject(i)
113
+ mapCatalogResource(type, resource)?.let { add(it) }
114
+ }
115
+ }
116
+ }
117
+
118
+ private fun mapCatalogResource(type: String, resource: JSONObject): Map<String, Any?>? {
119
+ val apiType = resource.optString("type", "")
120
+ return when (type) {
121
+ "songs" ->
122
+ if (apiType.contains("song")) AppleMusicJsonMapper.mapSong(resource) else null
123
+ "albums" ->
124
+ if (apiType.contains("album")) AppleMusicJsonMapper.mapAlbum(resource) else null
125
+ "artists" ->
126
+ if (apiType.contains("artist")) AppleMusicJsonMapper.mapArtist(resource) else null
127
+ "playlists" ->
128
+ if (apiType.contains("playlist")) AppleMusicJsonMapper.mapPlaylist(resource) else null
129
+ "stations" ->
130
+ if (apiType.contains("station")) AppleMusicJsonMapper.mapStation(resource) else null
131
+ "music-videos" ->
132
+ if (apiType.contains("music-video")) AppleMusicJsonMapper.mapMusicVideo(resource) else null
133
+ else -> null
134
+ }
135
+ }
136
+
137
+ suspend fun getCatalogAlbumTracks(
138
+ albumId: String,
139
+ limit: Int,
140
+ offset: Int,
141
+ ): List<Map<String, Any?>> =
142
+ getCatalogRelationship(
143
+ pathSuffix = "/albums/$albumId/tracks",
144
+ limit = limit,
145
+ offset = offset,
146
+ typeContains = "song",
147
+ mapper = AppleMusicJsonMapper::mapSong,
148
+ )
149
+
150
+ suspend fun getCatalogArtistAlbums(
151
+ artistId: String,
152
+ limit: Int,
153
+ offset: Int,
154
+ ): List<Map<String, Any?>> =
155
+ getCatalogRelationship(
156
+ pathSuffix = "/artists/$artistId/albums",
157
+ limit = limit,
158
+ offset = offset,
159
+ typeContains = "album",
160
+ mapper = AppleMusicJsonMapper::mapAlbum,
161
+ )
162
+
163
+ suspend fun getCatalogPlaylistTracks(
164
+ playlistId: String,
165
+ limit: Int,
166
+ offset: Int,
167
+ ): List<Map<String, Any?>> =
168
+ getCatalogRelationship(
169
+ pathSuffix = "/playlists/$playlistId/tracks",
170
+ limit = limit,
171
+ offset = offset,
172
+ typeContains = "song",
173
+ mapper = AppleMusicJsonMapper::mapSong,
174
+ )
175
+
176
+ data class CatalogChartsResult(
177
+ val songs: List<Map<String, Any?>>,
178
+ val albums: List<Map<String, Any?>>,
179
+ val playlists: List<Map<String, Any?>>,
180
+ val musicVideos: List<Map<String, Any?>>,
181
+ )
182
+
183
+ suspend fun getCatalogCharts(
184
+ types: List<String>,
185
+ limit: Int,
186
+ offset: Int,
187
+ genre: String?,
188
+ chart: String?,
189
+ ): CatalogChartsResult =
190
+ withContext(Dispatchers.IO) {
191
+ val storefrontId = storefront.getCatalogStorefront()
192
+ val typeParam =
193
+ types
194
+ .mapNotNull { catalogChartTypeParam(it) }
195
+ .distinct()
196
+ .joinToString(",")
197
+ .ifEmpty { "songs,albums" }
198
+
199
+ val query =
200
+ buildMap {
201
+ put("types", typeParam)
202
+ put("limit", limit.toString())
203
+ put("offset", offset.toString())
204
+ genre?.takeIf { it.isNotBlank() }?.let { put("genre", it) }
205
+ chart?.takeIf { it.isNotBlank() }?.let { put("chart", it) }
206
+ }
207
+
208
+ val json = transport.getJson(
209
+ null,
210
+ "/v1/catalog/$storefrontId/charts", query)
211
+ val results = json.optJSONObject("results") ?: JSONObject()
212
+ CatalogChartsResult(
213
+ songs = parseChartsEntries(results, "songs", "song", AppleMusicJsonMapper::mapSong),
214
+ albums = parseChartsEntries(results, "albums", "album", AppleMusicJsonMapper::mapAlbum),
215
+ playlists = parseChartsEntries(results, "playlists", "playlist", AppleMusicJsonMapper::mapPlaylist),
216
+ musicVideos =
217
+ parseChartsEntries(results, "music-videos", "music-video", AppleMusicJsonMapper::mapMusicVideo),
218
+ )
219
+ }
220
+
221
+ private suspend fun getCatalogRelationship(
222
+ pathSuffix: String,
223
+ limit: Int,
224
+ offset: Int,
225
+ typeContains: String,
226
+ mapper: (JSONObject) -> Map<String, Any?>,
227
+ ): List<Map<String, Any?>> =
228
+ withContext(Dispatchers.IO) {
229
+ val storefrontId = storefront.getCatalogStorefront()
230
+ val json =
231
+ transport.getJson(
232
+ null,
233
+ "/v1/catalog/$storefrontId$pathSuffix",
234
+ mapOf(
235
+ "limit" to limit.toString(),
236
+ "offset" to offset.toString(),
237
+ ),
238
+ )
239
+ val data = requireDataArray(json)
240
+ buildList {
241
+ for (i in 0 until data.length()) {
242
+ val resource = data.getJSONObject(i)
243
+ if (resource.optString("type", "").contains(typeContains)) {
244
+ add(mapper(resource))
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ private suspend fun getCatalogResource(
251
+ pathSuffix: String,
252
+ mapper: (JSONObject) -> Map<String, Any?>,
253
+ ): Map<String, Any?> =
254
+ withContext(Dispatchers.IO) {
255
+ val storefrontId = storefront.getCatalogStorefront()
256
+ val json = transport.getJson(
257
+ null,
258
+ "/v1/catalog/$storefrontId$pathSuffix")
259
+ val data = requireDataArray(json)
260
+ if (data.length() == 0) {
261
+ throw AppleMusicErrors.itemNotFound("Catalog item", false)
262
+ }
263
+ mapper(data.getJSONObject(0))
264
+ }
265
+
266
+ private fun parseChartsEntries(
267
+ results: JSONObject,
268
+ resultsKey: String,
269
+ typeContains: String,
270
+ mapper: (JSONObject) -> Map<String, Any?>,
271
+ ): List<Map<String, Any?>> {
272
+ val charts = results.optJSONArray(resultsKey) ?: return emptyList()
273
+ return buildList {
274
+ for (i in 0 until charts.length()) {
275
+ val chart = charts.optJSONObject(i) ?: continue
276
+ val data = chart.optJSONArray("data") ?: continue
277
+ for (j in 0 until data.length()) {
278
+ val resource = data.getJSONObject(j)
279
+ if (resource.optString("type", "").contains(typeContains)) {
280
+ add(mapper(resource))
281
+ }
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ private fun catalogChartTypeParam(type: String): String? =
288
+ when (type) {
289
+ "songs" -> "songs"
290
+ "albums" -> "albums"
291
+ "playlists" -> "playlists"
292
+ "music-videos" -> "music-videos"
293
+ else -> null
294
+ }
295
+
296
+ private fun catalogSearchTypeParam(type: String): String? =
297
+ when (type) {
298
+ "songs" -> "songs"
299
+ "albums" -> "albums"
300
+ "artists" -> "artists"
301
+ "playlists" -> "playlists"
302
+ "stations" -> "stations"
303
+ "music-videos" -> "music-videos"
304
+ else -> null
305
+ }
306
+ }
@@ -0,0 +1,152 @@
1
+ package expo.modules.applemusic
2
+
3
+ import com.apple.android.music.playback.model.MediaPlayerException
4
+ import expo.modules.applemusic.bridge.registerAuthBridge
5
+ import expo.modules.applemusic.bridge.registerCatalogBridge
6
+ import expo.modules.applemusic.bridge.registerHistoryBridge
7
+ import expo.modules.applemusic.bridge.registerLibraryBridge
8
+ import expo.modules.applemusic.bridge.registerLibraryMutationsBridge
9
+ import expo.modules.applemusic.bridge.registerPlayerBridge
10
+ import expo.modules.applemusic.bridge.registerRatingsBridge
11
+ import expo.modules.applemusic.bridge.registerRecommendationsBridge
12
+ import expo.modules.kotlin.exception.CodedException
13
+ import expo.modules.kotlin.activityresult.AppContextActivityResultLauncher
14
+ import expo.modules.kotlin.modules.Module
15
+ import expo.modules.kotlin.modules.ModuleDefinition
16
+ import kotlinx.coroutines.CoroutineScope
17
+ import kotlinx.coroutines.Dispatchers
18
+ import kotlinx.coroutines.SupervisorJob
19
+ import kotlinx.coroutines.launch
20
+
21
+ class ExpoAppleMusicModule : Module() {
22
+ private lateinit var authLauncher: AppContextActivityResultLauncher<MusicKitAuthInput, MusicKitAuthOutput>
23
+ private var playbackObserver: AndroidPlaybackObserver? = null
24
+
25
+ private val moduleScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
26
+
27
+ private val reactContext
28
+ get() = requireNotNull(appContext.reactContext) { "React Application Context is null" }
29
+
30
+ private val playbackController: AndroidPlaybackController
31
+ get() =
32
+ AndroidPlaybackController.getInstance(reactContext).also { controller ->
33
+ if (controller.playbackErrorHandler == null) {
34
+ controller.playbackErrorHandler = { error, operation ->
35
+ emitPlaybackError(error, operation)
36
+ }
37
+ }
38
+ }
39
+
40
+ private val catalogService: AndroidCatalogService
41
+ get() = AndroidCatalogService(reactContext)
42
+
43
+ private val libraryService: AndroidLibraryService
44
+ get() = AndroidLibraryService(reactContext)
45
+
46
+ private val historyService: AndroidHistoryService
47
+ get() = AndroidHistoryService(reactContext)
48
+
49
+ private val subscriptionService: AndroidSubscriptionService
50
+ get() = AndroidSubscriptionService(reactContext)
51
+
52
+ private val queueService: AndroidQueueService
53
+ get() = AndroidQueueService(reactContext, playbackController)
54
+
55
+ private val ratingsService: AndroidRatingsService
56
+ get() = AndroidRatingsService(reactContext)
57
+
58
+ private val libraryMutationsService: AndroidLibraryMutationsService
59
+ get() = AndroidLibraryMutationsService(reactContext)
60
+
61
+ private val recommendationsService: AndroidRecommendationsService
62
+ get() = AndroidRecommendationsService(reactContext)
63
+
64
+ override fun definition() = ModuleDefinition {
65
+ Name("ExpoAppleMusic")
66
+
67
+ Events(
68
+ "onPlaybackStateChange",
69
+ "onCurrentSongChange",
70
+ "onPlaybackTimeUpdate",
71
+ "onPlaybackError",
72
+ )
73
+
74
+ OnStartObserving {
75
+ val observer = AndroidPlaybackObserver(reactContext)
76
+ observer.delegate =
77
+ object : AndroidPlaybackObserverDelegate {
78
+ override fun onPlaybackStateChange(body: Map<String, Any?>) {
79
+ sendEvent("onPlaybackStateChange", body)
80
+ }
81
+
82
+ override fun onCurrentSongChange(body: Map<String, Any?>) {
83
+ sendEvent("onCurrentSongChange", body)
84
+ }
85
+
86
+ override fun onPlaybackTimeUpdate(playbackTime: Double) {
87
+ sendEvent("onPlaybackTimeUpdate", mapOf("playbackTime" to playbackTime))
88
+ }
89
+
90
+ override fun onPlaybackError(body: Map<String, Any?>) {
91
+ sendEvent("onPlaybackError", body)
92
+ }
93
+ }
94
+ observer.startObserving()
95
+ playbackObserver = observer
96
+ }
97
+
98
+ OnStopObserving {
99
+ playbackObserver?.stopObserving()
100
+ playbackObserver = null
101
+ }
102
+
103
+ RegisterActivityContracts {
104
+ authLauncher =
105
+ registerForActivityResult(
106
+ MusicKitAuthContract {
107
+ appContext.throwingActivity
108
+ },
109
+ )
110
+ }
111
+
112
+ registerAuthBridge(
113
+ reactContext = { reactContext },
114
+ authLauncher = { authLauncher },
115
+ subscriptionService = { subscriptionService },
116
+ libraryService = { libraryService },
117
+ )
118
+ registerCatalogBridge { catalogService }
119
+ registerLibraryBridge { libraryService }
120
+ registerHistoryBridge { historyService }
121
+ registerPlayerBridge(
122
+ moduleScope = moduleScope,
123
+ playbackController = { playbackController },
124
+ queueService = { queueService },
125
+ emitPlaybackError = ::emitPlaybackError,
126
+ emitPlaybackTimeUpdate = { time ->
127
+ sendEvent("onPlaybackTimeUpdate", mapOf("playbackTime" to time))
128
+ },
129
+ )
130
+ registerRatingsBridge { ratingsService }
131
+ registerLibraryMutationsBridge { libraryMutationsService }
132
+ registerRecommendationsBridge { recommendationsService }
133
+ }
134
+
135
+ private fun emitPlaybackError(error: Exception, operation: String) {
136
+ val code =
137
+ when (error) {
138
+ is MediaPlayerException -> error.errorCode
139
+ is CodedException -> error.code
140
+ else -> 0
141
+ }
142
+ sendEvent(
143
+ "onPlaybackError",
144
+ mapOf(
145
+ "message" to (error.message ?: "Playback error"),
146
+ "code" to code,
147
+ "domain" to error.javaClass.simpleName,
148
+ "operation" to operation,
149
+ ),
150
+ )
151
+ }
152
+ }