@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.
- package/ATTRIBUTION.md +24 -0
- package/LICENSE +190 -0
- package/NOTICE +7 -0
- package/README.md +81 -0
- package/android/build.gradle +26 -0
- package/android/libs/mediaplayback-release-1.1.1.aar +0 -0
- package/android/libs/musickitauth-release-1.1.2.aar +0 -0
- package/android/src/main/AndroidManifest.xml +16 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidCatalogService.kt +86 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidDeveloperToken.kt +39 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidHistoryService.kt +24 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidLibraryMutationsService.kt +30 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidLibraryService.kt +61 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidPlaybackController.kt +484 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidPlaybackObserver.kt +173 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidQueueService.kt +78 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidRatingsService.kt +27 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidRecommendationsService.kt +15 -0
- package/android/src/main/java/expo/modules/applemusic/AndroidSubscriptionService.kt +24 -0
- package/android/src/main/java/expo/modules/applemusic/AppleMusicErrorCodes.kt +13 -0
- package/android/src/main/java/expo/modules/applemusic/AppleMusicErrors.kt +46 -0
- package/android/src/main/java/expo/modules/applemusic/AppleMusicHttpMethod.kt +8 -0
- package/android/src/main/java/expo/modules/applemusic/AppleMusicJsonMapper.kt +258 -0
- package/android/src/main/java/expo/modules/applemusic/AppleMusicNativeLoader.kt +32 -0
- package/android/src/main/java/expo/modules/applemusic/AppleMusicRestJson.kt +40 -0
- package/android/src/main/java/expo/modules/applemusic/AppleMusicRestQuery.kt +12 -0
- package/android/src/main/java/expo/modules/applemusic/AppleMusicRestStack.kt +19 -0
- package/android/src/main/java/expo/modules/applemusic/AppleMusicRestTransport.kt +118 -0
- package/android/src/main/java/expo/modules/applemusic/AuthenticatedSession.kt +57 -0
- package/android/src/main/java/expo/modules/applemusic/BridgeResponses.kt +55 -0
- package/android/src/main/java/expo/modules/applemusic/CatalogRestClient.kt +306 -0
- package/android/src/main/java/expo/modules/applemusic/ExpoAppleMusicModule.kt +152 -0
- package/android/src/main/java/expo/modules/applemusic/HistoryRestClient.kt +60 -0
- package/android/src/main/java/expo/modules/applemusic/LibraryIds.kt +6 -0
- package/android/src/main/java/expo/modules/applemusic/LibraryMutationsRestClient.kt +95 -0
- package/android/src/main/java/expo/modules/applemusic/LibraryRestClient.kt +195 -0
- package/android/src/main/java/expo/modules/applemusic/MusicKitAuthContract.kt +78 -0
- package/android/src/main/java/expo/modules/applemusic/MusicKitAuthStorage.kt +76 -0
- package/android/src/main/java/expo/modules/applemusic/MusicKitTokenProvider.kt +13 -0
- package/android/src/main/java/expo/modules/applemusic/PaginationOptions.kt +14 -0
- package/android/src/main/java/expo/modules/applemusic/RatingsRestClient.kt +72 -0
- package/android/src/main/java/expo/modules/applemusic/RecommendationsRestClient.kt +37 -0
- package/android/src/main/java/expo/modules/applemusic/StorefrontRestClient.kt +44 -0
- package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeAuth.kt +69 -0
- package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeCatalog.kt +76 -0
- package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeHistory.kt +35 -0
- package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeLibrary.kt +54 -0
- package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeLibraryMutations.kt +30 -0
- package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgePlayer.kt +89 -0
- package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeRatings.kt +29 -0
- package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeRecommendations.kt +18 -0
- package/app.plugin.js +1 -0
- package/build/ExpoAppleMusicModule.web.d.ts +15 -0
- package/build/ExpoAppleMusicModule.web.d.ts.map +1 -0
- package/build/ExpoAppleMusicModule.web.js +33 -0
- package/build/ExpoAppleMusicModule.web.js.map +1 -0
- package/build/api/call-native.d.ts +6 -0
- package/build/api/call-native.d.ts.map +1 -0
- package/build/api/call-native.js +35 -0
- package/build/api/call-native.js.map +1 -0
- package/build/api/decode-jwt-exp.d.ts +5 -0
- package/build/api/decode-jwt-exp.d.ts.map +1 -0
- package/build/api/decode-jwt-exp.js +28 -0
- package/build/api/decode-jwt-exp.js.map +1 -0
- package/build/api/library-ids.d.ts +4 -0
- package/build/api/library-ids.d.ts.map +1 -0
- package/build/api/library-ids.js +11 -0
- package/build/api/library-ids.js.map +1 -0
- package/build/api/pagination.d.ts +12 -0
- package/build/api/pagination.d.ts.map +1 -0
- package/build/api/pagination.js +13 -0
- package/build/api/pagination.js.map +1 -0
- package/build/api/parse-authorize-result.d.ts +3 -0
- package/build/api/parse-authorize-result.d.ts.map +1 -0
- package/build/api/parse-authorize-result.js +27 -0
- package/build/api/parse-authorize-result.js.map +1 -0
- package/build/api/require-music-user-token.d.ts +2 -0
- package/build/api/require-music-user-token.d.ts.map +1 -0
- package/build/api/require-music-user-token.js +14 -0
- package/build/api/require-music-user-token.js.map +1 -0
- package/build/api/sync-developer-token.d.ts +4 -0
- package/build/api/sync-developer-token.d.ts.map +1 -0
- package/build/api/sync-developer-token.js +27 -0
- package/build/api/sync-developer-token.js.map +1 -0
- package/build/bridge/bridge-methods.d.ts +17 -0
- package/build/bridge/bridge-methods.d.ts.map +1 -0
- package/build/bridge/bridge-methods.js +71 -0
- package/build/bridge/bridge-methods.js.map +1 -0
- package/build/bridge/bridge-responses.d.ts +64 -0
- package/build/bridge/bridge-responses.d.ts.map +1 -0
- package/build/bridge/bridge-responses.js +67 -0
- package/build/bridge/bridge-responses.js.map +1 -0
- package/build/bridge/handlers/auth-bridge.d.ts +11 -0
- package/build/bridge/handlers/auth-bridge.d.ts.map +1 -0
- package/build/bridge/handlers/auth-bridge.js +49 -0
- package/build/bridge/handlers/auth-bridge.js.map +1 -0
- package/build/bridge/handlers/catalog-bridge.d.ts +34 -0
- package/build/bridge/handlers/catalog-bridge.d.ts.map +1 -0
- package/build/bridge/handlers/catalog-bridge.js +58 -0
- package/build/bridge/handlers/catalog-bridge.js.map +1 -0
- package/build/bridge/handlers/history-bridge.d.ts +19 -0
- package/build/bridge/handlers/history-bridge.d.ts.map +1 -0
- package/build/bridge/handlers/history-bridge.js +31 -0
- package/build/bridge/handlers/history-bridge.js.map +1 -0
- package/build/bridge/handlers/index.d.ts +124 -0
- package/build/bridge/handlers/index.d.ts.map +1 -0
- package/build/bridge/handlers/index.js +21 -0
- package/build/bridge/handlers/index.js.map +1 -0
- package/build/bridge/handlers/library-bridge.d.ts +23 -0
- package/build/bridge/handlers/library-bridge.d.ts.map +1 -0
- package/build/bridge/handlers/library-bridge.js +41 -0
- package/build/bridge/handlers/library-bridge.js.map +1 -0
- package/build/bridge/handlers/library-mutations-bridge.d.ts +16 -0
- package/build/bridge/handlers/library-mutations-bridge.d.ts.map +1 -0
- package/build/bridge/handlers/library-mutations-bridge.js +20 -0
- package/build/bridge/handlers/library-mutations-bridge.js.map +1 -0
- package/build/bridge/handlers/player-bridge.d.ts +18 -0
- package/build/bridge/handlers/player-bridge.d.ts.map +1 -0
- package/build/bridge/handlers/player-bridge.js +42 -0
- package/build/bridge/handlers/player-bridge.js.map +1 -0
- package/build/bridge/handlers/ratings-bridge.d.ts +15 -0
- package/build/bridge/handlers/ratings-bridge.d.ts.map +1 -0
- package/build/bridge/handlers/ratings-bridge.js +16 -0
- package/build/bridge/handlers/ratings-bridge.js.map +1 -0
- package/build/bridge/handlers/recommendations-bridge.d.ts +10 -0
- package/build/bridge/handlers/recommendations-bridge.d.ts.map +1 -0
- package/build/bridge/handlers/recommendations-bridge.js +14 -0
- package/build/bridge/handlers/recommendations-bridge.js.map +1 -0
- package/build/constants/apple-music-error-codes.d.ts +22 -0
- package/build/constants/apple-music-error-codes.d.ts.map +1 -0
- package/build/constants/apple-music-error-codes.js +21 -0
- package/build/constants/apple-music-error-codes.js.map +1 -0
- package/build/hooks/use-current-song.d.ts +10 -0
- package/build/hooks/use-current-song.d.ts.map +1 -0
- package/build/hooks/use-current-song.js +35 -0
- package/build/hooks/use-current-song.js.map +1 -0
- package/build/hooks/use-is-playing.d.ts +6 -0
- package/build/hooks/use-is-playing.d.ts.map +1 -0
- package/build/hooks/use-is-playing.js +21 -0
- package/build/hooks/use-is-playing.js.map +1 -0
- package/build/hooks/use-playback-state.d.ts +12 -0
- package/build/hooks/use-playback-state.d.ts.map +1 -0
- package/build/hooks/use-playback-state.js +41 -0
- package/build/hooks/use-playback-state.js.map +1 -0
- package/build/index.d.ts +52 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +44 -0
- package/build/index.js.map +1 -0
- package/build/mappers/apple-music-json-mapper.d.ts +94 -0
- package/build/mappers/apple-music-json-mapper.d.ts.map +1 -0
- package/build/mappers/apple-music-json-mapper.js +212 -0
- package/build/mappers/apple-music-json-mapper.js.map +1 -0
- package/build/modules/auth.d.ts +32 -0
- package/build/modules/auth.d.ts.map +1 -0
- package/build/modules/auth.js +60 -0
- package/build/modules/auth.js.map +1 -0
- package/build/modules/catalog.d.ts +46 -0
- package/build/modules/catalog.d.ts.map +1 -0
- package/build/modules/catalog.js +47 -0
- package/build/modules/catalog.js.map +1 -0
- package/build/modules/history.d.ts +17 -0
- package/build/modules/history.d.ts.map +1 -0
- package/build/modules/history.js +28 -0
- package/build/modules/history.js.map +1 -0
- package/build/modules/library-mutations.d.ts +9 -0
- package/build/modules/library-mutations.d.ts.map +1 -0
- package/build/modules/library-mutations.js +41 -0
- package/build/modules/library-mutations.js.map +1 -0
- package/build/modules/library.d.ts +21 -0
- package/build/modules/library.d.ts.map +1 -0
- package/build/modules/library.js +38 -0
- package/build/modules/library.js.map +1 -0
- package/build/modules/player.d.ts +53 -0
- package/build/modules/player.d.ts.map +1 -0
- package/build/modules/player.js +68 -0
- package/build/modules/player.js.map +1 -0
- package/build/modules/ratings.d.ts +10 -0
- package/build/modules/ratings.d.ts.map +1 -0
- package/build/modules/ratings.js +37 -0
- package/build/modules/ratings.js.map +1 -0
- package/build/modules/recommendations.d.ts +7 -0
- package/build/modules/recommendations.d.ts.map +1 -0
- package/build/modules/recommendations.js +15 -0
- package/build/modules/recommendations.js.map +1 -0
- package/build/native-module.d.ts +5 -0
- package/build/native-module.d.ts.map +1 -0
- package/build/native-module.js +5 -0
- package/build/native-module.js.map +1 -0
- package/build/native-module.web.d.ts +11 -0
- package/build/native-module.web.d.ts.map +1 -0
- package/build/native-module.web.js +11 -0
- package/build/native-module.web.js.map +1 -0
- package/build/rest/apple-music-rest-stack.d.ts +21 -0
- package/build/rest/apple-music-rest-stack.d.ts.map +1 -0
- package/build/rest/apple-music-rest-stack.js +21 -0
- package/build/rest/apple-music-rest-stack.js.map +1 -0
- package/build/rest/apple-music-rest-transport.d.ts +8 -0
- package/build/rest/apple-music-rest-transport.d.ts.map +1 -0
- package/build/rest/apple-music-rest-transport.js +2 -0
- package/build/rest/apple-music-rest-transport.js.map +1 -0
- package/build/rest/catalog-rest-client.d.ts +38 -0
- package/build/rest/catalog-rest-client.d.ts.map +1 -0
- package/build/rest/catalog-rest-client.js +189 -0
- package/build/rest/catalog-rest-client.js.map +1 -0
- package/build/rest/history-rest-client.d.ts +37 -0
- package/build/rest/history-rest-client.d.ts.map +1 -0
- package/build/rest/history-rest-client.js +30 -0
- package/build/rest/history-rest-client.js.map +1 -0
- package/build/rest/library-ids.d.ts +2 -0
- package/build/rest/library-ids.d.ts.map +1 -0
- package/build/rest/library-ids.js +2 -0
- package/build/rest/library-ids.js.map +1 -0
- package/build/rest/library-mutations-rest-client.d.ts +22 -0
- package/build/rest/library-mutations-rest-client.d.ts.map +1 -0
- package/build/rest/library-mutations-rest-client.js +37 -0
- package/build/rest/library-mutations-rest-client.js.map +1 -0
- package/build/rest/library-rest-client.d.ts +59 -0
- package/build/rest/library-rest-client.d.ts.map +1 -0
- package/build/rest/library-rest-client.js +141 -0
- package/build/rest/library-rest-client.js.map +1 -0
- package/build/rest/ratings-rest-client.d.ts +18 -0
- package/build/rest/ratings-rest-client.d.ts.map +1 -0
- package/build/rest/ratings-rest-client.js +37 -0
- package/build/rest/ratings-rest-client.js.map +1 -0
- package/build/rest/recommendations-rest-client.d.ts +32 -0
- package/build/rest/recommendations-rest-client.d.ts.map +1 -0
- package/build/rest/recommendations-rest-client.js +26 -0
- package/build/rest/recommendations-rest-client.js.map +1 -0
- package/build/rest/resource-ids-query.d.ts +2 -0
- package/build/rest/resource-ids-query.d.ts.map +1 -0
- package/build/rest/resource-ids-query.js +10 -0
- package/build/rest/resource-ids-query.js.map +1 -0
- package/build/rest/rest-json.d.ts +11 -0
- package/build/rest/rest-json.d.ts.map +1 -0
- package/build/rest/rest-json.js +29 -0
- package/build/rest/rest-json.js.map +1 -0
- package/build/rest/storefront-rest-client.d.ts +14 -0
- package/build/rest/storefront-rest-client.d.ts.map +1 -0
- package/build/rest/storefront-rest-client.js +36 -0
- package/build/rest/storefront-rest-client.js.map +1 -0
- package/build/types/album.d.ts +8 -0
- package/build/types/album.d.ts.map +1 -0
- package/build/types/album.js +2 -0
- package/build/types/album.js.map +1 -0
- package/build/types/albums-response.d.ts +5 -0
- package/build/types/albums-response.d.ts.map +1 -0
- package/build/types/albums-response.js +2 -0
- package/build/types/albums-response.js.map +1 -0
- package/build/types/android-authorize-options.d.ts +18 -0
- package/build/types/android-authorize-options.d.ts.map +1 -0
- package/build/types/android-authorize-options.js +2 -0
- package/build/types/android-authorize-options.js.map +1 -0
- package/build/types/artist.d.ts +9 -0
- package/build/types/artist.d.ts.map +1 -0
- package/build/types/artist.js +2 -0
- package/build/types/artist.js.map +1 -0
- package/build/types/auth-status.d.ts +19 -0
- package/build/types/auth-status.d.ts.map +1 -0
- package/build/types/auth-status.js +18 -0
- package/build/types/auth-status.js.map +1 -0
- package/build/types/authorize-result.d.ts +8 -0
- package/build/types/authorize-result.d.ts.map +1 -0
- package/build/types/authorize-result.js +2 -0
- package/build/types/authorize-result.js.map +1 -0
- package/build/types/catalog-album-tracks.d.ts +5 -0
- package/build/types/catalog-album-tracks.d.ts.map +1 -0
- package/build/types/catalog-album-tracks.js +2 -0
- package/build/types/catalog-album-tracks.js.map +1 -0
- package/build/types/catalog-charts.d.ts +25 -0
- package/build/types/catalog-charts.d.ts.map +1 -0
- package/build/types/catalog-charts.js +7 -0
- package/build/types/catalog-charts.js.map +1 -0
- package/build/types/catalog-resource-type.d.ts +11 -0
- package/build/types/catalog-resource-type.d.ts.map +1 -0
- package/build/types/catalog-resource-type.js +10 -0
- package/build/types/catalog-resource-type.js.map +1 -0
- package/build/types/catalog-search.d.ts +24 -0
- package/build/types/catalog-search.d.ts.map +1 -0
- package/build/types/catalog-search.js +9 -0
- package/build/types/catalog-search.js.map +1 -0
- package/build/types/check-subscription.d.ts +31 -0
- package/build/types/check-subscription.d.ts.map +1 -0
- package/build/types/check-subscription.js +5 -0
- package/build/types/check-subscription.js.map +1 -0
- package/build/types/library-music-videos.d.ts +5 -0
- package/build/types/library-music-videos.d.ts.map +1 -0
- package/build/types/library-music-videos.js +2 -0
- package/build/types/library-music-videos.js.map +1 -0
- package/build/types/library-mutations.d.ts +21 -0
- package/build/types/library-mutations.d.ts.map +1 -0
- package/build/types/library-mutations.js +8 -0
- package/build/types/library-mutations.js.map +1 -0
- package/build/types/library-search.d.ts +22 -0
- package/build/types/library-search.d.ts.map +1 -0
- package/build/types/library-search.js +9 -0
- package/build/types/library-search.js.map +1 -0
- package/build/types/music-item.d.ts +8 -0
- package/build/types/music-item.d.ts.map +1 -0
- package/build/types/music-item.js +7 -0
- package/build/types/music-item.js.map +1 -0
- package/build/types/music-video.d.ts +8 -0
- package/build/types/music-video.d.ts.map +1 -0
- package/build/types/music-video.js +2 -0
- package/build/types/music-video.js.map +1 -0
- package/build/types/pagination.d.ts +11 -0
- package/build/types/pagination.d.ts.map +1 -0
- package/build/types/pagination.js +2 -0
- package/build/types/pagination.js.map +1 -0
- package/build/types/playback-state.d.ts +9 -0
- package/build/types/playback-state.d.ts.map +1 -0
- package/build/types/playback-state.js +2 -0
- package/build/types/playback-state.js.map +1 -0
- package/build/types/playback-status.d.ts +10 -0
- package/build/types/playback-status.d.ts.map +1 -0
- package/build/types/playback-status.js +9 -0
- package/build/types/playback-status.js.map +1 -0
- package/build/types/playlist.d.ts +15 -0
- package/build/types/playlist.d.ts.map +1 -0
- package/build/types/playlist.js +2 -0
- package/build/types/playlist.js.map +1 -0
- package/build/types/rating.d.ts +34 -0
- package/build/types/rating.d.ts.map +1 -0
- package/build/types/rating.js +26 -0
- package/build/types/rating.js.map +1 -0
- package/build/types/recent-resource.d.ts +11 -0
- package/build/types/recent-resource.d.ts.map +1 -0
- package/build/types/recent-resource.js +2 -0
- package/build/types/recent-resource.js.map +1 -0
- package/build/types/recommendation.d.ts +37 -0
- package/build/types/recommendation.d.ts.map +1 -0
- package/build/types/recommendation.js +2 -0
- package/build/types/recommendation.js.map +1 -0
- package/build/types/song.d.ts +8 -0
- package/build/types/song.d.ts.map +1 -0
- package/build/types/song.js +2 -0
- package/build/types/song.js.map +1 -0
- package/build/types/station.d.ts +9 -0
- package/build/types/station.d.ts.map +1 -0
- package/build/types/station.js +2 -0
- package/build/types/station.js.map +1 -0
- package/build/types/storefront.d.ts +4 -0
- package/build/types/storefront.d.ts.map +1 -0
- package/build/types/storefront.js +2 -0
- package/build/types/storefront.js.map +1 -0
- package/build/types/tracks-from-library.d.ts +11 -0
- package/build/types/tracks-from-library.d.ts.map +1 -0
- package/build/types/tracks-from-library.js +2 -0
- package/build/types/tracks-from-library.js.map +1 -0
- package/build/utils/apple-music-error.d.ts +10 -0
- package/build/utils/apple-music-error.d.ts.map +1 -0
- package/build/utils/apple-music-error.js +13 -0
- package/build/utils/apple-music-error.js.map +1 -0
- package/build/utils/get-error-message.d.ts +2 -0
- package/build/utils/get-error-message.d.ts.map +1 -0
- package/build/utils/get-error-message.js +21 -0
- package/build/utils/get-error-message.js.map +1 -0
- package/build/utils/is-library-item.d.ts +2 -0
- package/build/utils/is-library-item.d.ts.map +1 -0
- package/build/utils/is-library-item.js +4 -0
- package/build/utils/is-library-item.js.map +1 -0
- package/build/utils/normalize-resource-ids.d.ts +4 -0
- package/build/utils/normalize-resource-ids.d.ts.map +1 -0
- package/build/utils/normalize-resource-ids.js +12 -0
- package/build/utils/normalize-resource-ids.js.map +1 -0
- package/build/web/MusicKitLoader.d.ts +11 -0
- package/build/web/MusicKitLoader.d.ts.map +1 -0
- package/build/web/MusicKitLoader.js +135 -0
- package/build/web/MusicKitLoader.js.map +1 -0
- package/build/web/WebAppleMusicApiClient.d.ts +151 -0
- package/build/web/WebAppleMusicApiClient.d.ts.map +1 -0
- package/build/web/WebAppleMusicApiClient.js +139 -0
- package/build/web/WebAppleMusicApiClient.js.map +1 -0
- package/build/web/WebAppleMusicRestTransport.d.ts +9 -0
- package/build/web/WebAppleMusicRestTransport.d.ts.map +1 -0
- package/build/web/WebAppleMusicRestTransport.js +31 -0
- package/build/web/WebAppleMusicRestTransport.js.map +1 -0
- package/build/web/WebPlaybackController.d.ts +12 -0
- package/build/web/WebPlaybackController.d.ts.map +1 -0
- package/build/web/WebPlaybackController.js +90 -0
- package/build/web/WebPlaybackController.js.map +1 -0
- package/build/web/WebPlaybackObserver.d.ts +22 -0
- package/build/web/WebPlaybackObserver.d.ts.map +1 -0
- package/build/web/WebPlaybackObserver.js +106 -0
- package/build/web/WebPlaybackObserver.js.map +1 -0
- package/build/web/WebQueueService.d.ts +10 -0
- package/build/web/WebQueueService.d.ts.map +1 -0
- package/build/web/WebQueueService.js +53 -0
- package/build/web/WebQueueService.js.map +1 -0
- package/build/web/WebSubscriptionService.d.ts +7 -0
- package/build/web/WebSubscriptionService.d.ts.map +1 -0
- package/build/web/WebSubscriptionService.js +24 -0
- package/build/web/WebSubscriptionService.js.map +1 -0
- package/build/web/apple-music-errors.d.ts +11 -0
- package/build/web/apple-music-errors.d.ts.map +1 -0
- package/build/web/apple-music-errors.js +31 -0
- package/build/web/apple-music-errors.js.map +1 -0
- package/build/web/extract-music-user-token.d.ts +4 -0
- package/build/web/extract-music-user-token.d.ts.map +1 -0
- package/build/web/extract-music-user-token.js +15 -0
- package/build/web/extract-music-user-token.js.map +1 -0
- package/build/web/map-auth-status.d.ts +17 -0
- package/build/web/map-auth-status.d.ts.map +1 -0
- package/build/web/map-auth-status.js +85 -0
- package/build/web/map-auth-status.js.map +1 -0
- package/build/web/music-kit-api.d.ts +18 -0
- package/build/web/music-kit-api.d.ts.map +1 -0
- package/build/web/music-kit-api.js +120 -0
- package/build/web/music-kit-api.js.map +1 -0
- package/build/web/musickit-types.d.ts +70 -0
- package/build/web/musickit-types.d.ts.map +1 -0
- package/build/web/musickit-types.js +3 -0
- package/build/web/musickit-types.js.map +1 -0
- package/build/web/pagination.d.ts +2 -0
- package/build/web/pagination.d.ts.map +1 -0
- package/build/web/pagination.js +2 -0
- package/build/web/pagination.js.map +1 -0
- package/expo-module.config.json +19 -0
- package/ios/AppleMusicBridgeError.swift +60 -0
- package/ios/AppleMusicErrorCodes.swift +11 -0
- package/ios/AppleMusicRestClient.swift +213 -0
- package/ios/AuthenticatedSession.swift +64 -0
- package/ios/BridgePagination.swift +17 -0
- package/ios/BridgeResponses.swift +82 -0
- package/ios/CatalogSearchStore.swift +13 -0
- package/ios/CatalogSearchStoreFactory.swift +31 -0
- package/ios/CatalogService.swift +307 -0
- package/ios/ExpoAppleMusic.podspec +29 -0
- package/ios/ExpoAppleMusicModule.swift +505 -0
- package/ios/HistoryService.swift +53 -0
- package/ios/LibraryMutationsService.swift +63 -0
- package/ios/LibraryService.swift +313 -0
- package/ios/MusicItemMapper.swift +171 -0
- package/ios/MusicKitAuthStorage.swift +38 -0
- package/ios/MusicKitCatalogSearchStore.swift +62 -0
- package/ios/PlaybackController.swift +201 -0
- package/ios/PlaybackObserver.swift +225 -0
- package/ios/QueueService.swift +166 -0
- package/ios/RatingsService.swift +66 -0
- package/ios/RecommendationsService.swift +34 -0
- package/ios/RestCatalogSearchStore.swift +62 -0
- package/ios/RestJsonMapper.swift +268 -0
- package/ios/StorefrontService.swift +55 -0
- package/ios/SubscriptionService.swift +119 -0
- package/ios/bridge/ExpoBridgeCatalog.swift +98 -0
- package/ios/bridge/ExpoBridgeHistory.swift +71 -0
- package/ios/bridge/ExpoBridgeLibrary.swift +93 -0
- package/ios/bridge/ExpoBridgeRecommendations.swift +28 -0
- package/package.json +89 -0
- package/plugin/build/index.d.ts +5 -0
- package/plugin/build/index.js +10 -0
- package/plugin/build/with-expo-apple-music.d.ts +10 -0
- 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,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
|
+
}
|