422 lines
15 KiB
Kotlin
422 lines
15 KiB
Kotlin
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)
|
||
}
|
||
}
|
||
} |