This commit is contained in:
ocean 2024-05-10 16:56:04 +08:00
parent 619a588559
commit 195ec0f242
18 changed files with 594 additions and 259 deletions

View File

@ -3,15 +3,17 @@ package com.player.musicoo.activity
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.util.Log
import com.gyf.immersionbar.ktx.immersionBar
import com.player.musicoo.databinding.ActivityLaunchBinding
import com.player.musicoo.util.LogTag
class LaunchActivity : BaseActivity() {
private lateinit var binding: ActivityLaunchBinding
private val totalTime = 3000 // 5秒
private val interval = 50 // 更新间隔,毫秒
private val totalTime = 3000L // 5秒
private val interval = 50L // 更新间隔,毫秒
private val steps = totalTime / interval
private val progressPerStep = 100 / steps
private val progressPerStep = 100f / steps.toFloat()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLaunchBinding.inflate(layoutInflater)
@ -26,13 +28,13 @@ class LaunchActivity : BaseActivity() {
private fun initTimer() {
val progressBar = binding.customProgressBar
val timer = object : CountDownTimer(totalTime.toLong(), interval.toLong()) {
val timer = object : CountDownTimer(totalTime, interval) {
override fun onTick(millisUntilFinished: Long) {
progressBar.setProgress(progressBar.getProgress() + progressPerStep)
}
override fun onFinish() {
progressBar.setProgress(100)
progressBar.setProgress(100f)
toMainActivity()
}
}

View File

@ -17,6 +17,7 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.player.musicoo.R
import com.player.musicoo.media.MediaControllerManager
import com.player.musicoo.sp.AppStore
import com.player.musicoo.util.LogTag
import kotlinx.coroutines.CoroutineScope
@ -40,7 +41,7 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
protected val TAG = LogTag.VO_ACT_LOG
protected val appStore by lazy { AppStore(this) }
protected val events = Channel<Event>(Channel.UNLIMITED)
protected val meController = MediaControllerManager.getController()
protected abstract suspend fun main()
private var defer: suspend () -> Unit = {}
private var deferRunning = false

View File

@ -1,6 +1,5 @@
package com.player.musicoo.activity
import android.util.Log
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.gyf.immersionbar.ktx.immersionBar
@ -8,6 +7,7 @@ import com.player.musicoo.adapter.DetailsListAdapter
import com.player.musicoo.databinding.ActivityDetailsBinding
import com.player.musicoo.innertube.Innertube
import com.player.musicoo.innertube.requests.moPlaylistPage
import com.player.musicoo.util.LogTag.LogD
class MoListDetailsActivity : MoBaseActivity() {
@ -27,7 +27,7 @@ class MoListDetailsActivity : MoBaseActivity() {
return
}
initView()
Log.d(TAG, "browseId->${browseId}")
LogD(TAG, "browseId->${browseId}")
initData(browseId)
}
@ -61,7 +61,7 @@ class MoListDetailsActivity : MoBaseActivity() {
binding.rv.adapter = adapter
}?.onFailure {
Log.d(TAG, "moPlaylistPage onFailure->${it}")
LogD(TAG, "moPlaylistPage onFailure->${it}")
}
}
}

View File

@ -3,33 +3,26 @@ package com.player.musicoo.activity
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import android.view.View
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaController
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.innertube.models.bodies.PlayerBody
import com.player.musicoo.innertube.requests.player
import com.player.musicoo.media.MediaControllerManager
import com.player.musicoo.media.SongRadio
import com.player.musicoo.service.LoginRequiredException
import com.player.musicoo.service.PlayableFormatNotFoundException
import com.player.musicoo.service.UnplayableException
import com.player.musicoo.service.VideoIdMismatchException
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.runBlocking
import kotlinx.coroutines.selects.select
import com.player.musicoo.util.LogTag.LogD
@OptIn(UnstableApi::class)
class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
@ -41,12 +34,15 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
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)
@ -59,77 +55,146 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
setContentView(binding.root)
initImmersionBar()
initClick()
initPlayerListener()
val videoId = intent.getStringExtra(PLAY_DETAILS_VIDEO_ID)
Log.d(TAG, "MoPlayDetailsActivity main videoId->$videoId")
val playlistId = intent.getStringExtra(PLAY_DETAILS_PLAY_LIST_ID)
Log.d(TAG, "MoPlayDetailsActivity main playlistId->$playlistId")
val playlistSetVideoId = intent.getStringExtra(PLAY_DETAILS_PLAY_LIST_SET_VIDEO_ID)
Log.d(TAG, "MoPlayDetailsActivity main playlistSetVideoId->$playlistSetVideoId")
val params = intent.getStringExtra(PLAY_DETAILS_PLAY_PARAMS)
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
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
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()
}
//传入进来的ID就是进入此界面的当前ID
currentVideoID = videoId
//根据进来界面的当前ID来获取资源。
initData(
videoId,
playlistId,
playlistSetVideoId,
params
)
}
private fun initClick() {
val currentPlayer = MediaControllerManager.getController()
binding.disableClicksLayout.setOnClickListener { }
binding.backBtn.setOnClickListener {
finish()
}
binding.playLayoutBtn.setOnClickListener {
if (currentPlayer != null) {
if (currentPlayer.isPlaying) {
currentPlayer.pause()
if (meController != null) {
if (meController.isPlaying) {
meController.pause()
updatePlayState(false)
} else {
currentPlayer.play()
meController.play()
updatePlayState(true)
}
updateProgressState()
}
}
binding.playSkipBackBtn.setOnClickListener {
if (currentPlayer != null) {
currentPlayer.seekToPreviousMediaItem()
if (meController != null) {
meController.seekToPreviousMediaItem()
updateProgressUi()
updateInfoUi(currentPlayer.currentMediaItem)
updateInfoUi(meController.currentMediaItem)
updateProgressState()
if (!meController.isPlaying) {
meController.prepare()
meController.play()
}
}
}
binding.playSkipForwardBtn.setOnClickListener {
if (currentPlayer != null) {
currentPlayer.seekToNextMediaItem()
if (meController != null) {
meController.seekToNextMediaItem()
updateProgressUi()
updateInfoUi(currentPlayer.currentMediaItem)
updateInfoUi(meController.currentMediaItem)
updateProgressState()
if (!meController.isPlaying) {
meController.prepare()
meController.play()
}
}
}
binding.listLayoutBtn.setOnClickListener {
Log.d(TAG, "currentPlayer?.mediaItemCount->${currentPlayer?.mediaItemCount}")
LogD(TAG, "meController?.mediaItemCount->${meController?.mediaItemCount}")
}
binding.progressBar.progress = 0
binding.sbProgress.value = 0f
binding.sbProgress.addOnChangeListener { slider, value, fromUser ->
if (fromUser) {
if (currentPlayer != null) {
currentPlayer.seekTo(value.toLong())
val ss = currentPlayer.isPlaying
if (meController != null) {
meController.seekTo(value.toLong())
val ss = meController.isPlaying
if (!ss) {
currentPlayer.play()
meController.play()
}
}
}
@ -149,12 +214,11 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
parameters
).let {
launch(Dispatchers.Main) {
val ss = it.process()//获取到的资源集合
val songRadioList = it.process()//获取到的资源集合
if (ss.isEmpty()) {//集合为空则展示错误提示
if (songRadioList.isEmpty()) {//集合为空则展示错误提示
binding.loadingView.visibility = View.GONE
binding.playbackErrorLayout.visibility = View.VISIBLE
binding.disableClicksLayout.visibility = View.VISIBLE
}
if (isFinishing) {
@ -162,9 +226,7 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
}
var mediaItem: MediaItem? = null
Log.d(TAG, "size = ${ss.size}")
Log.d(TAG, "MoPlayDetailsActivity initData currentVideoID->$currentVideoID")
for (song: Innertube.SongItem in ss) {
for (song: Innertube.SongItem in songRadioList) {
if (song.key == currentVideoID) {//判断当前ID得到一个mediaItem
mediaItem = song.asMediaItem
break
@ -175,7 +237,6 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
updateInfoUi(mediaItem)
binding.playbackErrorLayout.visibility = View.GONE
binding.disableClicksLayout.visibility = View.GONE
binding.totalDurationTv.visibility = View.GONE
@ -190,18 +251,18 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
)
.build()
val meController = MediaControllerManager.getController()
meController?.let {
it.addListener(playerListener)
it.setMediaItem(newMediaItem, true)
it.prepare()
it.play()
it.addMediaItems(ss.map(Innertube.SongItem::asMediaItem).drop(1))
//过滤掉进入页面的id得到集合addMediaItems
val mediaItems = songRadioList.map(Innertube.SongItem::asMediaItem)
.filter { filter -> filter.mediaId != videoId }
it.addMediaItems(mediaItems)
}
} else {
binding.playbackErrorLayout.visibility = View.VISIBLE
binding.disableClicksLayout.visibility = View.VISIBLE
}
}
}
@ -213,29 +274,30 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
newPosition: Player.PositionInfo,
reason: Int
) {
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
val meController = MediaControllerManager.getController()
//保证自动播放完毕当前歌曲与通知切换歌曲可以更新UI信息
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION
|| reason == Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT
) {
updateInfoUi(meController?.currentMediaItem)
}
}
override fun onPlayerError(error: PlaybackException) {
Log.d(TAG, "onPlayerError error= $error")
LogD(TAG, "onPlayerError error= $error")
binding.playbackErrorLayout.visibility = View.VISIBLE//展示错误提示
updatePlayState(false)
}
override fun onPlayWhenReadyChanged(
playWhenReady: Boolean,
reason: Int
) {
Log.d(TAG, "onPlayWhenReadyChanged = $playWhenReady")
updatePlayState(playWhenReady)
updateProgressState()
}
override fun onPlaybackStateChanged(playbackState: Int) {
Log.d(TAG, "onPlaybackStateChanged = $playbackState")
when (playbackState) {
Player.STATE_BUFFERING -> {
binding.loadingView.visibility = View.VISIBLE
@ -266,13 +328,14 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
override fun onDestroy() {
super.onDestroy()
MediaControllerManager.getController()?.removeListener(playerListener)
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?) {
@ -292,10 +355,9 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
* 更新播放进度
*/
private fun updateProgressState() {
val currentPlayer = MediaControllerManager.getController()
//判断是否ready与播放中否则停止更新进度
if (currentPlayer != null && currentPlayer.playbackState == Player.STATE_READY && currentPlayer.isPlaying) {
updatePlayState(currentPlayer.isPlaying)
if (meController != null && meController.playbackState == Player.STATE_READY && meController.isPlaying) {
updatePlayState(meController.isPlaying)
progressHandler.removeCallbacksAndMessages(null)
progressHandler.sendEmptyMessage(1)
} else {
@ -308,14 +370,13 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
*/
private val progressHandler = object : Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) {
val currentPlayer = MediaControllerManager.getController()
//判断是否ready与播放中否则停止更新进度
if (currentPlayer != null && currentPlayer.playbackState == Player.STATE_READY && currentPlayer.isPlaying) {
val currentPosition = currentPlayer.currentPosition
if (meController != null && meController.playbackState == Player.STATE_READY && meController.isPlaying) {
val currentPosition = meController.currentPosition
val currentString = convertMillisToMinutesAndSecondsString(currentPosition)
binding.progressDurationTv.text = currentString
val currentBufferedPosition = currentPlayer.bufferedPosition
val currentBufferedPosition = meController.bufferedPosition
binding.progressBar.progress = currentBufferedPosition.toInt()
// 更新 SeekBar 的进度
@ -330,8 +391,7 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
* 更新缓冲进度
*/
private fun updateProgressBufferingState() {
val currentPlayer = MediaControllerManager.getController()
if (currentPlayer != null && currentPlayer.isLoading) {
if (meController != null && meController.isLoading) {
progressBufferingHandler.removeCallbacksAndMessages(null)
progressBufferingHandler.sendEmptyMessage(1)
} else {
@ -344,9 +404,8 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
*/
private val progressBufferingHandler = object : Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) {
val currentPlayer = MediaControllerManager.getController()
if (currentPlayer != null && currentPlayer.isLoading) {
val currentBufferedPosition = currentPlayer.bufferedPosition
if (meController != null && meController.isLoading) {
val currentBufferedPosition = meController.bufferedPosition
binding.progressBar.progress = currentBufferedPosition.toInt()
sendEmptyMessageDelayed(1, 50)
}
@ -360,44 +419,4 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
binding.playImg.setImageResource(R.drawable.play_green_icon)
}
}
private fun getSourceUrl(videoId: String): String {
return runBlocking(Dispatchers.IO) {
Innertube.player(PlayerBody(videoId = videoId))
}?.mapCatching { body ->
if (body.videoDetails?.videoId != videoId) {
throw VideoIdMismatchException()
}
when (val status = body.playabilityStatus?.status) {
"OK" -> body.streamingData?.highestQualityFormat?.let { format ->
format.url
} ?: throw PlayableFormatNotFoundException()
"UNPLAYABLE" -> throw UnplayableException()
"LOGIN_REQUIRED" -> throw LoginRequiredException()
else -> throw PlaybackException(
status,
null,
PlaybackException.ERROR_CODE_REMOTE_ERROR
)
}
}?.getOrNull() ?: ""
}
private fun retryPlayback(
player: MediaController,
mediaId: String,
url: String,
mediaMetadata: MediaMetadata
) {
val mediaItem =
MediaItem.Builder()
.setMediaId(mediaId)
.setUri(url)
.setMediaMetadata(mediaMetadata)
.build()
player.setMediaItem(mediaItem)
player.prepare()
player.play()
}
}

View File

@ -1,11 +1,24 @@
package com.player.musicoo.activity
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import com.bumptech.glide.Glide
import com.player.musicoo.R
import com.player.musicoo.databinding.ActivityPrimaryBinding
import com.player.musicoo.fragment.ImportFragment
import com.player.musicoo.fragment.MoHomeFragment
import com.player.musicoo.media.MediaControllerManager
import com.player.musicoo.util.LogTag.LogD
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
class PrimaryActivity : MoBaseActivity() {
/**
@ -22,6 +35,9 @@ class PrimaryActivity : MoBaseActivity() {
binding = ActivityPrimaryBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
initPlayerListener()
onReceive()
}
private fun initView() {
@ -38,6 +54,28 @@ class PrimaryActivity : MoBaseActivity() {
changeFragment(1)
updateBtnState(1)
}
binding.playBlackBtn.setOnClickListener {
if (meController != null) {
if (meController.isPlaying) {
meController.pause()
updatePlayState(false)
} else {
meController.play()
updatePlayState(true)
}
updateProgressState()
}
}
binding.goDetailsBtn.setOnClickListener {
val intent = Intent(this, MoPlayDetailsActivity::class.java)
intent.putExtra(
MoPlayDetailsActivity.PLAY_DETAILS_COME_FROM,
PrimaryActivity::class.java
)
startActivity(intent)
}
}
private fun initFragment() {
@ -86,4 +124,141 @@ class PrimaryActivity : MoBaseActivity() {
)
}
}
private suspend fun onReceive() {
while (isActive) {
select<Unit> {
events.onReceive {
when (it) {
Event.ActivityOnResume -> {
activityOnResume()
}
else -> {}
}
}
}
}
}
private fun activityOnResume() {
if (meController != null && meController.mediaItemCount > 0 && meController.duration > 0) {
binding.playingStatusLayout.visibility = View.VISIBLE
updateInfoUi(meController.currentMediaItem)
} else {
binding.playingStatusLayout.visibility = View.GONE
}
}
private fun initPlayerListener() {
meController?.addListener(playerListener)
}
private val playerListener = object : Player.Listener {
override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
updateInfoUi(meController?.currentMediaItem)
}
}
override fun onPlaybackStateChanged(playbackState: Int) {
LogD(TAG, "playbackState->$playbackState")
updateProgressState()
when (playbackState) {
Player.STATE_READY -> {
if (meController != null) {
LogD(TAG, "meController.duration->${meController.duration}")
binding.progressBar.setMaxProgress(MediaControllerManager.getDuration())
val currentPosition = meController.currentPosition
binding.progressBar.setProgress(currentPosition)
}
}
else -> {}
}
}
override fun onPlayWhenReadyChanged(
playWhenReady: Boolean,
reason: Int
) {
updatePlayState(playWhenReady)
updateProgressState()
}
}
override fun onDestroy() {
super.onDestroy()
meController?.removeListener(playerListener)
}
private var backPressedTime: Long = 0
private val backToast: Toast by lazy {
Toast.makeText(baseContext, getString(R.string.exit_main_text), Toast.LENGTH_SHORT)
}
override fun onBackPressed() {
if (backPressedTime + 2000 > System.currentTimeMillis()) {
super.onBackPressed()
backToast.cancel()
return
} else {
backToast.show()
}
backPressedTime = System.currentTimeMillis()
}
/**
* 更新播放进度
*/
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
binding.progressBar.setProgress(currentPosition)
sendEmptyMessageDelayed(1, 50)
}
}
}
private fun updatePlayState(b: Boolean) {
if (b) {
binding.playStatusImg.setImageResource(R.drawable.playing_black_icon)
} else {
binding.playStatusImg.setImageResource(R.drawable.play_black_icon)
}
}
private fun updateInfoUi(mediaItem: MediaItem?) {
if (mediaItem == null) {
return
}
Glide.with(this@PrimaryActivity)
.load(mediaItem.mediaMetadata.artworkUri)
.into(binding.audioImg)
binding.name.text = mediaItem.mediaMetadata.title
binding.desc.text = mediaItem.mediaMetadata.artist
}
}

View File

@ -1,7 +1,7 @@
package com.player.musicoo.fragment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.gyf.immersionbar.ktx.immersionBar
import com.player.musicoo.databinding.FragmentMoHomeBinding
@ -9,16 +9,28 @@ import com.player.musicoo.innertube.Innertube
import com.player.musicoo.innertube.models.MusicCarouselShelfRenderer
import com.player.musicoo.innertube.requests.homePage
import com.player.musicoo.innertube.requests.homePageMore
import com.player.musicoo.util.LogTag.LogD
import com.player.musicoo.view.MusicResponsiveListView
import com.player.musicoo.view.MusicTowRowListView
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
class MoHomeFragment : MoBaseFragment<FragmentMoHomeBinding>() {
private val requests: Channel<Request> = Channel(Channel.UNLIMITED)
enum class Request {
TryAgain,
}
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentMoHomeBinding
get() = FragmentMoHomeBinding::inflate
override suspend fun onViewCreated() {
initView()
initData()
onReceive()
}
private fun initImmersionBar() {
@ -28,8 +40,30 @@ class MoHomeFragment : MoBaseFragment<FragmentMoHomeBinding>() {
}
}
private suspend fun initView() {
private suspend fun onReceive() {
while (isActive) {
select<Unit> {
requests.onReceive {
when (it) {
Request.TryAgain -> {
initData()
}
}
}
}
}
}
private fun initView() {
binding.tryAgainBtn.setOnClickListener {
requests.trySend(Request.TryAgain)
}
}
private suspend fun initData() {
showLoadingUi()
Innertube.homePage()?.onSuccess {
showDataUi()
if (it.homePage.isNotEmpty()) {
for (home: Innertube.HomePage in it.homePage) {
for (content: MusicCarouselShelfRenderer.Content in home.contents) {
@ -55,9 +89,12 @@ class MoHomeFragment : MoBaseFragment<FragmentMoHomeBinding>() {
}
initHomeDataMore(it)
} else {
Log.d(TAG, "homePage size 0")
LogD(TAG, "homePage size 0")
}
}?.onFailure { Log.d(TAG, "homePage onFailure->${it}") }
}?.onFailure {
showNoContentUi()
LogD(TAG, "homePage onFailure->${it}")
}
}
private suspend fun initHomeDataMore(baseHomePage: Innertube.BaseHomePage) {
@ -87,7 +124,7 @@ class MoHomeFragment : MoBaseFragment<FragmentMoHomeBinding>() {
}
initHomeDataMore(it)
}?.onFailure {
Log.d(TAG, "initHomeDataMore onFailure ->${it}")
LogD(TAG, "initHomeDataMore onFailure ->${it}")
}
}
}
@ -103,4 +140,19 @@ class MoHomeFragment : MoBaseFragment<FragmentMoHomeBinding>() {
initImmersionBar()
}
}
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

@ -167,42 +167,13 @@ object MediaControllerManager {
return false
}
fun isPlaying(): Boolean {
mediaController?.let {
return it.isPlaying
}
return false
}
fun play() {
mediaController?.play()
}
fun getMediaItemCount(): Int {
mediaController?.let {
return it.mediaItemCount
}
return 0
}
fun getCurrentMediaItem(): MediaItem? {
mediaController?.let {
return it.currentMediaItem
}
return null
}
fun getDuration(): Long {
mediaController?.let {
return it.duration
}
return 0
}
fun getTotalBufferedDuration():Long{
mediaController?.let {
return it.totalBufferedDuration
if (it.duration > 0) {
return it.duration
} else {
return 0
}
}
return 0
}

View File

@ -1,12 +1,15 @@
package com.player.musicoo.service
import android.content.Context
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.*
import androidx.media3.session.CommandButton
import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.MediaNotification
import androidx.media3.session.MediaSession
import com.google.common.collect.ImmutableList
import com.player.musicoo.util.LogTag
import com.player.musicoo.util.LogTag.LogD
@UnstableApi
class CustomMediaNotificationProvider(context: Context) :
@ -19,12 +22,12 @@ class CustomMediaNotificationProvider(context: Context) :
actionFactory: MediaNotification.ActionFactory
): IntArray {
Log.d(LogTag.VO_API_LOG, "mediaButtons->$mediaButtons")
LogD(LogTag.VO_API_LOG, "mediaButtons->$mediaButtons")
for (com: CommandButton in mediaButtons) {
Log.d(LogTag.VO_API_LOG, "displayName->${com.displayName}")
Log.d(LogTag.VO_API_LOG, "playerCommand->${com.playerCommand}")
Log.d(LogTag.VO_API_LOG, "------------------------------------------")
LogD(LogTag.VO_API_LOG, "displayName->${com.displayName}")
LogD(LogTag.VO_API_LOG, "playerCommand->${com.playerCommand}")
LogD(LogTag.VO_API_LOG, "------------------------------------------")
}
val notificationMediaButtons = ImmutableList.builder<CommandButton>().apply {

View File

@ -41,6 +41,7 @@ import com.player.musicoo.innertube.models.bodies.PlayerBody
import com.player.musicoo.innertube.requests.player
import com.player.musicoo.util.ExoPlayerDiskCacheMaxSize
import com.player.musicoo.util.LogTag
import com.player.musicoo.util.LogTag.LogD
import com.player.musicoo.util.RingBuffer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
@ -152,7 +153,6 @@ class PlaybackService : MediaSessionService(), Player.Listener {
return ResolvingDataSource.Factory(createCacheDataSource()) { dataSpec ->
val videoId = dataSpec.key ?: error("A key must be set")
Log.d(TAG,"111111 videoId")
if (cache.isCached(videoId, dataSpec.position, chunkLength)) {
dataSpec
} else {
@ -181,7 +181,7 @@ class PlaybackService : MediaSessionService(), Player.Listener {
)
}
}
Log.d(TAG,"1111 urlResult->$urlResult")
LogD(TAG,"service urlResult->$urlResult")
urlResult?.getOrThrow()?.let { url ->
ringBuffer.append(videoId to url.toUri())

View File

@ -1,8 +1,22 @@
package com.player.musicoo.util
import android.util.Log
object LogTag {
const val VO_ACT_LOG = "vo-act—log"
const val VO_FRAGMENT_LOG = "vo-fragment-log"
const val VO_API_LOG = "vo-api—log"
const val VO_SERVICE_LOG = "vo-service—log"
fun LogD(tag: String, message: String) {
Log.d(tag, message)
}
fun LogE(tag: String, message: String) {
Log.e(tag, message)
}
fun LogI(tag: String, message: String) {
Log.i(tag, message)
}
}

View File

@ -11,8 +11,8 @@ import android.util.AttributeSet
import android.view.View
class CustomProgressBar(context: Context, attrs: AttributeSet?) : View(context, attrs) {
private var progress = 0 // 当前进度
private val maxProgress = 100 // 最大进度
private var progress = 0f // 当前进度
private val maxProgress = 100f // 最大进度
private val progressBarHeight = 20f // 进度条高度
private val cornerRadius = 10f // 圆角半径
private val backgroundColor = Color.parseColor("#26FFFFFF")
@ -39,7 +39,7 @@ class CustomProgressBar(context: Context, attrs: AttributeSet?) : View(context,
// 计算进度条的宽度
val progressBarWidth = (width * progress.toFloat() / maxProgress).toInt()
val progressBarWidth = (width * progress / maxProgress)
// 创建颜色渐变对象
val gradient = LinearGradient(0f, 0f, width.toFloat(), 0f, intArrayOf(startColor, middleColor, endColor), null, Shader.TileMode.CLAMP)
@ -50,12 +50,12 @@ class CustomProgressBar(context: Context, attrs: AttributeSet?) : View(context,
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paintTow)
}
fun getProgress():Int{
fun getProgress():Float{
return progress
}
// 设置进度
fun setProgress(progress: Int) {
fun setProgress(progress: Float) {
this.progress = progress
invalidate() // 请求重绘
}

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="#FF80F988" />
<corners android:radius="24dp" />
</shape>

View File

@ -117,7 +117,10 @@
android:id="@+id/loading_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
android:layout_centerInParent="true"
android:indeterminateTint="@color/green"
android:progressBackgroundTint="@color/green"
android:progressTint="@color/green" />
<TextView
android:id="@+id/playback_error_layout"

View File

@ -10,12 +10,19 @@
android:layout_width="match_parent"
android:layout_height="0dp" />
<FrameLayout
android:id="@+id/frame_layout"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/bottom_layout"
android:layout_below="@+id/view" />
android:layout_below="@+id/view"
android:orientation="vertical">
<FrameLayout
android:id="@+id/frame_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_layout"
@ -29,8 +36,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:visibility="gone"
android:layout_marginEnd="12dp"
android:visibility="visible"
app:hl_cornerRadius="36dp"
app:hl_layoutBackground="#FF80F988"
app:hl_shadowColor="#40040604"
@ -39,76 +46,87 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="78dp"
android:layout_height="68dp"
android:layout_alignParentBottom="true"
android:gravity="center_vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:elevation="0dp"
app:cardCornerRadius="24dp"
app:cardElevation="0dp">
<ImageView
android:id="@+id/audio_img"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@mipmap/breathe" />
</androidx.cardview.widget.CardView>
<com.player.musicoo.view.CircularProgressBar
android:id="@+id/progressBar"
android:layout_width="54dp"
android:layout_height="54dp"
android:layout_centerInParent="true" />
</RelativeLayout>
<LinearLayout
android:id="@+id/goDetailsBtn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
android:orientation="horizontal">
<com.player.musicoo.view.MarqueeTextView
android:id="@+id/name"
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="marquee"
android:fontFamily="@font/medium_font"
android:text="@string/app_name"
android:textColor="@color/black"
android:textSize="16dp" />
android:layout_height="wrap_content">
<com.player.musicoo.view.MarqueeTextView
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/black_60"
android:textSize="12dp" />
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:elevation="0dp"
app:cardCornerRadius="24dp"
app:cardElevation="0dp">
<ImageView
android:id="@+id/audio_img"
android:layout_width="48dp"
android:scaleType="centerCrop"
android:layout_height="48dp"
android:src="@mipmap/musicoo_logo_img" />
</androidx.cardview.widget.CardView>
<com.player.musicoo.view.CircularProgressBar
android:id="@+id/progressBar"
android:layout_width="54dp"
android:layout_height="54dp"
android:layout_centerInParent="true" />
</RelativeLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
<com.player.musicoo.view.MarqueeTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fontFamily="@font/medium_font"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/black"
android:textSize="16dp" />
<com.player.musicoo.view.MarqueeTextView
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/black_60"
android:textSize="12dp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/alarm_clock_btn"
android:layout_width="42dp"
android:visibility="gone"
android:layout_height="42dp"
android:gravity="center">
android:gravity="center"
android:visibility="gone">
<ImageView
android:layout_width="34dp"

View File

@ -73,6 +73,9 @@
<ProgressBar
android:layout_width="wrap_content"
android:indeterminateTint="@color/green"
android:progressBackgroundTint="@color/green"
android:progressTint="@color/green"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -16,42 +16,105 @@
android:layout_width="match_parent"
android:layout_height="0dp" />
<androidx.core.widget.NestedScrollView
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/view"
android:layout_marginTop="-2dp">
android:orientation="vertical">
<LinearLayout
android:id="@+id/loadingLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:fontFamily="@font/medium_font"
android:gravity="center"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="20dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_marginStart="16dp"
android:layout_height="wrap_content"
android:orientation="vertical" />
<ProgressBar
android:layout_width="wrap_content"
android:indeterminateTint="@color/green"
android:progressBackgroundTint="@color/green"
android:progressTint="@color/green"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<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>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="-2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:fontFamily="@font/medium_font"
android:gravity="center"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="20dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="vertical" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</RelativeLayout>

View File

@ -19,4 +19,7 @@
<string name="expand">EXPAND</string>
<string name="description">Description</string>
<string name="playback_error">An unknown playback error has occurred</string>
<string name="exit_main_text">Press again to exit</string>
<string name="content_loading_failed">Content loading failed, click to try again.</string>
<string name="try_again">Try Again</string>
</resources>

View File

@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.Musicoo" parent="Theme.Material3.DayNight.NoActionBar">
<style name="Base.Theme.Musicoo" parent="Theme.Material3.Dark.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
<item name="android:progressTint">@color/green</item>