Skip to content

Commit 5b18d62

Browse files
committed
update ClientPlayerApi, expose ShowApi and EpisodeApi to app api
Signed-off-by: Adam Ratzman <[email protected]>
1 parent 5ecb201 commit 5b18d62

File tree

17 files changed

+790
-130
lines changed

17 files changed

+790
-130
lines changed

build.gradle.kts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ plugins {
99
id("com.android.library")
1010
kotlin("multiplatform") version "1.4.21"
1111
kotlin("plugin.serialization") version "1.4.20"
12-
id("com.diffplug.gradle.spotless") version "4.4.0"
12+
id("com.diffplug.spotless") version "5.9.0"
1313
id("com.moowork.node") version "1.3.1"
1414
id("org.jetbrains.dokka") version "1.4.20"
1515
id("kotlin-android-extensions")
@@ -134,7 +134,6 @@ kotlin {
134134
useChromeHeadless()
135135
webpackConfig.cssSupport.enabled = true
136136
}
137-
// this.
138137
}
139138
}
140139

@@ -147,10 +146,9 @@ kotlin {
147146
}
148147
}
149148

150-
val hostOs = System.getProperty("os.name")
151-
val isMainHost = hostOs.contains("mac", true)
152-
//val isMainPlatform =
153-
val isMingwX64 = hostOs.startsWith("Windows")
149+
// val hostOs = System.getProperty("os.name")
150+
// val isMainHost = hostOs.contains("mac", true)
151+
// val isMingwX64 = hostOs.startsWith("Windows")
154152

155153
macosX64 {
156154
mavenPublication {
@@ -168,9 +166,6 @@ kotlin {
168166
}
169167
}
170168

171-
val publicationsFromMainHost =
172-
listOf(jvm(), js()).map { it.name } + "kotlinMultiplatform"
173-
174169
publishing {
175170
if ("local" !in (version as String)) registerPublishing()
176171
}
@@ -180,7 +175,7 @@ kotlin {
180175
val coroutineVersion = "1.4.2-native-mt"
181176
val serializationVersion = "1.0.1"
182177
val ktorVersion = "1.5.0"
183-
val klockVersion = "2.0.3"
178+
val klockVersion = "2.0.5"
184179

185180
val commonMain by getting {
186181
dependencies {
@@ -222,7 +217,7 @@ kotlin {
222217
implementation(npm("text-encoding", "0.7.0"))
223218
implementation("io.ktor:ktor-client-js:$ktorVersion")
224219
implementation(npm("abort-controller", "3.0.0"))
225-
implementation(npm("node-fetch", "2.6.0"))
220+
implementation(npm("node-fetch", "2.6.1"))
226221
implementation(kotlin("stdlib-js"))
227222

228223
}

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

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
package com.adamratzman.spotify
55

66
import com.adamratzman.spotify.SpotifyException.BadRequestException
7-
import com.adamratzman.spotify.annotations.SpotifyExperimentalHttpApi
7+
import com.adamratzman.spotify.endpoints.client.ClientEpisodeApi
88
import com.adamratzman.spotify.endpoints.client.ClientFollowingApi
99
import com.adamratzman.spotify.endpoints.client.ClientLibraryApi
1010
import com.adamratzman.spotify.endpoints.client.ClientPersonalizationApi
1111
import com.adamratzman.spotify.endpoints.client.ClientPlayerApi
1212
import com.adamratzman.spotify.endpoints.client.ClientPlaylistApi
1313
import com.adamratzman.spotify.endpoints.client.ClientProfileApi
1414
import com.adamratzman.spotify.endpoints.client.ClientSearchApi
15+
import com.adamratzman.spotify.endpoints.client.ClientShowApi
1516
import com.adamratzman.spotify.endpoints.public.AlbumApi
1617
import com.adamratzman.spotify.endpoints.public.ArtistApi
1718
import com.adamratzman.spotify.endpoints.public.BrowseApi
@@ -53,6 +54,8 @@ import kotlinx.serialization.json.Json
5354
* @property browse Provides access to Spotify [browse endpoints](https://developer.spotify.com/documentation/web-api/reference/browse/)
5455
* @property artists Provides access to Spotify [artist endpoints](https://developer.spotify.com/documentation/web-api/reference/artists/)
5556
* @property tracks Provides access to Spotify [track endpoints](https://developer.spotify.com/documentation/web-api/reference/tracks/)
57+
* @property episodes Provides access to Spotify [episode endpoints](https://developer.spotify.com/documentation/web-api/reference/episodes/)
58+
* @property shows Provides access to Spotify [show endpoints](https://developer.spotify.com/documentation/web-api/reference/shows/)
5659
*/
5760
public sealed class SpotifyApi<T : SpotifyApi<T, B>, B : ISpotifyApiBuilder<T, B>>(
5861
public val clientId: String?,
@@ -78,6 +81,8 @@ public sealed class SpotifyApi<T : SpotifyApi<T, B>, B : ISpotifyApiBuilder<T, B
7881
public abstract val users: UserApi
7982
public abstract val tracks: TrackApi
8083
public abstract val following: FollowingApi
84+
public abstract val episodes: EpisodeApi
85+
public abstract val shows: ShowApi
8186

8287
/**
8388
* Base url for Spotify web api calls
@@ -358,6 +363,8 @@ public class SpotifyAppApi internal constructor(
358363
override val browse: BrowseApi = BrowseApi(this)
359364
override val artists: ArtistApi = ArtistApi(this)
360365
override val tracks: TrackApi = TrackApi(this)
366+
override val episodes: EpisodeApi = EpisodeApi(this)
367+
override val shows: ShowApi = ShowApi(this)
361368

362369
/**
363370
* Provides access to **public** Spotify [playlist endpoints](https://developer.spotify.com/documentation/web-api/reference/playlists/)
@@ -454,21 +461,8 @@ public open class SpotifyClientApi(
454461

455462
override val search: ClientSearchApi = ClientSearchApi(this)
456463

457-
/**
458-
* Provides access to [endpoints](https://developer.spotify.com/documentation/web-api/reference/episodes/) for retrieving
459-
* information about one or more episodes from the Spotify catalog.
460-
*
461-
* @since 3.1.0
462-
*/
463-
@SpotifyExperimentalHttpApi
464-
public val episodes: EpisodeApi = EpisodeApi(this)
465-
466-
/**
467-
* Provides access to [endpoints](https://developer.spotify.com/documentation/web-api/reference/shows/) for retrieving
468-
* information about one or more shows from the Spotify catalog.
469-
*/
470-
@SpotifyExperimentalHttpApi
471-
public val shows: ShowApi = ShowApi(this)
464+
override val episodes: ClientEpisodeApi = ClientEpisodeApi(this)
465+
override val shows: ClientShowApi = ClientShowApi(this)
472466

473467
/**
474468
* Provides access to [endpoints](https://developer.spotify.com/documentation/web-api/reference/playlists/) for retrieving
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* Spotify Web API, Kotlin Wrapper; MIT License, 2017-2021; Original author: Adam Ratzman */
2+
package com.adamratzman.spotify.endpoints.client
3+
4+
import com.adamratzman.spotify.GenericSpotifyApi
5+
import com.adamratzman.spotify.SpotifyException.BadRequestException
6+
import com.adamratzman.spotify.SpotifyScope
7+
import com.adamratzman.spotify.endpoints.public.EpisodeApi
8+
import com.adamratzman.spotify.http.encodeUrl
9+
import com.adamratzman.spotify.models.Episode
10+
import com.adamratzman.spotify.models.EpisodeList
11+
import com.adamratzman.spotify.models.EpisodeUri
12+
import com.adamratzman.spotify.models.serialization.toObject
13+
import com.adamratzman.spotify.utils.Market
14+
import com.adamratzman.spotify.utils.catch
15+
16+
/**
17+
* Endpoints for retrieving information about one or more episodes from the Spotify catalog.
18+
*
19+
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/episodes/)**
20+
*/
21+
public class ClientEpisodeApi(api: GenericSpotifyApi) : EpisodeApi(api) {
22+
/**
23+
* Get Spotify catalog information for a single episode identified by its unique Spotify ID. The [Market] associated with
24+
* the user account will be used.
25+
*
26+
* **Reading the user’s resume points on episode objects requires the [SpotifyScope.USER_READ_PLAYBACK_POSITION] scope**
27+
*
28+
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/episodes/get-an-episode/)**
29+
*
30+
* @param id The Spotify ID for the episode.
31+
*
32+
* @return possibly-null [Episode].
33+
*/
34+
public suspend fun getEpisode(id: String): Episode? {
35+
return catch {
36+
get(
37+
endpointBuilder("/episodes/${EpisodeUri(id).id.encodeUrl()}").toString()
38+
).toObject(Episode.serializer(), api, json)
39+
}
40+
}
41+
42+
/**
43+
* Get Spotify catalog information for multiple episodes based on their Spotify IDs. The [Market] associated with
44+
* the user account will be used.
45+
*
46+
* **Invalid episode ids will result in a [BadRequestException]
47+
*
48+
* **Reading the user’s resume points on episode objects requires the [SpotifyScope.USER_READ_PLAYBACK_POSITION] scope**
49+
*
50+
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/episodes/get-several-episodes/)**
51+
*
52+
* @param ids The id or uri for the episodes. Maximum **50**.
53+
*
54+
* @return List of possibly-null [Episode] objects.
55+
* @throws BadRequestException If any invalid show id is provided
56+
*/
57+
public suspend fun getEpisodes(vararg ids: String): List<Episode?> {
58+
checkBulkRequesting(50, ids.size)
59+
return bulkRequest(50, ids.toList()) { chunk ->
60+
get(
61+
endpointBuilder("/episodes")
62+
.with("ids", chunk.joinToString(",") { EpisodeUri(it).id.encodeUrl() })
63+
.toString()
64+
).toObject(EpisodeList.serializer(), api, json).episodes
65+
}.flatten()
66+
}
67+
}

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

Lines changed: 100 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import com.adamratzman.spotify.SpotifyException.BadRequestException
66
import com.adamratzman.spotify.SpotifyScope
77
import com.adamratzman.spotify.annotations.SpotifyExperimentalFunctionApi
88
import com.adamratzman.spotify.http.SpotifyEndpoint
9-
import com.adamratzman.spotify.models.CollectionUri
9+
import com.adamratzman.spotify.models.ContextUri
1010
import com.adamratzman.spotify.models.CurrentlyPlayingContext
1111
import com.adamratzman.spotify.models.CurrentlyPlayingObject
1212
import com.adamratzman.spotify.models.CursorBasedPagingObject
@@ -18,6 +18,13 @@ import com.adamratzman.spotify.models.serialization.mapToJsonString
1818
import com.adamratzman.spotify.models.serialization.toCursorBasedPagingObject
1919
import com.adamratzman.spotify.models.serialization.toInnerObject
2020
import com.adamratzman.spotify.models.serialization.toObject
21+
import com.adamratzman.spotify.models.toAlbumUri
22+
import com.adamratzman.spotify.models.toArtistUri
23+
import com.adamratzman.spotify.models.toEpisodeUri
24+
import com.adamratzman.spotify.models.toLocalTrackUri
25+
import com.adamratzman.spotify.models.toPlaylistUri
26+
import com.adamratzman.spotify.models.toShowUri
27+
import com.adamratzman.spotify.models.toTrackUri
2128
import com.adamratzman.spotify.utils.catch
2229
import com.adamratzman.spotify.utils.jsonMap
2330
import kotlinx.serialization.builtins.ListSerializer
@@ -157,13 +164,13 @@ public class ClientPlayerApi(api: GenericSpotifyApi) : SpotifyEndpoint(api) {
157164
*
158165
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/player/set-volume-for-users-playback/)**
159166
*
160-
* @param volume The volume to set. Must be a value from 0 to 100 inclusive.
167+
* @param volumePercent The volume to set. Must be a value from 0 to 100 inclusive.
161168
* @param deviceId The device to play on
162169
*/
163-
public suspend fun setVolume(volume: Int, deviceId: String? = null) {
164-
require(volume in 0..100) { "Volume must be within 0 to 100 inclusive. Provided: $volume" }
170+
public suspend fun setVolume(volumePercent: Int, deviceId: String? = null) {
171+
require(volumePercent in 0..100) { "Volume must be within 0 to 100 inclusive. Provided: $volumePercent" }
165172
put(
166-
endpointBuilder("/me/player/volume").with("volume_percent", volume).with(
173+
endpointBuilder("/me/player/volume").with("volume_percent", volumePercent).with(
167174
"device_id",
168175
deviceId
169176
).toString()
@@ -200,34 +207,107 @@ public class ClientPlayerApi(api: GenericSpotifyApi) : SpotifyEndpoint(api) {
200207
/**
201208
* Start or resume playback.
202209
*
203-
* **Note:** You can only use one of the following: [offsetNum], [offsetPlayableUri], or [collectionUri]
210+
* **Note:** You can only use one of the following: [offsetIndex], [offsetLocalTrackId], [offsetTrackId], [offsetEpisodeId]
204211
*
205212
* **Specify nothing to play to simply resume playback**
206213
*
207214
* **Requires** the [SpotifyScope.USER_MODIFY_PLAYBACK_STATE] scope
208215
*
209216
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/player/start-a-users-playback/)**
210217
*
211-
* @param collectionUri Start playing an album, artist, or playlist
212-
* @param playableUrisToPlay [PlayableUri] (Track or Local track URIs) uris to play. these are converted into URIs. Max 100
213-
* @param offsetNum Indicates from where in the context playback should start. Only available when [playableUrisToPlay] is used.
214-
* @param offsetPlayableUri Start playing at a track/local track uri instead of place number ([offsetNum])
218+
* @param artistId Start playing an artist
219+
* @param playlistId Start playing a playlist
220+
* @param albumId Start playing an album
221+
* @param artistId Start playing an artist
222+
*
223+
* @param offsetLocalTrackId Start playing at a local track in the given/current context
224+
* @param offsetTrackId Start playing at a track in the given/current context
225+
* @param offsetEpisodeId Start playing at an episode in the given/current context
226+
*
227+
* @param offsetIndex Indicates from where in the given/current context playback should start. Zero-based indexing.
228+
*
229+
* @param localTrackIdsToPlay A list of local track ids to play. Max 100 combined between [localTrackIdsToPlay], [trackIdsToPlay], and [episodeIdsToPlay]
230+
* @param trackIdsToPlay A list of track ids to play. Max 100 combined between [localTrackIdsToPlay], [trackIdsToPlay], and [episodeIdsToPlay]
231+
* @param episodeIdsToPlay A list of episode ids to play. Max 100 combined between [localTrackIdsToPlay], [trackIdsToPlay], and [episodeIdsToPlay]
232+
*
233+
* @param deviceId The device to play on
234+
*
235+
* @throws BadRequestException if more than one type of play type is specified or the offset is illegal.
236+
*/
237+
public suspend fun startPlayback(
238+
// context uris
239+
artistId: String? = null,
240+
playlistId: String? = null,
241+
albumId: String? = null,
242+
showId: String? = null,
243+
// offset playables
244+
offsetLocalTrackId: String? = null,
245+
offsetTrackId: String? = null,
246+
offsetEpisodeId: String? = null,
247+
// offset num
248+
offsetIndex: Int? = null,
249+
// ids of playables to play
250+
trackIdsToPlay: List<String>? = null,
251+
localTrackIdsToPlay: List<String>? = null,
252+
episodeIdsToPlay: List<String>? = null,
253+
deviceId: String? = null
254+
) {
255+
if (listOfNotNull(artistId, playlistId, albumId, showId).size > 1) {
256+
throw IllegalArgumentException("Only one of: artistId, playlistId, albumId, showId can be specified.")
257+
}
258+
val contextUri =
259+
artistId?.toArtistUri() ?: playlistId?.toPlaylistUri() ?: albumId?.toAlbumUri() ?: showId?.toShowUri()
260+
261+
if (listOfNotNull(offsetLocalTrackId, offsetTrackId, offsetEpisodeId, offsetIndex).size > 1) {
262+
throw IllegalArgumentException("Only one of: offsetXXId or offsetIndex can be specified.")
263+
}
264+
265+
val offsetPlayableUri =
266+
offsetLocalTrackId?.toLocalTrackUri() ?: offsetTrackId?.toTrackUri() ?: offsetEpisodeId?.toEpisodeUri()
267+
val playableUrisToPlay =
268+
localTrackIdsToPlay?.map { it.toLocalTrackUri() } ?: trackIdsToPlay?.map { it.toTrackUri() }
269+
?: episodeIdsToPlay?.map { it.toEpisodeUri() }
270+
271+
startPlayback(
272+
contextUri,
273+
offsetIndex,
274+
offsetPlayableUri,
275+
playableUrisToPlay,
276+
deviceId
277+
)
278+
}
279+
280+
/**
281+
* Start or resume playback.
282+
*
283+
* **Note:** You can only use one of the following: [offsetIndex], [offsetPlayableUri]
284+
*
285+
* **Specify nothing to play to simply resume playback**
286+
*
287+
* **Requires** the [SpotifyScope.USER_MODIFY_PLAYBACK_STATE] scope
288+
*
289+
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/player/start-a-users-playback/)**
290+
*
291+
* @param contextUri Start playing an album, artist, show, or playlist
292+
* @param playableUrisToPlay [PlayableUri] (Track, Local track, or Episode URIs) uris to play. these are converted into URIs. Max 100
293+
* @param offsetIndex Indicates from where in the given/current context playback should start. Only available when [playableUrisToPlay] is used.
294+
* @param offsetPlayableUri Start playing at a track/local track/episode uri in the given/current context instead of index ([offsetIndex])
215295
* @param deviceId The device to play on
216296
*
217297
* @throws BadRequestException if more than one type of play type is specified or the offset is illegal.
218298
*/
219299
public suspend fun startPlayback(
220-
collectionUri: CollectionUri? = null,
221-
offsetNum: Int? = null,
300+
contextUri: ContextUri? = null,
301+
offsetIndex: Int? = null,
222302
offsetPlayableUri: PlayableUri? = null,
223-
deviceId: String? = null,
224-
playableUrisToPlay: List<PlayableUri> = emptyList()
303+
playableUrisToPlay: List<PlayableUri>? = null,
304+
deviceId: String? = null
225305
) {
226306
val url = endpointBuilder("/me/player/play").with("device_id", deviceId).toString()
227307
val body = jsonMap()
228308
when {
229-
collectionUri != null -> body += buildJsonObject { put("context_uri", collectionUri.uri) }
230-
playableUrisToPlay.isNotEmpty() -> body += buildJsonObject {
309+
contextUri != null -> body += buildJsonObject { put("context_uri", contextUri.uri) }
310+
playableUrisToPlay?.isNotEmpty() == true -> body += buildJsonObject {
231311
put(
232312
"uris", JsonArray(
233313
playableUrisToPlay.map { it.uri }.map(::JsonPrimitive)
@@ -236,10 +316,10 @@ public class ClientPlayerApi(api: GenericSpotifyApi) : SpotifyEndpoint(api) {
236316
}
237317
}
238318
if (body.keys.isNotEmpty()) {
239-
if (offsetNum != null) body += buildJsonObject {
319+
if (offsetIndex != null) body += buildJsonObject {
240320
put(
241321
"offset",
242-
buildJsonObject { put("position", offsetNum) })
322+
buildJsonObject { put("position", offsetIndex) })
243323
}
244324
else if (offsetPlayableUri != null) body += buildJsonObject {
245325
put("offset", buildJsonObject { put("uri", offsetPlayableUri.uri) })
@@ -269,7 +349,7 @@ public class ClientPlayerApi(api: GenericSpotifyApi) : SpotifyEndpoint(api) {
269349
* @param deviceId The device to play on
270350
* @param shuffle Whether to enable shuffling of playback
271351
*/
272-
public suspend fun toggleShuffle(shuffle: Boolean = true, deviceId: String? = null): String =
352+
public suspend fun toggleShuffle(shuffle: Boolean, deviceId: String? = null): String =
273353
put(endpointBuilder("/me/player/shuffle").with("state", shuffle).with("device_id", deviceId).toString())
274354

275355
/**
@@ -296,7 +376,7 @@ public class ClientPlayerApi(api: GenericSpotifyApi) : SpotifyEndpoint(api) {
296376
*
297377
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/player/set-repeat-mode-on-users-playback/)**
298378
*/
299-
public enum class PlayerRepeatState(public val identifier: String): ResultEnum {
379+
public enum class PlayerRepeatState(public val identifier: String) : ResultEnum {
300380
/**
301381
* Repeat the current track
302382
*/

0 commit comments

Comments
 (0)