Skip to content

Commit

Permalink
Merge pull request #71 from mattcarter11/resume-downloads-on-startup
Browse files Browse the repository at this point in the history
download improvements

* centralize download requesting
* resume downloads that were left in non-terminal state (because of app crash, forced app closed, ...)
* add option to auto download liked songs
  • Loading branch information
mikooomich authored Dec 7, 2024
2 parents 0b7b59b + 427b231 commit c5eccbb
Show file tree
Hide file tree
Showing 20 changed files with 154 additions and 179 deletions.
2 changes: 2 additions & 0 deletions app/src/main/java/com/dd3boh/outertune/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ class MainActivity : ComponentActivity() {
val (lookupYtmArtists) = rememberPreference(LookupYtmArtistsKey, defaultValue = true)
val (autoScan) = rememberPreference(AutomaticScannerKey, defaultValue = true)
LaunchedEffect(Unit) {
downloadUtil.resumeDownloadsOnStart()

CoroutineScope(Dispatchers.IO).launch {
// Check if the permissions for local media access
if (autoScan && checkSelfPermission(mediaPermissionLevel) == PackageManager.PERMISSION_GRANTED) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ val SlimNavBarKey = booleanPreferencesKey("slimNavBar")
*/
const val SYSTEM_DEFAULT = "SYSTEM_DEFAULT"
val YtmSyncKey = booleanPreferencesKey("ytmSync")
val LikedAutoDownloadKey = stringPreferencesKey("likedAutoDownloadKey")
val ContentLanguageKey = stringPreferencesKey("contentLanguage")
val ContentCountryKey = stringPreferencesKey("contentCountry")
val ProxyEnabledKey = booleanPreferencesKey("proxyEnabled")
Expand Down Expand Up @@ -202,6 +203,10 @@ enum class SearchSource {
LOCAL, ONLINE
}

enum class LikedAutodownloadMode {
OFF, ON, WIFI_ONLY
}

val VisitorDataKey = stringPreferencesKey("visitorData")
val InnerTubeCookieKey = stringPreferencesKey("innerTubeCookie")
val AccountNameKey = stringPreferencesKey("accountName")
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/dd3boh/outertune/db/daos/SongsDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ interface SongsDao {
@Query("SELECT count from playCount WHERE song = :songId AND year = :year AND month = :month")
fun getPlayCountByMonth(songId: String?, year: Int, month: Int): Flow<Int>

@Query("SELECT * FROM song WHERE liked AND dateDownload IS NULL")
fun likedSongsNotDownloaded(): Flow<List<Song>>

// region Songs Sort
@Query("SELECT * FROM song WHERE inLibrary IS NOT NULL ORDER BY rowId")
fun songsByRowIdAsc(): Flow<List<Song>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.dd3boh.outertune.extensions
import android.content.Context
import com.dd3boh.outertune.constants.InnerTubeCookieKey
import com.dd3boh.outertune.constants.YtmSyncKey
import com.dd3boh.outertune.constants.LikedAutoDownloadKey
import com.dd3boh.outertune.constants.LikedAutodownloadMode
import com.dd3boh.outertune.utils.dataStore
import com.dd3boh.outertune.utils.get
import com.zionhuang.innertube.utils.parseCookieString
Expand All @@ -14,4 +16,8 @@ fun Context.isSyncEnabled(): Boolean {
val cookie = dataStore[InnerTubeCookieKey] ?: ""
ytmSync && "SAPISID" in parseCookieString(cookie)
}
}

fun Context.getLikeAutoDownload(): LikedAutodownloadMode {
return dataStore[LikedAutoDownloadKey].toEnum(LikedAutodownloadMode.OFF)
}
62 changes: 61 additions & 1 deletion app/src/main/java/com/dd3boh/outertune/playback/DownloadUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.dd3boh.outertune.playback

import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import androidx.media3.common.PlaybackException
Expand All @@ -12,11 +13,16 @@ import androidx.media3.datasource.okhttp.OkHttpDataSource
import androidx.media3.exoplayer.offline.Download
import androidx.media3.exoplayer.offline.DownloadManager
import androidx.media3.exoplayer.offline.DownloadNotificationHelper
import androidx.media3.exoplayer.offline.DownloadRequest
import androidx.media3.exoplayer.offline.DownloadService
import com.dd3boh.outertune.constants.AudioQuality
import com.dd3boh.outertune.constants.AudioQualityKey
import com.dd3boh.outertune.constants.LikedAutodownloadMode
import com.dd3boh.outertune.db.MusicDatabase
import com.dd3boh.outertune.db.entities.FormatEntity
import com.dd3boh.outertune.db.entities.SongEntity
import com.dd3boh.outertune.di.DownloadCache
import com.dd3boh.outertune.models.MediaMetadata
import com.dd3boh.outertune.utils.enumPreference
import com.zionhuang.innertube.YouTube
import dagger.hilt.android.qualifiers.ApplicationContext
Expand All @@ -35,10 +41,11 @@ import java.time.ZoneOffset
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Singleton
import com.dd3boh.outertune.extensions.getLikeAutoDownload

@Singleton
class DownloadUtil @Inject constructor(
@ApplicationContext context: Context,
@ApplicationContext private val context: Context,
val database: MusicDatabase,
val databaseProvider: DatabaseProvider,
@DownloadCache val downloadCache: SimpleCache,
Expand Down Expand Up @@ -120,8 +127,61 @@ class DownloadUtil @Inject constructor(
}
val downloads = MutableStateFlow<Map<String, Download>>(emptyMap())


fun getDownload(songId: String): Flow<Download?> = downloads.map { it[songId] }

fun download(songs: List<MediaMetadata>) {
songs.forEach { song -> downloadSong(song.id, song.title) }
}

fun download(song: MediaMetadata){
downloadSong(song.id, song.title)
}

fun download(song: SongEntity){
downloadSong(song.id, song.title)
}

private fun downloadSong(id: String, title: String){
val downloadRequest = DownloadRequest.Builder(id, id.toUri())
.setCustomCacheKey(id)
.setData(title.toByteArray())
.build()
DownloadService.sendAddDownload(
context,
ExoDownloadService::class.java,
downloadRequest,
false)
}

fun resumeDownloadsOnStart(){
DownloadService.sendResumeDownloads(
context,
ExoDownloadService::class.java,
false)
}

fun autoDownloadIfLiked(songs: List<SongEntity>){
songs.forEach { song -> autoDownloadIfLiked(song) }
}

fun autoDownloadIfLiked(song: SongEntity){
if (!song.liked || song.dateDownload != null){
return
}

val isWifiConnected = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ?: false

if (
context.getLikeAutoDownload() == LikedAutodownloadMode.ON
|| (context.getLikeAutoDownload() == LikedAutodownloadMode.WIFI_ONLY && isWifiConnected)
)
{
download(song)
}
}

init {
val result = mutableMapOf<String, Download>()
val cursor = downloadManager.downloadIndex.getDownloads()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ class MusicService : MediaLibraryService(),
@Inject
lateinit var database: MusicDatabase

@Inject
lateinit var downloadUtil: DownloadUtil

@Inject
lateinit var lyricsHelper: LyricsHelper

Expand Down Expand Up @@ -574,7 +577,9 @@ class MusicService : MediaLibraryService(),
fun toggleLike() {
database.query {
currentSong.value?.let {
update(it.song.toggleLike())
val song = it.song.toggleLike()
update(song)
downloadUtil.autoDownloadIfLiked(song)
}
}
}
Expand Down
18 changes: 4 additions & 14 deletions app/src/main/java/com/dd3boh/outertune/ui/menu/AlbumMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,10 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import androidx.media3.exoplayer.offline.Download.STATE_COMPLETED
import androidx.media3.exoplayer.offline.Download.STATE_DOWNLOADING
import androidx.media3.exoplayer.offline.Download.STATE_QUEUED
import androidx.media3.exoplayer.offline.Download.STATE_STOPPED
import androidx.media3.exoplayer.offline.DownloadRequest
import androidx.media3.exoplayer.offline.DownloadService
import androidx.navigation.NavController
import coil.compose.AsyncImage
Expand Down Expand Up @@ -305,18 +303,10 @@ fun AlbumMenu(
DownloadGridMenu(
state = downloadState,
onDownload = {
songs.filterNot { it.song.isLocal }.forEach { song ->
val downloadRequest = DownloadRequest.Builder(song.id, song.id.toUri())
.setCustomCacheKey(song.id)
.setData(song.song.title.toByteArray())
.build()
DownloadService.sendAddDownload(
context,
ExoDownloadService::class.java,
downloadRequest,
false
)
}
val _songs = songs
.filterNot { it.song.isLocal }
.map{ it.toMediaMetadata() }
downloadUtil.download(_songs)
},
onRemoveDownload = {
songs.forEach { song ->
Expand Down
22 changes: 7 additions & 15 deletions app/src/main/java/com/dd3boh/outertune/ui/menu/PlayerMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package com.dd3boh.outertune.ui.menu
import android.content.Intent
import android.text.format.Formatter
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -71,9 +69,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.DialogProperties
import androidx.core.net.toUri
import androidx.media3.common.PlaybackParameters
import androidx.media3.exoplayer.offline.DownloadRequest
import androidx.media3.exoplayer.offline.DownloadService
import androidx.navigation.NavController
import com.dd3boh.outertune.LocalDatabase
Expand Down Expand Up @@ -112,13 +108,13 @@ fun PlayerMenu(
mediaMetadata ?: return
val context = LocalContext.current
val database = LocalDatabase.current
val downloadUtil = LocalDownloadUtil.current
val clipboardManager = LocalClipboardManager.current

val playerConnection = LocalPlayerConnection.current ?: return
val playerVolume = playerConnection.service.playerVolume.collectAsState()
val currentFormat by playerConnection.currentFormat.collectAsState(initial = null)
val currentPlayCount by playerConnection.currentPlayCount.collectAsState(initial = null)
val activityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { }
val librarySong by database.song(mediaMetadata.id).collectAsState(initial = null)
val coroutineScope = rememberCoroutineScope()

Expand All @@ -128,6 +124,12 @@ fun PlayerMenu(
mutableStateOf(false)
}

LaunchedEffect(librarySong?.song?.liked) {
librarySong?.let {
downloadUtil.autoDownloadIfLiked(it.song)
}
}

AddToQueueDialog(
isVisible = showChooseQueueDialog,
onAdd = { queueName ->
Expand Down Expand Up @@ -412,16 +414,6 @@ fun PlayerMenu(
database.transaction {
insert(mediaMetadata)
}
val downloadRequest = DownloadRequest.Builder(mediaMetadata.id, mediaMetadata.id.toUri())
.setCustomCacheKey(mediaMetadata.id)
.setData(mediaMetadata.title.toByteArray())
.build()
DownloadService.sendAddDownload(
context,
ExoDownloadService::class.java,
downloadRequest,
false
)
},
onRemoveDownload = {
DownloadService.sendRemoveDownload(
Expand Down
16 changes: 2 additions & 14 deletions app/src/main/java/com/dd3boh/outertune/ui/menu/PlaylistMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.media3.exoplayer.offline.Download
import androidx.media3.exoplayer.offline.DownloadRequest
import androidx.media3.exoplayer.offline.DownloadService
import com.dd3boh.outertune.LocalDatabase
import com.dd3boh.outertune.LocalDownloadUtil
Expand Down Expand Up @@ -355,18 +353,8 @@ fun PlaylistMenu(
DownloadGridMenu(
state = downloadState,
onDownload = {
songs.filterNot { it.song.isLocal }.forEach { song ->
val downloadRequest = DownloadRequest.Builder(song.id, song.id.toUri())
.setCustomCacheKey(song.id)
.setData(song.song.title.toByteArray())
.build()
DownloadService.sendAddDownload(
context,
ExoDownloadService::class.java,
downloadRequest,
false
)
}
val _songs = songs.filterNot { it.song.isLocal }.map{ it.toMediaMetadata() }
downloadUtil.download(_songs)
},
onRemoveDownload = {
showRemoveDownloadDialog = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.media3.exoplayer.offline.Download
import androidx.media3.exoplayer.offline.DownloadRequest
import androidx.media3.exoplayer.offline.DownloadService
import com.dd3boh.outertune.LocalDatabase
import com.dd3boh.outertune.LocalDownloadUtil
Expand Down Expand Up @@ -256,7 +254,9 @@ fun SelectionMediaMetadataMenu(
}
} else {
selection.forEach { song ->
update(song.toSongEntity().copy(liked = true))
val likedSong = song.toSongEntity().copy(liked = true)
update(likedSong)
downloadUtil.autoDownloadIfLiked(likedSong)
}
}
}
Expand All @@ -265,18 +265,8 @@ fun SelectionMediaMetadataMenu(
DownloadGridMenu(
state = downloadState,
onDownload = {
selection.filterNot { it.isLocal }.forEach { song ->
val downloadRequest = DownloadRequest.Builder(song.id, song.id.toUri())
.setCustomCacheKey(song.id)
.setData(song.title.toByteArray())
.build()
DownloadService.sendAddDownload(
context,
ExoDownloadService::class.java,
downloadRequest,
false
)
}
val songs = selection.filterNot { it.isLocal }
downloadUtil.download(songs)
},
onRemoveDownload = {
showRemoveDownloadDialog = true
Expand Down
Loading

0 comments on commit c5eccbb

Please sign in to comment.