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 = 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 { 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 = 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 = 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() { override fun onResourceReady(resource: Bitmap, transition: Transition?) { 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(R.id.dialog_ok_btn) val cancelBtn = dialogView.findViewById(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() } } }