Skip to content

Commit

Permalink
Add new radio type item, and more changes to how extensions work
Browse files Browse the repository at this point in the history
  • Loading branch information
brahmkshatriya committed Sep 16, 2024
1 parent 613846e commit ef5ad69
Show file tree
Hide file tree
Showing 31 changed files with 389 additions and 141 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ android {

dependencies {
implementation(project(":common"))
implementation("com.github.brahmkshatriya:plugger:structure-overhaul-SNAPSHOT")
implementation("com.github.JeelPatel231:plugger:structure-overhaul-SNAPSHOT")

implementation("androidx.appcompat:appcompat:1.7.0")

Expand Down Expand Up @@ -87,7 +87,7 @@ dependencies {
implementation("com.flaviofaria:kenburnsview:1.0.7")
implementation("com.github.paramsen:noise:2.0.0")

implementation("com.github.Kyant0:taglib:1.0.0-alpha17")
implementation("com.github.Kyant0:taglib:1.0.0-alpha22")

//TODO : use fetch instead of download manager
// implementation("com.github.tonyofrancis.Fetch:xfetch2:3.1.6")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ class Downloader(
context.enqueueDownload(info, client, it, playlist.toMediaItem())
}
}

is EchoMediaItem.Lists.RadioItem -> Unit
}
}

Expand Down
120 changes: 64 additions & 56 deletions app/src/main/java/dev/brahmkshatriya/echo/offline/OfflineExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItem
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.Streamable
import dev.brahmkshatriya.echo.common.models.Streamable.Audio.Companion.toAudio
import dev.brahmkshatriya.echo.common.models.Streamable.Media.Companion.toMedia
Expand All @@ -41,6 +42,8 @@ import dev.brahmkshatriya.echo.plugger.ExtensionMetadata
import dev.brahmkshatriya.echo.plugger.ImportType
import dev.brahmkshatriya.echo.utils.getFromCache
import dev.brahmkshatriya.echo.utils.saveToCache
import dev.brahmkshatriya.echo.utils.toData
import dev.brahmkshatriya.echo.utils.toJson

class OfflineExtension(val context: Context) : ExtensionClient, HomeFeedClient, TrackClient,
AlbumClient, ArtistClient, PlaylistClient, RadioClient, SearchClient, LibraryClient,
Expand Down Expand Up @@ -109,29 +112,30 @@ class OfflineExtension(val context: Context) : ExtensionClient, HomeFeedClient,
else -> run {
val recentlyAdded = library.songList.sortedByDescending {
it.mediaMetadata.extras!!.getLong("ModifiedDate")
}.map { it.toTrack().toMediaItem() }
}.map { it.toTrack() }
val albums = library.albumList.map {
it.toAlbum().toMediaItem()
}.shuffled()
val artists = library.artistMap.values.map {
it.toArtist().toMediaItem()
}.shuffled()
listOf(
MediaItemsContainer.Category(
MediaItemsContainer.Tracks(
context.getString(R.string.recently_added),
recentlyAdded.take(10),
recentlyAdded.take(6),
MediaItemsContainer.Tracks.Type.List,
null,
PagedData.Single { recentlyAdded }),
PagedData.Single { recentlyAdded }.takeIf { albums.size > 6 }),
MediaItemsContainer.Category(
context.getString(R.string.albums),
albums.take(10),
null,
PagedData.Single { albums }),
PagedData.Single<EchoMediaItem> { albums }.takeIf { albums.size > 10 }),
MediaItemsContainer.Category(
context.getString(R.string.artists),
artists.take(10),
null,
PagedData.Single { artists })
PagedData.Single<EchoMediaItem> { artists }.takeIf { albums.size > 10 })
) + library.songList.map {
it.toTrack().toMediaItem().toMediaItemsContainer()
}
Expand Down Expand Up @@ -202,73 +206,77 @@ class OfflineExtension(val context: Context) : ExtensionClient, HomeFeedClient,
find(playlist)!!.toPlaylist()

override fun loadTracks(playlist: Playlist): PagedData<Track> = PagedData.Single {
if (playlist.id.startsWith("radio_")) radioMap[playlist.id]!!
else find(playlist)!!.songList.map { it.toTrack() }
find(playlist)!!.songList.map { it.toTrack() }
}

override fun getMediaItems(playlist: Playlist) = PagedData.Single<MediaItemsContainer> {
emptyList()
}

private val radioMap = mutableMapOf<String, List<Track>>()
private fun createRadioPlaylist(title: String, tracks: List<Track>): Playlist {
val id = "radio_${tracks.hashCode()}"
radioMap[id] = tracks
return Playlist(
private fun createRadioPlaylist(mediaItem: EchoMediaItem): Radio {
val id = "radio_${mediaItem.hashCode()}"
val title = mediaItem.title
return Radio(
id = id,
title = context.getString(R.string.item_radio, title),
cover = null,
isEditable = false,
authors = listOf(),
tracks = tracks.size,
creationDate = null,
subtitle = context.getString(R.string.radio_based_on_item, title)
extras = mapOf("mediaItem" to mediaItem.toJson())
)
}

override suspend fun radio(track: Track): Playlist {
val albumTracks = track.album?.let { loadTracks(loadAlbum(it)).loadAll() }
val artistTracks = track.artists.map { artist ->
find(artist)?.songList ?: emptyList()
}.flatten().map { it.toTrack() }
val randomTracks = library.songList.shuffled().take(25).map { it.toTrack() }
val allTracks =
listOfNotNull(albumTracks, artistTracks, randomTracks).flatten().distinctBy { it.id }
.toMutableList()
allTracks.removeIf { it.id == track.id }
allTracks.shuffle()
return createRadioPlaylist(track.title, allTracks)
}
override fun loadTracks(radio: Radio): PagedData<Track> = PagedData.Single {
val mediaItem = radio.extras["mediaItem"]!!.toData<EchoMediaItem>()
when (mediaItem) {
is EchoMediaItem.Lists.AlbumItem -> {
val album = mediaItem.album
val tracks = loadTracks(album).loadAll().asSequence()
.map { it.artists }.flatten()
.map { artist -> find(artist)?.songList?.map { it.toTrack() }!! }.flatten()
.filter { it.album?.id != album.id }.take(25)

val randomTracks = library.songList.shuffled().take(25).map { it.toTrack() }
(tracks + randomTracks).distinctBy { it.id }.toMutableList()
}

override suspend fun radio(album: Album): Playlist {
val tracks = loadTracks(album).loadAll().asSequence()
.map { it.artists }.flatten()
.map { artist -> find(artist)?.songList?.map { it.toTrack() }!! }.flatten()
.filter { it.album?.id != album.id }.take(25)
is EchoMediaItem.Lists.PlaylistItem -> {
val playlist = mediaItem.playlist
val tracks = loadTracks(playlist).loadAll()
val randomTracks = library.songList.shuffled().take(25).map { it.toTrack() }
(tracks + randomTracks).distinctBy { it.id }.toMutableList()
}

val randomTracks = library.songList.shuffled().take(25).map { it.toTrack() }
val allTracks = (tracks + randomTracks).distinctBy { it.id }.toMutableList()
allTracks.shuffle()
return createRadioPlaylist(album.title, allTracks)
}
is EchoMediaItem.Profile.ArtistItem -> {
val artist = mediaItem.artist
val tracks = find(artist)?.songList?.map { it.toTrack() } ?: emptyList()
val randomTracks = library.songList.shuffled().take(25).map { it.toTrack() }
(tracks + randomTracks).distinctBy { it.id }.toMutableList()
}

is EchoMediaItem.TrackItem -> {
val track = mediaItem.track
val albumTracks = track.album?.let { loadTracks(loadAlbum(it)).loadAll() }
val artistTracks = track.artists.map { artist ->
find(artist)?.songList ?: emptyList()
}.flatten().map { it.toTrack() }
val randomTracks = library.songList.shuffled().take(25).map { it.toTrack() }
val allTracks =
listOfNotNull(albumTracks, artistTracks, randomTracks).flatten()
.distinctBy { it.id }
.toMutableList()
allTracks.removeIf { it.id == track.id }
allTracks
}

override suspend fun radio(artist: Artist): Playlist {
val tracks = find(artist)?.songList?.map { it.toTrack() } ?: emptyList()
val randomTracks = library.songList.shuffled().take(25).map { it.toTrack() }
val allTracks = (tracks + randomTracks).distinctBy { it.id }.toMutableList()
allTracks.shuffle()
return createRadioPlaylist(artist.name, allTracks)
else -> throw IllegalAccessException()
}.shuffled()
}

override suspend fun radio(user: User): Playlist = throw IllegalAccessException()
override suspend fun radio(track: Track, context: EchoMediaItem?) =
createRadioPlaylist(track.toMediaItem())

override suspend fun radio(playlist: Playlist): Playlist {
val tracks = loadTracks(playlist).loadAll()
val randomTracks = library.songList.shuffled().take(25).map { it.toTrack() }
val allTracks = (tracks + randomTracks).distinctBy { it.id }.toMutableList()
allTracks.shuffle()
return createRadioPlaylist(playlist.title, allTracks)
}
override suspend fun radio(album: Album) = createRadioPlaylist(album.toMediaItem())
override suspend fun radio(artist: Artist) = createRadioPlaylist(artist.toMediaItem())
override suspend fun radio(playlist: Playlist) = createRadioPlaylist(playlist.toMediaItem())
override suspend fun radio(user: User): Radio = throw IllegalAccessException()

override suspend fun quickSearch(query: String?): List<QuickSearchItem> {
return if (query.isNullOrBlank()) {
Expand Down
19 changes: 11 additions & 8 deletions app/src/main/java/dev/brahmkshatriya/echo/offline/TestExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import dev.brahmkshatriya.echo.common.clients.TrackClient
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.EchoMediaItem
import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItem
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.Streamable.Audio.Companion.toAudio
import dev.brahmkshatriya.echo.common.models.Streamable.Media.Companion.toAudioVideoMedia
Expand Down Expand Up @@ -120,12 +122,13 @@ class TestExtension : ExtensionClient, LoginClient.UsernamePassword, TrackClient
)
}

private val emptyPlaylist = Playlist("empty", "empty", false)
override suspend fun radio(track: Track) = emptyPlaylist
override suspend fun radio(album: Album) = emptyPlaylist
override suspend fun radio(artist: Artist) = emptyPlaylist
override suspend fun radio(user: User) = emptyPlaylist
override suspend fun radio(playlist: Playlist) = emptyPlaylist
private val radio = Radio("empty", "empty")
override fun loadTracks(radio: Radio) = PagedData.Single<Track> { emptyList() }
override suspend fun radio(track: Track, context: EchoMediaItem?) = radio
override suspend fun radio(album: Album) = radio
override suspend fun radio(artist: Artist) = radio
override suspend fun radio(user: User) = radio
override suspend fun radio(playlist: Playlist) = radio

override suspend fun getLibraryTabs() = emptyList<Tab>()

Expand All @@ -134,7 +137,7 @@ class TestExtension : ExtensionClient, LoginClient.UsernamePassword, TrackClient
}

override suspend fun listEditablePlaylists(): List<Playlist> {
return listOf(emptyPlaylist)
return listOf()
}

override suspend fun likeTrack(track: Track, liked: Boolean): Boolean {
Expand All @@ -143,7 +146,7 @@ class TestExtension : ExtensionClient, LoginClient.UsernamePassword, TrackClient
}

override suspend fun createPlaylist(title: String, description: String?): Playlist {
return emptyPlaylist
return Playlist("", title, true)
}

override suspend fun deletePlaylist(playlist: Playlist) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ class PlayerSessionCallback(
val item = args.getSerialized<EchoMediaItem>("item") ?: return@future error
radioFlow.value = Radio.State.Loading
val loaded = Radio.start(
context, messageFlow, throwableFlow, extensionList, clientId, item, 0
context, messageFlow, throwableFlow, extensionList, clientId, item, null, 0
)
radioFlow.value = loaded ?: Radio.State.Empty
if (loaded == null) return@future error
val mediaItem = MediaItemUtils.build(
settings, loaded.tracks[0], loaded.clientId, loaded.playlist.toMediaItem()
settings, loaded.tracks[0], loaded.clientId, loaded.radio.toMediaItem()
)
player.setMediaItem(mediaItem)
player.prepare()
Expand Down
26 changes: 15 additions & 11 deletions app/src/main/java/dev/brahmkshatriya/echo/playback/Radio.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItem
import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Lists
import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Profile
import dev.brahmkshatriya.echo.common.models.EchoMediaItem.TrackItem
import dev.brahmkshatriya.echo.common.models.Playlist
import dev.brahmkshatriya.echo.common.models.Radio
import dev.brahmkshatriya.echo.common.models.Track
import dev.brahmkshatriya.echo.playback.MediaItemUtils.clientId
import dev.brahmkshatriya.echo.playback.MediaItemUtils.context
Expand Down Expand Up @@ -47,7 +47,7 @@ class Radio(
data object Empty : State()
data object Loading : State()
data class Loaded(
val clientId: String, val playlist: Playlist, val tracks: List<Track>, val played: Int
val clientId: String, val radio: Radio, val tracks: List<Track>, val played: Int
) : State()
}

Expand All @@ -59,6 +59,7 @@ class Radio(
extensionListFlow: StateFlow<List<MusicExtension>?>,
clientId: String,
item: EchoMediaItem,
itemContext: EchoMediaItem?,
play: Int = -1
): State.Loaded? {
val list = extensionListFlow.first { it != null }
Expand All @@ -78,28 +79,29 @@ class Radio(
tryWith(throwableFlow, extension.info) { block() }
}

val playlist = tryIO {
val radio = tryIO {
when (item) {
is TrackItem -> client.radio(item.track)
is TrackItem -> client.radio(item.track, itemContext)
is Lists.PlaylistItem -> client.radio(item.playlist)
is Lists.AlbumItem -> client.radio(item.album)
is Profile.ArtistItem -> client.radio(item.artist)
is Profile.UserItem -> client.radio(item.user)
is Lists.RadioItem -> throw IllegalStateException("Radio inside radio")
}
}

if (playlist != null) {
val tracks = tryIO { client.loadTracks(playlist).loadFirst() }
if (radio != null) {
val tracks = tryIO { client.loadTracks(radio).loadFirst() }
val state = if (!tracks.isNullOrEmpty()) State.Loaded(
clientId,
playlist,
radio,
tracks,
play
)
else {
messageFlow.emit(
SnackBar.Message(
context.getString(R.string.radio_playlist_empty)
context.getString(R.string.radio_empty)
)
)
null
Expand All @@ -115,7 +117,7 @@ class Radio(
private fun play(loaded: State.Loaded, play: Int): Boolean {
val track = loaded.tracks.getOrNull(play) ?: return false
val item = MediaItemUtils.build(
settings, track, loaded.clientId, loaded.playlist.toMediaItem()
settings, track, loaded.clientId, loaded.radio.toMediaItem()
)
player.addMediaItem(item)
player.prepare()
Expand All @@ -126,10 +128,12 @@ class Radio(
private fun loadPlaylist() {
val mediaItem = player.currentMediaItem ?: return
val client = mediaItem.clientId
val item = mediaItem.context ?: mediaItem.track.toMediaItem()
val item = mediaItem.track.toMediaItem()
val itemContext = mediaItem.context
stateFlow.value = State.Loading
scope.launch {
val loaded = start(context, messageFlow, throwFlow, extensionList, client, item, 0)
val loaded =
start(context, messageFlow, throwFlow, extensionList, client, item, itemContext, 0)
stateFlow.value = loaded ?: State.Empty
if (loaded != null) play(loaded, 0)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.paging.map
import dev.brahmkshatriya.echo.R
import dev.brahmkshatriya.echo.common.clients.TrackClient
import dev.brahmkshatriya.echo.common.models.EchoMediaItem
import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItem
import dev.brahmkshatriya.echo.common.models.MediaItemsContainer
import dev.brahmkshatriya.echo.common.models.MediaItemsContainer.Category
import dev.brahmkshatriya.echo.common.models.MediaItemsContainer.Container
Expand Down Expand Up @@ -97,6 +98,13 @@ class MediaClickListener(
container.more?.toFlow(),
transitionView
)

is MediaItemsContainer.Tracks -> openContainer(
clientId,
container.title,
container.more?.toFlow()?.map { it.map { item -> item.toMediaItem().toMediaItemsContainer() } },
transitionView
)
}
}

Expand Down
Loading

0 comments on commit ef5ad69

Please sign in to comment.