Browse Source

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.

housekeeping/remove-warnings
Antoine POPINEAU 4 years ago
parent
commit
e539cc26dd
No known key found for this signature in database
GPG Key ID: A78AC64694F84063
  1. 18
      app/src/main/java/com/github/apognu/otter/Otter.kt
  2. 23
      app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt
  3. 16
      app/src/main/java/com/github/apognu/otter/adapters/FavoritesAdapter.kt
  4. 17
      app/src/main/java/com/github/apognu/otter/adapters/TracksAdapter.kt
  5. 13
      app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt
  6. 8
      app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt
  7. 8
      app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt
  8. 1
      app/src/main/java/com/github/apognu/otter/utils/Models.kt
  9. 3
      app/src/main/res/values-night/colors.xml
  10. 3
      app/src/main/res/values/colors.xml

18
app/src/main/java/com/github/apognu/otter/Otter.kt

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

23
app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt

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

16
app/src/main/java/com/github/apognu/otter/adapters/FavoritesAdapter.kt

@ -2,6 +2,8 @@ package com.github.apognu.otter.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.Typeface import android.graphics.Typeface
import android.os.Build import android.os.Build
import android.view.Gravity 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)) 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) true -> holder.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
false -> holder.title.setCompoundDrawablesWithIntrinsicBounds(0, 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 { holder.favorite.setOnClickListener {
favoriteListener.onToggleFavorite(favorite.id, !favorite.favorite) favoriteListener.onToggleFavorite(favorite.id, !favorite.favorite)

17
app/src/main/java/com/github/apognu/otter/adapters/TracksAdapter.kt

@ -2,8 +2,7 @@ package com.github.apognu.otter.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.*
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Build import android.os.Build
import android.view.* 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) true -> holder.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
false -> holder.title.setCompoundDrawablesWithIntrinsicBounds(0, 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 { holder.actions.setOnClickListener {

13
app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt

@ -9,6 +9,8 @@ import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.android.exoplayer2.source.ConcatenatingMediaSource import com.google.android.exoplayer2.source.ConcatenatingMediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory 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.upstream.cache.CacheDataSourceFactory
import com.google.android.exoplayer2.util.Util import com.google.android.exoplayer2.util.Util
import com.google.gson.Gson 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
)
} }
} }

8
app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt

@ -1,6 +1,7 @@
package com.github.apognu.otter.repositories package com.github.apognu.otter.repositories
import android.content.Context import android.content.Context
import com.github.apognu.otter.Otter
import com.github.apognu.otter.utils.* import com.github.apognu.otter.utils.*
import com.github.kittinunf.fuel.Fuel import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitByteArrayResponseResult import com.github.kittinunf.fuel.coroutines.awaitByteArrayResponseResult
@ -26,6 +27,13 @@ class FavoritesRepository(override val context: Context?) : Repository<Track, Tr
data.map { track -> data.map { track ->
track.favorite = true track.favorite = true
track.downloaded = downloaded.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 track
} }
} }

8
app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt

@ -1,6 +1,7 @@
package com.github.apognu.otter.repositories package com.github.apognu.otter.repositories
import android.content.Context import android.content.Context
import com.github.apognu.otter.Otter
import com.github.apognu.otter.utils.* import com.github.apognu.otter.utils.*
import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.android.exoplayer2.offline.Download import com.google.android.exoplayer2.offline.Download
@ -48,6 +49,13 @@ class TracksRepository(override val context: Context?, albumId: Int) : Repositor
data.map { track -> data.map { track ->
track.favorite = favorites.contains(track.id) track.favorite = favorites.contains(track.id)
track.downloaded = downloaded.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 track
}.sortedBy { it.position } }.sortedBy { it.position }
} }

1
app/src/main/java/com/github/apognu/otter/utils/Models.kt

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

3
app/src/main/res/values-night/colors.xml

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

3
app/src/main/res/values/colors.xml

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

Loading…
Cancel
Save