993 lines
42 KiB
Kotlin
993 lines
42 KiB
Kotlin
package melody.offline.music.activity
|
||
|
||
import android.annotation.SuppressLint
|
||
import android.graphics.Bitmap
|
||
import android.graphics.Color
|
||
import android.graphics.drawable.ColorDrawable
|
||
import android.graphics.drawable.Drawable
|
||
import android.os.Handler
|
||
import android.os.Looper
|
||
import android.os.Message
|
||
import android.view.LayoutInflater
|
||
import android.view.View
|
||
import android.view.animation.AnimationUtils
|
||
import android.widget.TextView
|
||
import android.widget.Toast
|
||
import androidx.annotation.OptIn
|
||
import androidx.appcompat.app.AlertDialog
|
||
import androidx.core.net.toUri
|
||
import androidx.media3.common.MediaItem
|
||
import androidx.media3.common.PlaybackException
|
||
import androidx.media3.common.Player
|
||
import androidx.media3.common.util.UnstableApi
|
||
import androidx.media3.exoplayer.offline.Download
|
||
import androidx.media3.exoplayer.offline.DownloadRequest
|
||
import androidx.media3.exoplayer.offline.DownloadService
|
||
import androidx.recyclerview.widget.LinearLayoutManager
|
||
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 melody.offline.music.R
|
||
import melody.offline.music.adapter.DetailsPlayListAdapter
|
||
import melody.offline.music.databinding.ActivityMoPlayDetailsBinding
|
||
import melody.offline.music.innertube.Innertube
|
||
import melody.offline.music.media.MediaControllerManager
|
||
import melody.offline.music.media.SongRadio
|
||
import melody.offline.music.service.MyDownloadService
|
||
import melody.offline.music.service.ViewModelMain
|
||
import melody.offline.music.sp.AppStore
|
||
import melody.offline.music.util.DownloadUtil
|
||
import melody.offline.music.util.LogTag.LogD
|
||
import melody.offline.music.util.PlayMode
|
||
import melody.offline.music.util.asMediaItem
|
||
import melody.offline.music.util.convertMillisToMinutesAndSecondsString
|
||
import kotlinx.coroutines.Dispatchers
|
||
import kotlinx.coroutines.channels.Channel
|
||
import kotlinx.coroutines.isActive
|
||
import kotlinx.coroutines.launch
|
||
import kotlinx.coroutines.selects.select
|
||
import org.json.JSONObject
|
||
import melody.offline.music.App
|
||
import melody.offline.music.ads.AdPlacement
|
||
import melody.offline.music.ads.LolAdWrapper
|
||
import melody.offline.music.bean.PlaylistItem
|
||
import melody.offline.music.util.AnalysisUtil
|
||
|
||
@OptIn(UnstableApi::class)
|
||
class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||
|
||
private val requests: Channel<Request> = Channel(Channel.UNLIMITED)
|
||
|
||
sealed class Request {
|
||
data object OnFavorites : Request()
|
||
data class OnDownload(val mediaItem: MediaItem) : Request()
|
||
data class UpdateFavorite(val id: String) : Request()
|
||
data class UpdateDownloadUi(val id: String) : Request()
|
||
data class OnDownloadRemove(val mediaItem: MediaItem) : Request()
|
||
}
|
||
|
||
|
||
companion object {
|
||
const val PLAY_DETAILS_VIDEO_ID = "play_details_videoId"
|
||
const val PLAY_DETAILS_PLAY_LIST_ID = "play_details_playlistId"
|
||
const val PLAY_DETAILS_PLAY_LIST_SET_VIDEO_ID = "play_details_play_list_set_video_id"
|
||
const val PLAY_DETAILS_PLAY_PARAMS = "play_details_play_params"
|
||
const val PLAY_DETAILS_NAME = "play_details_name"
|
||
const val PLAY_DETAILS_DESC = "play_details_desc"
|
||
const val PLAY_DETAILS_COME_FROM = "PLAY_DETAILS_COME_FROM"
|
||
const val PLAY_LIST_ID = "play_list_id"
|
||
}
|
||
|
||
private lateinit var binding: ActivityMoPlayDetailsBinding
|
||
|
||
private var currentVideoID = ""
|
||
|
||
private var comeFrom: Class<*>? = null
|
||
private var detailsPlayListAdapter: DetailsPlayListAdapter? = null
|
||
|
||
private var startPlayTime = 0L
|
||
|
||
private fun initImmersionBar() {
|
||
immersionBar {
|
||
statusBarDarkFont(false)
|
||
statusBarView(binding.view)
|
||
}
|
||
}
|
||
|
||
override suspend fun main() {
|
||
binding = ActivityMoPlayDetailsBinding.inflate(layoutInflater)
|
||
setContentView(binding.root)
|
||
AnalysisUtil.logEvent(AnalysisUtil.PLAYER_B_PV)
|
||
showAD()
|
||
initImmersionBar()
|
||
initClick()
|
||
initPlayerListener()
|
||
initPlayListAdapter()
|
||
updatePlayModeUi()
|
||
val videoId = intent.getStringExtra(PLAY_DETAILS_VIDEO_ID)
|
||
val playlistId = intent.getStringExtra(PLAY_DETAILS_PLAY_LIST_ID)
|
||
val playlistSetVideoId = intent.getStringExtra(PLAY_DETAILS_PLAY_LIST_SET_VIDEO_ID)
|
||
val params = intent.getStringExtra(PLAY_DETAILS_PLAY_PARAMS)
|
||
|
||
comeFrom = intent.getSerializableExtra(PLAY_DETAILS_COME_FROM) as Class<*>?
|
||
if (comeFrom != null && comeFrom == PrimaryActivity::class.java) {
|
||
LogD(TAG, "从当前播放的悬浮layout进入")
|
||
// 处理来自 PrimaryActivity 的情况
|
||
updateCurrentMediaItemInfo()
|
||
if (meController != null && meController.currentMediaItem != null) {
|
||
updateInfoUi(meController.currentMediaItem)
|
||
}
|
||
} else if (comeFrom != null && comeFrom == MoOfflineSongsActivity::class.java) {
|
||
LogD(TAG, "从offline songs 进入")
|
||
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
|
||
binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
|
||
//从数据库获取所有的offline
|
||
val offlineBeans = App.appOfflineDBManager.getAllOfflineBeans()
|
||
//过滤只有大小大于0的才添加到集合中
|
||
val allFilteredBeans =
|
||
offlineBeans.filter { it.bytesDownloaded?.let { bytes -> bytes > 0 } == true }
|
||
//找到当前点击进来的歌曲media
|
||
val findCurrentMedia = allFilteredBeans.find { it.videoId == videoId }?.asMediaItem
|
||
if (findCurrentMedia != null) {
|
||
binding.likeAndDownloadLayout.visibility = View.VISIBLE
|
||
updateInfoUi(findCurrentMedia)
|
||
binding.playbackErrorLayout.visibility = View.GONE
|
||
binding.totalDurationTv.visibility = View.GONE
|
||
meController?.let {
|
||
it.setMediaItem(findCurrentMedia, true)
|
||
it.prepare()
|
||
it.play()
|
||
val mediaItems =
|
||
allFilteredBeans.map { mapAll -> mapAll.asMediaItem }//转换成MediaItem
|
||
.filter { filter -> filter.mediaId != videoId }//过滤掉id相等的。
|
||
it.addMediaItems(mediaItems)
|
||
}
|
||
updatePlayListDataAndAdapter()
|
||
} else {
|
||
binding.playbackErrorLayout.visibility = View.VISIBLE
|
||
}
|
||
} else if (comeFrom != null && comeFrom == MoLikedSongsActivity::class.java) {
|
||
LogD(TAG, "从liked songs 进入")
|
||
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
|
||
binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
|
||
val favoriteBeans = App.appFavoriteDBManager.getAllFavoriteBeans()
|
||
val allFilteredBeans = favoriteBeans.filter { it.isFavorite }//过滤只有为true的值
|
||
//找到当前点击进来的歌曲media
|
||
val findCurrentMedia = allFilteredBeans.find { it.videoId == videoId }?.asMediaItem
|
||
if (findCurrentMedia != null) {
|
||
binding.likeAndDownloadLayout.visibility = View.VISIBLE
|
||
updateInfoUi(findCurrentMedia)
|
||
binding.playbackErrorLayout.visibility = View.GONE
|
||
binding.totalDurationTv.visibility = View.GONE
|
||
meController?.let {
|
||
it.setMediaItem(findCurrentMedia, true)
|
||
it.prepare()
|
||
it.play()
|
||
val mediaItems =
|
||
allFilteredBeans.map { mapAll -> mapAll.asMediaItem }//转换成MediaItem
|
||
.filter { filter -> filter.mediaId != videoId }//过滤掉id相等的。
|
||
it.addMediaItems(mediaItems)
|
||
}
|
||
updatePlayListDataAndAdapter()
|
||
} else {
|
||
binding.playbackErrorLayout.visibility = View.VISIBLE
|
||
}
|
||
} else if (comeFrom != null && comeFrom == MoPlaylistSongsActivity::class.java) {
|
||
LogD(TAG, "从 playlist songs 进入")
|
||
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
|
||
binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
|
||
|
||
val playlistID = intent.getIntExtra(PLAY_LIST_ID, -1)
|
||
val playlistItems = App.appPlaylistDBManager.getPlaylistItems(playlistID)
|
||
|
||
//找到当前点击进来的歌曲media
|
||
val findCurrentMedia = playlistItems.find { it.videoId == videoId }?.asMediaItem
|
||
if (findCurrentMedia != null) {
|
||
binding.likeAndDownloadLayout.visibility = View.VISIBLE
|
||
updateInfoUi(findCurrentMedia)
|
||
binding.playbackErrorLayout.visibility = View.GONE
|
||
binding.totalDurationTv.visibility = View.GONE
|
||
meController?.let {
|
||
it.setMediaItem(findCurrentMedia, true)
|
||
it.prepare()
|
||
it.play()
|
||
val mediaItems =
|
||
playlistItems.map { mapAll -> mapAll.asMediaItem }//转换成MediaItem
|
||
.filter { filter -> filter.mediaId != videoId }//过滤掉id相等的。
|
||
it.addMediaItems(mediaItems)
|
||
}
|
||
updatePlayListDataAndAdapter()
|
||
} else {
|
||
binding.playbackErrorLayout.visibility = View.VISIBLE
|
||
}
|
||
} else {
|
||
LogD(TAG, "从点击任意歌曲进入")
|
||
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
|
||
)
|
||
}
|
||
loadAd()
|
||
initDownloadFlow()
|
||
onReceive()
|
||
}
|
||
|
||
private suspend fun onReceive() {
|
||
while (isActive) {
|
||
select<Unit> {
|
||
events.onReceive {
|
||
when (it) {
|
||
Event.ActivityOnResume -> {
|
||
activityOnResume()
|
||
}
|
||
|
||
else -> {}
|
||
}
|
||
}
|
||
requests.onReceive {
|
||
when (it) {
|
||
is Request.OnFavorites -> {
|
||
if (meController != null && meController.currentMediaItem != null) {
|
||
val currentMediaItem = meController.currentMediaItem
|
||
val jsonObject = JSONObject()
|
||
jsonObject.put(
|
||
"song_title", "${currentMediaItem?.mediaMetadata?.title}"
|
||
)
|
||
val songMap = mutableMapOf(
|
||
Pair(
|
||
AnalysisUtil.PARAM_VALUE, jsonObject.toString()
|
||
)
|
||
)
|
||
val currentFavoriteBean =
|
||
App.appFavoriteDBManager.getFavoriteBeanByID(currentMediaItem?.mediaId!!)
|
||
if (currentFavoriteBean != null) {
|
||
currentFavoriteBean.isFavorite = !currentFavoriteBean.isFavorite
|
||
App.appFavoriteDBManager.updateFavoriteBean(currentFavoriteBean)
|
||
if (currentFavoriteBean.isFavorite) {
|
||
AnalysisUtil.logEvent(
|
||
AnalysisUtil.PLAYER_B_LOVE_CLICK, songMap
|
||
)
|
||
} else {
|
||
AnalysisUtil.logEvent(
|
||
AnalysisUtil.PLAYER_B_UN_LOVE_CLICK, songMap
|
||
)
|
||
}
|
||
} else {
|
||
insertFavoriteData(currentMediaItem)
|
||
AnalysisUtil.logEvent(AnalysisUtil.PLAYER_B_LOVE_CLICK, songMap)
|
||
}
|
||
requests.trySend(Request.UpdateFavorite(currentMediaItem.mediaId))
|
||
}
|
||
}
|
||
|
||
is Request.OnDownload -> {
|
||
val contentId = it.mediaItem.mediaId
|
||
val offBean =
|
||
App.appOfflineDBManager.getOfflineBeanByID(contentId)//得到当前ID的本地数据
|
||
if (offBean != null && offBean.bytesDownloaded?.let { bytes -> bytes > 0 } == true) {//判断当前数据库是否有这条数据。
|
||
showRemoveDownloadDialogHint(it.mediaItem)
|
||
LogD(TAG, "Request.OnDownload 11111")
|
||
} else {
|
||
LogD(TAG, "Request.OnDownload 22222")
|
||
//已经存在这条数据,直接写入数据
|
||
if (DownloadUtil.downloadResourceExist(contentId)) {
|
||
insertOfflineData(it.mediaItem)
|
||
requests.trySend(Request.UpdateDownloadUi(contentId))
|
||
|
||
LogD(TAG, "Request.OnDownload 33333")
|
||
} else {
|
||
val downloadRequest =
|
||
DownloadRequest.Builder(contentId, contentId.toUri())
|
||
.setCustomCacheKey(contentId).build()
|
||
|
||
val downloadCount = DownloadUtil.getCurrentDownloads()
|
||
if (downloadCount >= 3) {
|
||
Toast.makeText(
|
||
this@MoPlayDetailsActivity,
|
||
getString(R.string.download_tips),
|
||
Toast.LENGTH_LONG
|
||
).show()
|
||
} else {
|
||
DownloadService.sendAddDownload(
|
||
this@MoPlayDetailsActivity,
|
||
MyDownloadService::class.java,
|
||
downloadRequest,
|
||
false
|
||
)
|
||
}
|
||
|
||
LolAdWrapper.shared.showAdTiming(
|
||
this@MoPlayDetailsActivity, AdPlacement.INST_DOWNLOAD
|
||
)
|
||
|
||
insertOfflineData(it.mediaItem)
|
||
val jsonObject = JSONObject()
|
||
jsonObject.put(
|
||
"download_id", it.mediaItem.mediaId
|
||
)
|
||
val songMap = mutableMapOf(
|
||
Pair(
|
||
AnalysisUtil.PARAM_VALUE, jsonObject.toString()
|
||
)
|
||
)
|
||
AnalysisUtil.logEvent(
|
||
AnalysisUtil.PLAYER_B_DOWNLOAD_CLICK, songMap
|
||
)
|
||
|
||
LolAdWrapper.shared.loadAdIfNotCached(
|
||
this@MoPlayDetailsActivity, AdPlacement.INST_DOWNLOAD
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
is Request.UpdateFavorite -> {
|
||
val currentFavoriteBean =
|
||
App.appFavoriteDBManager.getFavoriteBeanByID(it.id)
|
||
LogD(TAG, "UpdateFavorite->${currentFavoriteBean}")
|
||
if (currentFavoriteBean != null) {
|
||
updateFavoriteUi(currentFavoriteBean.isFavorite)
|
||
} else {
|
||
updateFavoriteUi(false)
|
||
}
|
||
}
|
||
|
||
is Request.UpdateDownloadUi -> {
|
||
val offlineBean = App.appOfflineDBManager.getOfflineBeanByID(it.id)
|
||
val isOffline = offlineBean?.isOffline ?: false
|
||
if (DownloadUtil.downloadResourceExist(it.id) && isOffline) {
|
||
downloadUi(true, it.id)
|
||
} else {
|
||
downloadUi(false, it.id)
|
||
}
|
||
}
|
||
|
||
is Request.OnDownloadRemove -> {
|
||
val currentOfflineBean =
|
||
App.appOfflineDBManager.getOfflineBeanByID(it.mediaItem.mediaId)
|
||
if (currentOfflineBean != null) {
|
||
App.appOfflineDBManager.deleteOfflineBean(currentOfflineBean)
|
||
}
|
||
|
||
requests.trySend(Request.UpdateDownloadUi(it.mediaItem.mediaId))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private suspend fun activityOnResume() {
|
||
//更新收藏按钮状态
|
||
// if (meController != null && meController.currentMediaItem != null) {
|
||
// val currentMediaItem = meController.currentMediaItem
|
||
// val currentFavoriteBean =
|
||
// App.appFavoriteDBManager.getFavoriteBeanByID(currentMediaItem?.mediaId!!)
|
||
// if (currentFavoriteBean != null) {
|
||
// updateFavoriteUi(currentFavoriteBean.isFavorite)
|
||
// }
|
||
// }
|
||
|
||
if (appStore.hideDownloadBtn) {
|
||
binding.downloadBtn.visibility = View.GONE
|
||
} else {
|
||
binding.downloadBtn.visibility = View.VISIBLE
|
||
}
|
||
}
|
||
|
||
private fun initDownloadFlow() {
|
||
ViewModelMain.modelDownloadsFlow.observe(this) { downloads ->
|
||
if (meController != null && meController.currentMediaItem != null) {
|
||
val id = meController.currentMediaItem?.mediaId
|
||
LogD(TAG, "initDownloadFlow id ->${id}")
|
||
val currentScreenDownloads = downloads[id]
|
||
if (currentScreenDownloads != null) {
|
||
LogD(TAG, "updateDownloadUI id->${currentScreenDownloads.request.id}")
|
||
updateDownloadUI(currentScreenDownloads)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun updateDownloadUI(download: Download) {
|
||
LogD(TAG, "download.state")
|
||
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
|
||
binding.downloadBtn.isClickable = true
|
||
binding.downloadBtn.isEnabled = true
|
||
|
||
val jsonObject = JSONObject()
|
||
jsonObject.put(
|
||
"download_id", download.request.id
|
||
)
|
||
val songMap = mutableMapOf(
|
||
Pair(
|
||
AnalysisUtil.PARAM_VALUE, jsonObject.toString()
|
||
)
|
||
)
|
||
AnalysisUtil.logEvent(AnalysisUtil.PLAYER_B_DOWNLOAD_SUCCESS_ACTION, songMap)
|
||
}
|
||
|
||
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) {
|
||
requests.trySend(Request.UpdateDownloadUi(id))
|
||
}
|
||
|
||
private fun downloadUi(b: Boolean, id: String) {
|
||
LogD(TAG, "downloadUi 1111")
|
||
if (b) {
|
||
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.download_icon)
|
||
binding.downloadImg.visibility = View.VISIBLE
|
||
}
|
||
|
||
val currentDownload = DownloadUtil.getCurrentIdDownload(id)
|
||
if (currentDownload != null && currentDownload.state == Download.STATE_DOWNLOADING) {
|
||
binding.downloadLoading.visibility = View.VISIBLE
|
||
binding.downloadImg.visibility = View.GONE
|
||
binding.downloadBtn.isClickable = false
|
||
binding.downloadBtn.isEnabled = false
|
||
} else {
|
||
binding.downloadBtn.isClickable = true
|
||
binding.downloadBtn.isEnabled = true
|
||
}
|
||
}
|
||
|
||
private fun updateFavoriteUi(b: Boolean) {
|
||
if (b) {
|
||
binding.favoritesImg.setImageResource(R.drawable.favorited_icon)
|
||
} else {
|
||
binding.favoritesImg.setImageResource(R.drawable.not_favorited_icon)
|
||
}
|
||
}
|
||
|
||
private fun initPlayerListener() {
|
||
meController?.addListener(playerListener)
|
||
}
|
||
|
||
private fun updateCurrentMediaItemInfo() {
|
||
if (meController != null && meController.currentMediaItem != null) {
|
||
binding.playbackErrorLayout.visibility = View.GONE
|
||
binding.loadingView.visibility = View.VISIBLE
|
||
binding.disableClicksLayout.visibility = View.GONE
|
||
val currentString =
|
||
convertMillisToMinutesAndSecondsString(MediaControllerManager.getCurrentPosition())
|
||
binding.progressDurationTv.text = currentString
|
||
if (MediaControllerManager.getDuration() > 0) {
|
||
binding.totalDurationTv.visibility = View.VISIBLE
|
||
binding.loadingView.visibility = View.GONE
|
||
binding.totalDurationTv.text =
|
||
convertMillisToMinutesAndSecondsString(MediaControllerManager.getDuration())
|
||
val valueTo = MediaControllerManager.getDuration().toFloat()
|
||
binding.sbProgress.valueTo = valueTo
|
||
val value = MediaControllerManager.getCurrentPosition().toFloat()
|
||
if (valueTo > value) {
|
||
binding.sbProgress.value = value
|
||
}
|
||
updateProgressState()
|
||
|
||
binding.progressBar.progress = MediaControllerManager.getBufferedPosition().toInt()
|
||
binding.progressBar.max = MediaControllerManager.getDuration().toInt()
|
||
updateProgressBufferingState()
|
||
updatePlayListDataAndAdapter()
|
||
} else {
|
||
binding.totalDurationTv.visibility = View.GONE
|
||
}
|
||
}
|
||
}
|
||
|
||
@SuppressLint("NotifyDataSetChanged")
|
||
private fun updatePlayListDataAndAdapter() {
|
||
if (meController != null && meController.currentMediaItem != null) {
|
||
val mediaItemCount = meController.mediaItemCount
|
||
val allMediaItems: MutableList<MediaItem> = mutableListOf()
|
||
for (index in 0 until mediaItemCount) {
|
||
val mediaItemAt = meController.getMediaItemAt(index)
|
||
allMediaItems.add(mediaItemAt)
|
||
}
|
||
playList.clear()
|
||
playList.addAll(allMediaItems)
|
||
detailsPlayListAdapter?.notifyDataSetChanged()
|
||
}
|
||
}
|
||
|
||
private var playList: MutableList<MediaItem> = mutableListOf()
|
||
|
||
private fun initPlayListAdapter() {
|
||
detailsPlayListAdapter = DetailsPlayListAdapter(
|
||
this@MoPlayDetailsActivity, playList
|
||
)
|
||
binding.playListRv.layoutManager = LinearLayoutManager(
|
||
this@MoPlayDetailsActivity, LinearLayoutManager.VERTICAL, false
|
||
)
|
||
binding.playListRv.adapter = detailsPlayListAdapter
|
||
}
|
||
|
||
@SuppressLint("NotifyDataSetChanged")
|
||
private fun initClick() {
|
||
binding.backBtn.setOnClickListener {
|
||
finish()
|
||
}
|
||
binding.tryAgainBtn.setOnClickListener {
|
||
if (meController != null) {
|
||
updateInfoUi(meController.currentMediaItem)
|
||
updateProgressState()
|
||
if (!meController.isPlaying) {
|
||
meController.prepare()
|
||
meController.play()
|
||
}
|
||
}
|
||
}
|
||
binding.playModeBtn.setOnClickListener {
|
||
if (meController != null) {
|
||
val playModeCounter = (appStore.playMusicMode + 1) % 3
|
||
appStore.playMusicMode = when (playModeCounter) {
|
||
0 -> PlayMode.LIST_LOOP.value
|
||
1 -> PlayMode.SINGLE_LOOP.value
|
||
else -> PlayMode.RANDOM.value
|
||
}
|
||
when (AppStore(this).playMusicMode) {
|
||
PlayMode.LIST_LOOP.value -> {
|
||
meController.repeatMode = Player.REPEAT_MODE_ALL
|
||
meController.shuffleModeEnabled = false
|
||
}
|
||
|
||
PlayMode.SINGLE_LOOP.value -> {
|
||
meController.repeatMode = Player.REPEAT_MODE_ONE
|
||
meController.shuffleModeEnabled = false
|
||
}
|
||
|
||
PlayMode.RANDOM.value -> {
|
||
meController.repeatMode = Player.REPEAT_MODE_ALL
|
||
meController.shuffleModeEnabled = true
|
||
}
|
||
}
|
||
updatePlayModeUi()
|
||
LogD(TAG, "repeatMode->${meController.repeatMode}")
|
||
LogD(TAG, "shuffleModeEnabled->${meController.shuffleModeEnabled}")
|
||
}
|
||
}
|
||
binding.playLayoutBtn.setOnClickListener {
|
||
if (meController != null) {
|
||
if (meController.isPlaying) {
|
||
meController.pause()
|
||
updatePlayState(false)
|
||
} else {
|
||
meController.play()
|
||
updatePlayState(true)
|
||
}
|
||
updateProgressState()
|
||
}
|
||
}
|
||
binding.playSkipBackBtn.setOnClickListener {
|
||
LolAdWrapper.shared.showAdTiming(this, AdPlacement.INST_CUTTING_SONG)
|
||
if (meController != null) {
|
||
meController.seekToPreviousMediaItem()
|
||
updateProgressUi()
|
||
updateInfoUi(meController.currentMediaItem)
|
||
updateProgressState()
|
||
if (!meController.isPlaying) {
|
||
meController.prepare()
|
||
meController.play()
|
||
}
|
||
}
|
||
LolAdWrapper.shared.loadAdIfNotCached(this, AdPlacement.INST_CUTTING_SONG)
|
||
}
|
||
binding.playSkipForwardBtn.setOnClickListener {
|
||
LolAdWrapper.shared.showAdTiming(this, AdPlacement.INST_CUTTING_SONG)
|
||
if (meController != null) {
|
||
meController.seekToNextMediaItem()
|
||
updateProgressUi()
|
||
updateInfoUi(meController.currentMediaItem)
|
||
updateProgressState()
|
||
if (!meController.isPlaying) {
|
||
meController.prepare()
|
||
meController.play()
|
||
}
|
||
}
|
||
LolAdWrapper.shared.loadAdIfNotCached(this, AdPlacement.INST_CUTTING_SONG)
|
||
}
|
||
binding.listLayoutBtn.setOnClickListener {
|
||
toggleBottomLayout()
|
||
}
|
||
binding.bottomCloseBtn.setOnClickListener {
|
||
toggleBottomLayout()
|
||
}
|
||
binding.bottomBlankLayout.setOnClickListener {
|
||
toggleBottomLayout()
|
||
}
|
||
binding.progressBar.progress = 0
|
||
if (binding.sbProgress.valueTo > 0f) {
|
||
binding.sbProgress.value = 0f
|
||
}
|
||
binding.sbProgress.addOnChangeListener { slider, value, fromUser ->
|
||
if (fromUser) {
|
||
if (meController != null) {
|
||
meController.seekTo(value.toLong())
|
||
val ss = meController.isPlaying
|
||
if (!ss) {
|
||
meController.play()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
binding.favoritesBtn.setOnClickListener {
|
||
requests.trySend(Request.OnFavorites)
|
||
}
|
||
binding.downloadBtn.setOnClickListener {
|
||
if (meController != null && meController.currentMediaItem != null) {
|
||
val currentMediaItem = meController.currentMediaItem
|
||
requests.trySend(Request.OnDownload(currentMediaItem!!))
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun updatePlayModeUi() {
|
||
binding.modePlayImg.setImageResource(
|
||
when (AppStore(this).playMusicMode) {
|
||
PlayMode.LIST_LOOP.value -> {
|
||
R.drawable.mode_cycle_play_icon
|
||
}
|
||
|
||
PlayMode.SINGLE_LOOP.value -> {
|
||
R.drawable.mode_single_play_icon
|
||
}
|
||
|
||
PlayMode.RANDOM.value -> {
|
||
R.drawable.mode_random_play_icon
|
||
}
|
||
|
||
else -> {
|
||
R.drawable.mode_cycle_play_icon
|
||
}
|
||
}
|
||
|
||
)
|
||
}
|
||
|
||
@SuppressLint("NotifyDataSetChanged")
|
||
private fun initData(
|
||
videoId: String,
|
||
playlistId: String? = null,
|
||
playlistSetVideoId: String? = null,
|
||
parameters: String? = null
|
||
) {
|
||
SongRadio(
|
||
videoId, playlistId, playlistSetVideoId, parameters
|
||
).let {
|
||
launch(Dispatchers.Main) {
|
||
val songRadioList = it.process()//获取到的资源集合
|
||
|
||
if (songRadioList.isEmpty()) {//集合为空则展示错误提示
|
||
binding.loadingView.visibility = View.GONE
|
||
binding.disableClicksLayout.visibility = View.GONE
|
||
binding.playbackErrorLayout.visibility = View.VISIBLE
|
||
}
|
||
|
||
if (isFinishing) {
|
||
return@launch
|
||
}
|
||
|
||
var mediaItem: MediaItem? = null
|
||
for (song: Innertube.SongItem in songRadioList) {
|
||
if (song.key == currentVideoID) {//判断当前ID得到一个mediaItem
|
||
mediaItem = song.asMediaItem
|
||
break
|
||
}
|
||
}
|
||
|
||
if (mediaItem != null) {
|
||
//数据请求完毕mediaItem不等于空就显示喜欢与下载按钮
|
||
binding.likeAndDownloadLayout.visibility = View.VISIBLE
|
||
updateInfoUi(mediaItem)
|
||
|
||
binding.playbackErrorLayout.visibility = View.GONE
|
||
|
||
binding.totalDurationTv.visibility = View.GONE
|
||
|
||
|
||
val newMediaItem = MediaItem.Builder().setMediaId(videoId).setUri(videoId)
|
||
.setCustomCacheKey(videoId).setMediaMetadata(
|
||
mediaItem.mediaMetadata
|
||
).build()
|
||
|
||
meController?.let {
|
||
it.setMediaItem(newMediaItem, true)
|
||
it.prepare()
|
||
it.play()
|
||
|
||
//过滤掉进入页面的id得到集合addMediaItems
|
||
val mediaItems = songRadioList.map(Innertube.SongItem::asMediaItem)
|
||
.filter { filter -> filter.mediaId != videoId }
|
||
it.addMediaItems(mediaItems)
|
||
}
|
||
|
||
updatePlayListDataAndAdapter()
|
||
} else {
|
||
binding.playbackErrorLayout.visibility = View.VISIBLE
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private val playerListener = object : Player.Listener {
|
||
@SuppressLint("NotifyDataSetChanged")
|
||
override fun onPositionDiscontinuity(
|
||
oldPosition: Player.PositionInfo, newPosition: Player.PositionInfo, reason: Int
|
||
) {
|
||
//保证自动播放完毕当前歌曲与通知切换歌曲可以更新UI信息
|
||
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION || reason == Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT) {
|
||
updateProgressUi()
|
||
updateInfoUi(meController?.currentMediaItem)
|
||
if (detailsPlayListAdapter != null) {
|
||
detailsPlayListAdapter?.notifyDataSetChanged()
|
||
}
|
||
}
|
||
}
|
||
|
||
override fun onPlayerError(error: PlaybackException) {
|
||
LogD(TAG, "onPlayerError error= $error")
|
||
binding.playbackErrorLayout.visibility = View.VISIBLE//展示错误提示
|
||
|
||
updatePlayState(false)
|
||
}
|
||
|
||
override fun onPlayWhenReadyChanged(
|
||
playWhenReady: Boolean, reason: Int
|
||
) {
|
||
updatePlayState(playWhenReady)
|
||
updateProgressState()
|
||
}
|
||
|
||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||
when (playbackState) {
|
||
Player.STATE_BUFFERING -> {
|
||
startPlayTime = System.currentTimeMillis()
|
||
LogD(TAG, "details STATE_BUFFERING")
|
||
binding.loadingView.visibility = View.VISIBLE
|
||
binding.disableClicksLayout.visibility = View.VISIBLE
|
||
binding.playbackErrorLayout.visibility = View.GONE
|
||
}
|
||
|
||
Player.STATE_READY -> {
|
||
val jsonObject = JSONObject()
|
||
jsonObject.put("play_time", System.currentTimeMillis() - startPlayTime)
|
||
val map = mutableMapOf(Pair(AnalysisUtil.PARAM_VALUE, jsonObject.toString()))
|
||
AnalysisUtil.logEvent(AnalysisUtil.PLAYER_B_DELAY_ACTION, map)
|
||
startPlayTime = 0L
|
||
LogD(TAG, "details STATE_READY")
|
||
AnalysisUtil.logEvent(AnalysisUtil.PLAYER_B_SUCCESS_ACTION)
|
||
binding.playbackErrorLayout.visibility = View.GONE
|
||
binding.loadingView.visibility = View.GONE
|
||
binding.disableClicksLayout.visibility = View.GONE
|
||
binding.totalDurationTv.visibility = View.VISIBLE
|
||
binding.totalDurationTv.text = convertMillisToMinutesAndSecondsString(
|
||
MediaControllerManager.getDuration()
|
||
)
|
||
binding.sbProgress.valueTo = MediaControllerManager.getDuration().toFloat()
|
||
binding.progressBar.max = MediaControllerManager.getDuration().toInt()
|
||
updateProgressBufferingState()
|
||
}
|
||
|
||
else -> {
|
||
binding.loadingView.visibility = View.GONE
|
||
binding.disableClicksLayout.visibility = View.GONE
|
||
}
|
||
}
|
||
updateProgressState()
|
||
}
|
||
}
|
||
|
||
override fun onDestroy() {
|
||
super.onDestroy()
|
||
meController?.removeListener(playerListener)
|
||
}
|
||
|
||
private fun updateProgressUi() {
|
||
if (binding.sbProgress.valueTo > 0f) {
|
||
binding.sbProgress.value = 0f
|
||
}
|
||
binding.progressBar.progress = 0
|
||
binding.progressDurationTv.text = convertMillisToMinutesAndSecondsString(0L)
|
||
binding.totalDurationTv.visibility = View.GONE
|
||
}
|
||
|
||
private fun updateInfoUi(mediaItem: MediaItem?) {
|
||
if (mediaItem == null) {
|
||
binding.playbackErrorLayout.visibility = View.VISIBLE
|
||
return
|
||
}
|
||
// currentVideoID = mediaItem.mediaId
|
||
updateDownloadUi(mediaItem.mediaId)
|
||
requests.trySend(Request.UpdateFavorite(mediaItem.mediaId))//更新喜欢状态
|
||
|
||
val currentDownload = DownloadUtil.getCurrentIdDownload(mediaItem.mediaId)
|
||
if (currentDownload != null) {
|
||
updateDownloadUI(currentDownload)
|
||
}
|
||
|
||
|
||
Glide.with(this).asBitmap().load(mediaItem.mediaMetadata.artworkUri)
|
||
.placeholder(R.mipmap.app_logo).into(object : CustomTarget<Bitmap>() {
|
||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||
binding.thumbnail.setImageBitmap(resource)
|
||
val blurredBitmap = applyGaussianBlur(resource, 25f, this@MoPlayDetailsActivity)
|
||
binding.imageView.setImageBitmap(blurredBitmap)
|
||
}
|
||
|
||
override fun onLoadCleared(placeholder: Drawable?) {
|
||
if (placeholder != null) {
|
||
binding.thumbnail.setImageDrawable(placeholder)
|
||
}
|
||
}
|
||
})
|
||
|
||
binding.nameTv.text = mediaItem.mediaMetadata.title
|
||
binding.descTv.text = mediaItem.mediaMetadata.artist
|
||
}
|
||
|
||
/**
|
||
* 更新播放进度
|
||
*/
|
||
private fun updateProgressState() {
|
||
//判断是否ready与播放中,否则停止更新进度
|
||
if (meController != null && meController.playbackState == Player.STATE_READY && meController.isPlaying) {
|
||
updatePlayState(meController.isPlaying)
|
||
progressHandler.removeCallbacksAndMessages(null)
|
||
progressHandler.sendEmptyMessage(1)
|
||
} else {
|
||
progressHandler.removeCallbacksAndMessages(null)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 播放进度
|
||
*/
|
||
private val progressHandler = object : Handler(Looper.myLooper()!!) {
|
||
override fun handleMessage(msg: Message) {
|
||
//判断是否ready与播放中,否则停止更新进度
|
||
if (meController != null && meController.playbackState == Player.STATE_READY && meController.isPlaying) {
|
||
val currentPosition = MediaControllerManager.getCurrentPosition()
|
||
val currentString = convertMillisToMinutesAndSecondsString(currentPosition)
|
||
binding.progressDurationTv.text = currentString
|
||
|
||
val currentBufferedPosition = MediaControllerManager.getBufferedPosition()
|
||
binding.progressBar.progress = currentBufferedPosition.toInt()
|
||
|
||
val value = currentPosition.toFloat()
|
||
if (binding.sbProgress.valueTo > value) {
|
||
binding.sbProgress.value = value
|
||
}
|
||
|
||
sendEmptyMessageDelayed(1, 50)
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新缓冲进度
|
||
*/
|
||
private fun updateProgressBufferingState() {
|
||
if (meController != null && meController.isLoading) {
|
||
progressBufferingHandler.removeCallbacksAndMessages(null)
|
||
progressBufferingHandler.sendEmptyMessage(1)
|
||
} else {
|
||
progressBufferingHandler.removeCallbacksAndMessages(null)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 缓冲进度
|
||
*/
|
||
private val progressBufferingHandler = object : Handler(Looper.myLooper()!!) {
|
||
override fun handleMessage(msg: Message) {
|
||
if (meController != null && meController.isLoading) {
|
||
val currentBufferedPosition = MediaControllerManager.getBufferedPosition()
|
||
binding.progressBar.progress = currentBufferedPosition.toInt()
|
||
sendEmptyMessageDelayed(1, 50)
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun updatePlayState(b: Boolean) {
|
||
if (b) {
|
||
binding.playImg.setImageResource(R.drawable.playing_green_icon)
|
||
} else {
|
||
binding.playImg.setImageResource(R.drawable.play_green_icon)
|
||
}
|
||
}
|
||
|
||
|
||
private fun toggleBottomLayout() {
|
||
if (binding.bottomLayout.visibility == View.VISIBLE) {
|
||
hideBottomLayout()
|
||
} else {
|
||
showBottomLayout()
|
||
}
|
||
}
|
||
|
||
private fun showBottomLayout() {
|
||
val slideUpAnimation = AnimationUtils.loadAnimation(this, R.anim.slide_up)
|
||
binding.bottomLayout.startAnimation(slideUpAnimation)
|
||
binding.bottomLayout.visibility = View.VISIBLE
|
||
}
|
||
|
||
private fun hideBottomLayout() {
|
||
val slideDownAnimation = AnimationUtils.loadAnimation(this, R.anim.slide_down)
|
||
binding.bottomLayout.startAnimation(slideDownAnimation)
|
||
binding.bottomLayout.visibility = View.GONE
|
||
}
|
||
|
||
private fun loadAd() {
|
||
LolAdWrapper.shared.loadAdIfNotCached(this, AdPlacement.INST_INTO_PLAY)
|
||
}
|
||
|
||
private fun showAD() {
|
||
LolAdWrapper.shared.showAdTiming(this, AdPlacement.INST_INTO_PLAY)
|
||
}
|
||
|
||
private fun showRemoveDownloadDialogHint(mediaItem: MediaItem) {
|
||
val inflater = LayoutInflater.from(this)
|
||
val dialogView = inflater.inflate(R.layout.dialog_hint, null)
|
||
val okBtn = dialogView.findViewById<TextView>(R.id.dialog_ok_btn)
|
||
val cancelBtn = dialogView.findViewById<TextView>(R.id.dialog_cancel_btn)
|
||
val dialogBuilder = AlertDialog.Builder(this).setView(dialogView)
|
||
val dialog = dialogBuilder.create()
|
||
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||
dialog.show()
|
||
okBtn.setOnClickListener {
|
||
dialog.dismiss()
|
||
requests.trySend(Request.OnDownloadRemove(mediaItem))
|
||
}
|
||
cancelBtn.setOnClickListener {
|
||
dialog.dismiss()
|
||
}
|
||
}
|
||
} |