This commit is contained in:
ocean 2024-05-11 11:39:32 +08:00
parent 195ec0f242
commit c90b8a6930
18 changed files with 685 additions and 140 deletions

View File

@ -1,5 +1,6 @@
package com.player.musicoo.activity package com.player.musicoo.activity
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.gyf.immersionbar.ktx.immersionBar import com.gyf.immersionbar.ktx.immersionBar
@ -45,8 +46,10 @@ class MoListDetailsActivity : MoBaseActivity() {
} }
private suspend fun initData(browseId: String) { private suspend fun initData(browseId: String) {
showLoadingUi()
Innertube.moPlaylistPage(browseId) Innertube.moPlaylistPage(browseId)
?.onSuccess { ?.onSuccess {
showDataUi()
Glide.with(this) Glide.with(this)
.load(it.thumbnail) .load(it.thumbnail)
.into(binding.imageView) .into(binding.imageView)
@ -61,7 +64,23 @@ class MoListDetailsActivity : MoBaseActivity() {
binding.rv.adapter = adapter binding.rv.adapter = adapter
}?.onFailure { }?.onFailure {
showNoContentUi()
LogD(TAG, "moPlaylistPage onFailure->${it}") LogD(TAG, "moPlaylistPage onFailure->${it}")
} }
} }
private fun showDataUi() {
binding.loadingLayout.visibility = View.GONE
binding.noContentLayout.visibility = View.GONE
}
private fun showLoadingUi() {
binding.loadingLayout.visibility = View.VISIBLE
binding.noContentLayout.visibility = View.GONE
}
private fun showNoContentUi() {
binding.loadingLayout.visibility = View.GONE
binding.noContentLayout.visibility = View.VISIBLE
}
} }

View File

@ -1,28 +1,36 @@
package com.player.musicoo.activity package com.player.musicoo.activity
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.view.View import android.view.View
import android.view.animation.AnimationUtils
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackException
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide 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 com.gyf.immersionbar.ktx.immersionBar
import com.player.musicoo.R import com.player.musicoo.R
import com.player.musicoo.adapter.PlayListAdapter
import com.player.musicoo.databinding.ActivityMoPlayDetailsBinding import com.player.musicoo.databinding.ActivityMoPlayDetailsBinding
import com.player.musicoo.innertube.Innertube import com.player.musicoo.innertube.Innertube
import com.player.musicoo.media.MediaControllerManager import com.player.musicoo.media.MediaControllerManager
import com.player.musicoo.media.SongRadio import com.player.musicoo.media.SongRadio
import com.player.musicoo.util.LogTag.LogD
import com.player.musicoo.util.asMediaItem import com.player.musicoo.util.asMediaItem
import com.player.musicoo.util.convertMillisToMinutesAndSecondsString import com.player.musicoo.util.convertMillisToMinutesAndSecondsString
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select import kotlinx.coroutines.selects.select
import com.player.musicoo.util.LogTag.LogD
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener { class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
@ -42,6 +50,7 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
private var currentVideoID = "" private var currentVideoID = ""
private var comeFrom: Class<*>? = null private var comeFrom: Class<*>? = null
private var playListAdapter: PlayListAdapter? = null
private fun initImmersionBar() { private fun initImmersionBar() {
immersionBar { immersionBar {
@ -120,9 +129,8 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
if (meController != null && meController.currentMediaItem != null) { if (meController != null && meController.currentMediaItem != null) {
binding.playbackErrorLayout.visibility = View.GONE binding.playbackErrorLayout.visibility = View.GONE
binding.loadingView.visibility = View.GONE binding.loadingView.visibility = View.GONE
binding.disableClicksLayout.visibility = View.GONE
val currentString = convertMillisToMinutesAndSecondsString(MediaControllerManager.getCurrentPosition())
val currentString = convertMillisToMinutesAndSecondsString(meController.currentPosition)
binding.progressDurationTv.text = currentString binding.progressDurationTv.text = currentString
if (MediaControllerManager.getDuration() > 0) { if (MediaControllerManager.getDuration() > 0) {
binding.totalDurationTv.visibility = View.VISIBLE binding.totalDurationTv.visibility = View.VISIBLE
@ -133,13 +141,31 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
binding.totalDurationTv.text = binding.totalDurationTv.text =
convertMillisToMinutesAndSecondsString(MediaControllerManager.getDuration()) convertMillisToMinutesAndSecondsString(MediaControllerManager.getDuration())
binding.sbProgress.value = meController.currentPosition.toFloat() binding.sbProgress.value = MediaControllerManager.getCurrentPosition().toFloat()
binding.sbProgress.valueTo = MediaControllerManager.getDuration().toFloat() binding.sbProgress.valueTo = MediaControllerManager.getDuration().toFloat()
updateProgressState() updateProgressState()
binding.progressBar.progress = meController.bufferedPosition.toInt() binding.progressBar.progress = MediaControllerManager.getBufferedPosition().toInt()
binding.progressBar.max = MediaControllerManager.getDuration().toInt() binding.progressBar.max = MediaControllerManager.getDuration().toInt()
updateProgressBufferingState() updateProgressBufferingState()
val mediaItemCount = meController.mediaItemCount
val allMediaItems: MutableList<MediaItem> = mutableListOf()
for (index in 0 until mediaItemCount) {
val mediaItemAt = meController.getMediaItemAt(index)
allMediaItems.add(mediaItemAt)
}
playListAdapter = PlayListAdapter(
this@MoPlayDetailsActivity,
allMediaItems
)
binding.playListRv.layoutManager =
LinearLayoutManager(
this@MoPlayDetailsActivity,
LinearLayoutManager.VERTICAL,
false
)
binding.playListRv.adapter = playListAdapter
} }
} }
@ -147,6 +173,16 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
binding.backBtn.setOnClickListener { binding.backBtn.setOnClickListener {
finish() finish()
} }
binding.tryAgainBtn.setOnClickListener {
if (meController != null) {
updateInfoUi(meController.currentMediaItem)
updateProgressState()
if (!meController.isPlaying) {
meController.prepare()
meController.play()
}
}
}
binding.playLayoutBtn.setOnClickListener { binding.playLayoutBtn.setOnClickListener {
if (meController != null) { if (meController != null) {
if (meController.isPlaying) { if (meController.isPlaying) {
@ -184,7 +220,13 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
} }
} }
binding.listLayoutBtn.setOnClickListener { binding.listLayoutBtn.setOnClickListener {
LogD(TAG, "meController?.mediaItemCount->${meController?.mediaItemCount}") toggleBottomLayout()
}
binding.bottomCloseBtn.setOnClickListener {
toggleBottomLayout()
}
binding.bottomBlankLayout.setOnClickListener {
toggleBottomLayout()
} }
binding.progressBar.progress = 0 binding.progressBar.progress = 0
binding.sbProgress.value = 0f binding.sbProgress.value = 0f
@ -218,7 +260,20 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
if (songRadioList.isEmpty()) {//集合为空则展示错误提示 if (songRadioList.isEmpty()) {//集合为空则展示错误提示
binding.loadingView.visibility = View.GONE binding.loadingView.visibility = View.GONE
binding.disableClicksLayout.visibility = View.GONE
binding.playbackErrorLayout.visibility = View.VISIBLE binding.playbackErrorLayout.visibility = View.VISIBLE
} else {
playListAdapter = PlayListAdapter(
this@MoPlayDetailsActivity,
songRadioList.map(Innertube.SongItem::asMediaItem)
)
binding.playListRv.layoutManager =
LinearLayoutManager(
this@MoPlayDetailsActivity,
LinearLayoutManager.VERTICAL,
false
)
binding.playListRv.adapter = playListAdapter
} }
if (isFinishing) { if (isFinishing) {
@ -269,6 +324,7 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
} }
private val playerListener = object : Player.Listener { private val playerListener = object : Player.Listener {
@SuppressLint("NotifyDataSetChanged")
override fun onPositionDiscontinuity( override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo, oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo, newPosition: Player.PositionInfo,
@ -278,7 +334,11 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION
|| reason == Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT || reason == Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT
) { ) {
updateProgressUi()
updateInfoUi(meController?.currentMediaItem) updateInfoUi(meController?.currentMediaItem)
if (playListAdapter != null) {
playListAdapter?.notifyDataSetChanged()
}
} }
} }
@ -301,11 +361,14 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
when (playbackState) { when (playbackState) {
Player.STATE_BUFFERING -> { Player.STATE_BUFFERING -> {
binding.loadingView.visibility = View.VISIBLE binding.loadingView.visibility = View.VISIBLE
binding.disableClicksLayout.visibility = View.VISIBLE
binding.playbackErrorLayout.visibility = View.GONE
} }
Player.STATE_READY -> { Player.STATE_READY -> {
binding.playbackErrorLayout.visibility = View.GONE binding.playbackErrorLayout.visibility = View.GONE
binding.loadingView.visibility = View.GONE binding.loadingView.visibility = View.GONE
binding.disableClicksLayout.visibility = View.GONE
binding.totalDurationTv.visibility = View.VISIBLE binding.totalDurationTv.visibility = View.VISIBLE
binding.totalDurationTv.text = binding.totalDurationTv.text =
convertMillisToMinutesAndSecondsString( convertMillisToMinutesAndSecondsString(
@ -320,6 +383,7 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
else -> { else -> {
binding.loadingView.visibility = View.GONE binding.loadingView.visibility = View.GONE
binding.disableClicksLayout.visibility = View.GONE
} }
} }
updateProgressState() updateProgressState()
@ -343,9 +407,24 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
binding.playbackErrorLayout.visibility = View.VISIBLE binding.playbackErrorLayout.visibility = View.VISIBLE
return return
} }
Glide.with(this@MoPlayDetailsActivity)
Glide.with(this)
.asBitmap()
.load(mediaItem.mediaMetadata.artworkUri) .load(mediaItem.mediaMetadata.artworkUri)
.into(binding.thumbnail) .placeholder(R.mipmap.musicoo_logo_img)
.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.nameTv.text = mediaItem.mediaMetadata.title
binding.descTv.text = mediaItem.mediaMetadata.artist binding.descTv.text = mediaItem.mediaMetadata.artist
@ -372,11 +451,11 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
override fun handleMessage(msg: Message) { override fun handleMessage(msg: Message) {
//判断是否ready与播放中否则停止更新进度 //判断是否ready与播放中否则停止更新进度
if (meController != null && meController.playbackState == Player.STATE_READY && meController.isPlaying) { if (meController != null && meController.playbackState == Player.STATE_READY && meController.isPlaying) {
val currentPosition = meController.currentPosition val currentPosition = MediaControllerManager.getCurrentPosition()
val currentString = convertMillisToMinutesAndSecondsString(currentPosition) val currentString = convertMillisToMinutesAndSecondsString(currentPosition)
binding.progressDurationTv.text = currentString binding.progressDurationTv.text = currentString
val currentBufferedPosition = meController.bufferedPosition val currentBufferedPosition = MediaControllerManager.getBufferedPosition()
binding.progressBar.progress = currentBufferedPosition.toInt() binding.progressBar.progress = currentBufferedPosition.toInt()
// 更新 SeekBar 的进度 // 更新 SeekBar 的进度
@ -405,7 +484,7 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
private val progressBufferingHandler = object : Handler(Looper.myLooper()!!) { private val progressBufferingHandler = object : Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) { override fun handleMessage(msg: Message) {
if (meController != null && meController.isLoading) { if (meController != null && meController.isLoading) {
val currentBufferedPosition = meController.bufferedPosition val currentBufferedPosition = MediaControllerManager.getBufferedPosition()
binding.progressBar.progress = currentBufferedPosition.toInt() binding.progressBar.progress = currentBufferedPosition.toInt()
sendEmptyMessageDelayed(1, 50) sendEmptyMessageDelayed(1, 50)
} }
@ -419,4 +498,25 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
binding.playImg.setImageResource(R.drawable.play_green_icon) 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
}
} }

View File

@ -10,6 +10,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.player.musicoo.App import com.player.musicoo.App
import com.player.musicoo.R import com.player.musicoo.R
import com.player.musicoo.activity.MoPlayDetailsActivity
import com.player.musicoo.activity.PlayDetailsActivity import com.player.musicoo.activity.PlayDetailsActivity
import com.player.musicoo.bean.Audio import com.player.musicoo.bean.Audio
import com.player.musicoo.databinding.DetailsListItemBinding import com.player.musicoo.databinding.DetailsListItemBinding
@ -27,6 +28,10 @@ class DetailsListAdapter(
) : ) :
RecyclerView.Adapter<DetailsListAdapter.ViewHolder>() { RecyclerView.Adapter<DetailsListAdapter.ViewHolder>() {
companion object {
const val FROM_TAG = "list_details_activity_to_adapter"
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = DetailsListItemBinding.inflate(LayoutInflater.from(context), parent, false) val binding = DetailsListItemBinding.inflate(LayoutInflater.from(context), parent, false)
return ViewHolder(binding) return ViewHolder(binding)
@ -35,6 +40,23 @@ class DetailsListAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val bean = list[position] val bean = list[position]
holder.bind(bean) holder.bind(bean)
val videoId = bean.videoId
holder.itemView.setOnClickListener {
val intent = Intent(context, MoPlayDetailsActivity::class.java)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_VIDEO_ID, videoId)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_PLAY_LIST_ID, bean.playlistId)
intent.putExtra(
MoPlayDetailsActivity.PLAY_DETAILS_PLAY_LIST_SET_VIDEO_ID,
bean.playlistSetVideoId
)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_PLAY_PARAMS, bean.params)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, bean.name)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, bean.title)
context.startActivity(intent)
}
} }
override fun getItemCount(): Int = list.size override fun getItemCount(): Int = list.size

View File

@ -0,0 +1,94 @@
package com.player.musicoo.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.player.musicoo.R
import com.player.musicoo.databinding.PlayListItemBinding
import com.player.musicoo.media.MediaControllerManager
class PlayListAdapter(
private val context: Context,
private val list: List<MediaItem>,
) :
RecyclerView.Adapter<PlayListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = PlayListItemBinding.inflate(LayoutInflater.from(context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val bean = list[position]
holder.bind(bean)
holder.itemView.setOnClickListener {
val meController = MediaControllerManager.getController()
if (meController != null && meController.currentMediaItem != null) {
var index = holder.bindingAdapterPosition
if (index > meController.mediaItemCount) {
index = 1
}
meController.seekTo(index, C.TIME_UNSET)
if (!meController.isPlaying) {
meController.prepare()
meController.play()
}
}
}
}
override fun getItemCount(): Int = list.size
inner class ViewHolder(private val binding: PlayListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(bean: MediaItem) {
binding.apply {
val meController = MediaControllerManager.getController()
if (meController != null && meController.currentMediaItem != null) {
if (meController.currentMediaItem?.mediaId == bean.mediaId) {
binding.currentPlayingLayout.visibility = View.VISIBLE
binding.title.setTextColor(context.getColor(R.color.green))
binding.name.setTextColor(context.getColor(R.color.green_60))
} else {
binding.currentPlayingLayout.visibility = View.GONE
binding.title.setTextColor(context.getColor(R.color.white))
binding.name.setTextColor(context.getColor(R.color.white_60))
}
}
Glide.with(context)
.load(bean.mediaMetadata.artworkUri)
.into(image)
title.text = bean.mediaMetadata.title
if (bean.mediaMetadata.artist.isNullOrEmpty()) {
name.visibility = View.GONE
} else {
name.visibility = View.VISIBLE
name.text = bean.mediaMetadata.artist
}
}
}
}
private var itemClickListener: OnItemClickListener? = null
fun setOnItemClickListener(listener: OnItemClickListener) {
itemClickListener = listener
}
interface OnItemClickListener {
fun onItemClick(position: Int)
}
}

View File

@ -7,6 +7,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.player.musicoo.activity.MoListDetailsActivity import com.player.musicoo.activity.MoListDetailsActivity
import com.player.musicoo.activity.MoPlayDetailsActivity
import com.player.musicoo.databinding.MusicTowRowItemBinding import com.player.musicoo.databinding.MusicTowRowItemBinding
import com.player.musicoo.innertube.models.MusicCarouselShelfRenderer import com.player.musicoo.innertube.models.MusicCarouselShelfRenderer
@ -34,38 +35,66 @@ class TowRowListAdapter(
val browseId = browseEndpoint?.browseId val browseId = browseEndpoint?.browseId
holder.bind(bean) val watchEndpoint = bean.musicTwoRowItemRenderer
?.navigationEndpoint
?.watchEndpoint
val videoId = watchEndpoint?.videoId
val playlistId = watchEndpoint?.playlistId
val playlistSetVideoId = watchEndpoint?.playlistSetVideoId
val params = watchEndpoint?.params
val url = bean.musicTwoRowItemRenderer
?.thumbnailRenderer
?.musicThumbnailRenderer
?.thumbnail
?.thumbnails
?.let { it.getOrNull(1) ?: it.getOrNull(0) }
?.url ?: ""
val name = bean.musicTwoRowItemRenderer
?.title
?.runs
?.firstOrNull()
?.text ?: ""
val desc = bean.musicTwoRowItemRenderer
?.subtitle
?.runs
?.map { it.text }
?.joinToString("") ?: ""
holder.bind(url = url, name = name, desc = desc)
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
if (browseId.isNullOrEmpty()) {
val intent = Intent(context, MoPlayDetailsActivity::class.java)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_VIDEO_ID, videoId)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_PLAY_LIST_ID, playlistId)
if (playlistSetVideoId != null) {
intent.putExtra(
MoPlayDetailsActivity.PLAY_DETAILS_PLAY_LIST_SET_VIDEO_ID,
playlistSetVideoId
)
}
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_PLAY_PARAMS, params)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, name)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, desc)
context.startActivity(intent)
} else {
val intent = Intent(context, MoListDetailsActivity::class.java) val intent = Intent(context, MoListDetailsActivity::class.java)
intent.putExtra(MoListDetailsActivity.PLAY_LIST_PAGE_BROWSE_ID, browseId) intent.putExtra(MoListDetailsActivity.PLAY_LIST_PAGE_BROWSE_ID, browseId)
context.startActivity(intent) context.startActivity(intent)
} }
} }
}
override fun getItemCount(): Int = list.size override fun getItemCount(): Int = list.size
inner class ViewHolder(private val binding: MusicTowRowItemBinding) : inner class ViewHolder(private val binding: MusicTowRowItemBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
fun bind(content: MusicCarouselShelfRenderer.Content) { fun bind(url: String, name: String, desc: String) {
val url = content.musicTwoRowItemRenderer
?.thumbnailRenderer
?.musicThumbnailRenderer
?.thumbnail
?.thumbnails
?.let { it.getOrNull(1) ?: it.getOrNull(0) }
?.url
val name = content.musicTwoRowItemRenderer
?.title
?.runs
?.firstOrNull()
?.text
val desc = content.musicTwoRowItemRenderer
?.subtitle
?.runs
?.map { it.text }
?.joinToString("")
binding.apply { binding.apply {
Glide.with(context) Glide.with(context)
.load(url) .load(url)

View File

@ -90,6 +90,7 @@ class MoHomeFragment : MoBaseFragment<FragmentMoHomeBinding>() {
initHomeDataMore(it) initHomeDataMore(it)
} else { } else {
LogD(TAG, "homePage size 0") LogD(TAG, "homePage size 0")
showNoContentUi()
} }
}?.onFailure { }?.onFailure {
showNoContentUi() showNoContentUi()
@ -141,16 +142,17 @@ class MoHomeFragment : MoBaseFragment<FragmentMoHomeBinding>() {
} }
} }
private fun showDataUi(){ private fun showDataUi() {
binding.loadingLayout.visibility = View.GONE binding.loadingLayout.visibility = View.GONE
binding.noContentLayout.visibility = View.GONE binding.noContentLayout.visibility = View.GONE
} }
private fun showLoadingUi(){
private fun showLoadingUi() {
binding.loadingLayout.visibility = View.VISIBLE binding.loadingLayout.visibility = View.VISIBLE
binding.noContentLayout.visibility = View.GONE binding.noContentLayout.visibility = View.GONE
} }
private fun showNoContentUi(){ private fun showNoContentUi() {
binding.loadingLayout.visibility = View.GONE binding.loadingLayout.visibility = View.GONE
binding.noContentLayout.visibility = View.VISIBLE binding.noContentLayout.visibility = View.VISIBLE
} }

View File

@ -193,7 +193,9 @@ object Innertube {
val timeText: String?, val timeText: String?,
val browseId: String, val browseId: String,
val videoId: String?, val videoId: String?,
val playlistId: String?, val playlistId: String? = null,
val playlistSetVideoId: String? = null,
val params: String? = null,
val musicVideoType: String?, val musicVideoType: String?,
val pageType: String?, val pageType: String?,
val thumbnailUrl: String? val thumbnailUrl: String?

View File

@ -80,7 +80,11 @@ suspend fun Innertube.moPlaylistPage(browseId: String): Result<Innertube.MoPlayl
val bean = Innertube.MoPlaylistOrAlbumPage.MoPlaylistOrAlbumListBean( val bean = Innertube.MoPlaylistOrAlbumPage.MoPlaylistOrAlbumListBean(
title = runs0?.text, title = runs0?.text,
name = runs1?.text, name = runs1?.text ?: musicDetailHeaderRenderer
?.subtitle
?.runs
?.map { it.text }
?.joinToString(""),
desc = runs2?.text, desc = runs2?.text,
timeText = content.musicResponsiveListItemRenderer timeText = content.musicResponsiveListItemRenderer
?.fixedColumns?.firstOrNull() ?.fixedColumns?.firstOrNull()
@ -92,6 +96,8 @@ suspend fun Innertube.moPlaylistPage(browseId: String): Result<Innertube.MoPlayl
browseId = browseEndpoint?.browseId.toString(), browseId = browseEndpoint?.browseId.toString(),
videoId = watchEndpoint?.videoId, videoId = watchEndpoint?.videoId,
playlistId = watchEndpoint?.playlistId, playlistId = watchEndpoint?.playlistId,
playlistSetVideoId = watchEndpoint?.playlistSetVideoId,
params = watchEndpoint?.params,
musicVideoType = watchEndpoint?.type, musicVideoType = watchEndpoint?.type,
pageType = browseEndpoint?.type, pageType = browseEndpoint?.type,
thumbnailUrl = thumbnailUrl thumbnailUrl = thumbnailUrl

View File

@ -177,4 +177,26 @@ object MediaControllerManager {
} }
return 0 return 0
} }
fun getCurrentPosition(): Long {
mediaController?.let {
if (it.currentPosition > 0 && it.currentPosition < it.duration) {
return it.currentPosition
} else {
return 0
}
}
return 0
}
fun getBufferedPosition(): Long {
mediaController?.let {
if (it.bufferedPosition > 0) {
return it.bufferedPosition
} else {
return 0
}
}
return 0
}
} }

View File

@ -57,12 +57,10 @@ class PlaybackService : MediaSessionService(), Player.Listener {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
val cacheEvictor = when (val size = ExoPlayerDiskCacheMaxSize.`2GB`){ val cacheEvictor = when (val size = ExoPlayerDiskCacheMaxSize.`2GB`) {
ExoPlayerDiskCacheMaxSize.Unlimited -> NoOpCacheEvictor() ExoPlayerDiskCacheMaxSize.Unlimited -> NoOpCacheEvictor()
else -> LeastRecentlyUsedCacheEvictor(size.bytes) else -> LeastRecentlyUsedCacheEvictor(size.bytes)
} }
// TODO: Remove in a future release
val directory = cacheDir.resolve("exoplayer").also { directory -> val directory = cacheDir.resolve("exoplayer").also { directory ->
if (directory.exists()) return@also if (directory.exists()) return@also
@ -80,7 +78,6 @@ class PlaybackService : MediaSessionService(), Player.Listener {
} }
cache = SimpleCache(directory, cacheEvictor, StandaloneDatabaseProvider(this)) cache = SimpleCache(directory, cacheEvictor, StandaloneDatabaseProvider(this))
player = ExoPlayer.Builder(this, createRendersFactory(), createMediaSourceFactory()) player = ExoPlayer.Builder(this, createRendersFactory(), createMediaSourceFactory())
.setHandleAudioBecomingNoisy(true) .setHandleAudioBecomingNoisy(true)
.setWakeMode(C.WAKE_MODE_LOCAL) .setWakeMode(C.WAKE_MODE_LOCAL)
@ -181,7 +178,7 @@ class PlaybackService : MediaSessionService(), Player.Listener {
) )
} }
} }
LogD(TAG,"service urlResult->$urlResult") LogD(TAG, "service urlResult->$urlResult")
urlResult?.getOrThrow()?.let { url -> urlResult?.getOrThrow()?.let { url ->
ringBuffer.append(videoId to url.toUri()) ringBuffer.append(videoId to url.toUri())

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF555555" />
<corners android:radius="20dp" />
</shape>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/bottom_layout_bg_color" />
<corners
android:topLeftRadius="18dp"
android:topRightRadius="18dp" />
</shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="17dp"
android:viewportWidth="14"
android:viewportHeight="17">
<path
android:pathData="M7,16.8C6.629,16.8 6.273,16.653 6.01,16.39C5.747,16.127 5.6,15.771 5.6,15.4V1.4C5.6,1.029 5.747,0.673 6.01,0.41C6.273,0.147 6.629,0 7,0C7.371,0 7.727,0.147 7.99,0.41C8.252,0.673 8.4,1.029 8.4,1.4V15.4C8.4,15.771 8.252,16.127 7.99,16.39C7.727,16.653 7.371,16.8 7,16.8ZM1.4,14C1.029,14 0.673,13.852 0.41,13.59C0.147,13.327 0,12.971 0,12.6V5.6C0,5.229 0.148,4.873 0.41,4.61C0.673,4.348 1.029,4.2 1.4,4.2C1.771,4.2 2.127,4.348 2.39,4.61C2.652,4.873 2.8,5.229 2.8,5.6V12.6C2.8,12.784 2.764,12.966 2.693,13.136C2.623,13.306 2.52,13.46 2.39,13.59C2.26,13.72 2.106,13.823 1.936,13.893C1.766,13.964 1.584,14 1.4,14ZM12.6,14C12.229,14 11.873,13.852 11.61,13.59C11.347,13.327 11.2,12.971 11.2,12.6V5.6C11.2,5.229 11.348,4.873 11.61,4.61C11.873,4.348 12.229,4.2 12.6,4.2C12.971,4.2 13.327,4.348 13.59,4.61C13.852,4.873 14,5.229 14,5.6V12.6C14,12.971 13.852,13.327 13.59,13.59C13.327,13.852 12.971,14 12.6,14Z"
android:fillColor="#FF80F988"/>
</vector>

View File

@ -16,11 +16,17 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" /> android:layout_height="0dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/view"
android:orientation="vertical">
<LinearLayout <LinearLayout
android:id="@+id/title_layout" android:id="@+id/title_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/view"
android:layout_margin="16dp" android:layout_margin="16dp"
android:orientation="horizontal"> android:orientation="horizontal">
@ -39,10 +45,65 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/loadingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone">
<ProgressBar
android:layout_width="wrap_content"
android:indeterminateTint="@color/green"
android:progressBackgroundTint="@color/green"
android:progressTint="@color/green"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/no_content_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:id="@+id/no_content_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/no_content_img" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:fontFamily="@font/medium_font"
android:gravity="center"
android:text="@string/content_loading_failed"
android:textSize="14dp" />
<TextView
android:id="@+id/tryAgainBtn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:background="@drawable/drw_btn_bg"
android:fontFamily="@font/medium_font"
android:gravity="center"
android:paddingStart="32dp"
android:paddingEnd="32dp"
android:text="@string/try_again"
android:textColor="@color/black"
android:textSize="16dp" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@+id/title_layout"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
@ -119,4 +180,6 @@
android:layout_margin="16dp" /> android:layout_margin="16dp" />
</LinearLayout> </LinearLayout>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -23,7 +23,7 @@
android:id="@+id/imageView" android:id="@+id/imageView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="5" android:layout_weight="8"
android:scaleType="centerCrop" /> android:scaleType="centerCrop" />
<View <View
@ -40,15 +40,15 @@
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="5" android:layout_weight="8"
android:background="@drawable/drw_details_bg" android:background="@drawable/drw_details_bg"
android:visibility="gone" /> android:visibility="visible" />
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:background="@color/main_bg_color" /> android:background="@color/black" />
</LinearLayout> </LinearLayout>
@ -122,19 +122,42 @@
android:progressBackgroundTint="@color/green" android:progressBackgroundTint="@color/green"
android:progressTint="@color/green" /> android:progressTint="@color/green" />
<TextView <LinearLayout
android:id="@+id/playback_error_layout" android:id="@+id/playback_error_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_centerInParent="true" android:gravity="center"
android:background="@color/black_60" android:background="@color/black_60"
android:padding="24dp" android:orientation="vertical"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:fontFamily="@font/medium_font" android:fontFamily="@font/medium_font"
android:gravity="center" android:gravity="center"
android:text="@string/playback_error" android:text="@string/playback_error"
android:textColor="@color/white_80" android:textColor="@color/white_80"
android:textSize="16dp" android:textSize="16dp" />
android:visibility="gone" />
<TextView
android:id="@+id/tryAgainBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/drw_btn_bg"
android:paddingStart="24dp"
android:paddingTop="8dp"
android:fontFamily="@font/medium_font"
android:paddingEnd="24dp"
android:paddingBottom="8dp"
android:text="@string/try_again"
android:textColor="@color/black"
android:textSize="16dp" />
</LinearLayout>
</RelativeLayout> </RelativeLayout>
@ -346,4 +369,50 @@
android:orientation="horizontal" /> android:orientation="horizontal" />
</FrameLayout> </FrameLayout>
<LinearLayout
android:id="@+id/bottom_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:visibility="gone">
<View
android:id="@+id/bottom_blank_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="68dp"
android:layout_weight="3" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="5"
android:background="@drawable/drw_bottom_layout_bg"
android:orientation="vertical"
android:visibility="visible">
<LinearLayout
android:id="@+id/bottomCloseBtn"
android:layout_width="match_parent"
android:layout_height="32dp"
android:gravity="center">
<TextView
android:layout_width="30dp"
android:layout_height="4dp"
android:background="@drawable/drw_bottom_close_btn_bg"
android:gravity="center" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playListRv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -78,6 +78,7 @@
</LinearLayout> </LinearLayout>
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/sv"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="-2dp"> android:layout_marginTop="-2dp">

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingDefaultResource">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="12dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="0dp"
android:visibility="visible"
app:cardCornerRadius="4dp"
app:cardElevation="0dp">
<RelativeLayout
android:layout_width="48dp"
android:layout_height="48dp">
<ImageView
android:id="@+id/image"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="centerCrop"
android:src="@mipmap/musicoo_logo_img" />
<RelativeLayout
android:id="@+id/currentPlayingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black_60"
android:gravity="center"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/playing_small_green_icon" />
</RelativeLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
<com.player.musicoo.view.MarqueeTextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/regular_font"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="14dp" />
<com.player.musicoo.view.MarqueeTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/regular_font"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/white_60"
android:textSize="12dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -8,4 +8,6 @@
<color name="white_80">#CCFFFFFF</color> <color name="white_80">#CCFFFFFF</color>
<color name="main_bg_color">#151718</color> <color name="main_bg_color">#151718</color>
<color name="green">#FF80F988</color> <color name="green">#FF80F988</color>
<color name="green_60">#9980F988</color>
<color name="bottom_layout_bg_color">#1A1A1A</color>
</resources> </resources>