Skip to content

Commit 27bed41

Browse files
committed
fix Playable deserialization
Signed-off-by: Adam Ratzman <[email protected]>
1 parent 8e32e75 commit 27bed41

File tree

10 files changed

+120
-15
lines changed

10 files changed

+120
-15
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ buildscript {
3232
}
3333

3434
group = "com.adamratzman"
35-
version = "3.4.02"
35+
version = "3.4.03"
3636

3737
tasks.withType<Test> {
3838
this.testLogging {

src/commonMain/kotlin/com.adamratzman.spotify/endpoints/public/PlaylistApi.kt

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

44
import com.adamratzman.spotify.GenericSpotifyApi
5+
import com.adamratzman.spotify.SpotifyAppApi
56
import com.adamratzman.spotify.SpotifyException.BadRequestException
67
import com.adamratzman.spotify.SpotifyScope
78
import com.adamratzman.spotify.http.SpotifyEndpoint
@@ -89,6 +90,8 @@ public open class PlaylistApi(api: GenericSpotifyApi) : SpotifyEndpoint(api) {
8990
*
9091
* **Note that** both Public and Private playlists belonging to any user are retrievable on provision of a valid access token.
9192
*
93+
* **Warning:** if the playlist contains podcasts, the tracks will be null if you are using [SpotifyAppApi].
94+
*
9295
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/playlists/get-playlists-tracks/)**
9396
*
9497
* @param playlist The id or uri for the playlist.

src/commonMain/kotlin/com.adamratzman.spotify/models/Albums.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public data class SimpleAlbum(
3737
@SerialName("external_urls") override val externalUrlsString: Map<String, String>,
3838
override val href: String,
3939
override val id: String,
40-
override val uri: AlbumUri,
40+
override val uri: SpotifyUri,
4141

4242
val artists: List<SimpleArtist>,
4343
val images: List<SpotifyImage>,

src/commonMain/kotlin/com.adamratzman.spotify/models/Artists.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public data class SimpleArtist(
1717
@SerialName("external_urls") override val externalUrlsString: Map<String, String>,
1818
override val href: String,
1919
override val id: String,
20-
override val uri: ArtistUri,
20+
override val uri: SpotifyUri,
2121

2222
val name: String,
2323
val type: String

src/commonMain/kotlin/com.adamratzman.spotify/models/Episode.kt

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,64 @@ import com.adamratzman.spotify.utils.Market
88
import kotlinx.serialization.SerialName
99
import kotlinx.serialization.Serializable
1010

11+
/**
12+
* An episode (podcast) on Spotify
13+
*
14+
* @param album The album on which the track appears. The album object includes a link in
15+
* href to full information about the album.
16+
* @param artists The artists who performed the track. Each artist object includes a link in href
17+
* to more detailed information about the artist.
18+
* @property availableMarkets A list of the countries in which the track can be played, identified by their ISO 3166-1 alpha-2 code.
19+
* @param discNumber The disc number (usually 1 unless the album consists of more than one disc).
20+
* @param durationMs The track length in milliseconds.
21+
*
22+
* @param explicit Whether or not the track has explicit lyrics ( true = yes it does; false = no it does not OR unknown).
23+
* @param isLocal Whether or not the track is from a local file.
24+
* @param isPlayable Part of the response when Track Relinking is applied. If true , the track is playable in the
25+
* given market. Otherwise false.
26+
* @param name The name of the track.
27+
* @param popularity The popularity of the track. The value will be between 0 and 100, with 100 being the most
28+
* popular. The popularity of a track is a value between 0 and 100, with 100 being the most popular. The popularity
29+
* is calculated by algorithm and is based, in the most part, on the total number of plays the track has had and how
30+
* recent those plays are. Generally speaking, songs that are being played a lot now will have a higher popularity
31+
* than songs that were played a lot in the past. Duplicate tracks (e.g. the same track from a single and an album)
32+
* are rated independently. Artist and album popularity is derived mathematically from track popularity. Note that
33+
* the popularity value may lag actual popularity by a few days: the value is not updated in real time.
34+
* @param previewUrl A link to a 30 second preview (MP3 format) of the track. Can be null.
35+
* @param track Whether this episode is also a track.
36+
* @param trackNumber The number of the track. If an album has several discs, the track number is the number on the specified disc.
37+
* @param type The object type: “episode”.
38+
*
39+
*/
40+
@Serializable
41+
public data class PodcastEpisodeTrack(
42+
val album: SimpleAlbum,
43+
val artists: List<SimpleArtist>,
44+
@SerialName("available_markets") private val availableMarketsString: List<String> = listOf(),
45+
@SerialName("disc_number") val discNumber: Int,
46+
@SerialName("duration_ms") val durationMs: Int,
47+
val episode: Boolean? = null,
48+
val explicit: Boolean,
49+
@SerialName("external_urls") override val externalUrlsString: Map<String, String>,
50+
@SerialName("external_ids") private val externalIdsString: Map<String, String> = hashMapOf(),
51+
override val href: String,
52+
override val id: String,
53+
@SerialName("is_local") val isLocal: Boolean? = null,
54+
@SerialName("is_playable") val isPlayable: Boolean = true,
55+
val name: String,
56+
val popularity: Int,
57+
@SerialName("preview_url") val previewUrl: String? = null,
58+
val track: Boolean? = null,
59+
@SerialName("track_number") val trackNumber: Int,
60+
override val type: String,
61+
override val uri: PlayableUri,
62+
override val linkedTrack: LinkedTrack? = null
63+
) : RelinkingAvailableResponse(), Playable {
64+
val availableMarkets: List<Market> get() = availableMarketsString.map { Market.valueOf(it) }
65+
66+
val externalIds: List<ExternalId> get() = externalIdsString.map { ExternalId(it.key, it.value) }
67+
}
68+
1169
/**
1270
* An episode (podcast) on Spotify
1371
*
@@ -47,9 +105,9 @@ public data class Episode(
47105
@SerialName("release_date_precision") val releaseDatePrecisionString: String,
48106
@SerialName("resume_point") val resumePoint: ResumePoint? = null,
49107
val show: SimpleShow,
50-
override val type: String,
108+
val type: String,
51109
override val uri: EpisodeUri
52-
) : CoreObject(), Playable {
110+
) : CoreObject() {
53111
val releaseDate: ReleaseDate get() = getReleaseDate(releaseDateString)
54112

55113
@Suppress("DEPRECATION")
@@ -96,22 +154,23 @@ public data class SimpleEpisode(
96154
@SerialName("release_date") private val releaseDateString: String,
97155
@SerialName("release_date_precision") val releaseDatePrecisionString: String,
98156
@SerialName("resume_point") val resumePoint: ResumePoint? = null,
99-
override val type: String,
100-
override val uri: EpisodeUri
101-
) : CoreObject(), Playable {
157+
val type: String,
158+
override val uri: SpotifyUri
159+
) : CoreObject() {
102160
val releaseDate: ReleaseDate get() = getReleaseDate(releaseDateString)
103161

104162
@Suppress("DEPRECATION")
105163
val languages: List<Locale>
106164
get() = (language?.let { showLanguagesPrivate + it } ?: showLanguagesPrivate)
107-
.map { Locale.valueOf(it.replace("-", "_")) }
165+
.map { Locale.valueOf(it.replace("-", "_")) }
108166

109167
/**
110168
* Converts this [SimpleEpisode] into a full [Episode] object
111169
*
112170
* @param market Provide this parameter if you want the list of returned items to be relevant to a particular country.
113171
*/
114-
public suspend fun toFullEpisode(market: Market? = null): Episode? = (api as? SpotifyClientApi)?.episodes?.getEpisode(id, market)
172+
public suspend fun toFullEpisode(market: Market? = null): Episode? =
173+
(api as? SpotifyClientApi)?.episodes?.getEpisode(id, market)
115174
}
116175

117176
/**

src/commonMain/kotlin/com.adamratzman.spotify/models/Playable.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,45 @@ import kotlinx.serialization.json.JsonObject
99
import kotlinx.serialization.json.contentOrNull
1010
import kotlinx.serialization.json.jsonPrimitive
1111

12+
/**
13+
* A local track, episode, or track.
14+
*
15+
* @property href A link to the Web API endpoint providing full details of the playable.
16+
* @property id The Spotify ID for the playable.
17+
* @property uri The URI associated with the object.
18+
* @property type The type of the playable.
19+
*/
1220
public interface Playable {
1321
public val href: String?
1422
public val id: String?
1523
public val uri: PlayableUri
1624
public val type: String
1725

26+
/**
27+
* This Playable as a local track.
28+
*
29+
*/
30+
public val asLocalTrack: LocalTrack? get() = this as? LocalTrack
31+
32+
/**
33+
* This Playable as an episode.
34+
*
35+
*/
36+
public val asPodcastEpisodeTrack: PodcastEpisodeTrack? get() = this as? PodcastEpisodeTrack
37+
38+
/**
39+
* This Playable as a track.
40+
*
41+
*/
42+
public val asTrack: Track? get() = this as? Track
43+
1844
@Suppress("EXPERIMENTAL_API_USAGE")
1945
@Serializer(forClass = Playable::class)
2046
public companion object : KSerializer<Playable> by object : JsonContentPolymorphicSerializer<Playable>(Playable::class) {
2147
override fun selectDeserializer(element: JsonElement): KSerializer<out Playable> {
22-
2348
return when (val uri: PlayableUri? = (element as? JsonObject)?.get("uri")?.jsonPrimitive?.contentOrNull?.let { PlayableUri(it) }) {
2449
is LocalTrackUri -> LocalTrack.serializer()
25-
is EpisodeUri -> Episode.serializer()
50+
is EpisodeUri -> PodcastEpisodeTrack.serializer()
2651
is SpotifyTrackUri -> Track.serializer()
2752
null -> throw IllegalStateException("Couldn't find a serializer for uri $uri")
2853
}

src/commonMain/kotlin/com.adamratzman.spotify/models/Playlist.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* Spotify Web API, Kotlin Wrapper; MIT License, 2017-2021; Original author: Adam Ratzman */
22
package com.adamratzman.spotify.models
33

4+
import com.adamratzman.spotify.SpotifyAppApi
45
import com.adamratzman.spotify.endpoints.client.PlaylistSnapshot
56
import com.adamratzman.spotify.utils.Market
67
import kotlinx.serialization.SerialName
@@ -34,7 +35,7 @@ public data class SimplePlaylist(
3435
@SerialName("external_urls") override val externalUrlsString: Map<String, String>,
3536
override val href: String,
3637
override val id: String,
37-
override val uri: PlaylistUri,
38+
override val uri: SpotifyUri,
3839

3940
val collaborative: Boolean,
4041
val images: List<SpotifyImage>,
@@ -67,6 +68,7 @@ public data class SimplePlaylist(
6768
* @param addedBy The Spotify user who added the track. Note that some very old playlists may return null in this field.
6869
* @param isLocal Whether this track is a local file or not.
6970
* @param track Information about the track. In rare occasions, this field may be null if this track's API entry is broken.
71+
* **Warning:** if this is a podcast, the track will be null if you are using [SpotifyAppApi].
7072
*/
7173
@Serializable
7274
public data class PlaylistTrack(

src/commonMain/kotlin/com.adamratzman.spotify/models/Show.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public data class SimpleShow(
3939
val name: String,
4040
val publisher: String,
4141
val type: String,
42-
override val uri: ShowUri
42+
override val uri: SpotifyUri
4343
) : CoreObject() {
4444
val availableMarkets: List<Market> get() = availableMarketsString.map { Market.valueOf(it) }
4545

src/commonMain/kotlin/com.adamratzman.spotify/models/Track.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public data class SimpleTrack(
4040
@SerialName("external_ids") private val externalIdsString: Map<String, String> = hashMapOf(),
4141
override val href: String,
4242
override val id: String,
43-
override val uri: PlayableUri,
43+
override val uri: SpotifyUri,
4444

4545
val artists: List<SimpleArtist>,
4646
@SerialName("disc_number") val discNumber: Int,

src/commonTest/kotlin/com.adamratzman/spotify/pub/PublicPlaylistsApiTest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
package com.adamratzman.spotify.pub
33

44
import com.adamratzman.spotify.GenericSpotifyApi
5+
import com.adamratzman.spotify.SpotifyClientApi
56
import com.adamratzman.spotify.SpotifyException
67
import com.adamratzman.spotify.assertFailsWithSuspend
78
import com.adamratzman.spotify.models.LocalTrack
9+
import com.adamratzman.spotify.models.PodcastEpisodeTrack
810
import com.adamratzman.spotify.models.Track
911
import com.adamratzman.spotify.runBlockingTest
1012
import com.adamratzman.spotify.spotifyApi
@@ -41,6 +43,15 @@ class PublicPlaylistsApiTest {
4143

4244
assertEquals("run2", api.playlists.getPlaylist("78eWnYKwDksmCHAjOUNPEj")?.name)
4345
assertNull(api.playlists.getPlaylist("nope"))
46+
assertTrue(api.playlists.getPlaylist("78eWnYKwDksmCHAjOUNPEj")!!.tracks.isNotEmpty())
47+
val playlistWithLocalAndNonLocalTracks = api.playlists.getPlaylist("0vzdw0N41qZLbRDqyx2cE0")!!.tracks
48+
assertEquals(LocalTrack::class, playlistWithLocalAndNonLocalTracks[0].track!!::class)
49+
assertEquals(Track::class, playlistWithLocalAndNonLocalTracks[1].track!!::class)
50+
51+
if (api is SpotifyClientApi) {
52+
val playlistWithPodcastsTracks = api.playlists.getPlaylist("37i9dQZF1DX8tN3OFXtAqt")!!.tracks
53+
assertEquals(PodcastEpisodeTrack::class, playlistWithPodcastsTracks[0].track!!::class)
54+
}
4455
}
4556
}
4657

@@ -54,6 +65,11 @@ class PublicPlaylistsApiTest {
5465
assertEquals(LocalTrack::class, playlist[0].track!!::class)
5566
assertEquals(Track::class, playlist[1].track!!::class)
5667
assertFailsWithSuspend<SpotifyException.BadRequestException> { api.playlists.getPlaylistTracks("adskjfjkasdf") }
68+
69+
if (api is SpotifyClientApi) {
70+
val playlistWithPodcasts = api.playlists.getPlaylistTracks("37i9dQZF1DX8tN3OFXtAqt")
71+
assertEquals(PodcastEpisodeTrack::class, playlistWithPodcasts[0].track!!::class)
72+
}
5773
}
5874
}
5975

0 commit comments

Comments
 (0)