update
This commit is contained in:
parent
619a588559
commit
195ec0f242
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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() // 请求重绘
|
||||
}
|
||||
|
||||
8
app/src/main/res/drawable/drw_btn_bg.xml
Normal file
8
app/src/main/res/drawable/drw_btn_bg.xml
Normal 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>
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user