Skip to content

Commit 52d9b6e

Browse files
committed
document Spotify web playback sdk on README and link to example project
Signed-off-by: Adam Ratzman <[email protected]>
1 parent 98a28a3 commit 52d9b6e

File tree

13 files changed

+375
-123
lines changed

13 files changed

+375
-123
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ supporting Kotlin/JS, Kotlin/Android, Kotlin/JVM, and Kotlin/Native
2929
+ [SpotifyApiBuilder block & setting API options](#spotifyapibuilder-block--setting-api-options)
3030
* [API options](#api-options)
3131
+ [Using the API](#using-the-api)
32+
* [Platform-specific wrappers and information]("#platform-specific-wrappers-and-information")
33+
+ [JavaScript: Spotify Web Playback SDK wrapper](#js-spotify-web-playback-sdk-wrapper)
3234
* [Tips](#tips)
3335
+ [Building the API](#building-the-api)
3436
* [Notes](#notes)
@@ -51,6 +53,9 @@ repositories {
5153
implementation("com.adamratzman:spotify-api-kotlin-core:VERSION")
5254
```
5355

56+
### JS
57+
Please see
58+
5459
### Android
5560
**If you declare any release types not named debug or release, you may see "Could not resolve com.adamratzman:spotify-api-kotlin-android:VERSION". You need to do the following for each release type not named debug or release:**
5661
```
@@ -369,6 +374,21 @@ APIs available only in `SpotifyClientApi` and `SpotifyImplicitGrantApi` instance
369374
- `ClientLibraryApi` (get and manage saved tracks and albums)
370375
- `ClientPlayerApi` (view and control Spotify playback)
371376

377+
## Platform-specific wrappers and information
378+
### Android authentication
379+
380+
### JS Spotify Web Playback SDK wrapper
381+
`spotify-web-api-kotlin` provides a wrapper around Spotify's [Web Playback SDK](https://developer.spotify.com/documentation/web-playback-sdk/reference/)
382+
for playing music via Spotify in the browser on your own site.
383+
384+
To do this, you need to create a `Player` instance and then use the associated methods to register listeners, play,
385+
and get current context.
386+
387+
**Please see an example of how to do this [here](https://github.com/adamint/spotify-web-api-browser-example/blob/95df60810611ddb961a7a2cb0c874a76d4471aa7/src/main/kotlin/com/adamratzman/layouts/HomePageComponent.kt#L38)**.
388+
An example project, [spotify-web-api-browser-example](https://github.com/adamint/spotify-web-api-browser-example),
389+
demonstrates how to create a frontend JS Kotlin application with Spotify integration and
390+
that will play music in the browser.
391+
372392
## Tips
373393

374394
### Building the API

gradle/wrapper/gradle-wrapper.jar

508 Bytes
Binary file not shown.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

gradlew

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ esac
8282

8383
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
8484

85+
8586
# Determine the Java command to use to start the JVM.
8687
if [ -n "$JAVA_HOME" ] ; then
8788
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -129,6 +130,7 @@ fi
129130
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130131
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131132
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133+
132134
JAVACMD=`cygpath --unix "$JAVACMD"`
133135

134136
# We build the pattern for arguments to be converted via cygpath

gradlew.bat

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
2929
set APP_BASE_NAME=%~n0
3030
set APP_HOME=%DIRNAME%
3131

32+
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
33+
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34+
3235
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
3336
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
3437

@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
3740

3841
set JAVA_EXE=java.exe
3942
%JAVA_EXE% -version >NUL 2>&1
40-
if "%ERRORLEVEL%" == "0" goto init
43+
if "%ERRORLEVEL%" == "0" goto execute
4144

4245
echo.
4346
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -51,7 +54,7 @@ goto fail
5154
set JAVA_HOME=%JAVA_HOME:"=%
5255
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
5356

54-
if exist "%JAVA_EXE%" goto init
57+
if exist "%JAVA_EXE%" goto execute
5558

5659
echo.
5760
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -61,28 +64,14 @@ echo location of your Java installation.
6164

6265
goto fail
6366

64-
:init
65-
@rem Get command-line arguments, handling Windows variants
66-
67-
if not "%OS%" == "Windows_NT" goto win9xME_args
68-
69-
:win9xME_args
70-
@rem Slurp the command line arguments.
71-
set CMD_LINE_ARGS=
72-
set _SKIP=2
73-
74-
:win9xME_args_slurp
75-
if "x%~1" == "x" goto execute
76-
77-
set CMD_LINE_ARGS=%*
78-
7967
:execute
8068
@rem Setup the command line
8169

8270
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
8371

72+
8473
@rem Execute Gradle
85-
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
74+
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
8675

8776
:end
8877
@rem End local scope for the variables with windows NT shell

src/commonMain/kotlin/com.adamratzman.spotify/Builder.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ public class SpotifyUserAuthorization(
863863
* (https://api.spotify.com/v1).
864864
* @param retryOnInternalServerErrorTimes Whether and how often to retry once if an internal server error (500..599) has been received. Set to 0
865865
* to avoid retrying at all, or set to null to keep retrying until success.
866-
*
866+
* @param enableDebugMode Whether to enable debug mode (false by default). With debug mode, all response JSON will be outputted to console.
867867
*/
868868
public data class SpotifyApiOptions(
869869
public var useCache: Boolean = true,
@@ -880,5 +880,6 @@ public data class SpotifyApiOptions(
880880
public var onTokenRefresh: (suspend (GenericSpotifyApi) -> Unit)? = null,
881881
public var requiredScopes: List<SpotifyScope>? = null,
882882
public var proxyBaseUrl: String? = null,
883-
public var retryOnInternalServerErrorTimes: Int? = 5
883+
public var retryOnInternalServerErrorTimes: Int? = 5,
884+
public var enableDebugMode: Boolean = false
884885
)

src/commonMain/kotlin/com.adamratzman.spotify/endpoints/client/ClientPlayerApi.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package com.adamratzman.spotify.endpoints.client
33

44
import com.adamratzman.spotify.GenericSpotifyApi
5+
import com.adamratzman.spotify.SpotifyException
56
import com.adamratzman.spotify.SpotifyException.BadRequestException
67
import com.adamratzman.spotify.SpotifyScope
78
import com.adamratzman.spotify.annotations.SpotifyExperimentalFunctionApi
@@ -97,12 +98,16 @@ public class ClientPlayerApi(api: GenericSpotifyApi) : SpotifyEndpoint(api) {
9798
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/player/get-the-users-currently-playing-track/)**
9899
*/
99100
public suspend fun getCurrentlyPlaying(): CurrentlyPlayingObject? {
100-
val obj =
101-
catch {
102-
get(endpointBuilder("/me/player/currently-playing").toString())
103-
.toObject(CurrentlyPlayingObject.serializer(), api, json)
104-
}
105-
return if (obj?.timestamp == null) null else obj
101+
return try {
102+
val obj =
103+
catch {
104+
get(endpointBuilder("/me/player/currently-playing").toString())
105+
.toObject(CurrentlyPlayingObject.serializer(), api, json)
106+
}
107+
if (obj?.timestamp == null) null else obj
108+
} catch (pe: SpotifyException.ParseException) {
109+
null
110+
}
106111
}
107112

108113
/**

src/commonMain/kotlin/com.adamratzman.spotify/http/HttpConnection.kt

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public class HttpConnection constructor(
8989
retryIfInternalServerErrorLeft: Int? = SpotifyApiOptions().retryOnInternalServerErrorTimes // default
9090
): HttpResponse {
9191
val httpRequest = buildRequest(additionalHeaders)
92-
92+
if (api?.spotifyApiOptions?.enableDebugMode == true) println("DEBUG MODE: Request: $this")
9393
try {
9494
return HttpClient().request<io.ktor.client.statement.HttpResponse>(httpRequest).let { response ->
9595
val respCode = response.status.value
@@ -118,6 +118,8 @@ public class HttpConnection constructor(
118118
}
119119

120120
val body = response.readText()
121+
if (api?.spotifyApiOptions?.enableDebugMode == true) println("DEBUG MODE: $body")
122+
121123
if (respCode == 401 && body.contains("access token") &&
122124
api != null && api.spotifyApiOptions.automaticRefresh
123125
) {
@@ -145,12 +147,23 @@ public class HttpConnection constructor(
145147
throw e
146148
} catch (e: ResponseException) {
147149
val errorBody = e.response.readText()
150+
if (api?.spotifyApiOptions?.enableDebugMode == true) println("DEBUG MODE: $errorBody")
148151
try {
149-
if (e.response.status.value == 429) {
152+
val respCode = e.response.status.value
153+
154+
if (respCode in 500..599 && (retryIfInternalServerErrorLeft == null || retryIfInternalServerErrorLeft == -1 || retryIfInternalServerErrorLeft > 0)) {
155+
return execute(
156+
additionalHeaders,
157+
retryIfInternalServerErrorLeft =
158+
if (retryIfInternalServerErrorLeft != null && retryIfInternalServerErrorLeft != -1) retryIfInternalServerErrorLeft - 1
159+
else retryIfInternalServerErrorLeft
160+
)
161+
}
162+
163+
if (respCode == 429) {
150164
val ratelimit = e.response.headers["Retry-After"]!!.toLong() + 1L
151165
if (api?.spotifyApiOptions?.retryWhenRateLimited == true) {
152-
println("The request ($url) was ratelimited for $ratelimit seconds at ${getCurrentTimeMs()}")
153-
166+
// println("The request ($url) was ratelimited for $ratelimit seconds at ${getCurrentTimeMs()}")
154167
delay(ratelimit * 1000)
155168
return execute(
156169
additionalHeaders,
@@ -165,7 +178,10 @@ public class HttpConnection constructor(
165178
api.refreshToken()
166179
val newAdditionalHeaders = additionalHeaders?.toMutableList() ?: mutableListOf()
167180
newAdditionalHeaders.add(0, HttpHeader("Authorization", "Bearer ${api.token.accessToken}"))
168-
return execute(newAdditionalHeaders, retryIfInternalServerErrorLeft = retryIfInternalServerErrorLeft)
181+
return execute(
182+
newAdditionalHeaders,
183+
retryIfInternalServerErrorLeft = retryIfInternalServerErrorLeft
184+
)
169185
}
170186

171187
val error = errorBody.toObject(

src/commonMain/kotlin/com.adamratzman.spotify/utils/Types.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ public expect class ConcurrentHashMap<K, V>() {
1111
public val entries: MutableSet<MutableMap.MutableEntry<K, V>>
1212
}
1313

14-
public expect fun <K, V> ConcurrentHashMap<K, V>.asList(): List<Pair<K, V>>
14+
public expect fun <K, V> ConcurrentHashMap<K, V>.asList(): List<Pair<K, V>>

src/commonTest/kotlin/com.adamratzman/spotify/priv/ClientPlaylistApiTest.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package com.adamratzman.spotify.priv
44
import com.adamratzman.spotify.SpotifyClientApi
55
import com.adamratzman.spotify.SpotifyException
66
import com.adamratzman.spotify.assertFailsWithSuspend
7+
import com.adamratzman.spotify.buildSpotifyApi
78
import com.adamratzman.spotify.endpoints.client.SpotifyTrackPositions
89
import com.adamratzman.spotify.models.Playlist
910
import com.adamratzman.spotify.models.SimplePlaylist
@@ -22,6 +23,12 @@ class ClientPlaylistApiTest {
2223
lateinit var createdPlaylist: Playlist
2324
lateinit var playlistsBefore: List<SimplePlaylist>
2425

26+
init {
27+
runBlockingTest {
28+
(buildSpotifyApi() as? SpotifyClientApi)?.let { api = it }
29+
}
30+
}
31+
2532
private suspend fun init() {
2633
if (::api.isInitialized) {
2734
playlistsBefore = api.playlists.getClientPlaylists().getAllItemsNotNull()

0 commit comments

Comments
 (0)