@wwdrew/expo-spotify-sdk 0.7.1 → 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/README.md +388 -108
- package/android/build.gradle +10 -0
- package/android/consumer-rules.pro +5 -0
- package/android/libs/SETUP.md +29 -0
- package/android/src/main/java/expo/modules/spotifysdk/ExpoSpotifySDKModule.kt +149 -12
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyAppRemoteCoordinator.kt +556 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyConfig.kt +1 -0
- package/android/src/main/java/expo/modules/spotifysdk/SpotifyErrors.kt +112 -0
- package/build/ExpoSpotifySDK.types.d.ts +12 -0
- package/build/ExpoSpotifySDK.types.d.ts.map +1 -1
- package/build/ExpoSpotifySDK.types.js.map +1 -1
- package/build/ExpoSpotifySDKModule.web.d.ts +23 -0
- package/build/ExpoSpotifySDKModule.web.d.ts.map +1 -1
- package/build/ExpoSpotifySDKModule.web.js +74 -5
- package/build/ExpoSpotifySDKModule.web.js.map +1 -1
- package/build/app-remote/error.d.ts +8 -0
- package/build/app-remote/error.d.ts.map +1 -0
- package/build/app-remote/error.js +10 -0
- package/build/app-remote/error.js.map +1 -0
- package/build/app-remote/index.d.ts +79 -0
- package/build/app-remote/index.d.ts.map +1 -0
- package/build/app-remote/index.js +110 -0
- package/build/app-remote/index.js.map +1 -0
- package/build/auth/error.d.ts +8 -0
- package/build/auth/error.d.ts.map +1 -0
- package/build/auth/error.js +10 -0
- package/build/auth/error.js.map +1 -0
- package/build/auth/index.d.ts +124 -0
- package/build/auth/index.d.ts.map +1 -0
- package/build/auth/index.js +162 -0
- package/build/auth/index.js.map +1 -0
- package/build/content/error.d.ts +8 -0
- package/build/content/error.d.ts.map +1 -0
- package/build/content/error.js +10 -0
- package/build/content/error.js.map +1 -0
- package/build/content/index.d.ts +38 -0
- package/build/content/index.d.ts.map +1 -0
- package/build/content/index.js +56 -0
- package/build/content/index.js.map +1 -0
- package/build/error.d.ts +24 -0
- package/build/error.d.ts.map +1 -0
- package/build/error.js +25 -0
- package/build/error.js.map +1 -0
- package/build/hooks/index.d.ts +90 -0
- package/build/hooks/index.d.ts.map +1 -0
- package/build/hooks/index.js +326 -0
- package/build/hooks/index.js.map +1 -0
- package/build/images/error.d.ts +8 -0
- package/build/images/error.d.ts.map +1 -0
- package/build/images/error.js +10 -0
- package/build/images/error.js.map +1 -0
- package/build/images/index.d.ts +36 -0
- package/build/images/index.d.ts.map +1 -0
- package/build/images/index.js +59 -0
- package/build/images/index.js.map +1 -0
- package/build/index.d.ts +44 -42
- package/build/index.d.ts.map +1 -1
- package/build/index.js +49 -122
- package/build/index.js.map +1 -1
- package/build/player/error.d.ts +8 -0
- package/build/player/error.d.ts.map +1 -0
- package/build/player/error.js +10 -0
- package/build/player/error.js.map +1 -0
- package/build/player/index.d.ts +140 -0
- package/build/player/index.d.ts.map +1 -0
- package/build/player/index.js +136 -0
- package/build/player/index.js.map +1 -0
- package/build/uri/index.d.ts +55 -0
- package/build/uri/index.d.ts.map +1 -0
- package/build/uri/index.js +57 -0
- package/build/uri/index.js.map +1 -0
- package/build/user/error.d.ts +8 -0
- package/build/user/error.d.ts.map +1 -0
- package/build/user/error.js +10 -0
- package/build/user/error.js.map +1 -0
- package/build/user/index.d.ts +44 -0
- package/build/user/index.d.ts.map +1 -0
- package/build/user/index.js +97 -0
- package/build/user/index.js.map +1 -0
- package/ios/ExpoSpotifySDKModule.swift +233 -6
- package/ios/SPTScopeSerializer.swift +5 -0
- package/ios/SpotifyAppRemoteCoordinator.swift +939 -0
- package/ios/SpotifyAuthCoordinator.swift +67 -13
- package/ios/SpotifyTokenRefreshClient.swift +32 -1
- package/package.json +4 -3
package/build/index.js
CHANGED
|
@@ -1,128 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Hooks (v1 public API)
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
export { useSession, useConnectionState, usePlayerState, useCurrentTrack, useIsPlaying, usePlaybackPosition, useCapabilities, useLibraryState, } from "./hooks";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Namespaces (v1 public API)
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export { Auth } from "./auth";
|
|
9
|
+
export { AppRemote } from "./app-remote";
|
|
10
|
+
export { Player } from "./player";
|
|
11
|
+
export { User } from "./user";
|
|
12
|
+
export { Content } from "./content";
|
|
13
|
+
export { Images } from "./images";
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Error hierarchy
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export { SpotifyError } from "./error";
|
|
18
|
+
export { AuthError } from "./auth";
|
|
19
|
+
export { AppRemoteError } from "./app-remote";
|
|
20
|
+
export { PlayerError } from "./player";
|
|
21
|
+
export { UserError } from "./user";
|
|
22
|
+
export { ContentError } from "./content";
|
|
23
|
+
export { ImagesError } from "./images";
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// URI helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
export { SpotifyURI } from "./uri";
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// v0.x backward-compatible exports (deprecated — remove at v2.0.0)
|
|
30
|
+
//
|
|
31
|
+
// These shims let existing callers continue to compile after upgrading to v1.
|
|
32
|
+
// Migrate to the namespaced API: see docs/V1_PLAN.md §8 (Migration).
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
import { Auth } from "./auth";
|
|
35
|
+
/** @deprecated Use `Auth.isAvailable()` */
|
|
36
|
+
export function isAvailable() {
|
|
37
|
+
return Auth.isAvailable();
|
|
15
38
|
}
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
function authenticateAsync(config) {
|
|
21
|
-
if (!config.scopes || config.scopes.length === 0) {
|
|
22
|
-
return Promise.reject(new SpotifyError("INVALID_CONFIG", "scopes must contain at least one entry"));
|
|
23
|
-
}
|
|
24
|
-
if (Platform.OS === "android" &&
|
|
25
|
-
!config.tokenSwapURL &&
|
|
26
|
-
!warnedAboutAndroidTokenFlow) {
|
|
27
|
-
warnedAboutAndroidTokenFlow = true;
|
|
28
|
-
console.warn(ANDROID_TOKEN_FLOW_WARNING);
|
|
29
|
-
}
|
|
30
|
-
return ExpoSpotifySDKModule.authenticateAsync(config)
|
|
31
|
-
.then(normaliseSession)
|
|
32
|
-
.catch(rethrowAsSpotifyError);
|
|
39
|
+
/** @deprecated Use `Auth.authenticate(config)` */
|
|
40
|
+
export function authenticateAsync(config) {
|
|
41
|
+
return Auth.authenticate(config);
|
|
33
42
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
const r = raw;
|
|
39
|
-
const accessToken = r.accessToken;
|
|
40
|
-
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
41
|
-
throw new SpotifyError("UNKNOWN", "Session is missing accessToken");
|
|
42
|
-
}
|
|
43
|
-
const expirationDate = r.expirationDate;
|
|
44
|
-
if (typeof expirationDate !== "number") {
|
|
45
|
-
throw new SpotifyError("UNKNOWN", "Session is missing expirationDate");
|
|
46
|
-
}
|
|
47
|
-
const refreshTokenRaw = r.refreshToken;
|
|
48
|
-
const refreshToken = typeof refreshTokenRaw === "string" && refreshTokenRaw.length > 0
|
|
49
|
-
? refreshTokenRaw
|
|
50
|
-
: null;
|
|
51
|
-
const scopesRaw = r.scopes;
|
|
52
|
-
const scopes = Array.isArray(scopesRaw)
|
|
53
|
-
? scopesRaw.filter((s) => typeof s === "string")
|
|
54
|
-
: [];
|
|
55
|
-
return { accessToken, refreshToken, expirationDate, scopes };
|
|
43
|
+
/** @deprecated Use `Auth.cancelPending()` */
|
|
44
|
+
export function cancelPendingAuthAsync() {
|
|
45
|
+
return Auth.cancelPending();
|
|
56
46
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* {@link SpotifyError}.
|
|
61
|
-
*/
|
|
62
|
-
function refreshSessionAsync(config) {
|
|
63
|
-
if (!config.refreshToken) {
|
|
64
|
-
return Promise.reject(new SpotifyError("INVALID_CONFIG", "refreshToken is required"));
|
|
65
|
-
}
|
|
66
|
-
if (!config.tokenRefreshURL) {
|
|
67
|
-
return Promise.reject(new SpotifyError("INVALID_CONFIG", "tokenRefreshURL is required"));
|
|
68
|
-
}
|
|
69
|
-
return ExpoSpotifySDKModule.refreshSessionAsync(config)
|
|
70
|
-
.then(normaliseSession)
|
|
71
|
-
.catch(rethrowAsSpotifyError);
|
|
47
|
+
/** @deprecated Use `Auth.refresh(config)` */
|
|
48
|
+
export function refreshSessionAsync(config) {
|
|
49
|
+
return Auth.refresh(config);
|
|
72
50
|
}
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
* Events are fired for every `authenticateAsync` and `refreshSessionAsync`
|
|
77
|
-
* call, regardless of whether the call was awaited. Useful for persisting
|
|
78
|
-
* tokens in a central store without coupling the store to the call sites.
|
|
79
|
-
*
|
|
80
|
-
* Returns a {@link Subscription} — call `.remove()` to unsubscribe.
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```ts
|
|
84
|
-
* const sub = addSessionChangeListener((event) => {
|
|
85
|
-
* if (event.type === "didInitiate" || event.type === "didRenew") {
|
|
86
|
-
* store.setSession(event.session);
|
|
87
|
-
* }
|
|
88
|
-
* });
|
|
89
|
-
* // later:
|
|
90
|
-
* sub.remove();
|
|
91
|
-
* ```
|
|
92
|
-
*/
|
|
93
|
-
function addSessionChangeListener(listener) {
|
|
94
|
-
return ExpoSpotifySDKModule.addListener("onSessionChange", listener);
|
|
51
|
+
/** @deprecated Use `Auth.addListener("sessionChange", cb)` */
|
|
52
|
+
export function addSessionChangeListener(listener) {
|
|
53
|
+
return Auth.addListener("sessionChange", listener);
|
|
95
54
|
}
|
|
96
|
-
const ERROR_PREFIX_RE = /^([A-Z_][A-Z0-9_]*):\s*(.*)$/s;
|
|
97
|
-
const VALID_CODES = new Set([
|
|
98
|
-
"USER_CANCELLED",
|
|
99
|
-
"AUTH_IN_PROGRESS",
|
|
100
|
-
"INVALID_CONFIG",
|
|
101
|
-
"NETWORK_ERROR",
|
|
102
|
-
"TOKEN_SWAP_FAILED",
|
|
103
|
-
"TOKEN_SWAP_PARSE_ERROR",
|
|
104
|
-
"SPOTIFY_NOT_INSTALLED",
|
|
105
|
-
"AUTH_ERROR",
|
|
106
|
-
"UNKNOWN",
|
|
107
|
-
]);
|
|
108
|
-
function rethrowAsSpotifyError(err) {
|
|
109
|
-
if (err instanceof SpotifyError)
|
|
110
|
-
throw err;
|
|
111
|
-
if (err instanceof Error) {
|
|
112
|
-
const m = err.message.match(ERROR_PREFIX_RE);
|
|
113
|
-
if (m && VALID_CODES.has(m[1])) {
|
|
114
|
-
throw new SpotifyError(m[1], m[2]);
|
|
115
|
-
}
|
|
116
|
-
const maybeCode = err.code;
|
|
117
|
-
if (maybeCode && VALID_CODES.has(maybeCode)) {
|
|
118
|
-
throw new SpotifyError(maybeCode, err.message);
|
|
119
|
-
}
|
|
120
|
-
throw new SpotifyError("UNKNOWN", err.message);
|
|
121
|
-
}
|
|
122
|
-
throw new SpotifyError("UNKNOWN", String(err));
|
|
123
|
-
}
|
|
124
|
-
const Authenticate = {
|
|
125
|
-
authenticateAsync,
|
|
126
|
-
};
|
|
127
|
-
export { isAvailable, authenticateAsync, refreshSessionAsync, addSessionChangeListener, Authenticate, SpotifyError, };
|
|
128
55
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAavC,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AA2DnC,8EAA8E;AAC9E,mEAAmE;AACnE,EAAE;AACF,8EAA8E;AAC9E,qEAAqE;AACrE,8EAA8E;AAE9E,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAM9B,2CAA2C;AAC3C,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC5B,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,iBAAiB,CAAC,MAAqB;IACrD,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,sBAAsB;IACpC,OAAO,IAAI,CAAC,aAAa,EAAE,CAAC;AAC9B,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,mBAAmB,CAAC,MAA4B;IAC9D,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,wBAAwB,CACtC,QAAgD;IAEhD,OAAO,IAAI,CAAC,WAAW,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;AACrD,CAAC","sourcesContent":["// ---------------------------------------------------------------------------\n// Hooks (v1 public API)\n// ---------------------------------------------------------------------------\n\nexport {\n useSession,\n useConnectionState,\n usePlayerState,\n useCurrentTrack,\n useIsPlaying,\n usePlaybackPosition,\n useCapabilities,\n useLibraryState,\n} from \"./hooks\";\n\n// ---------------------------------------------------------------------------\n// Namespaces (v1 public API)\n// ---------------------------------------------------------------------------\n\nexport { Auth } from \"./auth\";\nexport { AppRemote } from \"./app-remote\";\nexport { Player } from \"./player\";\nexport { User } from \"./user\";\nexport { Content } from \"./content\";\nexport { Images } from \"./images\";\n\n// ---------------------------------------------------------------------------\n// Error hierarchy\n// ---------------------------------------------------------------------------\n\nexport { SpotifyError } from \"./error\";\nexport { AuthError } from \"./auth\";\nexport { AppRemoteError } from \"./app-remote\";\nexport { PlayerError } from \"./player\";\nexport { UserError } from \"./user\";\nexport { ContentError } from \"./content\";\nexport { ImagesError } from \"./images\";\n\n// ---------------------------------------------------------------------------\n// Error code types\n// ---------------------------------------------------------------------------\n\nexport type { AuthErrorCode } from \"./auth\";\nexport type { AppRemoteErrorCode } from \"./app-remote\";\nexport type { PlayerErrorCode } from \"./player\";\nexport type { UserErrorCode } from \"./user\";\nexport type { ContentErrorCode } from \"./content\";\nexport type { ImagesErrorCode } from \"./images\";\n\n// ---------------------------------------------------------------------------\n// URI helpers\n// ---------------------------------------------------------------------------\n\nexport { SpotifyURI } from \"./uri\";\nexport type { SpotifyResourceType, SpotifyURI as SpotifyURIType } from \"./uri\";\n\n// ---------------------------------------------------------------------------\n// Auth types\n// ---------------------------------------------------------------------------\n\nexport type {\n SpotifySession,\n SpotifyScope,\n AuthenticateConfig,\n RefreshConfig,\n SessionChangeEvent,\n} from \"./auth\";\n\n// ---------------------------------------------------------------------------\n// App Remote types\n// ---------------------------------------------------------------------------\n\nexport type {\n ConnectionState,\n ConnectionStateChangeEvent,\n ConnectionErrorEvent,\n} from \"./app-remote\";\n\n// ---------------------------------------------------------------------------\n// Player types\n// ---------------------------------------------------------------------------\n\nexport type {\n RepeatMode,\n PodcastPlaybackSpeed,\n Artist,\n Album,\n Track,\n PlaybackOptions,\n PlaybackRestrictions,\n PlayerState,\n CrossfadeState,\n} from \"./player\";\n\n// ---------------------------------------------------------------------------\n// User types\n// ---------------------------------------------------------------------------\n\nexport type { Capabilities, LibraryState } from \"./user\";\n\n// ---------------------------------------------------------------------------\n// Content types\n// ---------------------------------------------------------------------------\n\nexport type { ContentType, ContentItem } from \"./content\";\n\n// ---------------------------------------------------------------------------\n// Images types\n// ---------------------------------------------------------------------------\n\nexport type { ImageSize, ImageResult, ImageRepresentable } from \"./images\";\n\n// ---------------------------------------------------------------------------\n// v0.x backward-compatible exports (deprecated — remove at v2.0.0)\n//\n// These shims let existing callers continue to compile after upgrading to v1.\n// Migrate to the namespaced API: see docs/V1_PLAN.md §8 (Migration).\n// ---------------------------------------------------------------------------\n\nimport { Auth } from \"./auth\";\nimport type {\n AuthenticateConfig as SpotifyConfig,\n RefreshConfig as SpotifyRefreshConfig,\n} from \"./auth\";\n\n/** @deprecated Use `Auth.isAvailable()` */\nexport function isAvailable(): boolean {\n return Auth.isAvailable();\n}\n\n/** @deprecated Use `Auth.authenticate(config)` */\nexport function authenticateAsync(config: SpotifyConfig) {\n return Auth.authenticate(config);\n}\n\n/** @deprecated Use `Auth.cancelPending()` */\nexport function cancelPendingAuthAsync() {\n return Auth.cancelPending();\n}\n\n/** @deprecated Use `Auth.refresh(config)` */\nexport function refreshSessionAsync(config: SpotifyRefreshConfig) {\n return Auth.refresh(config);\n}\n\n/** @deprecated Use `Auth.addListener(\"sessionChange\", cb)` */\nexport function addSessionChangeListener(\n listener: Parameters<typeof Auth.addListener>[1],\n) {\n return Auth.addListener(\"sessionChange\", listener);\n}\n\n// Re-export legacy type aliases so existing `import type { SpotifyConfig }`\n// consumers don't break.\nexport type { SpotifyConfig, SpotifyRefreshConfig };\n\n/**\n * @deprecated `SpotifyErrorCode` is now `AuthErrorCode`. Import via:\n * `import type { AuthErrorCode } from \"@wwdrew/expo-spotify-sdk\"`\n */\nexport type { AuthErrorCode as SpotifyErrorCode } from \"./auth\";\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SpotifyError } from "../error";
|
|
2
|
+
export type PlayerErrorCode = "NOT_CONNECTED" | "CONNECTION_LOST" | "PREMIUM_REQUIRED" | "INVALID_URI" | "INVALID_PARAMETER" | "OPERATION_NOT_ALLOWED" | "UNKNOWN";
|
|
3
|
+
export declare class PlayerError extends SpotifyError {
|
|
4
|
+
readonly namespace: "Player";
|
|
5
|
+
readonly code: PlayerErrorCode;
|
|
6
|
+
constructor(code: PlayerErrorCode, message: string);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/player/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,MAAM,eAAe,GACvB,eAAe,GACf,iBAAiB,GACjB,kBAAkB,GAClB,aAAa,GACb,mBAAmB,GACnB,uBAAuB,GACvB,SAAS,CAAC;AAEd,qBAAa,WAAY,SAAQ,YAAY;IAC3C,QAAQ,CAAC,SAAS,EAAG,QAAQ,CAAU;IACvC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBAEnB,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM;CAInD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/player/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAWxC,MAAM,OAAO,WAAY,SAAQ,YAAY;IAClC,SAAS,GAAG,QAAiB,CAAC;IAC9B,IAAI,CAAkB;IAE/B,YAAY,IAAqB,EAAE,OAAe;QAChD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF","sourcesContent":["import { SpotifyError } from \"../error\";\n\nexport type PlayerErrorCode =\n | \"NOT_CONNECTED\"\n | \"CONNECTION_LOST\"\n | \"PREMIUM_REQUIRED\"\n | \"INVALID_URI\"\n | \"INVALID_PARAMETER\"\n | \"OPERATION_NOT_ALLOWED\"\n | \"UNKNOWN\";\n\nexport class PlayerError extends SpotifyError {\n readonly namespace = \"Player\" as const;\n readonly code: PlayerErrorCode;\n\n constructor(code: PlayerErrorCode, message: string) {\n super(message);\n this.code = code;\n }\n}\n"]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { EventSubscription } from "expo-modules-core";
|
|
2
|
+
import { SpotifyURI } from "../uri";
|
|
3
|
+
export type { PlayerErrorCode } from "./error";
|
|
4
|
+
export { PlayerError } from "./error";
|
|
5
|
+
/** Repeat mode for the Spotify player. */
|
|
6
|
+
export type RepeatMode = 0 /** off */ | 1 /** repeat current track */ | 2; /** repeat current context */
|
|
7
|
+
/** Valid podcast playback speed multipliers. */
|
|
8
|
+
export type PodcastPlaybackSpeed = 0.5 | 0.8 | 1.0 | 1.2 | 1.5 | 2.0 | 3.0;
|
|
9
|
+
/** A Spotify artist. */
|
|
10
|
+
export interface Artist {
|
|
11
|
+
name: string;
|
|
12
|
+
uri: string;
|
|
13
|
+
}
|
|
14
|
+
/** A Spotify album. */
|
|
15
|
+
export interface Album {
|
|
16
|
+
name: string;
|
|
17
|
+
uri: string;
|
|
18
|
+
}
|
|
19
|
+
/** A track currently loaded in the Spotify player. */
|
|
20
|
+
export interface Track {
|
|
21
|
+
uri: string;
|
|
22
|
+
name: string;
|
|
23
|
+
/** Identifier used by `Images.load(...)`. */
|
|
24
|
+
imageIdentifier?: string;
|
|
25
|
+
/** Duration in milliseconds. */
|
|
26
|
+
duration: number;
|
|
27
|
+
artist: Artist;
|
|
28
|
+
album: Album;
|
|
29
|
+
isSaved: boolean;
|
|
30
|
+
isEpisode: boolean;
|
|
31
|
+
isPodcast: boolean;
|
|
32
|
+
isAdvertisement: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Current playback options (shuffle / repeat). */
|
|
35
|
+
export interface PlaybackOptions {
|
|
36
|
+
isShuffling: boolean;
|
|
37
|
+
/** 0 = off, 1 = repeat track, 2 = repeat context. */
|
|
38
|
+
repeatMode: RepeatMode;
|
|
39
|
+
}
|
|
40
|
+
/** Actions currently permitted by Spotify (gate UI buttons on these). */
|
|
41
|
+
export interface PlaybackRestrictions {
|
|
42
|
+
canSkipNext: boolean;
|
|
43
|
+
canSkipPrevious: boolean;
|
|
44
|
+
canRepeatTrack: boolean;
|
|
45
|
+
canRepeatContext: boolean;
|
|
46
|
+
canToggleShuffle: boolean;
|
|
47
|
+
canSeek: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** Full snapshot of the Spotify player at a point in time. */
|
|
50
|
+
export interface PlayerState {
|
|
51
|
+
track: Track;
|
|
52
|
+
/** Current playback position in milliseconds. */
|
|
53
|
+
playbackPosition: number;
|
|
54
|
+
playbackSpeed: number;
|
|
55
|
+
isPaused: boolean;
|
|
56
|
+
playbackOptions: PlaybackOptions;
|
|
57
|
+
playbackRestrictions: PlaybackRestrictions;
|
|
58
|
+
contextTitle: string;
|
|
59
|
+
contextUri: string;
|
|
60
|
+
}
|
|
61
|
+
/** Crossfade configuration from the Spotify app. */
|
|
62
|
+
export interface CrossfadeState {
|
|
63
|
+
isEnabled: boolean;
|
|
64
|
+
/** Crossfade duration in milliseconds (only meaningful when enabled). */
|
|
65
|
+
duration: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Spotify Player namespace. Transport controls, queue management, and
|
|
69
|
+
* player-state subscriptions. Requires `AppRemote.connect()` to be resolved
|
|
70
|
+
* before any call.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { Player, SpotifyURI } from "@wwdrew/expo-spotify-sdk";
|
|
75
|
+
*
|
|
76
|
+
* await Player.play(SpotifyURI.from("spotify:track:4uLU6hMCjMI75M1A2tKUQC"));
|
|
77
|
+
* const state = await Player.getPlayerState();
|
|
78
|
+
*
|
|
79
|
+
* const sub = Player.addListener("playerStateChange", (state) => {
|
|
80
|
+
* console.log("now playing:", state.track.name, "paused:", state.isPaused);
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare const Player: {
|
|
85
|
+
/**
|
|
86
|
+
* Asks Spotify to play the entity identified by the given URI.
|
|
87
|
+
* Requires Spotify Premium for on-demand track playback; throws
|
|
88
|
+
* `PlayerError("PREMIUM_REQUIRED", ...)` for Free users.
|
|
89
|
+
*/
|
|
90
|
+
readonly play: (uri: SpotifyURI) => Promise<void>;
|
|
91
|
+
/** Pauses playback. */
|
|
92
|
+
readonly pause: () => Promise<void>;
|
|
93
|
+
/** Resumes paused playback. */
|
|
94
|
+
readonly resume: () => Promise<void>;
|
|
95
|
+
/** Skips to the next track in the queue or context. */
|
|
96
|
+
readonly skipNext: () => Promise<void>;
|
|
97
|
+
/** Skips to the previous track. */
|
|
98
|
+
readonly skipPrevious: () => Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Seeks to the given position in milliseconds.
|
|
101
|
+
* Only valid when `PlaybackRestrictions.canSeek` is `true`.
|
|
102
|
+
*/
|
|
103
|
+
readonly seekTo: (positionMs: number) => Promise<void>;
|
|
104
|
+
/** Enables or disables shuffle. */
|
|
105
|
+
readonly setShuffle: (enabled: boolean) => Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Sets the repeat mode.
|
|
108
|
+
* @param mode 0 = off, 1 = repeat track, 2 = repeat context.
|
|
109
|
+
*/
|
|
110
|
+
readonly setRepeatMode: (mode: RepeatMode) => Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Sets the podcast playback speed. Only takes effect when a podcast episode
|
|
113
|
+
* is playing; valid speeds are `0.5 | 0.8 | 1.0 | 1.2 | 1.5 | 2.0 | 3.0`.
|
|
114
|
+
*/
|
|
115
|
+
readonly setPodcastPlaybackSpeed: (speed: PodcastPlaybackSpeed) => Promise<void>;
|
|
116
|
+
/** Adds a track URI to the end of the current playback queue. */
|
|
117
|
+
readonly queue: (uri: SpotifyURI) => Promise<void>;
|
|
118
|
+
/** Returns the current {@link PlayerState} as a one-shot pull. */
|
|
119
|
+
readonly getPlayerState: () => Promise<PlayerState>;
|
|
120
|
+
/** Returns the current {@link CrossfadeState} as a one-shot pull. */
|
|
121
|
+
readonly getCrossfadeState: () => Promise<CrossfadeState>;
|
|
122
|
+
/**
|
|
123
|
+
* Subscribes to player state changes. The callback fires whenever the
|
|
124
|
+
* Spotify app reports a state update (track change, pause/resume, seek,
|
|
125
|
+
* shuffle/repeat change, etc.).
|
|
126
|
+
*
|
|
127
|
+
* Returns a `Subscription` — call `.remove()` to unsubscribe.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* const sub = Player.addListener("playerStateChange", (state) => {
|
|
132
|
+
* console.log("track:", state.track.name, "paused:", state.isPaused);
|
|
133
|
+
* });
|
|
134
|
+
* // ...later:
|
|
135
|
+
* sub.remove();
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
readonly addListener: (event: "playerStateChange", listener: (state: PlayerState) => void) => EventSubscription;
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/player/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import ExpoSpotifySDKModule from "../ExpoSpotifySDKModule";
|
|
2
|
+
import { PlayerError } from "./error";
|
|
3
|
+
export { PlayerError } from "./error";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Internal helpers
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
const VALID_PLAYER_CODES = new Set([
|
|
8
|
+
"NOT_CONNECTED",
|
|
9
|
+
"CONNECTION_LOST",
|
|
10
|
+
"PREMIUM_REQUIRED",
|
|
11
|
+
"INVALID_URI",
|
|
12
|
+
"INVALID_PARAMETER",
|
|
13
|
+
"OPERATION_NOT_ALLOWED",
|
|
14
|
+
"UNKNOWN",
|
|
15
|
+
]);
|
|
16
|
+
const CAUSE_SEPARATOR = "→ Caused by: ";
|
|
17
|
+
function unwrapReason(message) {
|
|
18
|
+
const idx = message.lastIndexOf(CAUSE_SEPARATOR);
|
|
19
|
+
return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);
|
|
20
|
+
}
|
|
21
|
+
function rethrowAsPlayerError(err) {
|
|
22
|
+
if (err instanceof PlayerError)
|
|
23
|
+
throw err;
|
|
24
|
+
if (err instanceof Error) {
|
|
25
|
+
const reason = unwrapReason(err.message);
|
|
26
|
+
const maybeCode = err.code;
|
|
27
|
+
if (maybeCode && VALID_PLAYER_CODES.has(maybeCode)) {
|
|
28
|
+
throw new PlayerError(maybeCode, reason);
|
|
29
|
+
}
|
|
30
|
+
throw new PlayerError("UNKNOWN", reason);
|
|
31
|
+
}
|
|
32
|
+
throw new PlayerError("UNKNOWN", String(err));
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Player namespace
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
/**
|
|
38
|
+
* Spotify Player namespace. Transport controls, queue management, and
|
|
39
|
+
* player-state subscriptions. Requires `AppRemote.connect()` to be resolved
|
|
40
|
+
* before any call.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { Player, SpotifyURI } from "@wwdrew/expo-spotify-sdk";
|
|
45
|
+
*
|
|
46
|
+
* await Player.play(SpotifyURI.from("spotify:track:4uLU6hMCjMI75M1A2tKUQC"));
|
|
47
|
+
* const state = await Player.getPlayerState();
|
|
48
|
+
*
|
|
49
|
+
* const sub = Player.addListener("playerStateChange", (state) => {
|
|
50
|
+
* console.log("now playing:", state.track.name, "paused:", state.isPaused);
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export const Player = {
|
|
55
|
+
/**
|
|
56
|
+
* Asks Spotify to play the entity identified by the given URI.
|
|
57
|
+
* Requires Spotify Premium for on-demand track playback; throws
|
|
58
|
+
* `PlayerError("PREMIUM_REQUIRED", ...)` for Free users.
|
|
59
|
+
*/
|
|
60
|
+
play(uri) {
|
|
61
|
+
return ExpoSpotifySDKModule.playerPlay(uri).catch(rethrowAsPlayerError);
|
|
62
|
+
},
|
|
63
|
+
/** Pauses playback. */
|
|
64
|
+
pause() {
|
|
65
|
+
return ExpoSpotifySDKModule.playerPause().catch(rethrowAsPlayerError);
|
|
66
|
+
},
|
|
67
|
+
/** Resumes paused playback. */
|
|
68
|
+
resume() {
|
|
69
|
+
return ExpoSpotifySDKModule.playerResume().catch(rethrowAsPlayerError);
|
|
70
|
+
},
|
|
71
|
+
/** Skips to the next track in the queue or context. */
|
|
72
|
+
skipNext() {
|
|
73
|
+
return ExpoSpotifySDKModule.playerSkipNext().catch(rethrowAsPlayerError);
|
|
74
|
+
},
|
|
75
|
+
/** Skips to the previous track. */
|
|
76
|
+
skipPrevious() {
|
|
77
|
+
return ExpoSpotifySDKModule.playerSkipPrevious().catch(rethrowAsPlayerError);
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* Seeks to the given position in milliseconds.
|
|
81
|
+
* Only valid when `PlaybackRestrictions.canSeek` is `true`.
|
|
82
|
+
*/
|
|
83
|
+
seekTo(positionMs) {
|
|
84
|
+
return ExpoSpotifySDKModule.playerSeekTo(positionMs).catch(rethrowAsPlayerError);
|
|
85
|
+
},
|
|
86
|
+
/** Enables or disables shuffle. */
|
|
87
|
+
setShuffle(enabled) {
|
|
88
|
+
return ExpoSpotifySDKModule.playerSetShuffle(enabled).catch(rethrowAsPlayerError);
|
|
89
|
+
},
|
|
90
|
+
/**
|
|
91
|
+
* Sets the repeat mode.
|
|
92
|
+
* @param mode 0 = off, 1 = repeat track, 2 = repeat context.
|
|
93
|
+
*/
|
|
94
|
+
setRepeatMode(mode) {
|
|
95
|
+
return ExpoSpotifySDKModule.playerSetRepeatMode(mode).catch(rethrowAsPlayerError);
|
|
96
|
+
},
|
|
97
|
+
/**
|
|
98
|
+
* Sets the podcast playback speed. Only takes effect when a podcast episode
|
|
99
|
+
* is playing; valid speeds are `0.5 | 0.8 | 1.0 | 1.2 | 1.5 | 2.0 | 3.0`.
|
|
100
|
+
*/
|
|
101
|
+
setPodcastPlaybackSpeed(speed) {
|
|
102
|
+
return ExpoSpotifySDKModule.playerSetPodcastPlaybackSpeed(speed).catch(rethrowAsPlayerError);
|
|
103
|
+
},
|
|
104
|
+
/** Adds a track URI to the end of the current playback queue. */
|
|
105
|
+
queue(uri) {
|
|
106
|
+
return ExpoSpotifySDKModule.playerQueue(uri).catch(rethrowAsPlayerError);
|
|
107
|
+
},
|
|
108
|
+
/** Returns the current {@link PlayerState} as a one-shot pull. */
|
|
109
|
+
getPlayerState() {
|
|
110
|
+
return ExpoSpotifySDKModule.playerGetPlayerState().catch(rethrowAsPlayerError);
|
|
111
|
+
},
|
|
112
|
+
/** Returns the current {@link CrossfadeState} as a one-shot pull. */
|
|
113
|
+
getCrossfadeState() {
|
|
114
|
+
return ExpoSpotifySDKModule.playerGetCrossfadeState().catch(rethrowAsPlayerError);
|
|
115
|
+
},
|
|
116
|
+
/**
|
|
117
|
+
* Subscribes to player state changes. The callback fires whenever the
|
|
118
|
+
* Spotify app reports a state update (track change, pause/resume, seek,
|
|
119
|
+
* shuffle/repeat change, etc.).
|
|
120
|
+
*
|
|
121
|
+
* Returns a `Subscription` — call `.remove()` to unsubscribe.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* const sub = Player.addListener("playerStateChange", (state) => {
|
|
126
|
+
* console.log("track:", state.track.name, "paused:", state.isPaused);
|
|
127
|
+
* });
|
|
128
|
+
* // ...later:
|
|
129
|
+
* sub.remove();
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
addListener(event, listener) {
|
|
133
|
+
return ExpoSpotifySDKModule.addListener("onPlayerStateChange", listener);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/player/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventSubscription } from \"expo-modules-core\";\n\nimport ExpoSpotifySDKModule from \"../ExpoSpotifySDKModule\";\nimport { SpotifyURI } from \"../uri\";\nimport { PlayerError, PlayerErrorCode } from \"./error\";\n\nexport type { PlayerErrorCode } from \"./error\";\nexport { PlayerError } from \"./error\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Repeat mode for the Spotify player. */\nexport type RepeatMode =\n | 0 /** off */\n | 1 /** repeat current track */\n | 2; /** repeat current context */\n\n/** Valid podcast playback speed multipliers. */\nexport type PodcastPlaybackSpeed = 0.5 | 0.8 | 1.0 | 1.2 | 1.5 | 2.0 | 3.0;\n\n/** A Spotify artist. */\nexport interface Artist {\n name: string;\n uri: string;\n}\n\n/** A Spotify album. */\nexport interface Album {\n name: string;\n uri: string;\n}\n\n/** A track currently loaded in the Spotify player. */\nexport interface Track {\n uri: string;\n name: string;\n /** Identifier used by `Images.load(...)`. */\n imageIdentifier?: string;\n /** Duration in milliseconds. */\n duration: number;\n artist: Artist;\n album: Album;\n isSaved: boolean;\n isEpisode: boolean;\n isPodcast: boolean;\n isAdvertisement: boolean;\n}\n\n/** Current playback options (shuffle / repeat). */\nexport interface PlaybackOptions {\n isShuffling: boolean;\n /** 0 = off, 1 = repeat track, 2 = repeat context. */\n repeatMode: RepeatMode;\n}\n\n/** Actions currently permitted by Spotify (gate UI buttons on these). */\nexport interface PlaybackRestrictions {\n canSkipNext: boolean;\n canSkipPrevious: boolean;\n canRepeatTrack: boolean;\n canRepeatContext: boolean;\n canToggleShuffle: boolean;\n canSeek: boolean;\n}\n\n/** Full snapshot of the Spotify player at a point in time. */\nexport interface PlayerState {\n track: Track;\n /** Current playback position in milliseconds. */\n playbackPosition: number;\n playbackSpeed: number;\n isPaused: boolean;\n playbackOptions: PlaybackOptions;\n playbackRestrictions: PlaybackRestrictions;\n contextTitle: string;\n contextUri: string;\n}\n\n/** Crossfade configuration from the Spotify app. */\nexport interface CrossfadeState {\n isEnabled: boolean;\n /** Crossfade duration in milliseconds (only meaningful when enabled). */\n duration: number;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst VALID_PLAYER_CODES = new Set<PlayerErrorCode>([\n \"NOT_CONNECTED\",\n \"CONNECTION_LOST\",\n \"PREMIUM_REQUIRED\",\n \"INVALID_URI\",\n \"INVALID_PARAMETER\",\n \"OPERATION_NOT_ALLOWED\",\n \"UNKNOWN\",\n]);\n\nconst CAUSE_SEPARATOR = \"→ Caused by: \";\n\nfunction unwrapReason(message: string): string {\n const idx = message.lastIndexOf(CAUSE_SEPARATOR);\n return idx === -1 ? message : message.slice(idx + CAUSE_SEPARATOR.length);\n}\n\nfunction rethrowAsPlayerError(err: unknown): never {\n if (err instanceof PlayerError) throw err;\n if (err instanceof Error) {\n const reason = unwrapReason(err.message);\n const maybeCode = (err as Error & { code?: string }).code;\n if (maybeCode && VALID_PLAYER_CODES.has(maybeCode as PlayerErrorCode)) {\n throw new PlayerError(maybeCode as PlayerErrorCode, reason);\n }\n throw new PlayerError(\"UNKNOWN\", reason);\n }\n throw new PlayerError(\"UNKNOWN\", String(err));\n}\n\n// ---------------------------------------------------------------------------\n// Player namespace\n// ---------------------------------------------------------------------------\n\n/**\n * Spotify Player namespace. Transport controls, queue management, and\n * player-state subscriptions. Requires `AppRemote.connect()` to be resolved\n * before any call.\n *\n * @example\n * ```ts\n * import { Player, SpotifyURI } from \"@wwdrew/expo-spotify-sdk\";\n *\n * await Player.play(SpotifyURI.from(\"spotify:track:4uLU6hMCjMI75M1A2tKUQC\"));\n * const state = await Player.getPlayerState();\n *\n * const sub = Player.addListener(\"playerStateChange\", (state) => {\n * console.log(\"now playing:\", state.track.name, \"paused:\", state.isPaused);\n * });\n * ```\n */\nexport const Player = {\n /**\n * Asks Spotify to play the entity identified by the given URI.\n * Requires Spotify Premium for on-demand track playback; throws\n * `PlayerError(\"PREMIUM_REQUIRED\", ...)` for Free users.\n */\n play(uri: SpotifyURI): Promise<void> {\n return ExpoSpotifySDKModule.playerPlay(uri).catch(rethrowAsPlayerError);\n },\n\n /** Pauses playback. */\n pause(): Promise<void> {\n return ExpoSpotifySDKModule.playerPause().catch(rethrowAsPlayerError);\n },\n\n /** Resumes paused playback. */\n resume(): Promise<void> {\n return ExpoSpotifySDKModule.playerResume().catch(rethrowAsPlayerError);\n },\n\n /** Skips to the next track in the queue or context. */\n skipNext(): Promise<void> {\n return ExpoSpotifySDKModule.playerSkipNext().catch(rethrowAsPlayerError);\n },\n\n /** Skips to the previous track. */\n skipPrevious(): Promise<void> {\n return ExpoSpotifySDKModule.playerSkipPrevious().catch(rethrowAsPlayerError);\n },\n\n /**\n * Seeks to the given position in milliseconds.\n * Only valid when `PlaybackRestrictions.canSeek` is `true`.\n */\n seekTo(positionMs: number): Promise<void> {\n return ExpoSpotifySDKModule.playerSeekTo(positionMs).catch(rethrowAsPlayerError);\n },\n\n /** Enables or disables shuffle. */\n setShuffle(enabled: boolean): Promise<void> {\n return ExpoSpotifySDKModule.playerSetShuffle(enabled).catch(rethrowAsPlayerError);\n },\n\n /**\n * Sets the repeat mode.\n * @param mode 0 = off, 1 = repeat track, 2 = repeat context.\n */\n setRepeatMode(mode: RepeatMode): Promise<void> {\n return ExpoSpotifySDKModule.playerSetRepeatMode(mode).catch(rethrowAsPlayerError);\n },\n\n /**\n * Sets the podcast playback speed. Only takes effect when a podcast episode\n * is playing; valid speeds are `0.5 | 0.8 | 1.0 | 1.2 | 1.5 | 2.0 | 3.0`.\n */\n setPodcastPlaybackSpeed(speed: PodcastPlaybackSpeed): Promise<void> {\n return ExpoSpotifySDKModule.playerSetPodcastPlaybackSpeed(speed).catch(\n rethrowAsPlayerError,\n );\n },\n\n /** Adds a track URI to the end of the current playback queue. */\n queue(uri: SpotifyURI): Promise<void> {\n return ExpoSpotifySDKModule.playerQueue(uri).catch(rethrowAsPlayerError);\n },\n\n /** Returns the current {@link PlayerState} as a one-shot pull. */\n getPlayerState(): Promise<PlayerState> {\n return ExpoSpotifySDKModule.playerGetPlayerState().catch(rethrowAsPlayerError);\n },\n\n /** Returns the current {@link CrossfadeState} as a one-shot pull. */\n getCrossfadeState(): Promise<CrossfadeState> {\n return ExpoSpotifySDKModule.playerGetCrossfadeState().catch(rethrowAsPlayerError);\n },\n\n /**\n * Subscribes to player state changes. The callback fires whenever the\n * Spotify app reports a state update (track change, pause/resume, seek,\n * shuffle/repeat change, etc.).\n *\n * Returns a `Subscription` — call `.remove()` to unsubscribe.\n *\n * @example\n * ```ts\n * const sub = Player.addListener(\"playerStateChange\", (state) => {\n * console.log(\"track:\", state.track.name, \"paused:\", state.isPaused);\n * });\n * // ...later:\n * sub.remove();\n * ```\n */\n addListener(\n event: \"playerStateChange\",\n listener: (state: PlayerState) => void,\n ): EventSubscription {\n return ExpoSpotifySDKModule.addListener(\n \"onPlayerStateChange\",\n listener as (event: unknown) => void,\n ) as EventSubscription;\n },\n} as const;\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six Spotify resource types accepted by App Remote APIs.
|
|
3
|
+
*/
|
|
4
|
+
export type SpotifyResourceType = "track" | "album" | "playlist" | "artist" | "show" | "episode";
|
|
5
|
+
/**
|
|
6
|
+
* Branded string type for Spotify URIs (`spotify:<type>:<id>`).
|
|
7
|
+
*
|
|
8
|
+
* Construct via `SpotifyURI.from(str)` (validates) or
|
|
9
|
+
* `SpotifyURI.unsafe(str)` (skips validation — use only when the source is
|
|
10
|
+
* trusted, e.g. a URI that came back from the Spotify SDK itself).
|
|
11
|
+
*/
|
|
12
|
+
export type SpotifyURI = string & {
|
|
13
|
+
readonly __brand: "SpotifyURI";
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Helpers for constructing, validating and decomposing {@link SpotifyURI}
|
|
17
|
+
* values.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const uri = SpotifyURI.from("spotify:track:4uLU6hMCjMI75M1A2tKUQC");
|
|
22
|
+
* const { type, id } = SpotifyURI.parse(uri);
|
|
23
|
+
* const rebuilt = SpotifyURI.build("track", id);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare const SpotifyURI: {
|
|
27
|
+
/**
|
|
28
|
+
* Cast `uri` to {@link SpotifyURI} after validation.
|
|
29
|
+
* Throws a plain `Error` if the URI does not match the `spotify:<type>:<id>`
|
|
30
|
+
* format — use at app-code call sites where you want early feedback.
|
|
31
|
+
*/
|
|
32
|
+
readonly from: (uri: string) => SpotifyURI;
|
|
33
|
+
/**
|
|
34
|
+
* Cast `uri` to {@link SpotifyURI} without validation.
|
|
35
|
+
* Use only for URIs that originate from the Spotify SDK itself (i.e. already
|
|
36
|
+
* known-valid values coming back over the bridge).
|
|
37
|
+
*/
|
|
38
|
+
readonly unsafe: (uri: string) => SpotifyURI;
|
|
39
|
+
/**
|
|
40
|
+
* Decompose a {@link SpotifyURI} into its `{ type, id }` parts.
|
|
41
|
+
*/
|
|
42
|
+
readonly parse: (uri: SpotifyURI) => {
|
|
43
|
+
type: SpotifyResourceType;
|
|
44
|
+
id: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Build a {@link SpotifyURI} from a resource type and ID.
|
|
48
|
+
*/
|
|
49
|
+
readonly build: (type: SpotifyResourceType, id: string) => SpotifyURI;
|
|
50
|
+
/**
|
|
51
|
+
* Type-guard: returns `true` if `uri` is a valid Spotify URI string.
|
|
52
|
+
*/
|
|
53
|
+
readonly isValid: (uri: string) => uri is SpotifyURI;
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/uri/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAC3B,OAAO,GACP,OAAO,GACP,UAAU,GACV,QAAQ,GACR,MAAM,GACN,SAAS,CAAC;AAEd;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC;AAKrE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU;IACrB;;;;OAIG;yBACO,MAAM,KAAG,UAAU;IAU7B;;;;OAIG;2BACS,MAAM,KAAG,UAAU;IAI/B;;OAEG;0BACQ,UAAU,KAAG;QAAE,IAAI,EAAE,mBAAmB,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE;IAQjE;;OAEG;2BACS,mBAAmB,MAAM,MAAM,KAAG,UAAU;IAIxD;;OAEG;4BACU,MAAM,KAAG,GAAG,IAAI,UAAU;CAG/B,CAAC"}
|