@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,78 @@
1
+ package expo.modules.applemusic
2
+
3
+ import android.content.Context
4
+
5
+ internal class AndroidQueueService(
6
+ context: Context,
7
+ private val playback: AndroidPlaybackController,
8
+ private val library: LibraryRestClient,
9
+ ) {
10
+ constructor(context: Context, playback: AndroidPlaybackController) : this(
11
+ context,
12
+ playback,
13
+ AppleMusicRestStack.create(context).library,
14
+ )
15
+
16
+ enum class MediaType(val raw: String) {
17
+ SONG("song"),
18
+ ALBUM("album"),
19
+ PLAYLIST("playlist"),
20
+ STATION("station"),
21
+ ;
22
+
23
+ companion object {
24
+ fun from(raw: String): MediaType? = entries.find { it.raw == raw }
25
+ }
26
+ }
27
+
28
+ suspend fun setQueue(itemId: String, type: String) {
29
+ val mediaType =
30
+ MediaType.from(type) ?: throw AppleMusicErrors.unknownMediaType(type)
31
+ val isLibrary = LibraryIds.isLibraryId(itemId)
32
+
33
+ if (isLibrary) {
34
+ throw AppleMusicErrors.apiError(
35
+ "Library queue requires a music user token. Use Player.playLibrarySong or playLibraryPlaylist.",
36
+ )
37
+ } else {
38
+ setCatalogQueue(itemId, mediaType)
39
+ }
40
+ }
41
+
42
+ private suspend fun setCatalogQueue(itemId: String, type: MediaType) {
43
+ val provider =
44
+ when (type) {
45
+ MediaType.SONG -> playback.buildSongProvider(itemId)
46
+ MediaType.ALBUM -> playback.buildAlbumProvider(itemId)
47
+ MediaType.PLAYLIST -> playback.buildPlaylistProvider(itemId)
48
+ MediaType.STATION ->
49
+ throw AppleMusicErrors.apiError("Station playback is not supported on Android yet.")
50
+ }
51
+ playback.clearSongCache()
52
+ playback.prepareQueue(provider)
53
+ }
54
+
55
+ suspend fun playLibrarySong(musicUserToken: String, songId: String) {
56
+ val catalogId = library.resolveCatalogPlaybackId(musicUserToken, songId, "song")
57
+ playback.clearSongCache()
58
+ playback.prepareQueue(playback.buildSongProvider(catalogId), musicUserToken)
59
+ }
60
+
61
+ suspend fun playLibraryPlaylist(musicUserToken: String, playlistId: String, startingAt: Int) {
62
+ val catalogIds = library.resolveLibrarySongCatalogIds(musicUserToken, playlistId)
63
+ if (catalogIds.isEmpty()) {
64
+ throw AppleMusicErrors.noSongsInPlaylist()
65
+ }
66
+ val startIndex =
67
+ when {
68
+ startingAt == -1 -> 0
69
+ startingAt in catalogIds.indices -> startingAt
70
+ else -> 0
71
+ }
72
+ playback.clearSongCache()
73
+ playback.prepareQueue(
74
+ playback.buildSongProvider(*catalogIds.toTypedArray(), startIndex = startIndex),
75
+ musicUserToken,
76
+ )
77
+ }
78
+ }
@@ -0,0 +1,27 @@
1
+ package expo.modules.applemusic
2
+
3
+ import android.content.Context
4
+
5
+ internal class AndroidRatingsService(
6
+ private val ratings: RatingsRestClient,
7
+ ) {
8
+ constructor(context: Context) : this(AppleMusicRestStack.create(context).ratings)
9
+
10
+ suspend fun getRating(musicUserToken: String, resourceType: String, id: String): Map<String, Any?>? =
11
+ ratings.getRating(musicUserToken, resourceType, id)
12
+
13
+ suspend fun setRating(musicUserToken: String, resourceType: String, id: String, value: Int): Map<String, Any?> =
14
+ ratings.setRating(musicUserToken, resourceType, id, value)
15
+
16
+ suspend fun clearRating(musicUserToken: String, resourceType: String, id: String) {
17
+ ratings.clearRating(musicUserToken, resourceType, id)
18
+ }
19
+
20
+ suspend fun addToFavorites(musicUserToken: String, resourceIds: Map<String, List<String>>) {
21
+ ratings.addToFavorites(musicUserToken, resourceIds)
22
+ }
23
+
24
+ suspend fun removeFromFavorites(musicUserToken: String, resourceIds: Map<String, List<String>>) {
25
+ ratings.removeFromFavorites(musicUserToken, resourceIds)
26
+ }
27
+ }
@@ -0,0 +1,15 @@
1
+ package expo.modules.applemusic
2
+
3
+ import android.content.Context
4
+
5
+ internal class AndroidRecommendationsService(
6
+ private val recommendations: RecommendationsRestClient,
7
+ ) {
8
+ constructor(context: Context) : this(AppleMusicRestStack.create(context).recommendations)
9
+
10
+ suspend fun getRecommendations(musicUserToken: String, ids: List<String>?): List<Map<String, Any?>> =
11
+ recommendations.getRecommendations(musicUserToken, ids)
12
+
13
+ suspend fun getReplay(musicUserToken: String, year: Int?): List<Map<String, Any?>> =
14
+ recommendations.getReplay(musicUserToken, year)
15
+ }
@@ -0,0 +1,24 @@
1
+ package expo.modules.applemusic
2
+
3
+ import android.content.Context
4
+
5
+ /**
6
+ * Approximates iOS [MusicSubscription] fields — there is no equivalent on Android.
7
+ */
8
+ internal class AndroidSubscriptionService(
9
+ private val library: LibraryRestClient,
10
+ ) {
11
+ constructor(context: Context) : this(AppleMusicRestStack.create(context).library)
12
+
13
+ suspend fun checkSubscription(musicUserToken: String): Map<String, Any?> {
14
+ val libraryOk = library.probeLibraryAccess(musicUserToken)
15
+ val canPlay = libraryOk
16
+
17
+ return mapOf(
18
+ "canPlayCatalogContent" to canPlay,
19
+ "canBecomeSubscriber" to false,
20
+ "hasCloudLibraryEnabled" to libraryOk,
21
+ "isMusicCatalogSubscriptionEligible" to false,
22
+ )
23
+ }
24
+ }
@@ -0,0 +1,13 @@
1
+ package expo.modules.applemusic
2
+
3
+ /**
4
+ * Bridge rejection codes — keep in sync with [src/constants/apple-music-error-codes.ts]
5
+ * and [docs/ERROR_CODES.md].
6
+ */
7
+ internal object AppleMusicErrorCodes {
8
+ const val ERROR = "ERROR"
9
+ const val PERMISSION_DENIED = "permissionDenied"
10
+ const val MISSING_DEVELOPER_TOKEN = "MISSING_DEVELOPER_TOKEN"
11
+ const val MISSING_MUSIC_USER_TOKEN = "MISSING_MUSIC_USER_TOKEN"
12
+ const val PLAYBACK_ERROR = "PLAYBACK_ERROR"
13
+ }
@@ -0,0 +1,46 @@
1
+ package expo.modules.applemusic
2
+
3
+ import expo.modules.kotlin.exception.CodedException
4
+
5
+ internal object AppleMusicErrors {
6
+ fun missingMusicUserToken(): CodedException =
7
+ CodedException(
8
+ AppleMusicErrorCodes.MISSING_MUSIC_USER_TOKEN,
9
+ "Apple Music music user token is required. Call Auth.authorize(developerToken) first, then pass the token from AuthorizeResult on user-scoped APIs (or queue playback after authorize on Android).",
10
+ null,
11
+ )
12
+
13
+ fun missingTokens(): CodedException =
14
+ CodedException(
15
+ AppleMusicErrorCodes.PERMISSION_DENIED,
16
+ "Apple Music authorization required. Call Auth.authorize() first.",
17
+ null,
18
+ )
19
+
20
+ fun permissionDenied(): CodedException =
21
+ CodedException(
22
+ AppleMusicErrorCodes.PERMISSION_DENIED,
23
+ "Apple Music authorization required or subscription needed (403)",
24
+ null,
25
+ )
26
+
27
+ fun apiError(message: String, code: String = AppleMusicErrorCodes.ERROR): CodedException =
28
+ CodedException(code, message, null)
29
+
30
+ fun playlistNotFound(): CodedException =
31
+ CodedException(AppleMusicErrorCodes.ERROR, "Playlist not found in library", null)
32
+
33
+ fun itemNotFound(item: String, inLibrary: Boolean): CodedException {
34
+ val source = if (inLibrary) "library" else "catalog"
35
+ return CodedException(AppleMusicErrorCodes.ERROR, "$item not found in $source", null)
36
+ }
37
+
38
+ fun unknownMediaType(type: String): CodedException =
39
+ CodedException(AppleMusicErrorCodes.ERROR, "Unknown media type: $type", null)
40
+
41
+ fun unsupportedLibraryType(type: String): CodedException =
42
+ CodedException(AppleMusicErrorCodes.ERROR, "Unsupported library media type: $type", null)
43
+
44
+ fun noSongsInPlaylist(): CodedException =
45
+ CodedException(AppleMusicErrorCodes.ERROR, "No songs in playlist", null)
46
+ }
@@ -0,0 +1,8 @@
1
+ package expo.modules.applemusic
2
+
3
+ internal enum class AppleMusicHttpMethod {
4
+ GET,
5
+ POST,
6
+ PUT,
7
+ DELETE,
8
+ }
@@ -0,0 +1,258 @@
1
+ package expo.modules.applemusic
2
+
3
+ import org.json.JSONObject
4
+
5
+ /**
6
+ * Maps Apple Music API JSON resources to bridge dictionaries matching [ios/MusicItemMapper.swift].
7
+ */
8
+ internal object AppleMusicJsonMapper {
9
+ fun mapSong(resource: JSONObject): Map<String, Any?> {
10
+ val attributes = resource.optJSONObject("attributes") ?: JSONObject()
11
+ val id = catalogPlaybackId(resource) ?: resource.optString("id", "")
12
+ return mapOf(
13
+ "id" to id,
14
+ "title" to attributes.optString("name", ""),
15
+ "artistName" to attributes.optString("artistName", ""),
16
+ "artworkUrl" to artworkUrl(attributes.optJSONObject("artwork")),
17
+ "duration" to durationMillis(attributes),
18
+ )
19
+ }
20
+
21
+ fun mapAlbum(resource: JSONObject): Map<String, Any?> {
22
+ val attributes = resource.optJSONObject("attributes") ?: JSONObject()
23
+ return mapOf(
24
+ "id" to resource.optString("id", ""),
25
+ "title" to attributes.optString("name", ""),
26
+ "artistName" to attributes.optString("artistName", ""),
27
+ "artworkUrl" to artworkUrl(attributes.optJSONObject("artwork")),
28
+ "trackCount" to attributes.optInt("trackCount", 0),
29
+ )
30
+ }
31
+
32
+ fun mapArtist(resource: JSONObject): Map<String, Any?> {
33
+ val attributes = resource.optJSONObject("attributes") ?: JSONObject()
34
+ return mapOf(
35
+ "id" to resource.optString("id", ""),
36
+ "name" to attributes.optString("name", ""),
37
+ "artworkUrl" to artworkUrl(attributes.optJSONObject("artwork")),
38
+ )
39
+ }
40
+
41
+ fun mapPlaylist(resource: JSONObject): Map<String, Any?> {
42
+ val attributes = resource.optJSONObject("attributes") ?: JSONObject()
43
+ val trackCount =
44
+ when {
45
+ attributes.has("trackCount") -> attributes.optInt("trackCount", 0)
46
+ else -> 0
47
+ }
48
+ return mapOf(
49
+ "id" to resource.optString("id", ""),
50
+ "name" to attributes.optString("name", ""),
51
+ "description" to (attributes.optString("description", "").ifEmpty {
52
+ attributes.optJSONObject("description")?.optString("standard", "") ?: ""
53
+ }),
54
+ "artworkUrl" to artworkUrl(attributes.optJSONObject("artwork")),
55
+ "trackCount" to trackCount,
56
+ )
57
+ }
58
+
59
+ fun mapRecentResource(resource: JSONObject): Map<String, Any?> {
60
+ val attributes = resource.optJSONObject("attributes") ?: JSONObject()
61
+ val apiType = resource.optString("type", "")
62
+ val itemType =
63
+ when {
64
+ apiType.contains("album") -> "album"
65
+ apiType.contains("playlist") -> "playlist"
66
+ apiType.contains("station") -> "station"
67
+ else -> "unknown"
68
+ }
69
+ val subtitle =
70
+ attributes.optString("artistName", "").ifEmpty {
71
+ attributes.optString("curatorName", "").ifEmpty {
72
+ attributes.optJSONObject("description")?.optString("standard", "") ?: ""
73
+ }
74
+ }
75
+ return mapOf(
76
+ "id" to resource.optString("id", ""),
77
+ "title" to attributes.optString("name", ""),
78
+ "subtitle" to subtitle,
79
+ "type" to itemType,
80
+ )
81
+ }
82
+
83
+ fun mapRecentlyPlayed(resource: JSONObject): Map<String, Any?> = mapRecentResource(resource)
84
+
85
+ fun mapStation(resource: JSONObject): Map<String, Any?> {
86
+ val attributes = resource.optJSONObject("attributes") ?: JSONObject()
87
+ return mapOf(
88
+ "id" to resource.optString("id", ""),
89
+ "name" to attributes.optString("name", ""),
90
+ "artworkUrl" to artworkUrl(attributes.optJSONObject("artwork")),
91
+ )
92
+ }
93
+
94
+ fun mapMusicVideo(resource: JSONObject): Map<String, Any?> {
95
+ val attributes = resource.optJSONObject("attributes") ?: JSONObject()
96
+ val id = catalogPlaybackId(resource) ?: resource.optString("id", "")
97
+ return mapOf<String, Any?>(
98
+ "id" to id,
99
+ "title" to attributes.optString("name", ""),
100
+ "artistName" to attributes.optString("artistName", ""),
101
+ "artworkUrl" to artworkUrl(attributes.optJSONObject("artwork")),
102
+ "duration" to durationMillis(attributes),
103
+ )
104
+ }
105
+
106
+ fun mapPlayerMediaItem(item: com.apple.android.music.playback.model.PlayerMediaItem): Map<String, Any?> =
107
+ mapOf(
108
+ "id" to item.subscriptionStoreId.orEmpty().ifEmpty { item.a().orEmpty() },
109
+ "title" to item.title.orEmpty(),
110
+ "artistName" to item.artistName.orEmpty(),
111
+ "artworkUrl" to (item.getArtworkUrl(200, 200) ?: ""),
112
+ "duration" to (item.duration / 1000).toString(),
113
+ )
114
+
115
+ /** Maps ratings API envelope (`data[0].attributes.value`). */
116
+ fun mapRating(json: JSONObject): Map<String, Any?>? {
117
+ val data = json.optJSONArray("data") ?: return null
118
+ if (data.length() == 0) return null
119
+ val item = data.getJSONObject(0)
120
+ val attributes = item.optJSONObject("attributes") ?: return null
121
+ if (!attributes.has("value")) return null
122
+ return mapOf(
123
+ "id" to item.optString("id", ""),
124
+ "value" to attributes.getInt("value"),
125
+ )
126
+ }
127
+
128
+ fun mapRecommendation(resource: JSONObject): Map<String, Any?> {
129
+ val attributes = resource.optJSONObject("attributes") ?: JSONObject()
130
+ val title =
131
+ attributes.optJSONObject("title")?.optString("stringForDisplay", "").orEmpty()
132
+ val resourceTypes =
133
+ attributes.optJSONArray("resourceTypes")?.let { array ->
134
+ buildList(array.length()) {
135
+ for (i in 0 until array.length()) {
136
+ add(array.optString(i))
137
+ }
138
+ }
139
+ } ?: emptyList()
140
+ val contents = mapRecommendationContents(resource)
141
+ return mapOf(
142
+ "id" to resource.optString("id", ""),
143
+ "title" to title,
144
+ "resourceTypes" to resourceTypes,
145
+ "playlists" to contents.playlists,
146
+ "albums" to contents.albums,
147
+ "stations" to contents.stations,
148
+ )
149
+ }
150
+
151
+ fun mapReplaySummary(resource: JSONObject): Map<String, Any?> {
152
+ val attributes = resource.optJSONObject("attributes") ?: JSONObject()
153
+ val topSongs = mapRelationshipResources(resource, "top-songs", ::mapSong)
154
+ val topAlbums = mapRelationshipResources(resource, "top-albums", ::mapAlbum)
155
+ val topArtists = mapRelationshipResources(resource, "top-artists", ::mapArtist)
156
+ return buildMap {
157
+ put("id", resource.optString("id", ""))
158
+ put("type", resource.optString("type", ""))
159
+ put("name", attributes.optString("name", ""))
160
+ put("topSongs", topSongs)
161
+ put("topAlbums", topAlbums)
162
+ put("topArtists", topArtists)
163
+ if (attributes.has("year")) {
164
+ put("year", attributes.optInt("year"))
165
+ }
166
+ }
167
+ }
168
+
169
+ private data class RecommendationContents(
170
+ val playlists: List<Map<String, Any?>>,
171
+ val albums: List<Map<String, Any?>>,
172
+ val stations: List<Map<String, Any?>>,
173
+ )
174
+
175
+ private fun mapRecommendationContents(resource: JSONObject): RecommendationContents {
176
+ val data =
177
+ resource
178
+ .optJSONObject("relationships")
179
+ ?.optJSONObject("contents")
180
+ ?.optJSONArray("data")
181
+ ?: return RecommendationContents(emptyList(), emptyList(), emptyList())
182
+ val playlists = buildList {
183
+ for (i in 0 until data.length()) {
184
+ val item = data.getJSONObject(i)
185
+ if (item.optString("type", "").contains("playlist")) {
186
+ add(mapPlaylist(item))
187
+ }
188
+ }
189
+ }
190
+ val albums = buildList {
191
+ for (i in 0 until data.length()) {
192
+ val item = data.getJSONObject(i)
193
+ if (item.optString("type", "").contains("album")) {
194
+ add(mapAlbum(item))
195
+ }
196
+ }
197
+ }
198
+ val stations = buildList {
199
+ for (i in 0 until data.length()) {
200
+ val item = data.getJSONObject(i)
201
+ if (item.optString("type", "").contains("station")) {
202
+ add(mapStation(item))
203
+ }
204
+ }
205
+ }
206
+ return RecommendationContents(playlists, albums, stations)
207
+ }
208
+
209
+ private fun mapRelationshipResources(
210
+ resource: JSONObject,
211
+ relationshipKey: String,
212
+ mapper: (JSONObject) -> Map<String, Any?>,
213
+ ): List<Map<String, Any?>> {
214
+ val data =
215
+ resource
216
+ .optJSONObject("relationships")
217
+ ?.optJSONObject(relationshipKey)
218
+ ?.optJSONArray("data")
219
+ ?: return emptyList()
220
+ return buildList(data.length()) {
221
+ for (i in 0 until data.length()) {
222
+ add(mapper(data.getJSONObject(i)))
223
+ }
224
+ }
225
+ }
226
+
227
+ fun describePlaybackStatus(state: Int): String =
228
+ when (state) {
229
+ com.apple.android.music.playback.model.PlaybackState.PLAYING -> "playing"
230
+ com.apple.android.music.playback.model.PlaybackState.PAUSED -> "paused"
231
+ com.apple.android.music.playback.model.PlaybackState.STOPPED -> "stopped"
232
+ else -> "unknown"
233
+ }
234
+
235
+ private fun durationMillis(attributes: JSONObject): Long {
236
+ return when {
237
+ attributes.has("durationInMillis") -> attributes.optLong("durationInMillis", 0)
238
+ attributes.has("duration") -> (attributes.optDouble("duration", 0.0) * 1000).toLong()
239
+ else -> 0L
240
+ }
241
+ }
242
+
243
+ /** Catalog song id for [CatalogPlaybackQueueItemProvider] (playParams when present). */
244
+ fun catalogPlaybackId(resource: JSONObject): String? {
245
+ val playParams = resource.optJSONObject("attributes")?.optJSONObject("playParams") ?: return null
246
+ return playParams.optString("id", "").takeIf { it.isNotEmpty() }
247
+ ?: playParams.optString("catalogId", "").takeIf { it.isNotEmpty() }
248
+ }
249
+
250
+ private fun artworkUrl(artwork: JSONObject?, width: Int = 200, height: Int = 200): String {
251
+ if (artwork == null) return ""
252
+ val template = artwork.optString("url", "")
253
+ if (template.isEmpty()) return ""
254
+ return template
255
+ .replace("{w}", width.toString())
256
+ .replace("{h}", height.toString())
257
+ }
258
+ }
@@ -0,0 +1,32 @@
1
+ package expo.modules.applemusic
2
+
3
+ import android.util.Log
4
+
5
+ /**
6
+ * Loads Apple Music playback natives. Must run before [MediaPlayerControllerFactory].
7
+ * See Apple / community samples for the bytedeco memory properties.
8
+ */
9
+ internal object AppleMusicNativeLoader {
10
+ private const val TAG = "ExpoAppleMusic"
11
+
12
+ @Volatile
13
+ private var loaded = false
14
+
15
+ fun ensureLoaded() {
16
+ if (loaded) return
17
+ synchronized(this) {
18
+ if (loaded) return
19
+ try {
20
+ System.setProperty("org.bytedeco.javacpp.maxphysicalbytes", "0")
21
+ System.setProperty("org.bytedeco.javacpp.maxbytes", "0")
22
+ System.loadLibrary("c++_shared")
23
+ System.loadLibrary("appleMusicSDK")
24
+ loaded = true
25
+ Log.i(TAG, "native libraries loaded")
26
+ } catch (error: UnsatisfiedLinkError) {
27
+ Log.e(TAG, "failed to load playback natives", error)
28
+ throw error
29
+ }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,40 @@
1
+ package expo.modules.applemusic
2
+
3
+ import org.json.JSONArray
4
+ import org.json.JSONObject
5
+
6
+ /** Top-level list responses must include a `data` array (may be empty). */
7
+ internal fun requireDataArray(json: JSONObject, key: String = "data"): JSONArray {
8
+ if (!json.has(key) || json.isNull(key)) {
9
+ throw AppleMusicErrors.apiError("Apple Music API response missing \"$key\"")
10
+ }
11
+ when (val value = json.get(key)) {
12
+ is JSONArray -> return value
13
+ JSONObject.NULL -> return JSONArray()
14
+ else ->
15
+ throw AppleMusicErrors.apiError("Apple Music API response \"$key\" is not an array")
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Map a `data` array (or optional nested search bucket).
21
+ * `null` → empty (type omitted from search). Non-array → reject.
22
+ */
23
+ internal fun mapResourceArray(
24
+ array: JSONArray?,
25
+ mapper: (JSONObject) -> Map<String, Any?>,
26
+ ): List<Map<String, Any?>> {
27
+ if (array == null) return emptyList()
28
+ return buildList(array.length()) {
29
+ for (i in 0 until array.length()) {
30
+ add(mapper(array.getJSONObject(i)))
31
+ }
32
+ }
33
+ }
34
+
35
+ /** Parse required top-level `data`, then map resources. */
36
+ internal fun mapTopLevelResourceArray(
37
+ json: JSONObject,
38
+ key: String = "data",
39
+ mapper: (JSONObject) -> Map<String, Any?>,
40
+ ): List<Map<String, Any?>> = mapResourceArray(requireDataArray(json, key), mapper)
@@ -0,0 +1,12 @@
1
+ package expo.modules.applemusic
2
+
3
+ internal fun buildIdsQuery(resourceIds: Map<String, List<String>>): Map<String, String> {
4
+ val query = linkedMapOf<String, String>()
5
+ resourceIds.forEach { (type, ids) ->
6
+ val filtered = ids.filter { it.isNotBlank() }
7
+ if (filtered.isNotEmpty()) {
8
+ query["ids[$type]"] = filtered.joinToString(",")
9
+ }
10
+ }
11
+ return query
12
+ }
@@ -0,0 +1,19 @@
1
+ package expo.modules.applemusic
2
+
3
+ import android.content.Context
4
+
5
+ /** Wires shared REST transport + domain clients for Android services. */
6
+ internal class AppleMusicRestStack(context: Context) {
7
+ val transport: AppleMusicRestTransport = OkHttpAppleMusicRestTransport(context)
8
+ val storefront: StorefrontRestClient = StorefrontRestClient(transport)
9
+ val catalog: CatalogRestClient = CatalogRestClient(transport, storefront)
10
+ val library: LibraryRestClient = LibraryRestClient(transport)
11
+ val history: HistoryRestClient = HistoryRestClient(transport)
12
+ val ratings: RatingsRestClient = RatingsRestClient(transport)
13
+ val libraryMutations: LibraryMutationsRestClient = LibraryMutationsRestClient(transport)
14
+ val recommendations: RecommendationsRestClient = RecommendationsRestClient(transport)
15
+
16
+ companion object {
17
+ fun create(context: Context): AppleMusicRestStack = AppleMusicRestStack(context)
18
+ }
19
+ }