Manage cached and downloaded tracks separately. Downloaded track are not automatically evicted and do not count towards cache storage limit. Contributes to #37. Fixed an issue where the event bus on main would be duplicated.

This commit is contained in:
Antoine POPINEAU 2020-06-20 15:42:10 +02:00
parent 2eff3263d2
commit e539cc26dd
No known key found for this signature in database
GPG Key ID: A78AC64694F84063
10 changed files with 95 additions and 15 deletions

View File

@ -3,16 +3,14 @@ package com.github.apognu.otter
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import com.github.apognu.otter.playback.QueueManager
import com.github.apognu.otter.utils.Cache
import com.github.apognu.otter.utils.Command
import com.github.apognu.otter.utils.Event
import com.github.apognu.otter.utils.Request
import com.github.apognu.otter.utils.*
import com.google.android.exoplayer2.database.ExoDatabaseProvider
import com.google.android.exoplayer2.offline.DefaultDownloadIndex
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory
import com.google.android.exoplayer2.offline.DownloadManager
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache
import com.preference.PowerPreference
import kotlinx.coroutines.channels.BroadcastChannel
@ -36,6 +34,7 @@ class Otter : Application() {
val progressBus: BroadcastChannel<Triple<Int, Int, Int>> = ConflatedBroadcastChannel()
private val exoDatabase: ExoDatabaseProvider by lazy { ExoDatabaseProvider(this) }
val exoCache: SimpleCache by lazy {
PowerPreference.getDefaultFile().getInt("media_cache_size", 1).toLong().let {
SimpleCache(
@ -45,8 +44,17 @@ class Otter : Application() {
)
}
}
val exoDownloadCache: SimpleCache by lazy {
SimpleCache(
cacheDir.resolve("downloads"),
NoOpCacheEvictor(),
exoDatabase
)
}
val exoDownloadManager: DownloadManager by lazy {
DownloaderConstructorHelper(exoCache, QueueManager.factory(this)).run {
DownloaderConstructorHelper(exoDownloadCache, QueueManager.factory(this)).run {
DownloadManager(this@Otter, DefaultDownloadIndex(exoDatabase), DefaultDownloaderFactory(this))
}
}

View File

@ -41,9 +41,11 @@ import kotlinx.android.synthetic.main.partial_now_playing.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.random.Random
class MainActivity : AppCompatActivity() {
enum class ResultCode(val code: Int) {
@ -53,6 +55,8 @@ class MainActivity : AppCompatActivity() {
private val favoriteRepository = FavoritesRepository(this)
private val favoriteCheckRepository = FavoritedRepository(this)
private var bus: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -69,10 +73,6 @@ class MainActivity : AppCompatActivity() {
.beginTransaction()
.replace(R.id.container, BrowseFragment())
.commit()
watchEventBus()
CommandBus.send(Command.RefreshService)
}
override fun onResume() {
@ -116,6 +116,19 @@ class MainActivity : AppCompatActivity() {
landscape_queue?.let {
supportFragmentManager.beginTransaction().replace(R.id.landscape_queue, LandscapeQueueFragment()).commit()
}
if (bus == null) {
watchEventBus()
}
CommandBus.send(Command.RefreshService)
}
override fun onPause() {
super.onPause()
bus?.cancel()
bus = null
}
override fun onBackPressed() {
@ -212,7 +225,7 @@ class MainActivity : AppCompatActivity() {
@SuppressLint("NewApi")
private fun watchEventBus() {
GlobalScope.launch(Main) {
bus = GlobalScope.launch(Main) {
EventBus.get().collect { message ->
when (message) {
is Event.LogOut -> {

View File

@ -2,6 +2,8 @@ package com.github.apognu.otter.adapters
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.Typeface
import android.os.Build
import android.view.Gravity
@ -74,11 +76,23 @@ class FavoritesAdapter(private val context: Context?, private val favoriteListen
false -> holder.favorite.setColorFilter(context.getColor(R.color.colorSelected))
}
when (favorite.downloaded) {
when (favorite.cached || favorite.downloaded) {
true -> holder.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
false -> holder.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
}
if (favorite.cached && !favorite.downloaded) {
holder.title.compoundDrawables.forEach {
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
}
}
if (favorite.downloaded) {
holder.title.compoundDrawables.forEach {
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
}
}
holder.favorite.setOnClickListener {
favoriteListener.onToggleFavorite(favorite.id, !favorite.favorite)

View File

@ -2,8 +2,7 @@ package com.github.apognu.otter.adapters
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.*
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.view.*
@ -95,10 +94,22 @@ class TracksAdapter(private val context: Context?, private val favoriteListener:
}
}
when (track.downloaded) {
when (track.cached || track.downloaded) {
true -> holder.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
false -> holder.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
}
if (track.cached && !track.downloaded) {
holder.title.compoundDrawables.forEach {
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
}
}
if (track.downloaded) {
holder.title.compoundDrawables.forEach {
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
}
}
}
holder.actions.setOnClickListener {

View File

@ -9,6 +9,8 @@ import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.android.exoplayer2.source.ConcatenatingMediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
import com.google.android.exoplayer2.upstream.FileDataSource
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory
import com.google.android.exoplayer2.util.Util
import com.google.gson.Gson
@ -28,7 +30,16 @@ class QueueManager(val context: Context) {
}
}
return CacheDataSourceFactory(Otter.get().exoCache, http)
val playbackCache = CacheDataSourceFactory(Otter.get().exoCache, http)
return CacheDataSourceFactory(
Otter.get().exoDownloadCache,
playbackCache,
FileDataSource.Factory(),
null,
CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
null
)
}
}

View File

@ -1,6 +1,7 @@
package com.github.apognu.otter.repositories
import android.content.Context
import com.github.apognu.otter.Otter
import com.github.apognu.otter.utils.*
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitByteArrayResponseResult
@ -26,6 +27,13 @@ class FavoritesRepository(override val context: Context?) : Repository<Track, Tr
data.map { track ->
track.favorite = true
track.downloaded = downloaded.contains(track.id)
track.bestUpload()?.let { upload ->
val url = mustNormalizeUrl(upload.listen_url)
track.cached = Otter.get().exoCache.isCached(url, 0, upload.duration * 1000L)
}
track
}
}

View File

@ -1,6 +1,7 @@
package com.github.apognu.otter.repositories
import android.content.Context
import com.github.apognu.otter.Otter
import com.github.apognu.otter.utils.*
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.android.exoplayer2.offline.Download
@ -48,6 +49,13 @@ class TracksRepository(override val context: Context?, albumId: Int) : Repositor
data.map { track ->
track.favorite = favorites.contains(track.id)
track.downloaded = downloaded.contains(track.id)
track.bestUpload()?.let { upload ->
val url = mustNormalizeUrl(upload.listen_url)
track.cached = Otter.get().exoCache.isCached(url, 0, upload.duration * 1000L)
}
track
}.sortedBy { it.position }
}

View File

@ -100,6 +100,7 @@ data class Track(
) : SearchResult {
var current: Boolean = false
var favorite: Boolean = false
var cached: Boolean = false
var downloaded: Boolean = false
data class Upload(

View File

@ -16,4 +16,7 @@
<color name="whiteWhileLight">#000000</color>
<color name="blackWhileLight">#ffffff</color>
<color name="downloaded">@color/controlColor</color>
<color name="cached">#aeaeae</color>
</resources>

View File

@ -18,4 +18,7 @@
<color name="whiteWhileLight">#ffffff</color>
<color name="blackWhileLight">#000000</color>
<color name="downloaded">@color/colorPrimary</color>
<color name="cached">#999999</color>
</resources>