Skip to content

Commit

Permalink
Add Radio & Refactor Decryptor
Browse files Browse the repository at this point in the history
This update finally adds the other radios from deezer. As of now there are 3 versions.
The "Flow Radio" used on the Home Tab.
The "Track Radio" is used when you start a radio from a track or simply play a single track.
And the "Playlist/Album Radio" this one makes sure that the playlist/album doesn't end after the last track was played.

As for the decryptor, there was a problem which made flac quality tracks skip or go silent while listening. This should be fixed or at least shouldn't occur as often
  • Loading branch information
LuftVerbot committed Sep 17, 2024
1 parent 64fad56 commit 0191805
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dev.brahmkshatriya.echo.common.models.ImageHolder
import dev.brahmkshatriya.echo.common.models.ImageHolder.Companion.toImageHolder
import dev.brahmkshatriya.echo.common.models.MediaItemsContainer
import dev.brahmkshatriya.echo.common.models.Playlist
import dev.brahmkshatriya.echo.common.models.Radio
import dev.brahmkshatriya.echo.common.models.Streamable
import dev.brahmkshatriya.echo.common.models.Track
import kotlinx.serialization.json.JsonArray
Expand Down Expand Up @@ -51,6 +52,7 @@ fun JsonObject.toEchoMediaItem(): EchoMediaItem? {
"artist" in type -> EchoMediaItem.Profile.ArtistItem(toArtist())
"show" in type -> EchoMediaItem.Lists.AlbumItem(toShow())
"episode" in type -> EchoMediaItem.TrackItem(toEpisode())
"flow" in type -> EchoMediaItem.Lists.RadioItem(toRadio())
else -> null
}
}
Expand Down Expand Up @@ -136,7 +138,7 @@ fun JsonObject.toArtist(isFollowing: Boolean = false, loaded: Boolean = false):
fun JsonObject.toTrack(): Track {
val data = this["data"]?.jsonObject ?: this
val md5 = data["ALB_PICTURE"]?.jsonPrimitive?.content.orEmpty()
val artistObject = data["ARTISTS"]?.jsonArray?.firstOrNull()?.jsonObject ?: this
val artistObject = data["ARTISTS"]?.jsonArray?.firstOrNull()?.jsonObject ?: data
val artistMd5 = artistObject["ART_PICTURE"]?.jsonPrimitive?.content.orEmpty()
return Track(
id = data["SNG_ID"]?.jsonPrimitive?.content.orEmpty(),
Expand Down Expand Up @@ -190,6 +192,21 @@ fun JsonObject.toPlaylist(loaded: Boolean = false): Playlist {
)
}

fun JsonObject.toRadio(loaded: Boolean = false): Radio {
val data = this["data"]?.jsonObject ?: this
val imageObject = this["pictures"]?.jsonArray?.firstOrNull()?.jsonObject.orEmpty()
val md5 = imageObject["md5"]?.jsonPrimitive?.content.orEmpty()
val type = imageObject["type"]?.jsonPrimitive?.content.orEmpty()
return Radio(
id = data["id"]?.jsonPrimitive?.content.orEmpty(),
title = data["title"]?.jsonPrimitive?.content.orEmpty(),
cover = getCover(md5, type, loaded),
extras = mapOf(
"radio" to "flow"
)
)
}

private val quality: Int get() = DeezerUtils.settings?.getInt("image_quality") ?: 240

fun getCover(md5: String?, type: String?, loaded: Boolean = false): ImageHolder {
Expand Down
17 changes: 17 additions & 0 deletions ext/src/main/java/dev/brahmkshatriya/echo/extension/DeezerApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,10 @@ class DeezerApi {
ctxtT = "up_next_artist"
track.extras["artist_id"]
}
!track.extras["user_id"].isNullOrEmpty() -> {
ctxtT = "dynamic_page_user_radio"
userId
}
else -> {
ctxtT = ""
""
Expand Down Expand Up @@ -821,4 +825,17 @@ class DeezerApi {
)
return json.decodeFromString<JsonObject>(jsonData)
}

suspend fun flow(id: String): JsonObject {
val jsonData = callApi(
method = "radio.getUserRadio",
params = buildJsonObject {
if (id != "default") {
put("config_id", id)
}
put("user_id", userId)
}
)
return json.decodeFromString<JsonObject>(jsonData)
}
}
184 changes: 137 additions & 47 deletions ext/src/main/java/dev/brahmkshatriya/echo/extension/DeezerExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import dev.brahmkshatriya.echo.common.helpers.PagedData
import dev.brahmkshatriya.echo.common.models.Album
import dev.brahmkshatriya.echo.common.models.Artist
import dev.brahmkshatriya.echo.common.models.ClientException
import dev.brahmkshatriya.echo.common.models.EchoMediaItem
import dev.brahmkshatriya.echo.common.models.Lyric
import dev.brahmkshatriya.echo.common.models.Lyrics
import dev.brahmkshatriya.echo.common.models.MediaItemsContainer
import dev.brahmkshatriya.echo.common.models.Playlist
import dev.brahmkshatriya.echo.common.models.QuickSearchItem
import dev.brahmkshatriya.echo.common.models.Radio
import dev.brahmkshatriya.echo.common.models.Request.Companion.toRequest
import dev.brahmkshatriya.echo.common.models.Streamable
import dev.brahmkshatriya.echo.common.models.Streamable.Audio.Companion.toAudio
Expand Down Expand Up @@ -166,6 +168,7 @@ class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, RadioClien


private fun isSupportedSection(id: String) = listOf(
"b21892d3-7e9c-4b06-aff6-2c3be3266f68",
"348128f5-bed6-4ccb-9a37-8e5f5ed08a62",
"8d10a320-f130-4dcb-a610-38baf0c57896",
"2a7e897f-9bcf-4563-8e11-b93a601766e1",
Expand Down Expand Up @@ -545,53 +548,155 @@ class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, RadioClien

//<============= Radio =============>

override suspend fun radio(track: Track): Playlist {
return Playlist(
id = track.id,
title = track.title,
cover = track.cover,
isEditable = false,
extras = mapOf(
"radio" to "track"
override fun loadTracks(radio: Radio): PagedData<Track> = PagedData.Single {
val dataArray = when (radio.extras["radio"]) {
"track" -> {
val jsonObject = api.mix(radio.id)
jsonObject["results"]!!.jsonObject["data"]!!.jsonArray
}

"playlist", "album" -> {
val jsonObject = api.radio(radio.id, radio.extras["artist"] ?: "")
jsonObject["results"]!!.jsonObject["data"]!!.jsonArray
}

else -> {
val jsonObject = api.flow(radio.id)
jsonObject["results"]!!.jsonObject["data"]!!.jsonArray
}
}

dataArray.mapIndexed { index, song ->
val track = song.jsonObject.toTrack()
val nextTrack = dataArray.getOrNull(index + 1)?.jsonObject?.toTrack()
val nextTrackId = nextTrack?.id

Track(
id = track.id,
title = track.title,
cover = track.cover,
duration = track.duration,
releaseDate = track.releaseDate,
artists = track.artists,
extras = track.extras.plus(
mapOf(
"NEXT" to (nextTrackId ?: ""),
when (radio.extras["radio"]) {
"track" -> "artist_id" to track.artists[0].id
"playlist", "album" -> "artist_id" to (radio.extras["artist"] ?: "")
else -> "user_id" to "0"
}
)
)
)
)
}
}

override suspend fun radio(album: Album): Playlist {
override suspend fun radio(track: Track, context: EchoMediaItem?): Radio {
return when(context) {
null -> {
Radio(
id = track.id,
title = track.title,
cover = track.cover,
extras = mapOf(
"radio" to "track"
)
)
}

is EchoMediaItem.Lists.RadioItem -> {
when(context.radio.extras["radio"]) {
"track" -> {
Radio(
id = track.id,
title = track.title,
cover = track.cover,
extras = mapOf(
"radio" to "track"
)
)
}

"playlist", "album" -> {
Radio(
id = track.id,
title = track.title,
cover = track.cover,
extras = mapOf(
("radio" to context.radio.extras["radio"].orEmpty()),
"artist" to track.artists[0].id
)
)
}

else -> {
context.radio
}
}
}

is EchoMediaItem.Lists.PlaylistItem -> {
Radio(
id = track.id,
title = track.title,
cover = track.cover,
extras = mapOf(
"radio" to "playlist",
"artist" to track.artists[0].id
)
)
}

is EchoMediaItem.Lists.AlbumItem -> {
Radio(
id = track.id,
title = track.title,
cover = track.cover,
extras = mapOf(
"radio" to "album",
"artist" to track.artists[0].id
)
)
}

else -> throw Exception("Radio Error")
}
}

override suspend fun radio(album: Album): Radio {
val jsonObject = api.album(album)
val resultsObject = jsonObject["results"]!!.jsonObject
val songsObject = resultsObject["SONGS"]!!.jsonObject
val lastTrack = songsObject["data"]!!.jsonArray.reversed()[0].jsonObject.toTrack()
return Playlist(
return Radio(
id = lastTrack.id,
title = lastTrack.title,
cover = lastTrack.cover,
isEditable = false,
extras = mapOf(
"radio" to "album",
"artist" to lastTrack.artists[0].id
)
)
}

override suspend fun radio(artist: Artist): Playlist {
TODO("Not yet implemented")
override suspend fun radio(artist: Artist): Radio {
TODO("Not Planned")
}

override suspend fun radio(user: User): Playlist {
TODO("Not yet implemented")
override suspend fun radio(user: User): Radio {
TODO("Not Planned")
}

override suspend fun radio(playlist: Playlist): Playlist {
override suspend fun radio(playlist: Playlist): Radio {
val jsonObject = api.playlist(playlist)
val resultsObject = jsonObject["results"]!!.jsonObject
val songsObject = resultsObject["SONGS"]!!.jsonObject
val lastTrack = songsObject["data"]!!.jsonArray.reversed()[0].jsonObject.toTrack()
return Playlist(
return Radio(
id = lastTrack.id,
title = lastTrack.title,
cover = lastTrack.cover,
isEditable = false,
extras = mapOf(
"radio" to "playlist",
"artist" to lastTrack.artists[0].id
Expand Down Expand Up @@ -692,20 +797,8 @@ class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, RadioClien
}

override fun loadTracks(playlist: Playlist): PagedData<Track> = PagedData.Single {
val dataArray = when (playlist.extras["radio"]) {
"track" -> {
val jsonObject = api.mix(playlist.id)
jsonObject["results"]!!.jsonObject["data"]!!.jsonArray
}
"playlist", "album" -> {
val jsonObject = api.radio(playlist.id, playlist.extras["artist"] ?: "")
jsonObject["results"]!!.jsonObject["data"]!!.jsonArray
}
else -> {
val jsonObject = api.playlist(playlist)
jsonObject["results"]!!.jsonObject["SONGS"]!!.jsonObject["data"]!!.jsonArray
}
}
val jsonObject = api.playlist(playlist)
val dataArray = jsonObject["results"]!!.jsonObject["SONGS"]!!.jsonObject["data"]!!.jsonArray

dataArray.mapIndexed { index, song ->
val currentTrack = song.jsonObject.toTrack()
Expand All @@ -722,11 +815,7 @@ class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, RadioClien
extras = currentTrack.extras.plus(
mapOf(
"NEXT" to (nextTrackId ?: ""),
when (playlist.extras["radio"]) {
"track" -> "artist_id" to currentTrack.artists[0].id
"playlist", "album" -> "artist_id" to (playlist.extras["artist"] ?: "")
else -> "playlist_id" to playlist.id
}
"playlist_id" to playlist.id
)
)
)
Expand Down Expand Up @@ -889,15 +978,16 @@ class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, RadioClien

//<============= Share =============>

override suspend fun onShare(album: Album): String = "https://www.deezer.com/album/${album.id}"

override suspend fun onShare(artist: Artist): String = "https://www.deezer.com/artist/${artist.id}"

override suspend fun onShare(playlist: Playlist): String = "https://www.deezer.com/playlist/${playlist.id}"

override suspend fun onShare(track: Track): String = "https://www.deezer.com/track/${track.id}"

override suspend fun onShare(user: User): String = "https://www.deezer.com/profile/${user.id}"
override suspend fun onShare(item: EchoMediaItem): String {
return when(item) {
is EchoMediaItem.TrackItem -> "https://www.deezer.com/track/${item.id}"
is EchoMediaItem.Profile.ArtistItem -> "https://www.deezer.com/artist/${item.id}"
is EchoMediaItem.Profile.UserItem -> "https://www.deezer.com/profile/${item.id}"
is EchoMediaItem.Lists.AlbumItem -> "https://www.deezer.com/album/${item.id}"
is EchoMediaItem.Lists.PlaylistItem -> "https://www.deezer.com/playlist/${item.id}"
is EchoMediaItem.Lists.RadioItem -> TODO()
}
}

//<============= Utils =============>

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ android.useAndroidX=true
kotlin.code.style=official
android.nonTransitiveRClass=true

libVersion=4769b2de4a
libVersion=f2a7f86fbc

extType=music
extId=deezer
Expand Down

0 comments on commit 0191805

Please sign in to comment.