diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5ac8e8f..2c509b3 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -3,6 +3,7 @@ plugins {
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
id("org.jetbrains.kotlin.plugin.serialization")
+ id("kotlin-android")
}
android {
@@ -58,6 +59,9 @@ dependencies {
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.media3:media3-session:1.3.1")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
+// implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d462a8d..7d9e94d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -66,6 +66,9 @@
+
-
-
+
+
diff --git a/app/src/main/java/com/player/musicoo/App.kt b/app/src/main/java/com/player/musicoo/App.kt
index 1c4bdc6..481179f 100644
--- a/app/src/main/java/com/player/musicoo/App.kt
+++ b/app/src/main/java/com/player/musicoo/App.kt
@@ -7,11 +7,13 @@ import com.player.musicoo.bean.Audio
import com.player.musicoo.bean.CurrentPlayingAudio
import com.player.musicoo.bean.ResourcesList
import com.player.musicoo.database.AppDatabase
+import com.player.musicoo.database.AppOfflineDBManager
import com.player.musicoo.database.CurrentAudioDatabase
import com.player.musicoo.database.CurrentAudioManager
import com.player.musicoo.database.DatabaseManager
import com.player.musicoo.media.MediaControllerManager
import com.player.musicoo.util.CacheManager
+import com.player.musicoo.util.DownloadUtil
import com.player.musicoo.util.parseResources
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -24,6 +26,8 @@ class App : Application() {
companion object {
lateinit var app: App
private set
+ lateinit var appOfflineDBManager: AppOfflineDBManager
+ private set
lateinit var currentAudioManager: CurrentAudioManager
private set
lateinit var databaseManager: DatabaseManager
@@ -102,11 +106,13 @@ class App : Application() {
app = this
initialize(this)
MediaControllerManager.init(this)
+ appOfflineDBManager = AppOfflineDBManager.getInstance(this)
currentAudioManager = CurrentAudioManager.getInstance(this)
databaseManager = DatabaseManager.getInstance(this)
initCurrentPlayingAudio()
initImportAudio()
CacheManager.initializeCaches(this)
+ DownloadUtil.getDownloadManager(this)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/activity/MoBaseActivity.kt b/app/src/main/java/com/player/musicoo/activity/MoBaseActivity.kt
index 75fc331..1735d8d 100644
--- a/app/src/main/java/com/player/musicoo/activity/MoBaseActivity.kt
+++ b/app/src/main/java/com/player/musicoo/activity/MoBaseActivity.kt
@@ -2,12 +2,9 @@ package com.player.musicoo.activity
import android.content.Context
import android.graphics.Bitmap
-import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
import android.renderscript.Allocation
import android.renderscript.Element
import android.renderscript.RenderScript
@@ -19,16 +16,19 @@ import android.widget.RelativeLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.LifecycleOwner
import androidx.media3.common.Player
-import androidx.media3.session.MediaController
-import com.bumptech.glide.Glide
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.exoplayer.offline.Download
+import androidx.media3.exoplayer.offline.DownloadManager
import com.player.musicoo.App
import com.player.musicoo.R
+import com.player.musicoo.bean.OfflineBean
import com.player.musicoo.innertube.Innertube
-import com.player.musicoo.innertube.Innertube.TAG
import com.player.musicoo.media.MediaControllerManager
import com.player.musicoo.sp.AppStore
import com.player.musicoo.util.DownloadUtil
+import com.player.musicoo.util.FileSizeConverter
import com.player.musicoo.util.LogTag
import com.player.musicoo.view.MusicPlayerView
import kotlinx.coroutines.CoroutineScope
@@ -38,11 +38,12 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.io.IOException
-import java.io.InputStream
-abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope() {
+@UnstableApi
+abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope(),
+ LifecycleOwner {
private var playerListener: Player.Listener? = null
+ private var downloadManagerListener: DownloadManager.Listener? = null
enum class Event {
ActivityStart,
@@ -64,6 +65,7 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
this.defer = operation
}
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
musicPlayerView = MusicPlayerView(this, meController)
@@ -131,7 +133,6 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
override fun onDestroy() {
super.onDestroy()
- LogTag.LogD(TAG, "MoBaseActivity onDestroy")
if (meController != null && playerListener != null) {
meController.removeListener(playerListener!!)
}
@@ -147,10 +148,6 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
reason: Int
) {
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
- LogTag.LogD(
- Innertube.TAG,
- "MoBaseActivity DISCONTINUITY_REASON_AUTO_TRANSITION"
- )
if (meController != null) {
musicPlayerView.updateInfoUi(meController.currentMediaItem)
musicPlayerView.updateSetProgress(meController)
@@ -162,7 +159,6 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
}
override fun onPlaybackStateChanged(playbackState: Int) {
- LogTag.LogD(Innertube.TAG, "MoBaseActivity playbackState->$playbackState")
val meController = MediaControllerManager.getController()
if (meController != null) {
musicPlayerView.updateProgressState(meController)
@@ -181,10 +177,6 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
playWhenReady: Boolean,
reason: Int
) {
- LogTag.LogD(
- Innertube.TAG,
- "MoBaseActivity onPlayWhenReadyChanged->$playWhenReady"
- )
musicPlayerView.updatePlayState(playWhenReady)
val meController = MediaControllerManager.getController()
if (meController != null) {
diff --git a/app/src/main/java/com/player/musicoo/activity/MoListDetailsActivity.kt b/app/src/main/java/com/player/musicoo/activity/MoListDetailsActivity.kt
index 2458a62..7106ca8 100644
--- a/app/src/main/java/com/player/musicoo/activity/MoListDetailsActivity.kt
+++ b/app/src/main/java/com/player/musicoo/activity/MoListDetailsActivity.kt
@@ -103,6 +103,10 @@ class MoListDetailsActivity : MoBaseActivity() {
showLoadingUi()
Innertube.moPlaylistPage(browseId)
?.onSuccess {
+ if (this.isDestroyed || this.isFinishing) {
+ return
+ }
+
showDataUi()
Glide.with(this)
.load(it.thumbnail)
diff --git a/app/src/main/java/com/player/musicoo/activity/MoOfflineSongsActivity.kt b/app/src/main/java/com/player/musicoo/activity/MoOfflineSongsActivity.kt
new file mode 100644
index 0000000..c79eb47
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/activity/MoOfflineSongsActivity.kt
@@ -0,0 +1,143 @@
+package com.player.musicoo.activity
+
+import android.annotation.SuppressLint
+import android.view.View
+import androidx.media3.common.util.UnstableApi
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.bumptech.glide.Glide
+import com.gyf.immersionbar.ktx.immersionBar
+import com.player.musicoo.App
+import com.player.musicoo.adapter.DetailsListAdapter
+import com.player.musicoo.adapter.OfflineSongsAdapter
+import com.player.musicoo.bean.OfflineBean
+import com.player.musicoo.databinding.ActivityDetailsBinding
+import com.player.musicoo.databinding.ActivityOfflineSongsBinding
+import com.player.musicoo.innertube.Innertube
+import com.player.musicoo.innertube.requests.moPlaylistPage
+import com.player.musicoo.util.DownloadUtil
+import com.player.musicoo.util.LogTag.LogD
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.selects.select
+
+@UnstableApi
+class MoOfflineSongsActivity : MoBaseActivity() {
+ private val requests: Channel = Channel(Channel.UNLIMITED)
+
+ enum class Request {
+ TryAgain,
+ }
+
+ private lateinit var binding: ActivityOfflineSongsBinding
+ private var adapter: OfflineSongsAdapter? = null
+ private var offlineList: MutableList = mutableListOf()
+
+ override suspend fun main() {
+ binding = ActivityOfflineSongsBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ initImmersionBar()
+ initView()
+ initAdapter()
+ initData()
+ onReceive()
+ }
+
+ private fun initImmersionBar() {
+ immersionBar {
+ statusBarDarkFont(false)
+ statusBarView(binding.view)
+ }
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ private suspend fun onReceive() {
+ while (isActive) {
+ select {
+ requests.onReceive {
+ when (it) {
+ Request.TryAgain -> {
+ initData()
+ }
+ }
+ }
+ events.onReceive {
+ when (it) {
+ Event.ActivityOnResume -> {
+ activityOnResume()
+ }
+
+ Event.AutomaticallySwitchSongs -> {
+ if (adapter != null) {
+ adapter?.notifyDataSetChanged()
+ }
+ }
+
+ else -> {}
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ private fun activityOnResume() {
+ addMusicPlayerViewToLayout(binding.playMusicLayout)
+
+ if (adapter != null) {
+ adapter?.notifyDataSetChanged()
+ }
+ }
+
+ private fun initView() {
+ binding.backBtn.setOnClickListener {
+ finish()
+ }
+ binding.tryAgainBtn.setOnClickListener {
+ requests.trySend(Request.TryAgain)
+ }
+ }
+
+ private fun initAdapter() {
+ adapter = OfflineSongsAdapter(this, offlineList)
+ binding.rv.layoutManager =
+ LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
+ binding.rv.adapter = adapter
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ private suspend fun initData() {
+ showLoadingUi()
+
+ offlineList.clear()
+ offlineList.addAll(App.appOfflineDBManager.getAllOfflineBeans())
+
+ for (offline in offlineList){
+ LogD(TAG,"offline id->${offline.videoId}")
+ }
+
+ if (offlineList.size > 0) {
+ showDataUi()
+ } else {
+ showNoContentUi()
+ }
+ if (adapter != null) {
+ adapter?.notifyDataSetChanged()
+ }
+
+ }
+
+ private fun showDataUi() {
+ binding.loadingLayout.visibility = View.GONE
+ binding.noContentLayout.visibility = View.GONE
+ }
+
+ private fun showLoadingUi() {
+ binding.loadingLayout.visibility = View.VISIBLE
+ binding.noContentLayout.visibility = View.GONE
+ }
+
+ private fun showNoContentUi() {
+ binding.loadingLayout.visibility = View.GONE
+ binding.noContentLayout.visibility = View.VISIBLE
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/activity/MoPlayDetailsActivity.kt b/app/src/main/java/com/player/musicoo/activity/MoPlayDetailsActivity.kt
index d25d11f..be8fab6 100644
--- a/app/src/main/java/com/player/musicoo/activity/MoPlayDetailsActivity.kt
+++ b/app/src/main/java/com/player/musicoo/activity/MoPlayDetailsActivity.kt
@@ -10,6 +10,7 @@ import android.view.View
import android.view.animation.AnimationUtils
import androidx.annotation.OptIn
import androidx.core.net.toUri
+import androidx.lifecycle.LifecycleOwner
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
@@ -23,25 +24,29 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.gyf.immersionbar.ktx.immersionBar
+import com.player.musicoo.App
import com.player.musicoo.R
import com.player.musicoo.adapter.PlayListAdapter
+import com.player.musicoo.bean.OfflineBean
import com.player.musicoo.databinding.ActivityMoPlayDetailsBinding
import com.player.musicoo.innertube.Innertube
import com.player.musicoo.media.MediaControllerManager
import com.player.musicoo.media.SongRadio
import com.player.musicoo.service.MyDownloadService
+import com.player.musicoo.service.ViewModelMain
import com.player.musicoo.sp.AppStore
import com.player.musicoo.util.DownloadUtil
import com.player.musicoo.util.FileSizeConverter
+import com.player.musicoo.util.LogTag
import com.player.musicoo.util.LogTag.LogD
import com.player.musicoo.util.PlayMode
import com.player.musicoo.util.asMediaItem
import com.player.musicoo.util.convertMillisToMinutesAndSecondsString
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
-import java.lang.Exception
@OptIn(UnstableApi::class)
class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
@@ -77,7 +82,6 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
initImmersionBar()
initClick()
initPlayerListener()
- initDownloadListener()
initPlayListAdapter()
updatePlayModeUi()
val videoId = intent.getStringExtra(PLAY_DETAILS_VIDEO_ID)
@@ -87,33 +91,41 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
comeFrom = intent.getSerializableExtra(PLAY_DETAILS_COME_FROM) as Class<*>?
if (comeFrom != null && comeFrom == PrimaryActivity::class.java) {
+ LogD(TAG, "从当前播放的悬浮layout进入")
// 处理来自 PrimaryActivity 的情况
updateCurrentMediaItemInfo()
- } else {
- if (meController != null && meController.currentMediaItem != null && videoId == meController.currentMediaItem?.mediaId) {
- //进入的id与当前的id一样就不重新去获取播放
- updateCurrentMediaItemInfo()
+ if (meController != null && meController.currentMediaItem != null) {
updateInfoUi(meController.currentMediaItem)
- } else {
- binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
- binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
-
- if (videoId.isNullOrEmpty()) {
- finish()
- return
- }
- //传入进来的ID,就是进入此界面的当前ID
- currentVideoID = videoId
- //根据进来界面的当前ID来获取资源。
- initData(
- videoId,
- playlistId,
- playlistSetVideoId,
- params
- )
}
+ } else {
+ LogD(TAG, "从点击任意歌曲进入")
+// if (meController != null && meController.currentMediaItem != null && videoId == meController.currentMediaItem?.mediaId) {
+// //进入的id与当前的id一样就不重新去获取播放
+// updateCurrentMediaItemInfo()
+// updateInfoUi(meController.currentMediaItem)
+// } else {
+//
+// }
+ binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
+ binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
+ if (videoId.isNullOrEmpty()) {
+ finish()
+ return
+ }
+ //要加载数据的话就隐藏喜欢和下载按钮
+ binding.likeAndDownloadLayout.visibility = View.GONE
+ //传入进来的ID,就是进入此界面的当前ID
+ currentVideoID = videoId
+ //根据进来界面的当前ID来获取资源。
+ initData(
+ videoId,
+ playlistId,
+ playlistSetVideoId,
+ params
+ )
}
+ initDownloadFlow()
onReceive()
}
@@ -134,25 +146,72 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
}
private fun activityOnResume() {
- if (comeFrom != null && comeFrom == PrimaryActivity::class.java) {
+// if (meController != null && meController.currentMediaItem != null) {
+// updateInfoUi(meController.currentMediaItem)
+// }
+ }
+
+ private fun initDownloadFlow() {
+ ViewModelMain.modelDownloadsFlow.observe(this) { downloads ->
if (meController != null && meController.currentMediaItem != null) {
- updateInfoUi(meController.currentMediaItem)
+ val id = meController.currentMediaItem?.mediaId
+ LogD(TAG, "initDownloadFlow id ->${id}")
+ val currentScreenDownloads = downloads[id]
+ LogD(TAG, "currentScreenDownloads->${currentScreenDownloads}")
+ if (currentScreenDownloads != null) {
+ updateDownloadUI(currentScreenDownloads)
+ }
}
+
}
- if (meController != null && meController.currentMediaItem != null) {
- LogD(TAG,"meController.currentMediaItem != null->${meController.currentMediaItem?.mediaId!!}")
- updateDownloadUi(meController.currentMediaItem?.mediaId!!)
- } else {
- LogD(TAG,"currentVideoID->${currentVideoID}")
- updateDownloadUi(currentVideoID)
+ }
+
+ private fun updateDownloadUI(download: Download) {
+ when (download.state) {
+ Download.STATE_DOWNLOADING -> {
+ binding.downloadLoading.visibility = View.VISIBLE
+ binding.downloadImg.setImageResource(R.drawable.download_icon)
+ binding.downloadImg.visibility = View.GONE
+
+ binding.downloadBtn.isClickable = false
+ binding.downloadBtn.isEnabled = false
+ }
+
+ Download.STATE_COMPLETED -> {
+ binding.downloadLoading.visibility = View.GONE
+ binding.downloadImg.setImageResource(R.drawable.download_done_icon)
+ binding.downloadImg.visibility = View.VISIBLE
+ }
+
+ Download.STATE_FAILED -> {
+ binding.downloadLoading.visibility = View.GONE
+ binding.downloadImg.setImageResource(R.drawable.error)
+ binding.downloadImg.visibility = View.VISIBLE
+
+ binding.downloadBtn.isClickable = true
+ binding.downloadBtn.isEnabled = true
+ }
+
+ else -> {
+ binding.downloadLoading.visibility = View.GONE
+ binding.downloadImg.setImageResource(R.drawable.download_icon)
+ binding.downloadImg.visibility = View.VISIBLE
+
+ binding.downloadBtn.isClickable = true
+ binding.downloadBtn.isEnabled = true
+ }
}
}
private fun updateDownloadUi(id: String) {
- if (DownloadUtil.downloadResourceExist(id)) {
+ if (DownloadUtil.downloadResourceExist(id)) {//已经下载,按钮不可点击
binding.downloadImg.setImageResource(R.drawable.download_done_icon)
+ binding.downloadBtn.isClickable = false
+ binding.downloadBtn.isEnabled = false
} else {
binding.downloadImg.setImageResource(R.drawable.download_icon)
+ binding.downloadBtn.isClickable = true
+ binding.downloadBtn.isEnabled = true
}
}
@@ -161,40 +220,6 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
meController?.addListener(playerListener)
}
- private fun initDownloadListener() {
- downloadManager = DownloadUtil.getDownloadManager(this)
- if (downloadManager != null) {
- downloadManager?.addListener(object : DownloadManager.Listener {
- override fun onDownloadChanged(
- downloadManager: DownloadManager,
- download: Download,
- finalException: Exception?
- ) {
- when (download.state) {
- Download.STATE_DOWNLOADING -> {
- binding.downloadLoading.visibility = View.VISIBLE
- binding.downloadImg.setImageResource(R.drawable.download_icon)
- binding.downloadImg.visibility = View.GONE
- }
-
- Download.STATE_COMPLETED -> {
- binding.downloadLoading.visibility = View.GONE
- binding.downloadImg.setImageResource(R.drawable.download_done_icon)
- binding.downloadImg.visibility = View.VISIBLE
- }
-
- else -> {
- binding.downloadLoading.visibility = View.GONE
- binding.downloadImg.setImageResource(R.drawable.error)
- binding.downloadImg.visibility = View.VISIBLE
- }
- }
-
- }
- })
- }
- }
-
private fun updateCurrentMediaItemInfo() {
if (meController != null && meController.currentMediaItem != null) {
binding.playbackErrorLayout.visibility = View.GONE
@@ -289,11 +314,6 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
PlayMode.RANDOM.value -> {
meController.repeatMode = Player.REPEAT_MODE_ALL
-// val availableCommands = meController.availableCommands
-// //控制器支持设置随机播放模式的命令
-// if (availableCommands.contains(Player.COMMAND_SET_SHUFFLE_MODE)) {
-// meController.shuffleModeEnabled = true
-// }
meController.shuffleModeEnabled = true
}
}
@@ -363,35 +383,24 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
binding.downloadBtn.setOnClickListener {
if (meController != null && meController.currentMediaItem != null) {
- val contentId = meController.currentMediaItem?.mediaId!!
+ val currentMediaItem = meController.currentMediaItem
+ val contentId = currentMediaItem?.mediaId ?: ""
//如果已经存在就不进行下载
if (DownloadUtil.downloadResourceExist(contentId)) {
return@setOnClickListener
}
- val currentDownload = DownloadUtil.currentDownload(contentId)
- if (currentDownload != null) {
- if (currentDownload.state == Download.STATE_DOWNLOADING) {
- DownloadService.sendRemoveDownload(
- this,
- MyDownloadService::class.java,
- contentId,
- false
- )
- }
- } else {
- val downloadRequest = DownloadRequest
- .Builder(contentId, contentId.toUri())
- .setCustomCacheKey(contentId)
- .build()
- DownloadService.sendAddDownload(
- this,
- MyDownloadService::class.java,
- downloadRequest,
- false
- )
- }
-
+ val downloadRequest = DownloadRequest
+ .Builder(contentId, contentId.toUri())
+ .setCustomCacheKey(contentId)
+ .build()
+ DownloadService.sendAddDownload(
+ this,
+ MyDownloadService::class.java,
+ downloadRequest,
+ false
+ )
}
+
}
}
@@ -453,6 +462,8 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
}
if (mediaItem != null) {
+ //数据请求完毕mediaItem不等于空就显示喜欢与下载按钮
+ binding.likeAndDownloadLayout.visibility = View.VISIBLE
updateInfoUi(mediaItem)
binding.playbackErrorLayout.visibility = View.GONE
@@ -573,6 +584,8 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
binding.playbackErrorLayout.visibility = View.VISIBLE
return
}
+// currentVideoID = mediaItem.mediaId
+ updateDownloadUi(mediaItem.mediaId)
Glide.with(this)
.asBitmap()
diff --git a/app/src/main/java/com/player/musicoo/adapter/DetailsListAdapter.kt b/app/src/main/java/com/player/musicoo/adapter/DetailsListAdapter.kt
index 3b7e066..82ffa17 100644
--- a/app/src/main/java/com/player/musicoo/adapter/DetailsListAdapter.kt
+++ b/app/src/main/java/com/player/musicoo/adapter/DetailsListAdapter.kt
@@ -51,8 +51,8 @@ class DetailsListAdapter(
)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_PLAY_PARAMS, bean.params)
- intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, bean.name)
- intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, bean.title)
+ intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, bean.title)
+ intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, bean.name)
context.startActivity(intent)
}
}
diff --git a/app/src/main/java/com/player/musicoo/adapter/OfflineSongsAdapter.kt b/app/src/main/java/com/player/musicoo/adapter/OfflineSongsAdapter.kt
new file mode 100644
index 0000000..1decd0e
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/adapter/OfflineSongsAdapter.kt
@@ -0,0 +1,99 @@
+package com.player.musicoo.adapter
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.media3.common.C
+import androidx.media3.common.MediaItem
+import androidx.media3.common.Player
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.player.musicoo.R
+import com.player.musicoo.bean.OfflineBean
+import com.player.musicoo.databinding.OfflineListItemBinding
+import com.player.musicoo.databinding.PlayListItemBinding
+import com.player.musicoo.media.MediaControllerManager
+
+class OfflineSongsAdapter(
+ private val context: Context,
+ private val list: List,
+) :
+ RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val binding = OfflineListItemBinding.inflate(LayoutInflater.from(context), parent, false)
+ return ViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val bean = list[position]
+ holder.bind(bean)
+
+ holder.itemView.setOnClickListener {
+// val meController = MediaControllerManager.getController()
+// if (meController != null && meController.currentMediaItem != null) {
+// var index = holder.bindingAdapterPosition
+// if (index > meController.mediaItemCount) {
+// index = 1
+// }
+// meController.seekTo(index, C.TIME_UNSET)
+// if (!meController.isPlaying) {
+// meController.prepare()
+// meController.play()
+// }
+// }
+
+ }
+ }
+
+ override fun getItemCount(): Int = list.size
+
+ inner class ViewHolder(private val binding: OfflineListItemBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ @SuppressLint("SetTextI18n")
+ fun bind(bean: OfflineBean) {
+
+ binding.apply {
+ Glide.with(context)
+ .load(bean.thumbnail)
+ .into(image)
+ title.text = bean.title
+ if (bean.name.isEmpty()) {
+ name.visibility = View.GONE
+ } else {
+ name.visibility = View.VISIBLE
+ name.text = bean.name
+ }
+ size.text = bean.size
+
+ val meController = MediaControllerManager.getController()
+ if (meController != null && meController.currentMediaItem != null) {
+ if (meController.currentMediaItem?.mediaId == bean.videoId) {
+ binding.listPlayView.visibility = View.VISIBLE
+ binding.title.setTextColor(context.getColor(R.color.green))
+ binding.name.setTextColor(context.getColor(R.color.green_60))
+ binding.size.setTextColor(context.getColor(R.color.green_60))
+ } else {
+ binding.title.setTextColor(context.getColor(R.color.white))
+ binding.name.setTextColor(context.getColor(R.color.white_60))
+ binding.size.setTextColor(context.getColor(R.color.white_60))
+ binding.listPlayView.visibility = View.GONE
+ }
+ }
+ }
+ }
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(listener: OnItemClickListener) {
+ itemClickListener = listener
+ }
+
+ interface OnItemClickListener {
+ fun onItemClick(position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/bean/OfflineBean.kt b/app/src/main/java/com/player/musicoo/bean/OfflineBean.kt
new file mode 100644
index 0000000..5d3646f
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/bean/OfflineBean.kt
@@ -0,0 +1,22 @@
+package com.player.musicoo.bean
+
+import android.net.Uri
+import androidx.annotation.Keep
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import java.io.Serializable
+
+@Keep
+@Entity
+data class OfflineBean(
+ @ColumnInfo(name = "videoId") var videoId: String,
+ @ColumnInfo(name = "title") var title: String,
+ @ColumnInfo(name = "name") var name: String,
+ @ColumnInfo(name = "thumbnail") var thumbnail: String? = null,
+ @ColumnInfo(name = "size") var size: String? = null,
+ @ColumnInfo(name = "isOffline") var isOffline: Boolean
+) : Serializable {
+ @PrimaryKey(autoGenerate = true)
+ var id: Long = 0
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/database/AppOfflineDBManager.kt b/app/src/main/java/com/player/musicoo/database/AppOfflineDBManager.kt
new file mode 100644
index 0000000..075f74b
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/database/AppOfflineDBManager.kt
@@ -0,0 +1,83 @@
+package com.player.musicoo.database
+
+import android.content.Context
+import androidx.room.Room
+import com.player.musicoo.bean.OfflineBean
+import com.player.musicoo.util.LogTag
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class AppOfflineDBManager private constructor(context: Context) {
+
+ companion object {
+ @Volatile
+ private var instance: AppOfflineDBManager? = null
+
+ fun getInstance(context: Context): AppOfflineDBManager {
+ return instance ?: synchronized(this) {
+ instance ?: AppOfflineDBManager(context).also { instance = it }
+ }
+ }
+ }
+
+ private val database = Room.databaseBuilder(
+ context.applicationContext,
+ AppOfflineDatabase::class.java, "offline_data_base"
+ ).build()
+
+ private val dao = database.localOfflineDao()
+
+ suspend fun insertOfflineBean(bean: OfflineBean) {
+ withContext(Dispatchers.IO) {
+ val offlineBean = getOfflineBeanByID(bean.videoId)
+ if (offlineBean == null) {
+ LogTag.LogD(LogTag.VO_TEST_ONLY,"insertOfflineBean")
+ dao.insertOfflineBean(bean)
+ } else {
+ LogTag.LogD(LogTag.VO_TEST_ONLY,"updateOfflineBean")
+ dao.updateOfflineBean(bean)
+ }
+ }
+ }
+
+ suspend fun insertOfflineListBean(list: List) {
+ withContext(Dispatchers.IO) {
+ for (bean in list) {
+ val offlineBean = getOfflineBeanByID(bean.videoId)
+ if (offlineBean == null) {
+ dao.insertOfflineBean(bean)
+ } else {
+ dao.updateOfflineBean(bean)
+ }
+ }
+ }
+ }
+
+ suspend fun getAllOfflineBeans(): List {
+ return withContext(Dispatchers.IO) {
+ dao.getAllOfflineBeans()
+ }
+ }
+
+ suspend fun deleteOfflineBean(bean: OfflineBean) {
+ withContext(Dispatchers.IO) {
+ dao.deleteOfflineBean(bean)
+ }
+ }
+
+ suspend fun deleteAllOfflineBean() {
+ withContext(Dispatchers.IO) {
+ dao.deleteAllOfflineBean()
+ }
+ }
+
+ suspend fun updateOfflineBean(bean: OfflineBean) {
+ withContext(Dispatchers.IO) {
+ dao.updateOfflineBean(bean)
+ }
+ }
+
+ private suspend fun getOfflineBeanByID(id: String): OfflineBean? {
+ return dao.getOfflineBeanByID(id)
+ }
+}
diff --git a/app/src/main/java/com/player/musicoo/database/AppOfflineDatabase.kt b/app/src/main/java/com/player/musicoo/database/AppOfflineDatabase.kt
new file mode 100644
index 0000000..23f633e
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/database/AppOfflineDatabase.kt
@@ -0,0 +1,11 @@
+
+package com.player.musicoo.database
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import com.player.musicoo.bean.OfflineBean
+
+@Database(entities = [OfflineBean::class], version = 1, exportSchema = false)
+abstract class AppOfflineDatabase : RoomDatabase() {
+ abstract fun localOfflineDao(): OfflineDao
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/database/OfflineDao.kt b/app/src/main/java/com/player/musicoo/database/OfflineDao.kt
new file mode 100644
index 0000000..4aee0b7
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/database/OfflineDao.kt
@@ -0,0 +1,33 @@
+package com.player.musicoo.database
+
+import androidx.room.*
+import com.player.musicoo.bean.OfflineBean
+
+@Dao
+interface OfflineDao {
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertOfflineBean(bean: OfflineBean)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertOfflineBeans(beans: List)
+
+ @Query("SELECT * FROM OfflineBean")
+ suspend fun getAllOfflineBeans(): List
+
+ @Delete
+ suspend fun deleteOfflineBean(bean: OfflineBean)
+
+ @Query("DELETE FROM OfflineBean")
+ suspend fun deleteAllOfflineBean()
+
+ @Update
+ suspend fun updateOfflineBean(bean: OfflineBean)
+
+ @Query("SELECT * FROM OfflineBean WHERE videoId = :id LIMIT 1")
+ suspend fun getOfflineBeanByID(id: String): OfflineBean?
+
+ @Query("SELECT * FROM OfflineBean WHERE isOffline = 1")
+ suspend fun getOfflineBeanByIsOffline(): List
+
+}
diff --git a/app/src/main/java/com/player/musicoo/fragment/MoMeFragment.kt b/app/src/main/java/com/player/musicoo/fragment/MoMeFragment.kt
index 0d81ded..57f18c8 100644
--- a/app/src/main/java/com/player/musicoo/fragment/MoMeFragment.kt
+++ b/app/src/main/java/com/player/musicoo/fragment/MoMeFragment.kt
@@ -1,10 +1,15 @@
package com.player.musicoo.fragment
+import android.content.Intent
import android.view.LayoutInflater
import android.view.ViewGroup
+import android.widget.Toast
import com.gyf.immersionbar.ktx.immersionBar
-import com.player.musicoo.databinding.FragmentMoHomeBinding
+import com.player.musicoo.R
+import com.player.musicoo.activity.MoOfflineSongsActivity
import com.player.musicoo.databinding.FragmentMoMeBinding
+import com.player.musicoo.innertube.utils.BrotliEncoder.decode
+import com.player.musicoo.util.DownloadUtil
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
@@ -38,13 +43,39 @@ class MoMeFragment : MoBaseFragment() {
requests.onReceive {
}
+
+ events.onReceive {
+ when (it) {
+ Event.FragmentOnResume -> {
+ fragmentOnResume()
+ }
+ }
+ }
}
}
}
private fun initView() {
+ binding.likedSongsBtn.setOnClickListener {
+
+ }
+ binding.offlineSongsBtn.setOnClickListener {
+ if (DownloadUtil.getDownloadCount() > 0) {
+ val intent = Intent(context, MoOfflineSongsActivity::class.java)
+ startActivity(intent)
+ } else {
+ Toast.makeText(
+ activity,
+ getString(R.string.offline_songs_no_data_prompt),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
}
+ private fun fragmentOnResume() {
+ binding.offlineSongsTv.text = "${DownloadUtil.getDownloadCount()}"
+ }
override fun onResume() {
super.onResume()
diff --git a/app/src/main/java/com/player/musicoo/service/MyDownloadService.kt b/app/src/main/java/com/player/musicoo/service/MyDownloadService.kt
index 4fd2df9..0b746f9 100644
--- a/app/src/main/java/com/player/musicoo/service/MyDownloadService.kt
+++ b/app/src/main/java/com/player/musicoo/service/MyDownloadService.kt
@@ -12,8 +12,13 @@ import androidx.media3.exoplayer.offline.DownloadManager
import androidx.media3.exoplayer.offline.DownloadNotificationHelper
import androidx.media3.exoplayer.offline.DownloadService
import androidx.media3.exoplayer.scheduler.PlatformScheduler
+import com.player.musicoo.App
import com.player.musicoo.R
+import com.player.musicoo.bean.OfflineBean
import com.player.musicoo.util.DownloadUtil
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
@OptIn(UnstableApi::class)
@@ -37,8 +42,9 @@ class MyDownloadService : DownloadService(
val downloadManager = DownloadUtil.getDownloadManager(this)
downloadManager!!.maxParallelDownloads = 3
val downloadNotificationHelper = DownloadUtil.getDownloadNotificationHelper(this)
+
downloadManager.addListener(
- TerminalStateNotificationHelper(
+ DownloadUtil.TerminalStateNotificationHelper(
this, downloadNotificationHelper!!, FOREGROUND_NOTIFICATION_ID + 1
)
)
@@ -63,43 +69,4 @@ class MyDownloadService : DownloadService(
notMetRequirements
)
}
-
-
- private class TerminalStateNotificationHelper(
- private val context: Context,
- private val notificationHelper: DownloadNotificationHelper,
- private var nextNotificationId: Int
- ) :
- DownloadManager.Listener {
-
- override fun onDownloadChanged(
- downloadManager: DownloadManager,
- download: Download,
- finalException: Exception?
- ) {
- val notification: Notification = when (download.state) {
- Download.STATE_COMPLETED -> {
- notificationHelper.buildDownloadCompletedNotification(
- context,
- R.drawable.download_done_icon,
- null,
- Util.fromUtf8Bytes(download.request.data)
- )
- }
- Download.STATE_FAILED -> {
- notificationHelper.buildDownloadFailedNotification(
- context,
- R.drawable.error,
- null,
- Util.fromUtf8Bytes(download.request.data)
- )
- }
- else -> {
- return
- }
- }
- NotificationUtil.setNotification(context, nextNotificationId++, notification)
- }
- }
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/service/ViewModelMain.kt b/app/src/main/java/com/player/musicoo/service/ViewModelMain.kt
new file mode 100644
index 0000000..4d1ad74
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/service/ViewModelMain.kt
@@ -0,0 +1,9 @@
+package com.player.musicoo.service
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.media3.exoplayer.offline.Download
+
+object ViewModelMain : ViewModel() {
+ var modelDownloadsFlow = MutableLiveData