Skip to content

Commit

Permalink
Change how stream loading works
Browse files Browse the repository at this point in the history
  • Loading branch information
brahmkshatriya committed Nov 7, 2024
1 parent 3dbbb1d commit 602da3d
Show file tree
Hide file tree
Showing 27 changed files with 580 additions and 606 deletions.
23 changes: 20 additions & 3 deletions app/src/main/java/dev/brahmkshatriya/echo/PlayerService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import android.content.SharedPreferences
import androidx.annotation.OptIn
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.TrackSelectionParameters
import androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.cache.SimpleCache
import androidx.media3.exoplayer.ExoPlayer
Expand All @@ -15,6 +17,7 @@ import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import dagger.hilt.android.AndroidEntryPoint
import dev.brahmkshatriya.echo.common.MusicExtension
import dev.brahmkshatriya.echo.common.models.Streamable
import dev.brahmkshatriya.echo.extensions.ExtensionLoader
import dev.brahmkshatriya.echo.playback.Current
import dev.brahmkshatriya.echo.playback.PlayerCallback
Expand Down Expand Up @@ -63,6 +66,9 @@ class PlayerService : MediaLibraryService() {
@Inject
lateinit var current: MutableStateFlow<Current?>

@Inject
lateinit var currentSources: MutableStateFlow<Streamable.Media.Sources?>

@Inject
lateinit var fftAudioProcessor: FFTAudioProcessor

Expand All @@ -75,8 +81,15 @@ class PlayerService : MediaLibraryService() {
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
.build()

val audioOffloadPreferences = TrackSelectionParameters.AudioOffloadPreferences.Builder()
.setAudioOffloadMode(AUDIO_OFFLOAD_MODE_ENABLED)
// Add additional options as needed
.setIsGaplessSupportRequired(true)
.setIsSpeedChangeSupportRequired(true)
.build()

val factory = MediaFactory(
cache, this, scope, extListFlow, settings, throwFlow
cache, currentSources, this, scope, extListFlow, settings, throwFlow
)

ExoPlayer.Builder(this, factory)
Expand All @@ -86,7 +99,12 @@ class PlayerService : MediaLibraryService() {
.setSkipSilenceEnabled(settings.getBoolean(SKIP_SILENCE, true))
.setAudioAttributes(audioAttributes, true)
.build()
.also { factory.setPlayer(it) }
.also {
it.trackSelectionParameters = it.trackSelectionParameters
.buildUpon()
.setAudioOffloadPreferences(audioOffloadPreferences)
.build()
}
}

@OptIn(UnstableApi::class)
Expand Down Expand Up @@ -137,7 +155,6 @@ class PlayerService : MediaLibraryService() {
}
}

//TODO: Spotify
//TODO: EQ, Pitch, Tempo, Reverb & Sleep Timer(5m, 10m, 15m, 30m, 45m, 1hr, End of track)
// val equalizer = Equalizer(1, exoPlayer.audioSessionId)

Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/dev/brahmkshatriya/echo/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dev.brahmkshatriya.echo.EchoDatabase
import dev.brahmkshatriya.echo.common.models.Streamable
import dev.brahmkshatriya.echo.db.models.UserEntity
import dev.brahmkshatriya.echo.playback.Current
import dev.brahmkshatriya.echo.playback.listeners.Radio
Expand Down Expand Up @@ -65,6 +66,10 @@ class AppModule {
)
}

@Provides
@Singleton
fun provideCurrentSourcesFlow() = MutableStateFlow<Streamable.Media.Sources?>(null)

@Provides
@Singleton
fun currentMediaItemFlow() = MutableStateFlow<Current?>(null)
Expand Down
82 changes: 38 additions & 44 deletions app/src/main/java/dev/brahmkshatriya/echo/download/Downloader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package dev.brahmkshatriya.echo.download

import android.app.DownloadManager
import android.content.Context
import android.os.Environment
import androidx.core.net.toUri
import dev.brahmkshatriya.echo.EchoDatabase
import dev.brahmkshatriya.echo.common.Extension
import dev.brahmkshatriya.echo.common.MusicExtension
Expand All @@ -13,19 +11,15 @@ import dev.brahmkshatriya.echo.common.clients.TrackClient
import dev.brahmkshatriya.echo.common.models.Album
import dev.brahmkshatriya.echo.common.models.EchoMediaItem
import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItem
import dev.brahmkshatriya.echo.common.models.Streamable
import dev.brahmkshatriya.echo.common.models.Track
import dev.brahmkshatriya.echo.db.models.DownloadEntity
import dev.brahmkshatriya.echo.extensions.get
import dev.brahmkshatriya.echo.extensions.getExtension
import dev.brahmkshatriya.echo.ui.settings.AudioFragment.AudioPreference.Companion.selectAudioStream
import dev.brahmkshatriya.echo.utils.getFromCache
import dev.brahmkshatriya.echo.utils.saveToCache
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext
import java.io.File

class Downloader(
private val extensionList: MutableStateFlow<List<MusicExtension>?>,
Expand Down Expand Up @@ -85,47 +79,47 @@ class Downloader(
} ?: loaded.album
val track = loaded.copy(album = album)

val settings = getSharedPreferences(packageName, Context.MODE_PRIVATE)
val stream = selectAudioStream(settings, track.audioStreamables)
?: throw Exception("No Stream Found")
require(stream.mimeType == Streamable.MimeType.Progressive)
val media = extension.get<TrackClient, Streamable.Media.AudioOnly>(throwable) {
getStreamableMedia(stream) as Streamable.Media.AudioOnly
} ?: return@withContext

val folder = "Echo${parent?.title?.let { "/$it" } ?: ""}"
val id = when (val audio = media.audio) {
is Streamable.Audio.Http -> {
val request = audio.request
val downloadRequest = DownloadManager.Request(request.url.toUri()).apply {
request.headers.forEach {
addRequestHeader(it.key, it.value)
}
setTitle(track.title)
setDescription(track.artists.joinToString(", ") { it.name })
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
downloadDirectoryFor(folder)
setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
"$folder/${track.title}"
)
}
downloadManager().enqueue(downloadRequest)
}

else -> throw Exception("Not Supported")

}

dao.insertDownload(DownloadEntity(id, track.id, extension.id, parent?.title))
// val settings = getSharedPreferences(packageName, Context.MODE_PRIVATE)
// val stream = selectAudioStream(settings, track.sources)
// ?: throw Exception("No Stream Found")
// require(stream.mimeType == Streamable.MimeType.Progressive)
// val media = extension.get<TrackClient, Streamable.Media.AudioOnly>(throwable) {
// getStreamableMedia(stream) as Streamable.Media.AudioOnly
// } ?: return@withContext
//
// val folder = "Echo${parent?.title?.let { "/$it" } ?: ""}"
// val id = when (val audio = media.source) {
// is Streamable.Source.Http -> {
// val request = audio.request
// val downloadRequest = DownloadManager.Request(request.url.toUri()).apply {
// request.headers.forEach {
// addRequestHeader(it.key, it.value)
// }
// setTitle(track.title)
// setDescription(track.artists.joinToString(", ") { it.name })
// setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
// downloadDirectoryFor(folder)
// setDestinationInExternalPublicDir(
// Environment.DIRECTORY_DOWNLOADS,
// "$folder/${track.title}"
// )
// }
// downloadManager().enqueue(downloadRequest)
// }
//
// else -> throw Exception("Not Supported")
//
// }

// dao.insertDownload(DownloadEntity(id, track.id, extension.id, parent?.title))
saveToCache(track.id, track, "downloads")
}

private fun downloadDirectoryFor(folder: String?): File {
val directory = File("${Environment.DIRECTORY_DOWNLOADS}/$folder")
if (!directory.exists()) directory.mkdirs()
return directory
}
// private fun downloadDirectoryFor(folder: String?): File {
// val directory = File("${Environment.DIRECTORY_DOWNLOADS}/$folder")
// if (!directory.exists()) directory.mkdirs()
// return directory
// }

private fun Context.downloadManager() =
getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ class ApkFileManifestParser(
private val apkManifestParser: ManifestParser<AppInfo, Metadata>
) : ManifestParser<File, Metadata> {
override fun parseManifest(data: File): Metadata {
return apkManifestParser.parseManifest(
AppInfo(
data.path,
packageManager
.getPackageArchiveInfo(data.path, ApkPluginSource.PACKAGE_FLAGS)!!
.applicationInfo!!
)
)
val info = runCatching {
packageManager
.getPackageArchiveInfo(data.path, ApkPluginSource.PACKAGE_FLAGS)!!
.applicationInfo!!
}.getOrElse {
throw Exception("Failed to parse APK file: ${data.path}")
}
return apkManifestParser.parseManifest(AppInfo(data.path, info))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class FilePluginSource(
private var ignoreFile : File? = null
private fun loadAllPlugins() = run {
folder.setReadOnly()
folder.listFiles()!!.filter { it != ignoreFile }.onEach { it.setWritable(false) }
folder.listFiles()!!.filter {
it != ignoreFile && it.extension == "apk"
}.onEach { it.setWritable(false) }
}
private val pluginStateFlow = MutableStateFlow(loadAllPlugins())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class BuiltInExtensionRepo(

override fun getAllPlugins() = MutableStateFlow(
listOf(
// getLazy(TestExtension.metadata, TestExtension()),
getLazy(TestExtension.metadata, TestExtension()),
getLazy(OfflineExtension.metadata, extension),
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ object MediaStoreUtils {
}.toString().ifEmpty { null }
val liked = likedAudios.contains(id)
val song = Track(
id = id.toString(),
id = "offline:$id",
title = title,
artists = artists,
album = album,
Expand All @@ -356,7 +356,7 @@ object MediaStoreUtils {
isLiked = liked,
genre = genre,
description = description,
streamables = listOf(Streamable.audio(path, 0)),
streamables = listOf(Streamable.source(path, 0)),
extras = mapOf(
"addDate" to addDate.toString(),
"trackNumber" to trackNumber.toString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import dev.brahmkshatriya.echo.common.models.QuickSearch
import dev.brahmkshatriya.echo.common.models.Radio
import dev.brahmkshatriya.echo.common.models.Shelf
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
import dev.brahmkshatriya.echo.common.models.Streamable.Source.Companion.toSource
import dev.brahmkshatriya.echo.common.models.Tab
import dev.brahmkshatriya.echo.common.models.Track
import dev.brahmkshatriya.echo.common.models.User
Expand All @@ -47,7 +47,7 @@ import dev.brahmkshatriya.echo.offline.MediaStoreUtils.editPlaylist
import dev.brahmkshatriya.echo.offline.MediaStoreUtils.moveSongInPlaylist
import dev.brahmkshatriya.echo.offline.MediaStoreUtils.removeSongFromPlaylist
import dev.brahmkshatriya.echo.offline.MediaStoreUtils.searchBy
import dev.brahmkshatriya.echo.playback.MediaItemUtils.toIdAndIsVideo
import dev.brahmkshatriya.echo.playback.MediaItemUtils.toIdAndIndex
import dev.brahmkshatriya.echo.utils.getFromCache
import dev.brahmkshatriya.echo.utils.getSettings
import dev.brahmkshatriya.echo.utils.saveToCache
Expand Down Expand Up @@ -119,7 +119,7 @@ class OfflineExtension(

@OptIn(UnstableApi::class)
private fun getCachedTracks() = cache.keys.mapNotNull { key ->
val (id, _) = key.toIdAndIsVideo() ?: return@mapNotNull null
val (id, _) = key.toIdAndIndex() ?: return@mapNotNull null
context.getFromCache<Pair<String, Track>>(id, "track")
}.reversed()

Expand Down Expand Up @@ -195,7 +195,7 @@ class OfflineExtension(
)

override suspend fun getStreamableMedia(streamable: Streamable): Streamable.Media {
return streamable.id.toAudio().toMedia()
return streamable.id.toSource().toMedia()
}

override fun getShelves(track: Track): PagedData<Shelf> =
Expand Down
Loading

0 comments on commit 602da3d

Please sign in to comment.