Musicoo/app/src/main/java/melody/offline/music/activity/MoPlayDetailsActivity.kt
2024-07-04 15:37:18 +08:00

983 lines
41 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
// }
// }
}
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.GONE
binding.disableClicksLayout.visibility = View.GONE
val currentString =
convertMillisToMinutesAndSecondsString(MediaControllerManager.getCurrentPosition())
binding.progressDurationTv.text = currentString
if (MediaControllerManager.getDuration() > 0) {
binding.totalDurationTv.visibility = View.VISIBLE
} else {
binding.totalDurationTv.visibility = View.GONE
}
binding.totalDurationTv.text =
convertMillisToMinutesAndSecondsString(MediaControllerManager.getDuration())
binding.sbProgress.valueTo = MediaControllerManager.getDuration().toFloat()
val value = MediaControllerManager.getCurrentPosition().toFloat()
if (binding.sbProgress.valueTo > value) {
binding.sbProgress.value = value
}
updateProgressState()
binding.progressBar.progress = MediaControllerManager.getBufferedPosition().toInt()
binding.progressBar.max = MediaControllerManager.getDuration().toInt()
updateProgressBufferingState()
updatePlayListDataAndAdapter()
}
}
@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
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() {
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()
}
}
}