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 { 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) } } }