mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-15 06:40:30 +05:30
commit
a0ad5a1837
@ -0,0 +1,9 @@
|
|||||||
|
package com.github.libretube.obj
|
||||||
|
|
||||||
|
data class PreviewFrame(
|
||||||
|
val previewUrl: String,
|
||||||
|
val positionX: Int,
|
||||||
|
val positionY: Int,
|
||||||
|
val framesPerPageX: Int,
|
||||||
|
val framesPerPageY: Int
|
||||||
|
)
|
@ -86,6 +86,7 @@ import com.github.libretube.util.NowPlayingNotification
|
|||||||
import com.github.libretube.util.PlayerHelper
|
import com.github.libretube.util.PlayerHelper
|
||||||
import com.github.libretube.util.PlayingQueue
|
import com.github.libretube.util.PlayingQueue
|
||||||
import com.github.libretube.util.PreferenceHelper
|
import com.github.libretube.util.PreferenceHelper
|
||||||
|
import com.github.libretube.util.SeekbarPreviewListener
|
||||||
import com.github.libretube.util.TextUtils
|
import com.github.libretube.util.TextUtils
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl
|
import com.google.android.exoplayer2.DefaultLoadControl
|
||||||
@ -645,7 +646,8 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
// set media sources for the player
|
// set media sources for the player
|
||||||
setResolutionAndSubtitles()
|
setResolutionAndSubtitles()
|
||||||
prepareExoPlayerView()
|
prepareExoPlayerView()
|
||||||
initializePlayerView(streams)
|
initializePlayerView()
|
||||||
|
setupSeekbarPreview()
|
||||||
if (!isLive) seekToWatchPosition()
|
if (!isLive) seekToWatchPosition()
|
||||||
exoPlayer.prepare()
|
exoPlayer.prepare()
|
||||||
if (!PreferenceHelper.getBoolean(PreferenceKeys.DATA_SAVER_MODE, false)) exoPlayer.play()
|
if (!PreferenceHelper.getBoolean(PreferenceKeys.DATA_SAVER_MODE, false)) exoPlayer.play()
|
||||||
@ -798,7 +800,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
private fun initializePlayerView(response: Streams) {
|
private fun initializePlayerView() {
|
||||||
// initialize the player view actions
|
// initialize the player view actions
|
||||||
binding.player.initialize(
|
binding.player.initialize(
|
||||||
this,
|
this,
|
||||||
@ -809,36 +811,36 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
playerViewsInfo.text =
|
playerViewsInfo.text =
|
||||||
context?.getString(R.string.views, response.views.formatShort()) +
|
context?.getString(R.string.views, streams.views.formatShort()) +
|
||||||
if (!isLive) TextUtils.SEPARATOR + response.uploadDate else ""
|
if (!isLive) TextUtils.SEPARATOR + streams.uploadDate else ""
|
||||||
|
|
||||||
textLike.text = response.likes.formatShort()
|
textLike.text = streams.likes.formatShort()
|
||||||
textDislike.text = response.dislikes.formatShort()
|
textDislike.text = streams.dislikes.formatShort()
|
||||||
ImageHelper.loadImage(response.uploaderAvatar, binding.playerChannelImage)
|
ImageHelper.loadImage(streams.uploaderAvatar, binding.playerChannelImage)
|
||||||
playerChannelName.text = response.uploader
|
playerChannelName.text = streams.uploader
|
||||||
|
|
||||||
titleTextView.text = response.title
|
titleTextView.text = streams.title
|
||||||
|
|
||||||
playerTitle.text = response.title
|
playerTitle.text = streams.title
|
||||||
playerDescription.text = response.description
|
playerDescription.text = streams.description
|
||||||
|
|
||||||
playerChannelSubCount.text = context?.getString(
|
playerChannelSubCount.text = context?.getString(
|
||||||
R.string.subscribers,
|
R.string.subscribers,
|
||||||
response.uploaderSubscriberCount?.formatShort()
|
streams.uploaderSubscriberCount?.formatShort()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// duration that's not greater than 0 indicates that the video is live
|
// duration that's not greater than 0 indicates that the video is live
|
||||||
if (response.duration!! <= 0) {
|
if (streams.duration!! <= 0) {
|
||||||
isLive = true
|
isLive = true
|
||||||
handleLiveVideo()
|
handleLiveVideo()
|
||||||
}
|
}
|
||||||
|
|
||||||
playerBinding.exoTitle.text = response.title
|
playerBinding.exoTitle.text = streams.title
|
||||||
|
|
||||||
// init the chapters recyclerview
|
// init the chapters recyclerview
|
||||||
if (response.chapters != null) {
|
if (streams.chapters != null) {
|
||||||
chapters = response.chapters
|
chapters = streams.chapters.orEmpty()
|
||||||
initializeChapters()
|
initializeChapters()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -922,7 +924,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
})
|
})
|
||||||
|
|
||||||
binding.relPlayerDownload.setOnClickListener {
|
binding.relPlayerDownload.setOnClickListener {
|
||||||
if (response.duration <= 0) {
|
if (streams.duration!! <= 0) {
|
||||||
Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show()
|
||||||
} else if (!DownloadService.IS_DOWNLOAD_RUNNING) {
|
} else if (!DownloadService.IS_DOWNLOAD_RUNNING) {
|
||||||
val newFragment = DownloadDialog(videoId!!)
|
val newFragment = DownloadDialog(videoId!!)
|
||||||
@ -933,7 +935,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.hls != null) {
|
if (streams.hls != null) {
|
||||||
binding.relPlayerPip.setOnClickListener {
|
binding.relPlayerPip.setOnClickListener {
|
||||||
if (SDK_INT < Build.VERSION_CODES.O) return@setOnClickListener
|
if (SDK_INT < Build.VERSION_CODES.O) return@setOnClickListener
|
||||||
try {
|
try {
|
||||||
@ -943,9 +945,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initializeRelatedVideos(response.relatedStreams)
|
initializeRelatedVideos(streams.relatedStreams)
|
||||||
// set video description
|
// set video description
|
||||||
val description = response.description!!
|
val description = streams.description!!
|
||||||
|
|
||||||
// detect whether the description is html formatted
|
// detect whether the description is html formatted
|
||||||
if (description.contains("<") && description.contains(">")) {
|
if (description.contains("<") && description.contains(">")) {
|
||||||
@ -958,7 +960,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
|
|
||||||
binding.playerChannel.setOnClickListener {
|
binding.playerChannel.setOnClickListener {
|
||||||
val activity = view?.context as MainActivity
|
val activity = view?.context as MainActivity
|
||||||
val bundle = bundleOf(IntentData.channelId to response.uploaderUrl)
|
val bundle = bundleOf(IntentData.channelId to streams.uploaderUrl)
|
||||||
activity.navController.navigate(R.id.channelFragment, bundle)
|
activity.navController.navigate(R.id.channelFragment, bundle)
|
||||||
activity.binding.mainMotionLayout.transitionToEnd()
|
activity.binding.mainMotionLayout.transitionToEnd()
|
||||||
binding.playerMotionLayout.transitionToEnd()
|
binding.playerMotionLayout.transitionToEnd()
|
||||||
@ -966,8 +968,8 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
|
|
||||||
// update the subscribed state
|
// update the subscribed state
|
||||||
binding.playerSubscribe.setupSubscriptionButton(
|
binding.playerSubscribe.setupSubscriptionButton(
|
||||||
streams.uploaderUrl?.toID(),
|
this.streams.uploaderUrl?.toID(),
|
||||||
streams.uploader
|
this.streams.uploader
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.relPlayerSave.setOnClickListener {
|
binding.relPlayerSave.setOnClickListener {
|
||||||
@ -1446,6 +1448,17 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
private fun setupSeekbarPreview() {
|
||||||
|
playerBinding.seekbarPreview.visibility = View.GONE
|
||||||
|
playerBinding.exoProgress.addListener(
|
||||||
|
SeekbarPreviewListener(
|
||||||
|
streams.previewFrames.orEmpty(),
|
||||||
|
playerBinding.seekbarPreview,
|
||||||
|
streams.duration!! * 1000
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun shouldStartPiP(): Boolean {
|
private fun shouldStartPiP(): Boolean {
|
||||||
if (!PlayerHelper.pipEnabled || SDK_INT >= Build.VERSION_CODES.S) {
|
if (!PlayerHelper.pipEnabled || SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
return false
|
return false
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
package com.github.libretube.util
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import com.github.libretube.api.obj.PreviewFrames
|
||||||
|
import com.github.libretube.obj.PreviewFrame
|
||||||
|
import com.google.android.exoplayer2.ui.TimeBar
|
||||||
|
|
||||||
|
class SeekbarPreviewListener(
|
||||||
|
private val previewFrames: List<PreviewFrames>,
|
||||||
|
private val previewIv: ImageView,
|
||||||
|
private val duration: Long
|
||||||
|
) : TimeBar.OnScrubListener {
|
||||||
|
private var moving = false
|
||||||
|
|
||||||
|
override fun onScrubStart(timeBar: TimeBar, position: Long) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a preview of the scrubber position
|
||||||
|
*/
|
||||||
|
override fun onScrubMove(timeBar: TimeBar, position: Long) {
|
||||||
|
moving = true
|
||||||
|
val preview = getPreviewFrame(position) ?: return
|
||||||
|
updatePreviewX(position)
|
||||||
|
|
||||||
|
val request = ImageRequest.Builder(previewIv.context)
|
||||||
|
.data(preview.previewUrl)
|
||||||
|
.target {
|
||||||
|
if (!moving) return@target
|
||||||
|
val bitmap = it.toBitmap()
|
||||||
|
val heightPerFrame = bitmap.height / preview.framesPerPageY
|
||||||
|
val widthPerFrame = bitmap.width / preview.framesPerPageX
|
||||||
|
val frame = Bitmap.createBitmap(
|
||||||
|
bitmap,
|
||||||
|
preview.positionX * widthPerFrame,
|
||||||
|
preview.positionY * heightPerFrame,
|
||||||
|
widthPerFrame,
|
||||||
|
heightPerFrame
|
||||||
|
)
|
||||||
|
previewIv.setImageBitmap(frame)
|
||||||
|
previewIv.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
ImageHelper.imageLoader.enqueue(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the seekbar preview with a short delay
|
||||||
|
*/
|
||||||
|
override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) {
|
||||||
|
moving = false
|
||||||
|
previewIv.animate()
|
||||||
|
.alpha(0f)
|
||||||
|
.translationYBy(30f)
|
||||||
|
.setDuration(200)
|
||||||
|
.withEndAction {
|
||||||
|
previewIv.visibility = View.GONE
|
||||||
|
previewIv.translationY = previewIv.translationY - 30f
|
||||||
|
previewIv.alpha = 1f
|
||||||
|
}
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the preview frame according to the current position
|
||||||
|
*/
|
||||||
|
private fun getPreviewFrame(position: Long): PreviewFrame? {
|
||||||
|
var startPosition: Long = 0
|
||||||
|
val frames = previewFrames.sortedBy { it.frameHeight }.lastOrNull()
|
||||||
|
frames?.urls?.forEach { url ->
|
||||||
|
for (y in 0 until frames.framesPerPageY!!) {
|
||||||
|
for (x in 0 until frames.framesPerPageX!!) {
|
||||||
|
val endPosition = startPosition + frames.durationPerFrame!!.toLong()
|
||||||
|
if (position in startPosition until endPosition) {
|
||||||
|
return PreviewFrame(url, x, y, frames.framesPerPageX, frames.framesPerPageY)
|
||||||
|
}
|
||||||
|
startPosition = endPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePreviewX(position: Long) {
|
||||||
|
val params = previewIv.layoutParams as MarginLayoutParams
|
||||||
|
val parentWidth = (previewIv.parent as View).width
|
||||||
|
// calculate the center-offset of the preview image view
|
||||||
|
val offset = parentWidth * (position.toFloat() / duration.toFloat()) - previewIv.width / 2
|
||||||
|
// normalize the offset to keep a minimum distance at left and right
|
||||||
|
params.marginStart = normalizeOffset(
|
||||||
|
offset.toInt(),
|
||||||
|
MIN_PADDING,
|
||||||
|
parentWidth - MIN_PADDING - previewIv.width
|
||||||
|
)
|
||||||
|
previewIv.layoutParams = params
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizeOffset(offset: Int, min: Int, max: Int): Int {
|
||||||
|
return maxOf(min, minOf(max, offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MIN_PADDING = 20
|
||||||
|
}
|
||||||
|
}
|
@ -95,6 +95,96 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@id/exo_center_controls"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="20dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/skip_prev"
|
||||||
|
style="@style/PlayerControlCenter"
|
||||||
|
android:background="?android:selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_prev"
|
||||||
|
android:visibility="invisible"
|
||||||
|
app:tint="@android:color/white" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/rewindBTN"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:scaleX="0.8"
|
||||||
|
android:scaleY="0.8"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_rewind"
|
||||||
|
app:tint="@android:color/white" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/rewindTV"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/playPauseBTN"
|
||||||
|
style="@style/ExoStyledControls.Button.Center.PlayPause"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:background="?android:selectableItemBackgroundBorderless"
|
||||||
|
app:tint="@android:color/white" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/forwardBTN"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:scaleX="0.8"
|
||||||
|
android:scaleY="0.8"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_forward"
|
||||||
|
app:tint="@android:color/white" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/forwardTV"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/skip_next"
|
||||||
|
style="@style/PlayerControlCenter"
|
||||||
|
android:background="?android:selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_next"
|
||||||
|
android:visibility="invisible"
|
||||||
|
app:tint="@android:color/white" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@id/exo_bottom_bar"
|
android:id="@id/exo_bottom_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -103,6 +193,17 @@
|
|||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/seekbar_preview"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="70dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:translationY="27dp"
|
||||||
|
android:translationZ="1dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:shapeAppearanceOverlay="@style/ShapeAppearance.Material3.Corner.Small" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -231,94 +332,4 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@id/exo_center_controls"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="20dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/skip_prev"
|
|
||||||
style="@style/PlayerControlCenter"
|
|
||||||
android:background="?android:selectableItemBackgroundBorderless"
|
|
||||||
android:src="@drawable/ic_prev"
|
|
||||||
android:visibility="invisible"
|
|
||||||
app:tint="@android:color/white" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/rewindBTN"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginHorizontal="10dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:scaleX="0.8"
|
|
||||||
android:scaleY="0.8"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_rewind"
|
|
||||||
app:tint="@android:color/white" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/rewindTV"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/playPauseBTN"
|
|
||||||
style="@style/ExoStyledControls.Button.Center.PlayPause"
|
|
||||||
android:layout_marginHorizontal="10dp"
|
|
||||||
android:background="?android:selectableItemBackgroundBorderless"
|
|
||||||
app:tint="@android:color/white" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/forwardBTN"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginHorizontal="10dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:scaleX="0.8"
|
|
||||||
android:scaleY="0.8"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_forward"
|
|
||||||
app:tint="@android:color/white" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/forwardTV"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/skip_next"
|
|
||||||
style="@style/PlayerControlCenter"
|
|
||||||
android:background="?android:selectableItemBackgroundBorderless"
|
|
||||||
android:src="@drawable/ic_next"
|
|
||||||
android:visibility="invisible"
|
|
||||||
app:tint="@android:color/white" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</merge>
|
</merge>
|
Loading…
Reference in New Issue
Block a user