Musicoo/app/src/main/java/com/player/musicoo/activity/MoPlayDetailsActivity.kt
2024-05-10 16:56:04 +08:00

422 lines
15 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 com.player.musicoo.activity
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.view.View
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import com.bumptech.glide.Glide
import com.gyf.immersionbar.ktx.immersionBar
import com.player.musicoo.R
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.util.asMediaItem
import com.player.musicoo.util.convertMillisToMinutesAndSecondsString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import com.player.musicoo.util.LogTag.LogD
@OptIn(UnstableApi::class)
class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
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"
}
private lateinit var binding: ActivityMoPlayDetailsBinding
private var currentVideoID = ""
private var comeFrom: Class<*>? = null
private fun initImmersionBar() {
immersionBar {
statusBarDarkFont(false)
statusBarView(binding.view)
}
}
override suspend fun main() {
binding = ActivityMoPlayDetailsBinding.inflate(layoutInflater)
setContentView(binding.root)
initImmersionBar()
initClick()
initPlayerListener()
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) {
// 处理来自 PrimaryActivity 的情况
updateCurrentMediaItemInfo()
} 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
)
}
onReceive()
}
private suspend fun onReceive() {
while (isActive) {
select<Unit> {
events.onReceive {
when (it) {
Event.ActivityOnResume -> {
activityOnResume()
}
else -> {}
}
}
}
}
}
private fun activityOnResume() {
if (comeFrom != null && comeFrom == PrimaryActivity::class.java) {
if (meController != null && meController.currentMediaItem != null) {
updateInfoUi(meController.currentMediaItem)
}
}
}
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
val currentString = convertMillisToMinutesAndSecondsString(meController.currentPosition)
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.value = meController.currentPosition.toFloat()
binding.sbProgress.valueTo = MediaControllerManager.getDuration().toFloat()
updateProgressState()
binding.progressBar.progress = meController.bufferedPosition.toInt()
binding.progressBar.max = MediaControllerManager.getDuration().toInt()
updateProgressBufferingState()
}
}
private fun initClick() {
binding.backBtn.setOnClickListener {
finish()
}
binding.playLayoutBtn.setOnClickListener {
if (meController != null) {
if (meController.isPlaying) {
meController.pause()
updatePlayState(false)
} else {
meController.play()
updatePlayState(true)
}
updateProgressState()
}
}
binding.playSkipBackBtn.setOnClickListener {
if (meController != null) {
meController.seekToPreviousMediaItem()
updateProgressUi()
updateInfoUi(meController.currentMediaItem)
updateProgressState()
if (!meController.isPlaying) {
meController.prepare()
meController.play()
}
}
}
binding.playSkipForwardBtn.setOnClickListener {
if (meController != null) {
meController.seekToNextMediaItem()
updateProgressUi()
updateInfoUi(meController.currentMediaItem)
updateProgressState()
if (!meController.isPlaying) {
meController.prepare()
meController.play()
}
}
}
binding.listLayoutBtn.setOnClickListener {
LogD(TAG, "meController?.mediaItemCount->${meController?.mediaItemCount}")
}
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()
}
}
}
}
}
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.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) {
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)
}
} else {
binding.playbackErrorLayout.visibility = View.VISIBLE
}
}
}
}
private val playerListener = object : Player.Listener {
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
) {
updateInfoUi(meController?.currentMediaItem)
}
}
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 -> {
binding.loadingView.visibility = View.VISIBLE
}
Player.STATE_READY -> {
binding.playbackErrorLayout.visibility = View.GONE
binding.loadingView.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
}
}
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
}
Glide.with(this@MoPlayDetailsActivity)
.load(mediaItem.mediaMetadata.artworkUri)
.into(binding.thumbnail)
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 = meController.currentPosition
val currentString = convertMillisToMinutesAndSecondsString(currentPosition)
binding.progressDurationTv.text = currentString
val currentBufferedPosition = meController.bufferedPosition
binding.progressBar.progress = currentBufferedPosition.toInt()
// 更新 SeekBar 的进度
binding.sbProgress.value = currentPosition.toFloat()
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 = meController.bufferedPosition
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)
}
}
}