update
This commit is contained in:
parent
830ad770eb
commit
2a0d2abfea
@ -29,6 +29,13 @@ android {
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
// debug {
|
||||
// isMinifyEnabled = true
|
||||
// proguardFiles(
|
||||
// getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
// "proguard-rules.pro"
|
||||
// )
|
||||
// }
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
@ -18,4 +18,6 @@
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||
@ -7,6 +7,9 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
@ -48,7 +51,10 @@
|
||||
android:name=".activity.AboutActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activity.DetailsActivity"
|
||||
android:name=".activity.MoListDetailsActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activity.MoPlayDetailsActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<service
|
||||
|
||||
@ -9,7 +9,7 @@ import com.player.musicoo.databinding.ActivityDetailsBinding
|
||||
import com.player.musicoo.innertube.Innertube
|
||||
import com.player.musicoo.innertube.requests.moPlaylistPage
|
||||
|
||||
class DetailsActivity : MoBaseActivity() {
|
||||
class MoListDetailsActivity : MoBaseActivity() {
|
||||
|
||||
companion object {
|
||||
const val PLAY_LIST_PAGE_BROWSE_ID = "play_list_page_browse_id"
|
||||
@ -0,0 +1,262 @@
|
||||
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 android.widget.SeekBar
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.bumptech.glide.Glide
|
||||
import com.gyf.immersionbar.ktx.immersionBar
|
||||
import com.player.musicoo.R
|
||||
import com.player.musicoo.databinding.ActivityMoPlayDetailsBinding
|
||||
import com.player.musicoo.innertube.Innertube
|
||||
import com.player.musicoo.innertube.models.bodies.PlayerBody
|
||||
import com.player.musicoo.innertube.requests.moNextPage
|
||||
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.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||||
|
||||
companion object {
|
||||
const val PLAY_DETAILS_VIDEO_ID = "play_details_videoId"
|
||||
const val PLAY_DETAILS_PLAY_LIST_ID = "play_details_playlistId"
|
||||
const val PLAY_DETAILS_PLAY_LIST_SET_VIDEO_ID = "play_details_play_list_set_video_id"
|
||||
const val PLAY_DETAILS_PLAY_PARAMS = "play_details_play_params"
|
||||
const val PLAY_DETAILS_NAME = "play_details_name"
|
||||
const val PLAY_DETAILS_DESC = "play_details_desc"
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityMoPlayDetailsBinding
|
||||
|
||||
private var currentVideoID = ""
|
||||
|
||||
private fun initImmersionBar() {
|
||||
immersionBar {
|
||||
statusBarDarkFont(false)
|
||||
statusBarView(binding.view)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun main() {
|
||||
binding = ActivityMoPlayDetailsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
initImmersionBar()
|
||||
initClick()
|
||||
|
||||
val videoId = intent.getStringExtra(PLAY_DETAILS_VIDEO_ID)
|
||||
val playlistId = intent.getStringExtra(PLAY_DETAILS_PLAY_LIST_ID)
|
||||
val playlistSetVideoId = intent.getStringExtra(PLAY_DETAILS_PLAY_LIST_SET_VIDEO_ID)
|
||||
val params = intent.getStringExtra(PLAY_DETAILS_PLAY_PARAMS)
|
||||
|
||||
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
|
||||
binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
|
||||
|
||||
if (videoId.isNullOrEmpty()) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
currentVideoID = videoId
|
||||
initData(
|
||||
videoId,
|
||||
playlistId,
|
||||
playlistSetVideoId,
|
||||
params
|
||||
)
|
||||
}
|
||||
|
||||
private fun initClick() {
|
||||
binding.backBtn.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
binding.playImg.setOnClickListener {
|
||||
Log.d(TAG, "点击了播放按钮")
|
||||
}
|
||||
|
||||
binding.sbProgress.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar?) {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun initData(
|
||||
videoId: String,
|
||||
playlistId: String? = null,
|
||||
playlistSetVideoId: String? = null,
|
||||
parameters: String? = null
|
||||
) {
|
||||
SongRadio(
|
||||
videoId,
|
||||
playlistId,
|
||||
playlistSetVideoId,
|
||||
parameters
|
||||
).let {
|
||||
launch(Dispatchers.Main) {
|
||||
val ss = it.process()
|
||||
|
||||
if (ss.isEmpty()) {
|
||||
binding.loadingView.visibility = View.GONE
|
||||
binding.playbackErrorLayout.visibility = View.VISIBLE
|
||||
binding.disableClicksLayout.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (isFinishing) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
var mediaItem: MediaItem? = null
|
||||
Log.d(TAG, "size = ${ss.size}")
|
||||
|
||||
for (song: Innertube.SongItem in ss) {
|
||||
if (song.key == currentVideoID) {
|
||||
mediaItem = song.asMediaItem
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaItem != null) {
|
||||
binding.loadingView.visibility = View.GONE
|
||||
Glide.with(this@MoPlayDetailsActivity)
|
||||
.load(mediaItem.mediaMetadata.artworkUri)
|
||||
.into(binding.thumbnail)
|
||||
|
||||
binding.nameTv.text = mediaItem.mediaMetadata.title
|
||||
binding.descTv.text = mediaItem.mediaMetadata.artist
|
||||
|
||||
val urlResult = 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
|
||||
)
|
||||
}
|
||||
}
|
||||
val url = urlResult?.getOrNull()
|
||||
if (url != null) {
|
||||
binding.playbackErrorLayout.visibility = View.GONE
|
||||
binding.disableClicksLayout.visibility = View.GONE
|
||||
|
||||
binding.totalDurationTv.visibility = View.GONE
|
||||
|
||||
val newMediaItem =
|
||||
MediaItem.Builder()
|
||||
.setUri(url)
|
||||
.setMediaMetadata(mediaItem.mediaMetadata)
|
||||
.build()
|
||||
|
||||
MediaControllerManager.getController()?.let {
|
||||
it.addListener(object : Player.Listener {
|
||||
override fun onPlayerError(error: PlaybackException) {
|
||||
Log.d(TAG, "onPlayerError = $error")
|
||||
}
|
||||
|
||||
override fun onPlayWhenReadyChanged(
|
||||
playWhenReady: Boolean,
|
||||
reason: Int
|
||||
) {
|
||||
Log.d(TAG, "onPlayWhenReadyChanged = $playWhenReady")
|
||||
updateProgressState()
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
Log.d(TAG, "onPlaybackStateChanged = $playbackState")
|
||||
if (playbackState == Player.STATE_READY) {
|
||||
binding.totalDurationTv.visibility = View.VISIBLE
|
||||
binding.totalDurationTv.text =
|
||||
convertMillisToMinutesAndSecondsString(
|
||||
MediaControllerManager.getDuration()
|
||||
)
|
||||
binding.sbProgress.max =
|
||||
MediaControllerManager.getDuration().toInt()
|
||||
}
|
||||
updateProgressState()
|
||||
}
|
||||
})
|
||||
it.setMediaItem(newMediaItem)
|
||||
it.repeatMode = Player.REPEAT_MODE_ALL
|
||||
it.prepare()
|
||||
it.play()
|
||||
}
|
||||
} else {
|
||||
binding.playbackErrorLayout.visibility = View.VISIBLE
|
||||
binding.disableClicksLayout.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProgressState() {
|
||||
val currentPlayer = MediaControllerManager.getController()
|
||||
if (currentPlayer != null && currentPlayer.playbackState == Player.STATE_READY && currentPlayer.isPlaying) {
|
||||
// updatePlayState(currentPlayer.isPlaying)
|
||||
progressHandler.removeCallbacksAndMessages(null)
|
||||
progressHandler.sendEmptyMessage(1)
|
||||
} else {
|
||||
progressHandler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
}
|
||||
|
||||
private val progressHandler = object : Handler(Looper.myLooper()!!) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
val currentPlayer = MediaControllerManager.getController()
|
||||
if (currentPlayer != null && currentPlayer.playbackState == Player.STATE_READY && currentPlayer.isPlaying) {
|
||||
val currentPosition = currentPlayer.currentPosition
|
||||
val currentString = convertMillisToMinutesAndSecondsString(currentPosition)
|
||||
binding.progressDurationTv.text = currentString
|
||||
|
||||
// 更新 SeekBar 的进度
|
||||
binding.sbProgress.progress = currentPosition.toInt()
|
||||
|
||||
sendEmptyMessageDelayed(1, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePlayState(b: Boolean) {
|
||||
if (b) {
|
||||
binding.playImg.setImageResource(R.drawable.playing_green_icon)
|
||||
} else {
|
||||
binding.playImg.setImageResource(R.drawable.play_green_icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,20 +3,13 @@ package com.player.musicoo.adapter
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.player.musicoo.App
|
||||
import com.player.musicoo.R
|
||||
import com.player.musicoo.activity.PlayDetailsActivity
|
||||
import com.player.musicoo.bean.Audio
|
||||
import com.player.musicoo.activity.MoPlayDetailsActivity
|
||||
import com.player.musicoo.databinding.MusicResponsiveItemBinding
|
||||
import com.player.musicoo.databinding.SoundsOfAppliancesLayoutBinding
|
||||
import com.player.musicoo.databinding.SoundsOfNatureLayoutBinding
|
||||
import com.player.musicoo.innertube.models.MusicCarouselShelfRenderer
|
||||
import com.player.musicoo.util.convertMillisToMinutesAndSecondsString
|
||||
import com.player.musicoo.util.getAudioDurationFromAssets
|
||||
import com.player.musicoo.innertube.models.bodies.NextBody
|
||||
|
||||
class ResponsiveListAdapter(
|
||||
private val context: Context,
|
||||
@ -32,7 +25,61 @@ class ResponsiveListAdapter(
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val bean = list[position]
|
||||
holder.bind(bean)
|
||||
|
||||
val url = bean.musicResponsiveListItemRenderer
|
||||
?.thumbnail
|
||||
?.musicThumbnailRenderer
|
||||
?.thumbnail
|
||||
?.thumbnails
|
||||
?.let { it.getOrNull(1) ?: it.getOrNull(0) }
|
||||
?.url
|
||||
val name = bean.musicResponsiveListItemRenderer
|
||||
?.flexColumns?.get(0)
|
||||
?.musicResponsiveListItemFlexColumnRenderer
|
||||
?.text
|
||||
?.runs
|
||||
?.firstOrNull()
|
||||
?.text
|
||||
val desc = bean.musicResponsiveListItemRenderer
|
||||
?.flexColumns?.get(1)
|
||||
?.musicResponsiveListItemFlexColumnRenderer
|
||||
?.text
|
||||
?.runs
|
||||
?.firstOrNull()
|
||||
?.text
|
||||
|
||||
val watchEndpoint = bean.musicResponsiveListItemRenderer
|
||||
?.flexColumns
|
||||
?.firstOrNull()
|
||||
?.musicResponsiveListItemFlexColumnRenderer
|
||||
?.text
|
||||
?.runs
|
||||
?.firstOrNull()
|
||||
?.navigationEndpoint
|
||||
?.watchEndpoint
|
||||
|
||||
val videoId = watchEndpoint?.videoId
|
||||
val playlistId = watchEndpoint?.playlistId
|
||||
val playlistSetVideoId = watchEndpoint?.playlistSetVideoId
|
||||
val params = watchEndpoint?.params
|
||||
|
||||
holder.bind(url, name, desc)
|
||||
|
||||
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, 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)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = list.size
|
||||
@ -40,28 +87,8 @@ class ResponsiveListAdapter(
|
||||
inner class ViewHolder(private val binding: MusicResponsiveItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(content: MusicCarouselShelfRenderer.Content) {
|
||||
val url = content.musicResponsiveListItemRenderer
|
||||
?.thumbnail
|
||||
?.musicThumbnailRenderer
|
||||
?.thumbnail
|
||||
?.thumbnails
|
||||
?.let { it.getOrNull(1) ?: it.getOrNull(0) }
|
||||
?.url
|
||||
val name = content.musicResponsiveListItemRenderer
|
||||
?.flexColumns?.get(0)
|
||||
?.musicResponsiveListItemFlexColumnRenderer
|
||||
?.text
|
||||
?.runs
|
||||
?.firstOrNull()
|
||||
?.text
|
||||
val desc = content.musicResponsiveListItemRenderer
|
||||
?.flexColumns?.get(1)
|
||||
?.musicResponsiveListItemFlexColumnRenderer
|
||||
?.text
|
||||
?.runs
|
||||
?.firstOrNull()
|
||||
?.text
|
||||
fun bind(url: String?, name: String?, desc: String?) {
|
||||
|
||||
binding.apply {
|
||||
Glide.with(context)
|
||||
.load(url)
|
||||
|
||||
@ -2,25 +2,13 @@ package com.player.musicoo.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.player.musicoo.App
|
||||
import com.player.musicoo.R
|
||||
import com.player.musicoo.activity.DetailsActivity
|
||||
import com.player.musicoo.activity.PlayDetailsActivity
|
||||
import com.player.musicoo.bean.Audio
|
||||
import com.player.musicoo.databinding.MusicResponsiveItemBinding
|
||||
import com.player.musicoo.activity.MoListDetailsActivity
|
||||
import com.player.musicoo.databinding.MusicTowRowItemBinding
|
||||
import com.player.musicoo.databinding.SoundsOfAppliancesLayoutBinding
|
||||
import com.player.musicoo.databinding.SoundsOfNatureLayoutBinding
|
||||
import com.player.musicoo.innertube.models.MusicCarouselShelfRenderer
|
||||
import com.player.musicoo.util.LogTag
|
||||
import com.player.musicoo.util.convertMillisToMinutesAndSecondsString
|
||||
import com.player.musicoo.util.getAudioDurationFromAssets
|
||||
|
||||
class TowRowListAdapter(
|
||||
private val context: Context,
|
||||
@ -49,8 +37,8 @@ class TowRowListAdapter(
|
||||
holder.bind(bean)
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
val intent = Intent(context, DetailsActivity::class.java)
|
||||
intent.putExtra(DetailsActivity.PLAY_LIST_PAGE_BROWSE_ID, browseId)
|
||||
val intent = Intent(context, MoListDetailsActivity::class.java)
|
||||
intent.putExtra(MoListDetailsActivity.PLAY_LIST_PAGE_BROWSE_ID, browseId)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,6 +99,7 @@ object Innertube {
|
||||
val authors: List<Info<NavigationEndpoint.Endpoint.Browse>>?,
|
||||
val album: Info<NavigationEndpoint.Endpoint.Browse>?,
|
||||
val durationText: String?,
|
||||
val bigThumbnail: Thumbnail?,
|
||||
override val thumbnail: Thumbnail?
|
||||
) : Item() {
|
||||
override val key get() = info!!.endpoint!!.videoId!!
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.player.musicoo.innertube.requests
|
||||
|
||||
import android.util.Log
|
||||
import com.player.musicoo.innertube.Innertube
|
||||
import com.player.musicoo.innertube.models.BrowseResponse
|
||||
import com.player.musicoo.innertube.models.Context
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
package com.player.musicoo.innertube.requests
|
||||
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import com.player.musicoo.innertube.Innertube
|
||||
import com.player.musicoo.innertube.models.ContinuationResponse
|
||||
import com.player.musicoo.innertube.models.NextResponse
|
||||
import com.player.musicoo.innertube.models.bodies.ContinuationBody
|
||||
import com.player.musicoo.innertube.models.bodies.NextBody
|
||||
import com.player.musicoo.innertube.utils.from
|
||||
import com.player.musicoo.innertube.utils.runCatchingNonCancellable
|
||||
|
||||
|
||||
suspend fun Innertube.moNextPage(
|
||||
videoId: String,
|
||||
playlistId: String? = null,
|
||||
params: String? = null,
|
||||
playlistSetVideoId: String? = null
|
||||
) =
|
||||
runCatchingNonCancellable {
|
||||
val response = client.post(next) {
|
||||
setBody(
|
||||
NextBody(
|
||||
videoId = videoId,
|
||||
playlistId = playlistId,
|
||||
playlistSetVideoId = playlistSetVideoId,
|
||||
params = params
|
||||
)
|
||||
)
|
||||
}.body<NextResponse>()
|
||||
|
||||
val tabs = response
|
||||
.contents
|
||||
?.singleColumnMusicWatchNextResultsRenderer
|
||||
?.tabbedRenderer
|
||||
?.watchNextTabbedResultsRenderer
|
||||
?.tabs
|
||||
|
||||
val playlistPanelRenderer = tabs
|
||||
?.getOrNull(0)
|
||||
?.tabRenderer
|
||||
?.content
|
||||
?.musicQueueRenderer
|
||||
?.content
|
||||
?.playlistPanelRenderer
|
||||
|
||||
val endpoint = playlistPanelRenderer
|
||||
?.contents
|
||||
?.lastOrNull()
|
||||
?.automixPreviewVideoRenderer
|
||||
?.content
|
||||
?.automixPlaylistVideoRenderer
|
||||
?.navigationEndpoint
|
||||
?.watchPlaylistEndpoint
|
||||
|
||||
|
||||
}
|
||||
@ -39,6 +39,16 @@ fun Innertube.SongItem.Companion.from(renderer: MusicResponsiveListItemRenderer)
|
||||
?.runs
|
||||
?.firstOrNull()
|
||||
?.let(Innertube::Info),
|
||||
bigThumbnail = renderer
|
||||
.thumbnail
|
||||
?.musicThumbnailRenderer
|
||||
?.thumbnail
|
||||
?.thumbnails
|
||||
?.let {
|
||||
it.getOrNull(5) ?: it.getOrNull(4)
|
||||
?: it.getOrNull(3) ?: it.getOrNull(2)
|
||||
?: it.getOrNull(1) ?: it.getOrNull(0)
|
||||
},
|
||||
thumbnail = renderer
|
||||
.thumbnail
|
||||
?.musicThumbnailRenderer
|
||||
|
||||
@ -35,6 +35,8 @@ fun Innertube.SongItem.Companion.from(content: MusicShelfRenderer.Content): Inne
|
||||
durationText = otherRuns
|
||||
.lastOrNull()
|
||||
?.firstOrNull()?.text,
|
||||
bigThumbnail = content
|
||||
.thumbnail,
|
||||
thumbnail = content
|
||||
.thumbnail
|
||||
).takeIf { it.info?.endpoint?.videoId != null }
|
||||
|
||||
@ -24,6 +24,14 @@ fun Innertube.SongItem.Companion.from(renderer: PlaylistPanelVideoRenderer): Inn
|
||||
?.getOrNull(1)
|
||||
?.getOrNull(0)
|
||||
?.let(Innertube::Info),
|
||||
bigThumbnail = renderer
|
||||
.thumbnail
|
||||
?.thumbnails
|
||||
?.let {
|
||||
it.getOrNull(5) ?: it.getOrNull(4)
|
||||
?: it.getOrNull(3) ?: it.getOrNull(2)
|
||||
?: it.getOrNull(1) ?: it.getOrNull(0)
|
||||
},
|
||||
thumbnail = renderer
|
||||
.thumbnail
|
||||
?.thumbnails
|
||||
|
||||
@ -3,7 +3,6 @@ package com.player.musicoo.media
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.Player
|
||||
@ -44,6 +43,28 @@ object MediaControllerManager {
|
||||
}
|
||||
}
|
||||
|
||||
fun setupMedia(id: String, listener: Player.Listener) {
|
||||
val mediaItem =
|
||||
MediaItem.Builder()
|
||||
.setUri(id)
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setArtist("测试")
|
||||
.setTitle("测试")
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
if (isConnected()) {
|
||||
mediaController?.let {
|
||||
it.addListener(listener)
|
||||
it.setMediaItem(mediaItem)
|
||||
it.repeatMode = Player.REPEAT_MODE_ALL
|
||||
it.prepare()
|
||||
it.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setupMedia(context: Context, audio: Audio, listener: Player.Listener) {
|
||||
if (currentAudioFile != audio.file) {
|
||||
currentAudioFile = audio.file
|
||||
@ -77,7 +98,7 @@ object MediaControllerManager {
|
||||
mediaController?.let {
|
||||
it.addListener(listener)
|
||||
it.setMediaItem(mediaItem)
|
||||
it.repeatMode = Player.REPEAT_MODE_ONE
|
||||
it.repeatMode = Player.REPEAT_MODE_ALL
|
||||
it.prepare()
|
||||
it.play()
|
||||
val currentPlayingAudio =
|
||||
@ -136,7 +157,7 @@ object MediaControllerManager {
|
||||
mediaController?.let {
|
||||
it.addListener(listener)
|
||||
it.setMediaItem(mediaItem)
|
||||
it.repeatMode = Player.REPEAT_MODE_ONE
|
||||
it.repeatMode = Player.REPEAT_MODE_ALL
|
||||
it.prepare()
|
||||
it.play()
|
||||
val currentPlayingAudio =
|
||||
@ -174,4 +195,29 @@ object MediaControllerManager {
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package com.player.musicoo.media
|
||||
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.session.MediaController
|
||||
|
||||
fun MediaController.forcePlay(mediaItem: MediaItem){
|
||||
setMediaItem(mediaItem)
|
||||
prepare()
|
||||
play()
|
||||
}
|
||||
50
app/src/main/java/com/player/musicoo/media/SongRadio.kt
Normal file
50
app/src/main/java/com/player/musicoo/media/SongRadio.kt
Normal file
@ -0,0 +1,50 @@
|
||||
package com.player.musicoo.media
|
||||
|
||||
import com.player.musicoo.innertube.Innertube
|
||||
import com.player.musicoo.innertube.models.bodies.ContinuationBody
|
||||
import com.player.musicoo.innertube.models.bodies.NextBody
|
||||
import com.player.musicoo.innertube.requests.nextPage
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
data class SongRadio(
|
||||
private val videoId: String? = null,
|
||||
private var playlistId: String? = null,
|
||||
private var playlistSetVideoId: String? = null,
|
||||
private var parameters: String? = null
|
||||
) {
|
||||
private var nextContinuation: String? = null
|
||||
|
||||
suspend fun process(): List<Innertube.SongItem> {
|
||||
var songItems: List<Innertube.SongItem>? = null
|
||||
|
||||
nextContinuation = withContext(Dispatchers.IO) {
|
||||
val continuation = nextContinuation
|
||||
|
||||
if (continuation == null) {
|
||||
Innertube.nextPage(
|
||||
NextBody(
|
||||
videoId = videoId,
|
||||
playlistId = playlistId,
|
||||
params = parameters,
|
||||
playlistSetVideoId = playlistSetVideoId
|
||||
)
|
||||
)?.map { nextResult ->
|
||||
playlistId = nextResult.playlistId
|
||||
parameters = nextResult.params
|
||||
playlistSetVideoId = nextResult.playlistSetVideoId
|
||||
|
||||
nextResult.itemsPage
|
||||
}
|
||||
} else {
|
||||
Innertube.nextPage(ContinuationBody(continuation = continuation))
|
||||
}?.getOrNull()?.let { songsPage ->
|
||||
songItems = songsPage.items
|
||||
songsPage.continuation?.takeUnless { nextContinuation == it }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return songItems ?: emptyList()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
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 com.google.common.collect.ImmutableList
|
||||
import com.player.musicoo.util.LogTag
|
||||
|
||||
@UnstableApi
|
||||
class CustomMediaNotificationProvider(context: Context) :
|
||||
DefaultMediaNotificationProvider(context) {
|
||||
|
||||
override fun addNotificationActions(
|
||||
mediaSession: MediaSession,
|
||||
mediaButtons: ImmutableList<CommandButton>,
|
||||
builder: NotificationCompat.Builder,
|
||||
actionFactory: MediaNotification.ActionFactory
|
||||
): IntArray {
|
||||
|
||||
Log.d(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, "------------------------------------------")
|
||||
}
|
||||
|
||||
val notificationMediaButtons = ImmutableList.builder<CommandButton>().apply {
|
||||
add(NotificationCustomButton.SKIP_BACK.commandButton)
|
||||
add(NotificationCustomButton.SKIP_FORWARD.commandButton)
|
||||
}.build()
|
||||
|
||||
return super.addNotificationActions(
|
||||
mediaSession,
|
||||
notificationMediaButtons,
|
||||
builder,
|
||||
actionFactory
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.player.musicoo.service
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.media3.session.CommandButton
|
||||
import androidx.media3.session.SessionCommand
|
||||
import com.player.musicoo.R
|
||||
|
||||
private const val CUSTOM_COMMAND_SKIP_BACK_ACTION_ID = "Skip_back"
|
||||
private const val CUSTOM_COMMAND_SKIP_FORWARD_ACTION_ID = "Skip_Forward"
|
||||
|
||||
enum class NotificationCustomButton(val customAction: String, val commandButton: CommandButton) {
|
||||
SKIP_BACK(
|
||||
customAction = CUSTOM_COMMAND_SKIP_BACK_ACTION_ID,
|
||||
commandButton = CommandButton.Builder()
|
||||
.setDisplayName("SkipBack")
|
||||
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_SKIP_BACK_ACTION_ID, Bundle()))
|
||||
.setIconResId(R.drawable.play_skip_back)
|
||||
.build()
|
||||
|
||||
),
|
||||
SKIP_FORWARD(
|
||||
customAction = CUSTOM_COMMAND_SKIP_FORWARD_ACTION_ID,
|
||||
commandButton = CommandButton.Builder()
|
||||
.setDisplayName("SkipForward")
|
||||
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_SKIP_FORWARD_ACTION_ID, Bundle()))
|
||||
.setIconResId(R.drawable.play_skip_forward)
|
||||
.build()
|
||||
|
||||
),
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package com.player.musicoo.service
|
||||
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class PlayableFormatNotFoundException : PlaybackException(null, null, ERROR_CODE_REMOTE_ERROR)
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class UnplayableException : PlaybackException(null, null, ERROR_CODE_REMOTE_ERROR)
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class LoginRequiredException : PlaybackException(null, null, ERROR_CODE_REMOTE_ERROR)
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class VideoIdMismatchException : PlaybackException(null, null, ERROR_CODE_REMOTE_ERROR)
|
||||
@ -2,36 +2,53 @@ package com.player.musicoo.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.media3.common.AudioAttributes
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT
|
||||
import androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM
|
||||
import androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS
|
||||
import androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.session.CommandButton
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.session.MediaSessionService
|
||||
import androidx.media3.session.SessionCommand
|
||||
import androidx.media3.session.SessionResult
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.player.musicoo.R
|
||||
import com.player.musicoo.util.LogTag
|
||||
|
||||
|
||||
@UnstableApi
|
||||
class PlaybackService : MediaSessionService() {
|
||||
|
||||
private val TAG = LogTag.VO_SERVICE_LOG
|
||||
private var mediaSession: MediaSession? = null
|
||||
private val notificationCustomButtons =
|
||||
NotificationCustomButton.entries.map { command -> command.commandButton }
|
||||
|
||||
// Create your player and media session in the onCreate lifecycle event
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
val player = ExoPlayer.Builder(this).build()
|
||||
mediaSession = MediaSession.Builder(this, player)
|
||||
val player = ExoPlayer.Builder(this, createMediaSourceFactory())
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.build()
|
||||
|
||||
mediaSession = MediaSession.Builder(this, player)
|
||||
// .setCallback(MyCallback())
|
||||
// .setCustomLayout(notificationCustomButtons)
|
||||
.build()
|
||||
|
||||
// setMediaNotificationProvider(MyMediaNotificationProvider(this))
|
||||
val customMediaNotificationProvider = CustomMediaNotificationProvider(this).apply {
|
||||
setSmallIcon(R.mipmap.musicoo_logo_img)
|
||||
}
|
||||
|
||||
// setMediaNotificationProvider(customMediaNotificationProvider)
|
||||
}
|
||||
|
||||
private fun createMediaSourceFactory(): MediaSource.Factory {
|
||||
Log.d(TAG, "createMediaSourceFactory")
|
||||
return DefaultMediaSourceFactory(this)
|
||||
}
|
||||
|
||||
// The user dismissed the app from the recent tasks
|
||||
@ -59,4 +76,49 @@ class PlaybackService : MediaSessionService() {
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private inner class MyCallback : MediaSession.Callback {
|
||||
override fun onConnect(
|
||||
session: MediaSession,
|
||||
controller: MediaSession.ControllerInfo
|
||||
): MediaSession.ConnectionResult {
|
||||
val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
|
||||
if (session.isMediaNotificationController(controller)) {
|
||||
val playerCommands =
|
||||
MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
|
||||
.removeAll()
|
||||
|
||||
val connectionResult = super.onConnect(session, controller)
|
||||
val defaultPlayerCommands = connectionResult.availablePlayerCommands
|
||||
Log.d(TAG, defaultPlayerCommands.toString())
|
||||
notificationCustomButtons.forEach { commandButton ->
|
||||
commandButton.sessionCommand?.let(sessionCommands::add)
|
||||
}
|
||||
|
||||
return MediaSession.ConnectionResult.accept(
|
||||
sessionCommands.build(),
|
||||
playerCommands.build()
|
||||
)
|
||||
} else if (session.isAutoCompanionController(controller)) {
|
||||
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(sessionCommands.build())
|
||||
.build()
|
||||
}
|
||||
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
|
||||
}
|
||||
|
||||
override fun onCustomCommand(
|
||||
session: MediaSession,
|
||||
controller: MediaSession.ControllerInfo,
|
||||
customCommand: SessionCommand,
|
||||
args: Bundle
|
||||
): ListenableFuture<SessionResult> {
|
||||
when (customCommand.customAction) {
|
||||
NotificationCustomButton.SKIP_BACK.customAction -> mediaSession?.player?.seekToPrevious()
|
||||
NotificationCustomButton.SKIP_FORWARD.customAction -> mediaSession?.player?.seekToNext()
|
||||
}
|
||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,4 +4,5 @@ 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"
|
||||
}
|
||||
77
app/src/main/java/com/player/musicoo/util/Utils.kt
Normal file
77
app/src/main/java/com/player/musicoo/util/Utils.kt
Normal file
@ -0,0 +1,77 @@
|
||||
package com.player.musicoo.util
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.text.format.DateUtils
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import com.player.musicoo.innertube.Innertube
|
||||
import com.player.musicoo.innertube.models.bodies.ContinuationBody
|
||||
import com.player.musicoo.innertube.requests.playlistPage
|
||||
import com.player.musicoo.innertube.utils.plus
|
||||
|
||||
val Innertube.SongItem.asMediaItem: MediaItem
|
||||
get() = MediaItem.Builder()
|
||||
.setMediaId(key)
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setTitle(info?.name)
|
||||
.setArtist(authors?.joinToString("") { it.name ?: "" })
|
||||
.setAlbumTitle(album?.name)
|
||||
.setArtworkUri(bigThumbnail?.url?.toUri())
|
||||
.setExtras(
|
||||
bundleOf(
|
||||
"albumId" to album?.endpoint?.browseId,
|
||||
"durationText" to durationText,
|
||||
"artistNames" to authors?.filter { it.endpoint != null }?.mapNotNull { it.name },
|
||||
"artistIds" to authors?.mapNotNull { it.endpoint?.browseId },
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
fun String?.thumbnail(size: Int): String? {
|
||||
return when {
|
||||
this?.startsWith("https://lh3.googleusercontent.com") == true -> "$this-w$size-h$size"
|
||||
this?.startsWith("https://yt3.ggpht.com") == true -> "$this-w$size-h$size-s$size"
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
fun Uri?.thumbnail(size: Int): Uri? {
|
||||
return toString().thumbnail(size)?.toUri()
|
||||
}
|
||||
|
||||
fun formatAsDuration(millis: Long) = DateUtils.formatElapsedTime(millis / 1000).removePrefix("0")
|
||||
|
||||
suspend fun Result<Innertube.PlaylistOrAlbumPage>.completed(): Result<Innertube.PlaylistOrAlbumPage>? {
|
||||
var playlistPage = getOrNull() ?: return null
|
||||
|
||||
while (playlistPage.songsPage?.continuation != null) {
|
||||
val continuation = playlistPage.songsPage?.continuation!!
|
||||
val otherPlaylistPageResult = Innertube.playlistPage(ContinuationBody(continuation = continuation)) ?: break
|
||||
|
||||
if (otherPlaylistPageResult.isFailure) break
|
||||
|
||||
otherPlaylistPageResult.getOrNull()?.let { otherSongsPage ->
|
||||
playlistPage = playlistPage.copy(songsPage = playlistPage.songsPage + otherSongsPage)
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success(playlistPage)
|
||||
}
|
||||
|
||||
inline val isAtLeastAndroid6
|
||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
|
||||
inline val isAtLeastAndroid8
|
||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
|
||||
inline val isAtLeastAndroid12
|
||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
|
||||
inline val isAtLeastAndroid13
|
||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
25
app/src/main/res/drawable/bg_playing_playback_progress.xml
Normal file
25
app/src/main/res/drawable/bg_playing_playback_progress.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background">
|
||||
<shape>
|
||||
<corners android:radius="1dp" />
|
||||
<solid android:color="@color/white_30" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/secondaryProgress">
|
||||
<clip>
|
||||
<shape>
|
||||
<corners android:radius="1dp" />
|
||||
<solid android:color="@color/white_60" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
<item android:id="@android:id/progress">
|
||||
<clip>
|
||||
<shape>
|
||||
<corners android:radius="1dp" />
|
||||
<solid android:color="@color/white" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
</layer-list>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/white" />
|
||||
<size
|
||||
android:width="12dp"
|
||||
android:height="12dp" />
|
||||
</shape>
|
||||
9
app/src/main/res/drawable/music_list_icon.xml
Normal file
9
app/src/main/res/drawable/music_list_icon.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M18.522,10.432C18.622,10.432 18.718,10.461 18.801,10.516L22.564,13.033L21.458,14.696L20.017,13.733V18L20,17.977V18C20,18.638 19.796,19.259 19.419,19.773C19.042,20.288 18.511,20.668 17.902,20.86C17.294,21.052 16.64,21.045 16.036,20.84C15.432,20.635 14.909,20.243 14.543,19.721C14.177,19.199 13.987,18.573 14.001,17.935C14.014,17.298 14.231,16.681 14.619,16.175C15.007,15.668 15.546,15.299 16.158,15.12C16.77,14.941 17.423,14.962 18.023,15.179V10.932C18.023,10.866 18.036,10.801 18.061,10.74C18.086,10.679 18.123,10.624 18.169,10.578C18.216,10.531 18.271,10.495 18.332,10.47C18.392,10.444 18.457,10.432 18.523,10.432H18.522ZM10.476,18C10.542,18 10.607,18.013 10.668,18.038C10.729,18.063 10.784,18.1 10.83,18.146C10.877,18.193 10.913,18.248 10.938,18.309C10.964,18.369 10.976,18.434 10.976,18.5V19.5C10.976,19.566 10.964,19.631 10.938,19.691C10.913,19.752 10.877,19.807 10.83,19.854C10.784,19.9 10.729,19.937 10.668,19.962C10.607,19.987 10.542,20 10.476,20H3.5C3.434,20 3.369,19.987 3.309,19.962C3.248,19.937 3.193,19.9 3.146,19.854C3.1,19.807 3.063,19.752 3.038,19.691C3.013,19.631 3,19.566 3,19.5V18.5C3,18.434 3.013,18.369 3.038,18.309C3.063,18.248 3.1,18.193 3.146,18.146C3.193,18.1 3.248,18.063 3.309,18.038C3.369,18.013 3.434,18 3.5,18H10.476ZM17,17C16.735,17 16.48,17.105 16.293,17.293C16.105,17.48 16,17.735 16,18C16,18.265 16.105,18.52 16.293,18.707C16.48,18.895 16.735,19 17,19C17.265,19 17.52,18.895 17.707,18.707C17.895,18.52 18,18.265 18,18C18,17.735 17.895,17.48 17.707,17.293C17.52,17.105 17.265,17 17,17ZM14.465,11C14.531,11 14.596,11.013 14.656,11.038C14.717,11.063 14.772,11.1 14.819,11.146C14.865,11.193 14.902,11.248 14.927,11.309C14.952,11.369 14.965,11.434 14.965,11.5V12.5C14.965,12.566 14.952,12.631 14.927,12.691C14.902,12.752 14.865,12.807 14.819,12.854C14.772,12.9 14.717,12.937 14.656,12.962C14.596,12.987 14.531,13 14.465,13H3.5C3.434,13 3.369,12.987 3.309,12.962C3.248,12.937 3.193,12.9 3.146,12.854C3.1,12.807 3.063,12.752 3.038,12.691C3.013,12.631 3,12.566 3,12.5V11.5C3,11.434 3.013,11.369 3.038,11.309C3.063,11.248 3.1,11.193 3.146,11.146C3.193,11.1 3.248,11.063 3.309,11.038C3.369,11.013 3.434,11 3.5,11H14.465ZM19.5,4C19.633,4 19.76,4.053 19.854,4.146C19.947,4.24 20,4.367 20,4.5V5.5C20,5.633 19.947,5.76 19.854,5.854C19.76,5.947 19.633,6 19.5,6H3.5C3.434,6 3.369,5.987 3.309,5.962C3.248,5.937 3.193,5.9 3.146,5.854C3.1,5.807 3.063,5.752 3.038,5.691C3.013,5.631 3,5.566 3,5.5V4.5C3,4.434 3.013,4.369 3.038,4.309C3.063,4.248 3.1,4.193 3.146,4.146C3.193,4.1 3.248,4.063 3.309,4.038C3.369,4.013 3.434,4 3.5,4H19.5Z" />
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/pause.xml
Normal file
12
app/src/main/res/drawable/pause.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M208,432H160a16,16 0,0 1,-16 -16V96a16,16 0,0 1,16 -16h48a16,16 0,0 1,16 16V416A16,16 0,0 1,208 432Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M352,432H304a16,16 0,0 1,-16 -16V96a16,16 0,0 1,16 -16h48a16,16 0,0 1,16 16V416A16,16 0,0 1,352 432Z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/play.xml
Normal file
9
app/src/main/res/drawable/play.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m171.2,440a35.37,35.37 0,0 1,-17.5 -4.67c-12,-6.8 -19.46,-20 -19.46,-34.33v-290c0,-14.37 7.46,-27.53 19.46,-34.33a35.13,35.13 0,0 1,35.77 0.45l247.85,148.36a36,36 0,0 1,0 61l-247.89,148.4a35.5,35.5 0,0 1,-18.23 5.12z"/>
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/play_mode_icon.xml
Normal file
12
app/src/main/res/drawable/play_mode_icon.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h24v24h-24z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M21.806,6.569C23.229,8.017 24.021,9.97 24.011,12C24.014,12.695 23.92,13.386 23.734,14.055C23.673,14.267 23.53,14.446 23.338,14.553C23.145,14.66 22.918,14.686 22.706,14.625C22.492,14.566 22.31,14.425 22.198,14.233C22.086,14.041 22.053,13.814 22.106,13.598C22.243,13.076 22.313,12.539 22.316,12C22.33,10.443 21.727,8.943 20.639,7.828C19.552,6.714 18.067,6.075 16.51,6.051H8.603V7.589C8.603,8.077 8.265,8.272 7.853,8.009L4.237,5.759C4.146,5.717 4.069,5.649 4.016,5.565C3.962,5.481 3.933,5.382 3.933,5.282C3.933,5.182 3.962,5.084 4.016,5C4.069,4.915 4.146,4.848 4.237,4.806L7.86,2.368C8.265,2.105 8.611,2.285 8.611,2.781V4.318H16.51C17.501,4.319 18.481,4.518 19.394,4.905C20.306,5.291 21.131,5.857 21.821,6.569H21.806ZM20.41,18.279C20.501,18.321 20.578,18.388 20.632,18.473C20.686,18.557 20.715,18.655 20.715,18.755C20.715,18.855 20.686,18.953 20.632,19.038C20.578,19.122 20.501,19.189 20.41,19.232L16.78,21.632C16.375,21.895 16.03,21.715 16.03,21.219V19.682H7.508C6.517,19.681 5.536,19.482 4.624,19.095C3.711,18.709 2.886,18.143 2.197,17.431C0.98,16.174 0.221,14.545 0.041,12.805C-0.138,11.065 0.272,9.315 1.207,7.837C1.265,7.742 1.343,7.659 1.434,7.595C1.525,7.53 1.628,7.485 1.738,7.461C1.847,7.437 1.96,7.435 2.069,7.456C2.179,7.477 2.284,7.52 2.377,7.582C2.567,7.707 2.701,7.903 2.748,8.126C2.796,8.349 2.754,8.582 2.632,8.774C2.023,9.74 1.701,10.858 1.702,12C1.688,13.557 2.29,15.057 3.378,16.172C4.466,17.286 5.951,17.925 7.508,17.949H16.052V16.411C16.052,15.923 16.382,15.728 16.802,15.991L20.418,18.279H20.41Z" />
|
||||
</group>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/play_skip_back.xml
Normal file
9
app/src/main/res/drawable/play_skip_back.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M112,64a16,16 0,0 1,16 16V216.43L360.77,77.11a35.13,35.13 0,0 1,35.77 -0.44c12,6.8 19.46,20 19.46,34.33V401c0,14.37 -7.46,27.53 -19.46,34.33a35.14,35.14 0,0 1,-35.77 -0.45L128,295.57V432a16,16 0,0 1,-32 0V80A16,16 0,0 1,112 64Z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/play_skip_back_white_icon.xml
Normal file
9
app/src/main/res/drawable/play_skip_back_white_icon.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M2.813,17.857H2.12C2.029,17.86 1.939,17.844 1.854,17.81C1.769,17.776 1.692,17.726 1.627,17.662C1.563,17.597 1.512,17.521 1.478,17.436C1.444,17.352 1.427,17.261 1.429,17.17V2.732C1.429,2.437 1.725,2.143 2.121,2.143H2.813C3.208,2.143 3.505,2.438 3.505,2.83V17.17C3.406,17.563 3.11,17.857 2.813,17.857ZM18.432,17.17C18.037,17.759 17.246,17.956 16.653,17.563L5.877,11.277C5.68,11.179 5.581,10.982 5.482,10.786C5.087,10.196 5.284,9.411 5.877,9.018L16.554,2.634C16.752,2.536 16.949,2.438 17.246,2.438C17.937,2.438 18.531,3.027 18.531,3.714V16.482C18.63,16.777 18.531,16.974 18.432,17.17Z" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/play_skip_forward.xml
Normal file
9
app/src/main/res/drawable/play_skip_forward.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M400,64a16,16 0,0 0,-16 16V216.43L151.23,77.11a35.13,35.13 0,0 0,-35.77 -0.44C103.46,83.47 96,96.63 96,111V401c0,14.37 7.46,27.53 19.46,34.33a35.14,35.14 0,0 0,35.77 -0.45L384,295.57V432a16,16 0,0 0,32 0V80A16,16 0,0 0,400 64Z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M17.187,17.857H17.88C17.971,17.86 18.062,17.844 18.146,17.81C18.231,17.776 18.308,17.726 18.372,17.662C18.437,17.597 18.488,17.521 18.522,17.436C18.556,17.352 18.573,17.261 18.571,17.17V2.732C18.571,2.437 18.275,2.143 17.879,2.143H17.187C16.792,2.143 16.495,2.438 16.495,2.83V17.17C16.594,17.563 16.89,17.857 17.187,17.857ZM1.568,17.17C1.963,17.759 2.754,17.956 3.347,17.563L14.123,11.277C14.32,11.179 14.419,10.982 14.518,10.786C14.913,10.196 14.716,9.411 14.123,9.018L3.446,2.634C3.248,2.536 3.051,2.438 2.754,2.438C2.063,2.438 1.469,3.027 1.469,3.714V16.482C1.37,16.777 1.469,16.974 1.568,17.17Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
320
app/src/main/res/layout/activity_mo_play_details.xml
Normal file
320
app/src/main/res/layout/activity_mo_play_details.xml
Normal file
@ -0,0 +1,320 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:id="@+id/view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/relative"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="5"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="5"
|
||||
android:background="@drawable/drw_details_bg"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/main_bg_color" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/title_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/view"
|
||||
android:layout_margin="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/back_btn"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:background="@drawable/drw_back_bg">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:src="@drawable/arrow_bottom_icon" />
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="42dp"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18dp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/center_card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@+id/layout"
|
||||
android:layout_below="@+id/title_layout"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="20dp"
|
||||
android:background="#00000000"
|
||||
android:elevation="0dp"
|
||||
app:cardBackgroundColor="#000000"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playback_error_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_margin="24dp"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:gravity="center"
|
||||
android:text="@string/playback_error"
|
||||
android:textColor="@color/white_80"
|
||||
android:textSize="16dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.player.musicoo.view.MarqueeTextView
|
||||
android:id="@+id/name_tv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="marquee"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:gravity="center"
|
||||
android:maxLines="2"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="24dp" />
|
||||
|
||||
<com.player.musicoo.view.MarqueeTextView
|
||||
android:id="@+id/desc_tv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="marquee"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/white_60"
|
||||
android:textSize="14dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/sbProgress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:maxHeight="4dp"
|
||||
android:minHeight="4dp"
|
||||
android:progressDrawable="@drawable/bg_playing_playback_progress"
|
||||
android:thumb="@drawable/ic_playing_playback_progress_thumb" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="14dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/progress_duration_tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:text="00:00"
|
||||
android:textColor="#D9FFFFFF"
|
||||
android:textSize="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/total_duration_tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:text="00:00"
|
||||
android:textColor="#D9FFFFFF"
|
||||
android:textSize="12dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginBottom="68dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/play_mode_icon" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/play_skip_back_white_icon" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="66dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/play_img"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/play_green_icon" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/play_skip_forward_white_icon" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/music_list_icon" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/disable_clicks_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal" />
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
@ -3,6 +3,7 @@
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="black_60">#99000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="white_30">#4DFFFFFF</color>
|
||||
<color name="white_60">#99FFFFFF</color>
|
||||
<color name="white_80">#CCFFFFFF</color>
|
||||
<color name="main_bg_color">#151718</color>
|
||||
|
||||
@ -18,4 +18,5 @@
|
||||
<string name="resource_loading">Resource Loading…</string>
|
||||
<string name="expand">EXPAND</string>
|
||||
<string name="description">Description</string>
|
||||
<string name="playback_error">An unknown playback error has occurred</string>
|
||||
</resources>
|
||||
Loading…
Reference in New Issue
Block a user