Better handling of download progress and event. Added an option to retry failed downloads. Performance improvement around downloads UI.
This commit is contained in:
parent
94fd3d51aa
commit
a2c35595c7
|
@ -111,6 +111,7 @@ dependencies {
|
|||
implementation("com.android.support.constraint:constraint-layout:1.1.3")
|
||||
|
||||
implementation("com.google.android.exoplayer:exoplayer-core:2.11.5")
|
||||
implementation("com.google.android.exoplayer:exoplayer-ui:2.11.5")
|
||||
implementation("com.google.android.exoplayer:extension-mediasession:2.11.5")
|
||||
|
||||
implementation("com.aliassadi:power-preference-lib:1.4.1")
|
||||
|
|
|
@ -2,11 +2,16 @@ 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.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.SimpleCache
|
||||
import com.preference.PowerPreference
|
||||
|
@ -30,8 +35,21 @@ class Otter : Application() {
|
|||
val requestBus: BroadcastChannel<Request> = BroadcastChannel(10)
|
||||
val progressBus: BroadcastChannel<Triple<Int, Int, Int>> = ConflatedBroadcastChannel()
|
||||
|
||||
var exoCache: SimpleCache? = null
|
||||
var exoDatabase: ExoDatabaseProvider? = null
|
||||
private val exoDatabase: ExoDatabaseProvider by lazy { ExoDatabaseProvider(this) }
|
||||
val exoCache: SimpleCache by lazy {
|
||||
PowerPreference.getDefaultFile().getInt("media_cache_size", 1).toLong().let {
|
||||
SimpleCache(
|
||||
cacheDir.resolve("media"),
|
||||
LeastRecentlyUsedCacheEvictor(it * 1024 * 1024 * 1024),
|
||||
exoDatabase
|
||||
)
|
||||
}
|
||||
}
|
||||
val exoDownloadManager: DownloadManager by lazy {
|
||||
DownloaderConstructorHelper(exoCache, QueueManager.factory(this)).run {
|
||||
DownloadManager(this@Otter, DefaultDownloadIndex(exoDatabase), DefaultDownloaderFactory(this))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
@ -41,15 +59,6 @@ class Otter : Application() {
|
|||
Thread.setDefaultUncaughtExceptionHandler(CrashReportHandler())
|
||||
|
||||
instance = this
|
||||
exoDatabase = ExoDatabaseProvider(this)
|
||||
|
||||
PowerPreference.getDefaultFile().getInt("media_cache_size", 1).toLong().also {
|
||||
exoCache = SimpleCache(
|
||||
cacheDir.resolve("media"),
|
||||
LeastRecentlyUsedCacheEvictor(it * 1024 * 1024 * 1024),
|
||||
exoDatabase
|
||||
)
|
||||
}
|
||||
|
||||
when (PowerPreference.getDefaultFile().getString("night_mode")) {
|
||||
"on" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
package com.github.apognu.otter.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.adapters.DownloadsAdapter
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.google.gson.Gson
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import kotlinx.android.synthetic.main.activity_downloads.*
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DownloadsActivity : AppCompatActivity() {
|
||||
lateinit var adapter: DownloadsAdapter
|
||||
|
@ -21,17 +23,24 @@ class DownloadsActivity : AppCompatActivity() {
|
|||
|
||||
setContentView(R.layout.activity_downloads)
|
||||
|
||||
adapter = DownloadsAdapter(this, RefreshListener()).also {
|
||||
adapter = DownloadsAdapter(this, DownloadChangedListener()).also {
|
||||
downloads.layoutManager = LinearLayoutManager(this)
|
||||
downloads.adapter = it
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(Main) {
|
||||
while (true) {
|
||||
refresh()
|
||||
delay(1000)
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
GlobalScope.launch(IO) {
|
||||
EventBus.get().collect { event ->
|
||||
if (event is Event.DownloadChanged) {
|
||||
refreshTrack(event.download)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
|
@ -42,7 +51,7 @@ class DownloadsActivity : AppCompatActivity() {
|
|||
while (response.cursor.moveToNext()) {
|
||||
val download = response.cursor.download
|
||||
|
||||
Gson().fromJson(String(download.request.data), DownloadInfo::class.java)?.let { info ->
|
||||
download.getMetadata()?.let { info ->
|
||||
adapter.downloads.add(info.apply {
|
||||
this.download = download
|
||||
})
|
||||
|
@ -54,9 +63,26 @@ class DownloadsActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
inner class RefreshListener : DownloadsAdapter.OnRefreshListener {
|
||||
override fun refresh() {
|
||||
this@DownloadsActivity.refresh()
|
||||
private suspend fun refreshTrack(download: Download) {
|
||||
if (download.state == Download.STATE_COMPLETED) {
|
||||
download.getMetadata()?.let { info ->
|
||||
adapter.downloads.withIndex().associate { it.value to it.index }.filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match ->
|
||||
withContext(Main) {
|
||||
adapter.downloads[match.second] = info.apply {
|
||||
this.download = download
|
||||
}
|
||||
|
||||
adapter.notifyItemChanged(match.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class DownloadChangedListener : DownloadsAdapter.OnDownloadChangedListener {
|
||||
override fun onItemRemoved(index: Int) {
|
||||
adapter.downloads.removeAt(index)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,14 +8,14 @@ import android.view.ViewGroup
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.playback.PinService
|
||||
import com.github.apognu.otter.utils.DownloadInfo
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import com.google.android.exoplayer2.offline.DownloadService
|
||||
import kotlinx.android.synthetic.main.row_download.view.*
|
||||
|
||||
class DownloadsAdapter(private val context: Context, private val listener: OnRefreshListener) : RecyclerView.Adapter<DownloadsAdapter.ViewHolder>() {
|
||||
interface OnRefreshListener {
|
||||
fun refresh()
|
||||
class DownloadsAdapter(private val context: Context, private val listener: OnDownloadChangedListener) : RecyclerView.Adapter<DownloadsAdapter.ViewHolder>() {
|
||||
interface OnDownloadChangedListener {
|
||||
fun onItemRemoved(index: Int)
|
||||
}
|
||||
|
||||
var downloads: MutableList<DownloadInfo> = mutableListOf()
|
||||
|
@ -38,7 +38,15 @@ class DownloadsAdapter(private val context: Context, private val listener: OnRef
|
|||
when (state.isTerminalState) {
|
||||
true -> {
|
||||
holder.progress.visibility = View.GONE
|
||||
holder.toggle.visibility = View.GONE
|
||||
|
||||
when (state.state) {
|
||||
Download.STATE_FAILED -> {
|
||||
holder.toggle.setImageDrawable(context.getDrawable(R.drawable.retry))
|
||||
holder.progress.visibility = View.GONE
|
||||
}
|
||||
|
||||
else -> holder.toggle.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
false -> {
|
||||
|
@ -53,25 +61,29 @@ class DownloadsAdapter(private val context: Context, private val listener: OnRef
|
|||
}
|
||||
|
||||
Download.STATE_STOPPED -> holder.toggle.setImageIcon(Icon.createWithResource(context, R.drawable.play))
|
||||
|
||||
else -> holder.toggle.setImageIcon(Icon.createWithResource(context, R.drawable.pause))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
holder.toggle.setOnClickListener {
|
||||
if (state.state == Download.STATE_DOWNLOADING) {
|
||||
DownloadService.sendSetStopReason(context, PinService::class.java, download.contentId, 1, false)
|
||||
} else {
|
||||
DownloadService.sendSetStopReason(context, PinService::class.java, download.contentId, Download.STOP_REASON_NONE, false)
|
||||
}
|
||||
when (state.state) {
|
||||
Download.STATE_DOWNLOADING -> DownloadService.sendSetStopReason(context, PinService::class.java, download.contentId, 1, false)
|
||||
|
||||
listener.refresh()
|
||||
Download.STATE_FAILED -> {
|
||||
Track(download.id, download.title, Artist(0, download.artist, listOf()),Album(0, Album.Artist(""), "", Covers("")), 0, listOf(Track.Upload(download.contentId, 0, 0))).also {
|
||||
PinService.download(context, it)
|
||||
}
|
||||
}
|
||||
|
||||
else -> DownloadService.sendSetStopReason(context, PinService::class.java, download.contentId, Download.STOP_REASON_NONE, false)
|
||||
}
|
||||
}
|
||||
|
||||
holder.delete.setOnClickListener {
|
||||
listener.onItemRemoved(position)
|
||||
DownloadService.sendRemoveDownload(context, PinService::class.java, download.contentId, false)
|
||||
|
||||
listener.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,16 @@ import com.github.apognu.otter.adapters.TracksAdapter
|
|||
import com.github.apognu.otter.repositories.FavoritesRepository
|
||||
import com.github.apognu.otter.repositories.TracksRepository
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import com.squareup.picasso.Picasso
|
||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||
import kotlinx.android.synthetic.main.fragment_tracks.*
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class TracksFragment : FunkwhaleFragment<Track, TracksAdapter>() {
|
||||
override val viewRes = R.layout.fragment_tracks
|
||||
|
@ -78,11 +81,15 @@ class TracksFragment : FunkwhaleFragment<Track, TracksAdapter>() {
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
GlobalScope.launch(Main) {
|
||||
GlobalScope.launch(IO) {
|
||||
RequestBus.send(Request.GetCurrentTrack).wait<Response.CurrentTrack>()?.let { response ->
|
||||
adapter.currentTrack = response.track
|
||||
adapter.notifyDataSetChanged()
|
||||
withContext(Main) {
|
||||
adapter.currentTrack = response.track
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
refreshDownloadedTracks()
|
||||
}
|
||||
|
||||
play.setOnClickListener {
|
||||
|
@ -117,29 +124,46 @@ class TracksFragment : FunkwhaleFragment<Track, TracksAdapter>() {
|
|||
}
|
||||
|
||||
private fun watchEventBus() {
|
||||
GlobalScope.launch(Main) {
|
||||
GlobalScope.launch(IO) {
|
||||
EventBus.get().collect { message ->
|
||||
when (message) {
|
||||
is Event.TrackPlayed -> refreshCurrentTrack()
|
||||
is Event.RefreshTrack -> refreshCurrentTrack()
|
||||
is Event.DownloadChanged -> {
|
||||
val downloaded = TracksRepository.getDownloadedIds() ?: listOf()
|
||||
is Event.DownloadChanged -> refreshDownloadedTrack(message.download)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adapter.data = adapter.data.map {
|
||||
it.downloaded = downloaded.contains(it.id)
|
||||
it
|
||||
}.toMutableList()
|
||||
private suspend fun refreshDownloadedTracks() {
|
||||
val downloaded = TracksRepository.getDownloadedIds() ?: listOf()
|
||||
|
||||
adapter.notifyDataSetChanged()
|
||||
withContext(Main) {
|
||||
adapter.data = adapter.data.map {
|
||||
it.downloaded = downloaded.contains(it.id)
|
||||
it
|
||||
}.toMutableList()
|
||||
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshDownloadedTrack(download: Download) {
|
||||
if (download.state == Download.STATE_COMPLETED) {
|
||||
download.getMetadata()?.let { info ->
|
||||
adapter.data.withIndex().associate { it.value to it.index }.filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match ->
|
||||
withContext(Main) {
|
||||
adapter.data[match.second].downloaded = true
|
||||
adapter.notifyItemChanged(match.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshCurrentTrack() {
|
||||
GlobalScope.launch(Main) {
|
||||
RequestBus.send(Request.GetCurrentTrack).wait<Response.CurrentTrack>()?.let { response ->
|
||||
private suspend fun refreshCurrentTrack() {
|
||||
RequestBus.send(Request.GetCurrentTrack).wait<Response.CurrentTrack>()?.let { response ->
|
||||
withContext(Main) {
|
||||
adapter.currentTrack = response.track
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
|
|
@ -1,25 +1,45 @@
|
|||
package com.github.apognu.otter.playback
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.google.android.exoplayer2.offline.*
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import com.google.android.exoplayer2.offline.DownloadManager
|
||||
import com.google.android.exoplayer2.offline.DownloadRequest
|
||||
import com.google.android.exoplayer2.offline.DownloadService
|
||||
import com.google.android.exoplayer2.scheduler.Scheduler
|
||||
import com.google.android.exoplayer2.ui.DownloadNotificationHelper
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
|
||||
private val manager by lazy {
|
||||
val database = Otter.get().exoDatabase
|
||||
val cache = Otter.get().exoCache
|
||||
val helper = DownloaderConstructorHelper(cache, QueueManager.factory(this))
|
||||
companion object {
|
||||
fun download(context: Context, track: Track) {
|
||||
track.bestUpload()?.let { upload ->
|
||||
val url = mustNormalizeUrl(upload.listen_url)
|
||||
val data = Gson().toJson(
|
||||
DownloadInfo(
|
||||
track.id,
|
||||
url,
|
||||
track.title,
|
||||
track.artist.name,
|
||||
null
|
||||
)
|
||||
).toByteArray()
|
||||
|
||||
DownloadManager(this, DefaultDownloadIndex(database), DefaultDownloaderFactory(helper))
|
||||
DownloadRequest(url, DownloadRequest.TYPE_PROGRESSIVE, Uri.parse(url), Collections.emptyList(), null, data).also {
|
||||
sendAddDownload(context, PinService::class.java, it, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
|
@ -36,22 +56,25 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
|
|||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
override fun getDownloadManager() = manager
|
||||
override fun getDownloadManager() = Otter.get().exoDownloadManager.apply {
|
||||
addListener(DownloadListener())
|
||||
}
|
||||
|
||||
override fun getScheduler(): Scheduler? = null
|
||||
|
||||
override fun getForegroundNotification(downloads: MutableList<Download>?): Notification {
|
||||
val quantity = downloads?.size ?: 0
|
||||
val description = resources.getQuantityString(R.plurals.downloads_description, quantity, quantity)
|
||||
override fun getForegroundNotification(downloads: MutableList<Download>): Notification {
|
||||
val description = resources.getQuantityString(R.plurals.downloads_description, downloads.size, downloads.size)
|
||||
|
||||
return DownloadNotificationHelper(this, AppContext.NOTIFICATION_CHANNEL_DOWNLOADS).buildProgressNotification(R.drawable.downloads, null, description, downloads)
|
||||
}
|
||||
|
||||
override fun onDownloadChanged(download: Download?) {
|
||||
super.onDownloadChanged(download)
|
||||
private fun getDownloads() = downloadManager.downloadIndex.getDownloads()
|
||||
|
||||
EventBus.send(Event.DownloadChanged)
|
||||
inner class DownloadListener : DownloadManager.Listener {
|
||||
override fun onDownloadChanged(downloadManager: DownloadManager, download: Download) {
|
||||
super.onDownloadChanged(downloadManager, download)
|
||||
|
||||
EventBus.send(Event.DownloadChanged(download))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDownloads() = manager.downloadIndex.getDownloads()
|
||||
}
|
|
@ -8,7 +8,6 @@ import android.content.IntentFilter
|
|||
import android.media.AudioAttributes
|
||||
import android.media.AudioFocusRequest
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
|
@ -16,13 +15,13 @@ import android.view.KeyEvent
|
|||
import com.github.apognu.otter.Otter
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.google.android.exoplayer2.*
|
||||
import com.google.android.exoplayer2.C
|
||||
import com.google.android.exoplayer2.ExoPlaybackException
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
||||
import com.google.android.exoplayer2.offline.DownloadRequest
|
||||
import com.google.android.exoplayer2.offline.DownloadService.sendAddDownload
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
|
@ -30,7 +29,6 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
class PlayerService : Service() {
|
||||
private lateinit var queue: QueueManager
|
||||
|
@ -88,7 +86,7 @@ class PlayerService : Service() {
|
|||
|
||||
mediaControlsManager = MediaControlsManager(this, mediaSession)
|
||||
|
||||
player = ExoPlayerFactory.newSimpleInstance(this).apply {
|
||||
player = SimpleExoPlayer.Builder(this).build().apply {
|
||||
playWhenReady = false
|
||||
|
||||
playerEventListener = PlayerEventListener().also {
|
||||
|
@ -98,12 +96,12 @@ class PlayerService : Service() {
|
|||
MediaSessionConnector(mediaSession).also {
|
||||
it.setPlayer(this)
|
||||
it.setMediaButtonEventHandler { player, _, mediaButtonEvent ->
|
||||
mediaButtonEvent?.extras?.getParcelable<KeyEvent>(Intent.EXTRA_KEY_EVENT)?.let { key ->
|
||||
mediaButtonEvent.extras?.getParcelable<KeyEvent>(Intent.EXTRA_KEY_EVENT)?.let { key ->
|
||||
if (key.action == KeyEvent.ACTION_UP) {
|
||||
when (key.keyCode) {
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY -> state(true)
|
||||
KeyEvent.KEYCODE_MEDIA_PAUSE -> state(false)
|
||||
KeyEvent.KEYCODE_MEDIA_NEXT -> player?.next()
|
||||
KeyEvent.KEYCODE_MEDIA_NEXT -> player.next()
|
||||
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> previousTrack()
|
||||
}
|
||||
}
|
||||
|
@ -193,8 +191,8 @@ class PlayerService : Service() {
|
|||
|
||||
is Command.SetRepeatMode -> player.repeatMode = message.mode
|
||||
|
||||
is Command.PinTrack -> download(message.track)
|
||||
is Command.PinTracks -> message.tracks.forEach { download(it) }
|
||||
is Command.PinTrack -> PinService.download(this@PlayerService, message.track)
|
||||
is Command.PinTracks -> message.tracks.forEach { PinService.download(this@PlayerService, it) }
|
||||
}
|
||||
|
||||
if (player.playWhenReady) {
|
||||
|
@ -255,7 +253,7 @@ class PlayerService : Service() {
|
|||
state(false)
|
||||
player.release()
|
||||
|
||||
Otter.get().exoCache?.release()
|
||||
Otter.get().exoCache.release()
|
||||
|
||||
stopForeground(true)
|
||||
stopSelf()
|
||||
|
@ -340,25 +338,6 @@ class PlayerService : Service() {
|
|||
player.seekTo(duration.toLong())
|
||||
}
|
||||
|
||||
private fun download(track: Track) {
|
||||
track.bestUpload()?.let { upload ->
|
||||
val url = mustNormalizeUrl(upload.listen_url)
|
||||
val data = Gson().toJson(
|
||||
DownloadInfo(
|
||||
track.id,
|
||||
url,
|
||||
track.title,
|
||||
track.artist.name,
|
||||
null
|
||||
)
|
||||
).toByteArray()
|
||||
|
||||
DownloadRequest(url, DownloadRequest.TYPE_PROGRESSIVE, Uri.parse(url), Collections.emptyList(), null, data).also {
|
||||
sendAddDownload(this@PlayerService, PinService::class.java, it, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class PlayerEventListener : Player.EventListener {
|
||||
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
|
||||
super.onPlayerStateChanged(playWhenReady, playbackState)
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
|||
import com.github.apognu.otter.utils.*
|
||||
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.toList
|
||||
|
@ -26,7 +25,7 @@ class TracksRepository(override val context: Context?, albumId: Int) : Repositor
|
|||
while (response.cursor.moveToNext()) {
|
||||
val download = response.cursor.download
|
||||
|
||||
Gson().fromJson(String(download.request.data), DownloadInfo::class.java)?.let {
|
||||
download.getMetadata()?.let {
|
||||
if (download.state == Download.STATE_COMPLETED) {
|
||||
ids.add(it.id)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.github.apognu.otter.utils
|
||||
|
||||
import com.github.apognu.otter.Otter
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import com.google.android.exoplayer2.offline.DownloadCursor
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
|
@ -47,7 +48,7 @@ sealed class Event {
|
|||
object QueueChanged : Event()
|
||||
object RadioStarted : Event()
|
||||
object ListingsChanged : Event()
|
||||
object DownloadChanged : Event()
|
||||
class DownloadChanged(val download: Download) : Event()
|
||||
}
|
||||
|
||||
sealed class Request(var channel: Channel<Response>? = null) {
|
||||
|
|
|
@ -6,6 +6,8 @@ import com.github.apognu.otter.R
|
|||
import com.github.apognu.otter.fragments.BrowseFragment
|
||||
import com.github.apognu.otter.repositories.Repository
|
||||
import com.github.kittinunf.fuel.core.Request
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import com.google.gson.Gson
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.squareup.picasso.RequestCreator
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
|
@ -62,8 +64,8 @@ fun <T> T.applyOnApi(api: Int, block: T.() -> T): T {
|
|||
}
|
||||
|
||||
fun Picasso.maybeLoad(url: String?): RequestCreator {
|
||||
if (url == null) return load(R.drawable.cover)
|
||||
else return load(url)
|
||||
return if (url == null) load(R.drawable.cover)
|
||||
else load(url)
|
||||
}
|
||||
|
||||
fun Request.authorize(): Request {
|
||||
|
@ -73,3 +75,5 @@ fun Request.authorize(): Request {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Download.getMetadata(): DownloadInfo? = Gson().fromJson(String(this.request.data), DownloadInfo::class.java)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
||||
</vector>
|
Loading…
Reference in New Issue