@wwdrew/expo-spotify-sdk 2.1.0 → 2.2.1
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/README.md +32 -2
- package/android/build.gradle +4 -5
- package/android/libs/SETUP.md +9 -24
- package/android/spotify-native-sdk.gradle +116 -0
- package/android/src/main/java/expo/modules/spotifysdk/ExpoSpotifySDKModule.kt +12 -1
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyAppRemoteCoordinator.kt +32 -0
- package/build/app-remote/index.d.ts +9 -0
- package/build/app-remote/index.d.ts.map +1 -1
- package/build/app-remote/index.js +11 -0
- package/build/app-remote/index.js.map +1 -1
- package/ios/ExpoSpotifyAppDelegate.swift +8 -1
- package/ios/ExpoSpotifySDK.podspec +26 -2
- package/ios/ExpoSpotifySDKModule.swift +16 -1
- package/ios/SpotifyAppRemoteConnection.swift +221 -0
- package/ios/SpotifyAppRemoteCoordinator.swift +14 -114
- package/ios/SpotifyAuthCoordinator.swift +3 -8
- package/ios/SpotifyMainThread.swift +14 -0
- package/ios/spotify-native-sdk-versions.json +12 -0
- package/package.json +10 -4
- package/ios/SpotifySDK/Licenses/MPMessagePack-LICENSE.md +0 -20
- package/ios/SpotifySDK/SpotifyiOS.xcframework/Info.plist +0 -40
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemote.h +0 -287
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteAlbum.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteArtist.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteCommon.h +0 -37
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteConnectionParams.h +0 -65
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityAPI.h +0 -55
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityState.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteContentAPI.h +0 -78
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteContentItem.h +0 -49
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteCrossfadeState.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteImageAPI.h +0 -25
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteImageRepresentable.h +0 -16
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteLibraryState.h +0 -19
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackOptions.h +0 -32
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackRestrictions.h +0 -32
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlayerAPI.h +0 -267
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePlayerState.h +0 -40
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemotePodcastPlaybackSpeed.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteTrack.h +0 -52
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteUserAPI.h +0 -102
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTAppRemoteUserCapabilities.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTConfiguration.h +0 -66
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTError.h +0 -31
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTLogin.h +0 -10
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTMacros.h +0 -125
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTScope.h +0 -75
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTSession.h +0 -33
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SPTSessionManager.h +0 -170
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SpotifyAppRemote.h +0 -28
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Headers/SpotifyiOS.h +0 -7
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Info.plist +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/Modules/module.modulemap +0 -5
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/PrivacyInfo.xcprivacy +0 -14
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64/SpotifyiOS.framework/SpotifyiOS +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemote.h +0 -287
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteAlbum.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteArtist.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteCommon.h +0 -37
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteConnectionParams.h +0 -65
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityAPI.h +0 -55
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteConnectivityState.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteContentAPI.h +0 -78
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteContentItem.h +0 -49
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteCrossfadeState.h +0 -18
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteImageAPI.h +0 -25
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteImageRepresentable.h +0 -16
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteLibraryState.h +0 -19
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackOptions.h +0 -32
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlaybackRestrictions.h +0 -32
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlayerAPI.h +0 -267
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePlayerState.h +0 -40
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemotePodcastPlaybackSpeed.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteTrack.h +0 -52
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteUserAPI.h +0 -102
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTAppRemoteUserCapabilities.h +0 -15
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTConfiguration.h +0 -66
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTError.h +0 -31
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTLogin.h +0 -10
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTMacros.h +0 -125
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTScope.h +0 -75
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTSession.h +0 -33
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SPTSessionManager.h +0 -170
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyAppRemote.h +0 -28
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Headers/SpotifyiOS.h +0 -7
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Info.plist +0 -0
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/Modules/module.modulemap +0 -5
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/PrivacyInfo.xcprivacy +0 -14
- package/ios/SpotifySDK/SpotifyiOS.xcframework/ios-arm64_x86_64-simulator/SpotifyiOS.framework/SpotifyiOS +0 -0
package/README.md
CHANGED
|
@@ -90,6 +90,7 @@ Top-level v0-style functions were removed in **`2.x`**. If you still use them, s
|
|
|
90
90
|
npx expo install @wwdrew/expo-spotify-sdk
|
|
91
91
|
|
|
92
92
|
# Expo SDK 55 only: npx expo install @wwdrew/expo-spotify-sdk@1
|
|
93
|
+
# (or @sdk55 — npm dist-tag for the v1 maintenance lane)
|
|
93
94
|
|
|
94
95
|
# 2. Add the config plugin to app.config.ts / app.json (see Configuration below)
|
|
95
96
|
# 3. Regenerate native projects
|
|
@@ -98,6 +99,20 @@ npx expo prebuild
|
|
|
98
99
|
|
|
99
100
|
For bare React Native (no Expo CLI), see [Installation in bare React Native](#installation-in-bare-react-native).
|
|
100
101
|
|
|
102
|
+
## Native SDK binaries
|
|
103
|
+
|
|
104
|
+
| Platform | Distribution |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| **iOS** | `SpotifyiOS` resolved from Spotify's GitHub via SPM at `pod install` ([ADR-0008](./docs/adr/0008-ios-spotify-sdk-via-spm.md)). Uses React Native's `spm_dependency` (RN 0.75+). Supported Expo SDK lanes: [Platform support](#platform-support). |
|
|
107
|
+
| **Android** | App Remote AAR downloaded from Spotify's GitHub at Gradle build. Auth SDK from Maven Central. |
|
|
108
|
+
|
|
109
|
+
| You are… | Action |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| **Using the published npm package** | Network on first native build per platform (`pod install` / Gradle). |
|
|
112
|
+
| **Developing this repo from git** | Same — no manual fetch scripts. |
|
|
113
|
+
|
|
114
|
+
See [Native SDK distribution](./docs/guides/native-sdk-distribution.md).
|
|
115
|
+
|
|
101
116
|
## Installation in bare React Native
|
|
102
117
|
|
|
103
118
|
This library is an [Expo Module](https://docs.expo.dev/modules/overview/) and therefore requires `expo-modules-core` as a peer. If your project does not use the Expo managed workflow you will need to set this up manually.
|
|
@@ -340,7 +355,20 @@ export function useAutoConnectAppRemote(options?: { once?: boolean }) {
|
|
|
340
355
|
}
|
|
341
356
|
```
|
|
342
357
|
|
|
343
|
-
For iOS, if `connect()` fails with `CONNECTION_FAILED`,
|
|
358
|
+
For iOS, if `connect()` fails with `CONNECTION_FAILED`, the Spotify app has likely been suspended in the background — `connect()` can only attach to an already-running Spotify. Instead of asking the user to foreground Spotify and retry manually, call `AppRemote.authorizeAndPlay(accessToken, uri?)`, which wakes Spotify (launching it if needed), starts playback, and then connects:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
try {
|
|
362
|
+
await AppRemote.connect(session.accessToken);
|
|
363
|
+
} catch (e) {
|
|
364
|
+
if (e instanceof AppRemoteError && e.code === "CONNECTION_FAILED") {
|
|
365
|
+
// Spotify was suspended — wake it, start playing, and connect.
|
|
366
|
+
await AppRemote.authorizeAndPlay(session.accessToken);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Note that `authorizeAndPlay()` always starts (or resumes) playback and briefly switches to the Spotify app — see [Troubleshooting](#troubleshooting).
|
|
344
372
|
|
|
345
373
|
## Spotify Premium and App Remote
|
|
346
374
|
|
|
@@ -379,6 +407,7 @@ Need the deprecated shims temporarily? Pin **`1.x`**: `npm install @wwdrew/expo-
|
|
|
379
407
|
| [Error codes](./docs/error-codes.md) | Per-namespace codes with when/what-to-do |
|
|
380
408
|
| [App Remote error mapping](./docs/app-remote-error-mapping.md) | iOS/Android native → JS mapping matrix |
|
|
381
409
|
| [Platform differences](./docs/guides/platform-differences.md) | iOS vs Android parity |
|
|
410
|
+
| [Native SDK distribution](./docs/guides/native-sdk-distribution.md) | How iOS/Android binaries are fetched, packaged, and bumped |
|
|
382
411
|
| [Token swap server](./docs/guides/token-swap-server.md) | Swap/refresh endpoints + example app setup |
|
|
383
412
|
|
|
384
413
|
## Troubleshooting
|
|
@@ -400,7 +429,7 @@ On iOS this can also be a stuck state when Spotify never redirects back. Call `A
|
|
|
400
429
|
**App Remote: `CONNECTION_FAILED` / `Connection refused` (iOS code 61)**
|
|
401
430
|
The Spotify app is installed but its App Remote transport is not accepting connections. Common causes:
|
|
402
431
|
|
|
403
|
-
1. **Spotify is not in the foreground** —
|
|
432
|
+
1. **Spotify is suspended / not in the foreground** — `connect()` only attaches to a running Spotify. Call `AppRemote.authorizeAndPlay(accessToken, uri?)` to wake it (it launches Spotify, starts playback, and connects), or have the user switch to Spotify and retry `AppRemote.connect()`.
|
|
404
433
|
2. **Connect ran before auth finished** — call `AppRemote.connect()` only after `Auth.authenticate()` resolves.
|
|
405
434
|
3. **Stale access token** — refresh or re-authenticate, then reconnect.
|
|
406
435
|
4. **Retry loop on startup** — avoid calling `connect()` on every render while `connectionState === "disconnected"`; gate on a one-shot flag or user action.
|
|
@@ -414,6 +443,7 @@ App Remote player state is limited for non-Premium users. Check `GET /v1/me` →
|
|
|
414
443
|
## Related docs
|
|
415
444
|
|
|
416
445
|
- [CONTEXT.md](./CONTEXT.md) — terminology (Auth SDK vs App Remote vs Web API)
|
|
446
|
+
- [docs/guides/native-sdk-distribution.md](./docs/guides/native-sdk-distribution.md) — how native binaries are fetched and packaged
|
|
417
447
|
- [docs/api-reference.md](./docs/api-reference.md) — method reference
|
|
418
448
|
- [docs/error-codes.md](./docs/error-codes.md) — error code tables
|
|
419
449
|
- [docs/QA_CHECKLIST.md](./docs/QA_CHECKLIST.md) — manual QA before a `2.x` release on `main` (or `1.x` on `v1`)
|
package/android/build.gradle
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
|
+
apply from: 'spotify-native-sdk.gradle'
|
|
2
3
|
|
|
3
4
|
group = 'expo.modules.spotifysdk'
|
|
4
5
|
version = '0.5.0'
|
|
@@ -49,11 +50,9 @@ repositories {
|
|
|
49
50
|
dependencies {
|
|
50
51
|
implementation "com.spotify.android:auth:4.0.1"
|
|
51
52
|
|
|
52
|
-
// Spotify App Remote SDK — NOT on Maven Central
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
// See android/libs/SETUP.md for full instructions.
|
|
56
|
-
implementation files('libs/spotify-app-remote-release-0.8.0.aar')
|
|
53
|
+
// Spotify App Remote SDK — NOT on Maven Central; downloaded at build time
|
|
54
|
+
// (see android/spotify-native-sdk.gradle and ios/spotify-native-sdk-versions.json).
|
|
55
|
+
implementation files(spotifyAppRemoteAarFile)
|
|
57
56
|
// App Remote is consumed as a local AAR, so transitive deps like Gson must
|
|
58
57
|
// be declared explicitly to avoid NoClassDefFoundError at runtime.
|
|
59
58
|
implementation "com.google.code.gson:gson:2.11.0"
|
package/android/libs/SETUP.md
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
|
-
# Android App Remote SDK
|
|
1
|
+
# Android App Remote SDK
|
|
2
2
|
|
|
3
|
-
The Spotify App Remote SDK for Android is not
|
|
4
|
-
|
|
3
|
+
The Spotify App Remote SDK for Android is **not** on Maven Central. This module
|
|
4
|
+
downloads `spotify-app-remote-release-<version>.aar` from Spotify's GitHub
|
|
5
|
+
releases at Gradle build time (`android/spotify-native-sdk.gradle`).
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
Version pin: `ios/spotify-native-sdk-versions.json` (`android` section).
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
2. Download the latest release archive (e.g. `spotify-android-sdk-x.x.x.zip`)
|
|
10
|
-
3. Extract `spotify-app-remote-release-x.x.x.aar` from the archive
|
|
11
|
-
4. Rename it to `spotify-app-remote-release-0.8.0.aar` (or update the version in
|
|
12
|
-
`android/build.gradle` to match the downloaded version)
|
|
13
|
-
5. Place the `.aar` file in this directory (`android/libs/`)
|
|
9
|
+
The downloaded `.aar` is gitignored under `android/libs/`.
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
## Bumping the App Remote version
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
The Spotify Auth SDK (`com.spotify.android:auth`) is on Maven Central, but the App Remote
|
|
20
|
-
SDK is distributed exclusively via GitHub releases. This directory follows the same pattern
|
|
21
|
-
as the iOS side, where `SpotifyiOS.xcframework` is committed directly to `ios/SpotifySDK/`.
|
|
22
|
-
|
|
23
|
-
## Proguard
|
|
24
|
-
|
|
25
|
-
`consumer-rules.pro` already keeps the App Remote classes:
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
-keep class com.spotify.android.appremote.** { *; }
|
|
29
|
-
```
|
|
13
|
+
Update `appRemoteVersion`, `appRemoteReleaseTag`, and `appRemoteSha256` in
|
|
14
|
+
`ios/spotify-native-sdk-versions.json`, then ship a new release of this package.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Downloads Spotify App Remote AAR at Gradle build time (pinned in ios/spotify-native-sdk-versions.json).
|
|
2
|
+
def spotifyNativeVersionsFile = file("${projectDir}/../ios/spotify-native-sdk-versions.json")
|
|
3
|
+
if (!spotifyNativeVersionsFile.exists()) {
|
|
4
|
+
throw new GradleException(
|
|
5
|
+
"Missing ${spotifyNativeVersionsFile}. Native SDK versions must be pinned in ios/spotify-native-sdk-versions.json."
|
|
6
|
+
)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
def spotifyNative = new groovy.json.JsonSlurper().parse(spotifyNativeVersionsFile)
|
|
10
|
+
def androidSdk = spotifyNative.android
|
|
11
|
+
if (androidSdk == null) {
|
|
12
|
+
throw new GradleException("ios/spotify-native-sdk-versions.json is missing an \"android\" section.")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def appRemoteVersion = androidSdk.appRemoteVersion as String
|
|
16
|
+
def appRemoteReleaseTag = androidSdk.appRemoteReleaseTag as String
|
|
17
|
+
def appRemoteSha256 = androidSdk.appRemoteSha256 as String
|
|
18
|
+
|
|
19
|
+
if (!appRemoteVersion || !appRemoteReleaseTag || !appRemoteSha256) {
|
|
20
|
+
throw new GradleException(
|
|
21
|
+
"ios/spotify-native-sdk-versions.json android section is missing required fields: " +
|
|
22
|
+
"appRemoteVersion, appRemoteReleaseTag, appRemoteSha256"
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def libsDir = file("${projectDir}/libs")
|
|
27
|
+
def aarFile = file("${libsDir}/spotify-app-remote-release-${appRemoteVersion}.aar")
|
|
28
|
+
def versionMarker = file("${libsDir}/.version")
|
|
29
|
+
def downloadUrl =
|
|
30
|
+
"https://github.com/spotify/android-sdk/releases/download/${appRemoteReleaseTag}/spotify-app-remote-release-${appRemoteVersion}.aar"
|
|
31
|
+
|
|
32
|
+
def sha256File = { File f ->
|
|
33
|
+
def digest = java.security.MessageDigest.getInstance("SHA-256")
|
|
34
|
+
f.withInputStream { stream ->
|
|
35
|
+
def buffer = new byte[8192]
|
|
36
|
+
int read
|
|
37
|
+
while ((read = stream.read(buffer)) > 0) {
|
|
38
|
+
digest.update(buffer, 0, read)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
digest.digest().collect { String.format("%02x", it) }.join("")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def appRemoteAarUpToDate = {
|
|
45
|
+
if (!aarFile.exists()) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
if (!versionMarker.exists() || versionMarker.text.trim() != appRemoteVersion) {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
sha256File(aarFile) == appRemoteSha256
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
tasks.register("downloadSpotifyAppRemoteAar") {
|
|
55
|
+
group = "spotify"
|
|
56
|
+
description = "Download Spotify App Remote AAR from GitHub releases"
|
|
57
|
+
|
|
58
|
+
outputs.file(aarFile)
|
|
59
|
+
outputs.file(versionMarker)
|
|
60
|
+
|
|
61
|
+
onlyIf {
|
|
62
|
+
!appRemoteAarUpToDate()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
doLast {
|
|
66
|
+
libsDir.mkdirs()
|
|
67
|
+
logger.lifecycle("ExpoSpotifySDK: downloading Spotify App Remote ${appRemoteVersion}")
|
|
68
|
+
|
|
69
|
+
def tmp = File.createTempFile("spotify-app-remote", ".aar")
|
|
70
|
+
try {
|
|
71
|
+
ant.get(
|
|
72
|
+
src: downloadUrl,
|
|
73
|
+
dest: tmp,
|
|
74
|
+
verbose: true
|
|
75
|
+
)
|
|
76
|
+
def actual = sha256File(tmp)
|
|
77
|
+
if (actual != appRemoteSha256) {
|
|
78
|
+
throw new GradleException(
|
|
79
|
+
"Spotify App Remote AAR checksum mismatch\n" +
|
|
80
|
+
" expected ${appRemoteSha256}\n" +
|
|
81
|
+
" actual ${actual}"
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
libsDir.listFiles()?.each { f ->
|
|
86
|
+
if (f.name.endsWith(".aar") && f.name != aarFile.name) {
|
|
87
|
+
f.delete()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (aarFile.exists()) {
|
|
92
|
+
aarFile.delete()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!tmp.renameTo(aarFile)) {
|
|
96
|
+
tmp.withInputStream { input ->
|
|
97
|
+
aarFile.withOutputStream { output ->
|
|
98
|
+
output << input
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
versionMarker.text = appRemoteVersion
|
|
104
|
+
} finally {
|
|
105
|
+
tmp.delete()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
logger.lifecycle("ExpoSpotifySDK: Spotify App Remote AAR ready at ${aarFile}")
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
ext.spotifyAppRemoteAarFile = aarFile
|
|
113
|
+
|
|
114
|
+
tasks.named("preBuild").configure {
|
|
115
|
+
dependsOn("downloadSpotifyAppRemoteAar")
|
|
116
|
+
}
|
|
@@ -8,7 +8,7 @@ import expo.modules.kotlin.functions.Coroutine
|
|
|
8
8
|
import expo.modules.kotlin.modules.Module
|
|
9
9
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
10
10
|
|
|
11
|
-
private const val SDK_VERSION = "2.1
|
|
11
|
+
private const val SDK_VERSION = "2.2.1" // x-release-please-version
|
|
12
12
|
private const val EVENT_SESSION_CHANGE = "onSessionChange"
|
|
13
13
|
private const val EVENT_CONNECTION_STATE_CHANGE = "onConnectionStateChange"
|
|
14
14
|
private const val EVENT_CONNECTION_ERROR = "onConnectionError"
|
|
@@ -202,6 +202,17 @@ class ExpoSpotifySDKModule : Module() {
|
|
|
202
202
|
)
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
AsyncFunction("appRemoteAuthorizeAndPlay") Coroutine { accessToken: String, uri: String ->
|
|
206
|
+
val manifest = readManifestConfig()
|
|
207
|
+
appRemoteCoordinator.authorizeAndPlay(
|
|
208
|
+
context = context,
|
|
209
|
+
clientId = manifest.clientId,
|
|
210
|
+
redirectUri = manifest.redirectUri,
|
|
211
|
+
accessToken = accessToken,
|
|
212
|
+
uri = uri,
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
205
216
|
AsyncFunction("appRemoteDisconnect") Coroutine { ->
|
|
206
217
|
appRemoteCoordinator.disconnect()
|
|
207
218
|
}
|
|
@@ -131,6 +131,38 @@ class SpotifyAppRemoteCoordinator {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Wakes the Spotify app and starts playback, then ensures an active App
|
|
136
|
+
* Remote connection — the cross-platform counterpart of iOS
|
|
137
|
+
* `authorizeAndPlayURI`.
|
|
138
|
+
*
|
|
139
|
+
* On Android there is no dedicated "authorize and play" entry point:
|
|
140
|
+
* [SpotifyAppRemote.connect] already launches/wakes the Spotify service when
|
|
141
|
+
* it is installed, so this simply connects (if not already connected) and
|
|
142
|
+
* then issues a play (or resume, for an empty [uri]) command. The resulting
|
|
143
|
+
* playback keeps Spotify alive, mirroring the iOS behaviour.
|
|
144
|
+
*
|
|
145
|
+
* @param uri Spotify URI to play, or empty to resume the last/contextual track.
|
|
146
|
+
*/
|
|
147
|
+
suspend fun authorizeAndPlay(
|
|
148
|
+
context: Context,
|
|
149
|
+
clientId: String,
|
|
150
|
+
redirectUri: String,
|
|
151
|
+
accessToken: String,
|
|
152
|
+
uri: String,
|
|
153
|
+
) {
|
|
154
|
+
if (appRemote?.isConnected != true) {
|
|
155
|
+
connect(context, clientId, redirectUri, accessToken)
|
|
156
|
+
}
|
|
157
|
+
if (uri.isEmpty()) {
|
|
158
|
+
requireConnected("AppRemote.authorizeAndPlay").playerApi.resume()
|
|
159
|
+
.awaitVoid("AppRemote.authorizeAndPlay")
|
|
160
|
+
} else {
|
|
161
|
+
requireConnected("AppRemote.authorizeAndPlay").playerApi.play(uri)
|
|
162
|
+
.awaitVoid("AppRemote.authorizeAndPlay")
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
134
166
|
/**
|
|
135
167
|
* Disconnects from the Spotify app. Safe to call when already disconnected.
|
|
136
168
|
*/
|
|
@@ -48,6 +48,15 @@ export declare const AppRemote: {
|
|
|
48
48
|
* Calling `connect()` while already connected is a no-op.
|
|
49
49
|
*/
|
|
50
50
|
readonly connect: (accessToken: string) => Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Wakes Spotify (launching if suspended), starts or resumes playback, and
|
|
53
|
+
* connects. Use when {@link connect} may fail because Spotify is not running.
|
|
54
|
+
* Always foregrounds Spotify and starts audio — see package api-reference.
|
|
55
|
+
*
|
|
56
|
+
* @param accessToken Access token from `Auth.authenticate()`.
|
|
57
|
+
* @param uri Spotify URI to play, or empty (default) to resume last/contextual track.
|
|
58
|
+
*/
|
|
59
|
+
readonly authorizeAndPlay: (accessToken: string, uri?: string) => Promise<void>;
|
|
51
60
|
/**
|
|
52
61
|
* Disconnects from the Spotify app. Safe to call when already disconnected.
|
|
53
62
|
* Resolves once the disconnection is complete.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app-remote/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGlE,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAMzC,8DAA8D;AAC9D,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,CAAC;AAE1E,oDAAoD;AACpD,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,eAAe,CAAC;CACxB;AAED,8CAA8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,SAAS;IACpB;;;;;;;;;;;OAWG;oCACkB,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;IAM3C;;;OAGG;+BACW,OAAO,CAAC,IAAI,CAAC;IAI3B;;;;OAIG;gCACY,OAAO;IAItB;;;OAGG;uCACmB,OAAO,CAAC,eAAe,CAAC;IAI9C;;;;;;;;;OASG;2BACS,CAAC,SAAS,uBAAuB,GAAG,iBAAiB,SACxD,CAAC,YACE,CAAC,SAAS,uBAAuB,GACvC,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAC3C,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,KACxC,iBAAiB;CAUZ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app-remote/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGlE,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAMzC,8DAA8D;AAC9D,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,CAAC;AAE1E,oDAAoD;AACpD,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,eAAe,CAAC;CACxB;AAED,8CAA8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,SAAS;IACpB;;;;;;;;;;;OAWG;oCACkB,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;IAM3C;;;;;;;OAOG;6CAC2B,MAAM,QAAO,MAAM,KAAQ,OAAO,CAAC,IAAI,CAAC;IAOtE;;;OAGG;+BACW,OAAO,CAAC,IAAI,CAAC;IAI3B;;;;OAIG;gCACY,OAAO;IAItB;;;OAGG;uCACmB,OAAO,CAAC,eAAe,CAAC;IAI9C;;;;;;;;;OASG;2BACS,CAAC,SAAS,uBAAuB,GAAG,iBAAiB,SACxD,CAAC,YACE,CAAC,SAAS,uBAAuB,GACvC,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,GAC3C,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,KACxC,iBAAiB;CAUZ,CAAC"}
|
|
@@ -55,6 +55,17 @@ export const AppRemote = {
|
|
|
55
55
|
connect(accessToken) {
|
|
56
56
|
return ExpoSpotifySDKModule.appRemoteConnect(accessToken).catch(rethrowAsAppRemoteError);
|
|
57
57
|
},
|
|
58
|
+
/**
|
|
59
|
+
* Wakes Spotify (launching if suspended), starts or resumes playback, and
|
|
60
|
+
* connects. Use when {@link connect} may fail because Spotify is not running.
|
|
61
|
+
* Always foregrounds Spotify and starts audio — see package api-reference.
|
|
62
|
+
*
|
|
63
|
+
* @param accessToken Access token from `Auth.authenticate()`.
|
|
64
|
+
* @param uri Spotify URI to play, or empty (default) to resume last/contextual track.
|
|
65
|
+
*/
|
|
66
|
+
authorizeAndPlay(accessToken, uri = "") {
|
|
67
|
+
return ExpoSpotifySDKModule.appRemoteAuthorizeAndPlay(accessToken, uri).catch(rethrowAsAppRemoteError);
|
|
68
|
+
},
|
|
58
69
|
/**
|
|
59
70
|
* Disconnects from the Spotify app. Safe to call when already disconnected.
|
|
60
71
|
* Resolves once the disconnection is complete.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app-remote/index.ts"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAA2B,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAGrE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAoBzC,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,uBAAuB,GAAG,wBAAwB,CAAC;IACvD,UAAU,EAAE,cAAc;IAC1B,WAAW,EAAE,SAAS;IACtB,UAAU,EAAE,IAAI,GAAG,CAAqB;QACtC,mBAAmB;QACnB,iBAAiB;QACjB,eAAe;QACf,SAAS;KACV,CAAC;CACH,CAAC,CAAC;AAEH,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,WAAmB;QACzB,OAAO,oBAAoB,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,KAAK,CAC7D,uBAAuB,CACxB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,UAAU;QACR,OAAO,oBAAoB,CAAC,mBAAmB,EAAE,CAAC;IACpD,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,OAAO,oBAAoB,CAAC,oBAAoB,EAAE,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,kBAAkB;QAChB,OAAO,oBAAoB,CAAC,2BAA2B,EAAE,CAAC;IAC5D,CAAC;IAED;;;;;;;;;OASG;IACH,WAAW,CACT,KAAQ,EACR,QAEyC;QAEzC,MAAM,WAAW,GACf,KAAK,KAAK,uBAAuB;YAC/B,CAAC,CAAC,yBAAyB;YAC3B,CAAC,CAAC,mBAAmB,CAAC;QAC1B,OAAO,oBAAoB,CAAC,WAAW,CACrC,WAAW,EACX,QAAoC,CAChB,CAAC;IACzB,CAAC;CACO,CAAC","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { AppRemoteError, type AppRemoteErrorCode } from \"./error\";\nimport { createNativeErrorRethrow } from \"../internal/native-errors\";\n\nexport type { AppRemoteErrorCode } from \"./error\";\nexport { AppRemoteError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Current state of the IPC connection to the Spotify app. */\nexport type ConnectionState = \"disconnected\" | \"connecting\" | \"connected\";\n\n/** Payload of the `connectionStateChange` event. */\nexport interface ConnectionStateChangeEvent {\n state: ConnectionState;\n}\n\n/** Payload of the `connectionError` event. */\nexport interface ConnectionErrorEvent {\n code: AppRemoteErrorCode;\n message: string;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst rethrowAsAppRemoteError = createNativeErrorRethrow({\n ErrorClass: AppRemoteError,\n unknownCode: \"UNKNOWN\",\n validCodes: new Set<AppRemoteErrorCode>([\n \"CONNECTION_FAILED\",\n \"CONNECTION_LOST\",\n \"NOT_CONNECTED\",\n \"UNKNOWN\",\n ]),\n});\n\n// ---------------------------------------------------------------------------\n// AppRemote namespace\n// ---------------------------------------------------------------------------\n\n/**\n * Spotify App Remote namespace. Manages the IPC connection to the running\n * Spotify app. All `Player`, `User`, `Content`, and `Images` calls require an\n * active connection established via `AppRemote.connect()`.\n *\n * @example\n * ```ts\n * import { AppRemote } from \"@wwdrew/expo-spotify-sdk\";\n *\n * // Connect using the access token from Auth.authenticate()\n * await AppRemote.connect(session.accessToken);\n *\n * const sub = AppRemote.addListener(\"connectionStateChange\", ({ state }) => {\n * console.log(\"connection state:\", state);\n * });\n *\n * // later:\n * await AppRemote.disconnect();\n * sub.remove();\n * ```\n */\nexport const AppRemote = {\n /**\n * Opens a connection to the running Spotify app using the provided access\n * token. Resolves when the connection is established; rejects with an\n * {@link AppRemoteError} if the connection fails.\n *\n * **Android note:** The access token is accepted for API parity with iOS but\n * the Android App Remote SDK does not accept it directly — it uses the\n * session cached in the Spotify app from your earlier `Auth.authenticate()`\n * call. Ensure `Auth.authenticate()` has succeeded before calling `connect()`.\n *\n * Calling `connect()` while already connected is a no-op.\n */\n connect(accessToken: string): Promise<void> {\n return ExpoSpotifySDKModule.appRemoteConnect(accessToken).catch(\n rethrowAsAppRemoteError,\n );\n },\n\n /**\n * Disconnects from the Spotify app. Safe to call when already disconnected.\n * Resolves once the disconnection is complete.\n */\n disconnect(): Promise<void> {\n return ExpoSpotifySDKModule.appRemoteDisconnect();\n },\n\n /**\n * Returns `true` if currently connected to the Spotify app.\n * This is a synchronous snapshot — subscribe to `\"connectionStateChange\"`\n * for reactive updates.\n */\n isConnected(): boolean {\n return ExpoSpotifySDKModule.appRemoteIsConnected();\n },\n\n /**\n * Returns the current {@link ConnectionState} synchronously.\n * Subscribe to `\"connectionStateChange\"` for reactive updates.\n */\n getConnectionState(): Promise<ConnectionState> {\n return ExpoSpotifySDKModule.appRemoteGetConnectionState();\n },\n\n /**\n * Subscribes to connection lifecycle events.\n *\n * | Event | Payload | When |\n * |---|---|---|\n * | `\"connectionStateChange\"` | `{ state: ConnectionState }` | State transitions |\n * | `\"connectionError\"` | `{ code, message }` | Connection failures and drops |\n *\n * Returns a `Subscription` — call `.remove()` to unsubscribe.\n */\n addListener<E extends \"connectionStateChange\" | \"connectionError\">(\n event: E,\n listener: E extends \"connectionStateChange\"\n ? (event: ConnectionStateChangeEvent) => void\n : (event: ConnectionErrorEvent) => void,\n ): EventSubscription {\n const nativeEvent =\n event === \"connectionStateChange\"\n ? \"onConnectionStateChange\"\n : \"onConnectionError\";\n return ExpoSpotifySDKModule.addListener(\n nativeEvent,\n listener as (event: unknown) => void,\n ) as EventSubscription;\n },\n} as const;\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app-remote/index.ts"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAA2B,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAGrE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAoBzC,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,uBAAuB,GAAG,wBAAwB,CAAC;IACvD,UAAU,EAAE,cAAc;IAC1B,WAAW,EAAE,SAAS;IACtB,UAAU,EAAE,IAAI,GAAG,CAAqB;QACtC,mBAAmB;QACnB,iBAAiB;QACjB,eAAe;QACf,SAAS;KACV,CAAC;CACH,CAAC,CAAC;AAEH,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,WAAmB;QACzB,OAAO,oBAAoB,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,KAAK,CAC7D,uBAAuB,CACxB,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,gBAAgB,CAAC,WAAmB,EAAE,MAAc,EAAE;QACpD,OAAO,oBAAoB,CAAC,yBAAyB,CACnD,WAAW,EACX,GAAG,CACJ,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACnC,CAAC;IAED;;;OAGG;IACH,UAAU;QACR,OAAO,oBAAoB,CAAC,mBAAmB,EAAE,CAAC;IACpD,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,OAAO,oBAAoB,CAAC,oBAAoB,EAAE,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,kBAAkB;QAChB,OAAO,oBAAoB,CAAC,2BAA2B,EAAE,CAAC;IAC5D,CAAC;IAED;;;;;;;;;OASG;IACH,WAAW,CACT,KAAQ,EACR,QAEyC;QAEzC,MAAM,WAAW,GACf,KAAK,KAAK,uBAAuB;YAC/B,CAAC,CAAC,yBAAyB;YAC3B,CAAC,CAAC,mBAAmB,CAAC;QAC1B,OAAO,oBAAoB,CAAC,WAAW,CACrC,WAAW,EACX,QAAoC,CAChB,CAAC;IACzB,CAAC;CACO,CAAC","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { AppRemoteError, type AppRemoteErrorCode } from \"./error\";\nimport { createNativeErrorRethrow } from \"../internal/native-errors\";\n\nexport type { AppRemoteErrorCode } from \"./error\";\nexport { AppRemoteError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Current state of the IPC connection to the Spotify app. */\nexport type ConnectionState = \"disconnected\" | \"connecting\" | \"connected\";\n\n/** Payload of the `connectionStateChange` event. */\nexport interface ConnectionStateChangeEvent {\n state: ConnectionState;\n}\n\n/** Payload of the `connectionError` event. */\nexport interface ConnectionErrorEvent {\n code: AppRemoteErrorCode;\n message: string;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst rethrowAsAppRemoteError = createNativeErrorRethrow({\n ErrorClass: AppRemoteError,\n unknownCode: \"UNKNOWN\",\n validCodes: new Set<AppRemoteErrorCode>([\n \"CONNECTION_FAILED\",\n \"CONNECTION_LOST\",\n \"NOT_CONNECTED\",\n \"UNKNOWN\",\n ]),\n});\n\n// ---------------------------------------------------------------------------\n// AppRemote namespace\n// ---------------------------------------------------------------------------\n\n/**\n * Spotify App Remote namespace. Manages the IPC connection to the running\n * Spotify app. All `Player`, `User`, `Content`, and `Images` calls require an\n * active connection established via `AppRemote.connect()`.\n *\n * @example\n * ```ts\n * import { AppRemote } from \"@wwdrew/expo-spotify-sdk\";\n *\n * // Connect using the access token from Auth.authenticate()\n * await AppRemote.connect(session.accessToken);\n *\n * const sub = AppRemote.addListener(\"connectionStateChange\", ({ state }) => {\n * console.log(\"connection state:\", state);\n * });\n *\n * // later:\n * await AppRemote.disconnect();\n * sub.remove();\n * ```\n */\nexport const AppRemote = {\n /**\n * Opens a connection to the running Spotify app using the provided access\n * token. Resolves when the connection is established; rejects with an\n * {@link AppRemoteError} if the connection fails.\n *\n * **Android note:** The access token is accepted for API parity with iOS but\n * the Android App Remote SDK does not accept it directly — it uses the\n * session cached in the Spotify app from your earlier `Auth.authenticate()`\n * call. Ensure `Auth.authenticate()` has succeeded before calling `connect()`.\n *\n * Calling `connect()` while already connected is a no-op.\n */\n connect(accessToken: string): Promise<void> {\n return ExpoSpotifySDKModule.appRemoteConnect(accessToken).catch(\n rethrowAsAppRemoteError,\n );\n },\n\n /**\n * Wakes Spotify (launching if suspended), starts or resumes playback, and\n * connects. Use when {@link connect} may fail because Spotify is not running.\n * Always foregrounds Spotify and starts audio — see package api-reference.\n *\n * @param accessToken Access token from `Auth.authenticate()`.\n * @param uri Spotify URI to play, or empty (default) to resume last/contextual track.\n */\n authorizeAndPlay(accessToken: string, uri: string = \"\"): Promise<void> {\n return ExpoSpotifySDKModule.appRemoteAuthorizeAndPlay(\n accessToken,\n uri,\n ).catch(rethrowAsAppRemoteError);\n },\n\n /**\n * Disconnects from the Spotify app. Safe to call when already disconnected.\n * Resolves once the disconnection is complete.\n */\n disconnect(): Promise<void> {\n return ExpoSpotifySDKModule.appRemoteDisconnect();\n },\n\n /**\n * Returns `true` if currently connected to the Spotify app.\n * This is a synchronous snapshot — subscribe to `\"connectionStateChange\"`\n * for reactive updates.\n */\n isConnected(): boolean {\n return ExpoSpotifySDKModule.appRemoteIsConnected();\n },\n\n /**\n * Returns the current {@link ConnectionState} synchronously.\n * Subscribe to `\"connectionStateChange\"` for reactive updates.\n */\n getConnectionState(): Promise<ConnectionState> {\n return ExpoSpotifySDKModule.appRemoteGetConnectionState();\n },\n\n /**\n * Subscribes to connection lifecycle events.\n *\n * | Event | Payload | When |\n * |---|---|---|\n * | `\"connectionStateChange\"` | `{ state: ConnectionState }` | State transitions |\n * | `\"connectionError\"` | `{ code, message }` | Connection failures and drops |\n *\n * Returns a `Subscription` — call `.remove()` to unsubscribe.\n */\n addListener<E extends \"connectionStateChange\" | \"connectionError\">(\n event: E,\n listener: E extends \"connectionStateChange\"\n ? (event: ConnectionStateChangeEvent) => void\n : (event: ConnectionErrorEvent) => void,\n ): EventSubscription {\n const nativeEvent =\n event === \"connectionStateChange\"\n ? \"onConnectionStateChange\"\n : \"onConnectionError\";\n return ExpoSpotifySDKModule.addListener(\n nativeEvent,\n listener as (event: unknown) => void,\n ) as EventSubscription;\n },\n} as const;\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ExpoModulesCore
|
|
2
2
|
import SpotifyiOS
|
|
3
3
|
|
|
4
|
-
/// Delivers Spotify
|
|
4
|
+
/// Delivers Spotify redirect URLs received via the legacy
|
|
5
5
|
/// `application(_:open:url:)` AppDelegate path.
|
|
6
6
|
public class ExpoSpotifyAppDelegate: ExpoAppDelegateSubscriber {
|
|
7
7
|
public func application(
|
|
@@ -9,6 +9,13 @@ public class ExpoSpotifyAppDelegate: ExpoAppDelegateSubscriber {
|
|
|
9
9
|
open url: URL,
|
|
10
10
|
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
|
|
11
11
|
) -> Bool {
|
|
12
|
+
// Both the auth flow (SPTSessionManager) and App Remote's
|
|
13
|
+
// `authorizeAndPlay()` redirect through the same URL scheme. Offer the URL
|
|
14
|
+
// to the App Remote coordinator first — it only claims the redirect when an
|
|
15
|
+
// `authorizeAndPlay()` flow is actually pending — then fall back to auth.
|
|
16
|
+
if SpotifyAppRemoteCoordinator.shared?.handleAuthorizeRedirect(url) == true {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
12
19
|
return SpotifyAuthCoordinator.shared?.handleOpenURL(url, options: options) ?? false
|
|
13
20
|
}
|
|
14
21
|
}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
require 'json'
|
|
2
2
|
|
|
3
|
+
absolute_react_native_path = ''
|
|
4
|
+
if !ENV['REACT_NATIVE_PATH'].nil?
|
|
5
|
+
absolute_react_native_path = File.expand_path(ENV['REACT_NATIVE_PATH'], Pod::Config.instance.project_root)
|
|
6
|
+
else
|
|
7
|
+
absolute_react_native_path = File.dirname(`node --print "require.resolve('react-native/package.json')"`)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
unless defined?(spm_dependency)
|
|
11
|
+
require File.join(absolute_react_native_path, "scripts/react_native_pods")
|
|
12
|
+
end
|
|
13
|
+
|
|
3
14
|
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
15
|
+
spotify_native = JSON.parse(File.read(File.join(__dir__, 'spotify-native-sdk-versions.json')))
|
|
4
16
|
|
|
5
17
|
Pod::Spec.new do |s|
|
|
6
18
|
s.name = 'ExpoSpotifySDK'
|
|
@@ -23,6 +35,18 @@ Pod::Spec.new do |s|
|
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
s.source_files = "**/*.{h,m,swift}"
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
|
|
39
|
+
ios_sdk = spotify_native['ios']
|
|
40
|
+
if ios_sdk.nil? ||
|
|
41
|
+
ios_sdk['spmRepositoryUrl'].to_s.empty? ||
|
|
42
|
+
ios_sdk['spmVersion'].to_s.empty? ||
|
|
43
|
+
ios_sdk['spmProduct'].to_s.empty?
|
|
44
|
+
raise "ios/spotify-native-sdk-versions.json is missing required iOS fields: spmRepositoryUrl, spmVersion, spmProduct"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
spm_dependency(s,
|
|
48
|
+
url: ios_sdk['spmRepositoryUrl'],
|
|
49
|
+
requirement: { kind: 'exactVersion', version: ios_sdk['spmVersion'] },
|
|
50
|
+
products: [ios_sdk['spmProduct']]
|
|
51
|
+
)
|
|
28
52
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ExpoModulesCore
|
|
2
2
|
import SpotifyiOS
|
|
3
3
|
|
|
4
|
-
private let SDK_VERSION = "2.1
|
|
4
|
+
private let SDK_VERSION = "2.2.1" // x-release-please-version
|
|
5
5
|
private let EVENT_SESSION_CHANGE = "onSessionChange"
|
|
6
6
|
private let EVENT_CONNECTION_STATE_CHANGE = "onConnectionStateChange"
|
|
7
7
|
private let EVENT_CONNECTION_ERROR = "onConnectionError"
|
|
@@ -138,6 +138,21 @@ public class ExpoSpotifySDKModule: Module {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
AsyncFunction("appRemoteAuthorizeAndPlay") { (accessToken: String, uri: String) async throws -> Void in
|
|
142
|
+
guard let coordinator = SpotifyAppRemoteCoordinator.shared else {
|
|
143
|
+
throw AppRemoteException(AppRemoteError.connectionFailed(
|
|
144
|
+
"Missing `ExpoSpotifySDK` configuration in Info.plist."
|
|
145
|
+
))
|
|
146
|
+
}
|
|
147
|
+
do {
|
|
148
|
+
try await coordinator.authorizeAndPlay(uri: uri, accessToken: accessToken)
|
|
149
|
+
} catch let error as AppRemoteError {
|
|
150
|
+
throw AppRemoteException(error)
|
|
151
|
+
} catch let error as NativePlayerError {
|
|
152
|
+
throw PlayerException(error)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
141
156
|
AsyncFunction("appRemoteDisconnect") { () async -> Void in
|
|
142
157
|
await SpotifyAppRemoteCoordinator.shared?.disconnect()
|
|
143
158
|
}
|