mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-27 23:40:33 +05:30
Merge branch 'master' into KotlinX_Serialization
# Conflicts: # app/src/main/java/com/github/libretube/ui/adapters/SearchAdapter.kt
This commit is contained in:
commit
56763ec1a9
@ -93,7 +93,6 @@ object PreferenceKeys {
|
|||||||
/**
|
/**
|
||||||
* Background mode
|
* Background mode
|
||||||
*/
|
*/
|
||||||
const val BACKGROUND_PLAYBACK_SPEED = "background_playback_speed"
|
|
||||||
const val AUDIO_ONLY_MODE = "audio_only_mode"
|
const val AUDIO_ONLY_MODE = "audio_only_mode"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,7 +22,6 @@ import com.github.libretube.api.obj.Streams
|
|||||||
import com.github.libretube.constants.BACKGROUND_CHANNEL_ID
|
import com.github.libretube.constants.BACKGROUND_CHANNEL_ID
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.constants.PLAYER_NOTIFICATION_ID
|
import com.github.libretube.constants.PLAYER_NOTIFICATION_ID
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
|
||||||
import com.github.libretube.db.DatabaseHolder.Companion.Database
|
import com.github.libretube.db.DatabaseHolder.Companion.Database
|
||||||
import com.github.libretube.db.obj.WatchPosition
|
import com.github.libretube.db.obj.WatchPosition
|
||||||
import com.github.libretube.extensions.TAG
|
import com.github.libretube.extensions.TAG
|
||||||
@ -32,8 +31,8 @@ import com.github.libretube.extensions.toID
|
|||||||
import com.github.libretube.extensions.toStreamItem
|
import com.github.libretube.extensions.toStreamItem
|
||||||
import com.github.libretube.util.NowPlayingNotification
|
import com.github.libretube.util.NowPlayingNotification
|
||||||
import com.github.libretube.util.PlayerHelper
|
import com.github.libretube.util.PlayerHelper
|
||||||
|
import com.github.libretube.util.PlayerHelper.loadPlaybackParams
|
||||||
import com.github.libretube.util.PlayingQueue
|
import com.github.libretube.util.PlayingQueue
|
||||||
import com.github.libretube.util.PreferenceHelper
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.PlaybackException
|
import com.google.android.exoplayer2.PlaybackException
|
||||||
@ -66,7 +65,7 @@ class BackgroundMode : Service() {
|
|||||||
/**
|
/**
|
||||||
* The [ExoPlayer] player. Followed tutorial [here](https://developer.android.com/codelabs/exoplayer-intro)
|
* The [ExoPlayer] player. Followed tutorial [here](https://developer.android.com/codelabs/exoplayer-intro)
|
||||||
*/
|
*/
|
||||||
private var player: ExoPlayer? = null
|
var player: ExoPlayer? = null
|
||||||
private var playWhenReadyPlayer = true
|
private var playWhenReadyPlayer = true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -224,13 +223,6 @@ class BackgroundMode : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the playback speed
|
|
||||||
val playbackSpeed = PreferenceHelper.getString(
|
|
||||||
PreferenceKeys.BACKGROUND_PLAYBACK_SPEED,
|
|
||||||
"1"
|
|
||||||
).toFloat()
|
|
||||||
player?.setPlaybackSpeed(playbackSpeed)
|
|
||||||
|
|
||||||
fetchSponsorBlockSegments()
|
fetchSponsorBlockSegments()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,6 +237,7 @@ class BackgroundMode : Service() {
|
|||||||
.setAudioAttributes(PlayerHelper.getAudioAttributes(), true)
|
.setAudioAttributes(PlayerHelper.getAudioAttributes(), true)
|
||||||
.setLoadControl(PlayerHelper.getLoadControl())
|
.setLoadControl(PlayerHelper.getLoadControl())
|
||||||
.build()
|
.build()
|
||||||
|
.loadPlaybackParams()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens for changed playbackStates (e.g. pause, end)
|
* Listens for changed playbackStates (e.g. pause, end)
|
||||||
|
@ -97,7 +97,7 @@ class MainActivity : BaseActivity() {
|
|||||||
|
|
||||||
// sets the navigation bar color to the previously calculated color
|
// sets the navigation bar color to the previously calculated color
|
||||||
window.navigationBarColor = if (binding.bottomNav.menu.size() > 0) {
|
window.navigationBarColor = if (binding.bottomNav.menu.size() > 0) {
|
||||||
SurfaceColors.getColorForElevation(this, 10F)
|
SurfaceColors.getColorForElevation(this, binding.bottomNav.elevation)
|
||||||
} else {
|
} else {
|
||||||
ThemeHelper.getThemeColor(this, android.R.attr.colorBackground)
|
ThemeHelper.getThemeColor(this, android.R.attr.colorBackground)
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ class MainActivity : BaseActivity() {
|
|||||||
val navHostFragment =
|
val navHostFragment =
|
||||||
supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment?
|
supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment?
|
||||||
// get the current fragment
|
// get the current fragment
|
||||||
val fragment = navHostFragment?.childFragmentManager?.fragments?.get(0)
|
val fragment = navHostFragment?.childFragmentManager?.fragments?.getOrNull(0)
|
||||||
tryScrollToTop(fragment?.requireView() as? ViewGroup)
|
tryScrollToTop(fragment?.requireView() as? ViewGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import com.github.libretube.ui.base.BaseActivity
|
|||||||
import com.github.libretube.ui.extensions.setAspectRatio
|
import com.github.libretube.ui.extensions.setAspectRatio
|
||||||
import com.github.libretube.ui.models.PlayerViewModel
|
import com.github.libretube.ui.models.PlayerViewModel
|
||||||
import com.github.libretube.util.PlayerHelper
|
import com.github.libretube.util.PlayerHelper
|
||||||
|
import com.github.libretube.util.PlayerHelper.loadPlaybackParams
|
||||||
import com.github.libretube.util.WindowHelper
|
import com.github.libretube.util.WindowHelper
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
@ -81,6 +82,7 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
.loadPlaybackParams()
|
||||||
|
|
||||||
playerView = binding.player
|
playerView = binding.player
|
||||||
playerView.setShowSubtitleButton(true)
|
playerView.setShowSubtitleButton(true)
|
||||||
|
@ -7,6 +7,7 @@ import android.view.LayoutInflater
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.github.libretube.databinding.QueueRowBinding
|
import com.github.libretube.databinding.QueueRowBinding
|
||||||
|
import com.github.libretube.extensions.toID
|
||||||
import com.github.libretube.ui.viewholders.PlayingQueueViewHolder
|
import com.github.libretube.ui.viewholders.PlayingQueueViewHolder
|
||||||
import com.github.libretube.util.ImageHelper
|
import com.github.libretube.util.ImageHelper
|
||||||
import com.github.libretube.util.PlayingQueue
|
import com.github.libretube.util.PlayingQueue
|
||||||
@ -46,10 +47,16 @@ class PlayingQueueAdapter : RecyclerView.Adapter<PlayingQueueViewHolder>() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
val oldIndex = PlayingQueue.currentIndex()
|
val oldPosition = PlayingQueue.currentIndex()
|
||||||
PlayingQueue.onQueueItemSelected(position)
|
// get the new position from the queue to work properly after reordering the queue
|
||||||
notifyItemChanged(oldIndex)
|
val newPosition = PlayingQueue.getStreams().indexOfFirst {
|
||||||
notifyItemChanged(position)
|
it.url?.toID() == streamItem.url?.toID()
|
||||||
|
}.takeIf { it >= 0 } ?: return@setOnClickListener
|
||||||
|
|
||||||
|
// select the new item in the queue and update the selected item in the UI
|
||||||
|
PlayingQueue.onQueueItemSelected(newPosition)
|
||||||
|
notifyItemChanged(oldPosition)
|
||||||
|
notifyItemChanged(newPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ class PlaylistAdapter(
|
|||||||
videoInfo.text = streamItem.uploaderName
|
videoInfo.text = streamItem.uploaderName
|
||||||
channelImage.visibility = View.GONE
|
channelImage.visibility = View.GONE
|
||||||
|
|
||||||
thumbnailDuration.setFormattedDuration(streamItem.duration!!)
|
thumbnailDuration.setFormattedDuration(streamItem.duration!!, streamItem.isShort)
|
||||||
ImageHelper.loadImage(streamItem.thumbnail, thumbnail)
|
ImageHelper.loadImage(streamItem.thumbnail, thumbnail)
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
NavigationHelper.navigateVideo(root.context, streamItem.url, playlistId)
|
NavigationHelper.navigateVideo(root.context, streamItem.url, playlistId)
|
||||||
|
@ -83,7 +83,7 @@ class SearchAdapter(
|
|||||||
private fun bindWatch(item: ContentItem, binding: VideoRowBinding) {
|
private fun bindWatch(item: ContentItem, binding: VideoRowBinding) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
ImageHelper.loadImage(item.thumbnail, thumbnail)
|
ImageHelper.loadImage(item.thumbnail, thumbnail)
|
||||||
thumbnailDuration.setFormattedDuration(item.duration)
|
thumbnailDuration.setFormattedDuration(item.duration, item.isShort)
|
||||||
ImageHelper.loadImage(item.uploaderAvatar, channelImage)
|
ImageHelper.loadImage(item.uploaderAvatar, channelImage)
|
||||||
videoTitle.text = item.title
|
videoTitle.text = item.title
|
||||||
val viewsString = if (item.views != -1L) item.views.formatShort() else ""
|
val viewsString = if (item.views != -1L) item.views.formatShort() else ""
|
||||||
|
@ -133,7 +133,7 @@ class VideosAdapter(
|
|||||||
TextUtils.SEPARATOR + video.uploaded?.let {
|
TextUtils.SEPARATOR + video.uploaded?.let {
|
||||||
DateUtils.getRelativeTimeSpanString(it)
|
DateUtils.getRelativeTimeSpanString(it)
|
||||||
}
|
}
|
||||||
video.duration?.let { thumbnailDuration.setFormattedDuration(it) }
|
video.duration?.let { thumbnailDuration.setFormattedDuration(it, video.isShort) }
|
||||||
channelImage.setOnClickListener {
|
channelImage.setOnClickListener {
|
||||||
NavigationHelper.navigateChannel(root.context, video.uploaderUrl)
|
NavigationHelper.navigateChannel(root.context, video.uploaderUrl)
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ class WatchHistoryAdapter(
|
|||||||
videoTitle.text = video.title
|
videoTitle.text = video.title
|
||||||
channelName.text = video.uploader
|
channelName.text = video.uploader
|
||||||
videoInfo.text = video.uploadDate
|
videoInfo.text = video.uploadDate
|
||||||
thumbnailDuration.setFormattedDuration(video.duration!!)
|
thumbnailDuration.setFormattedDuration(video.duration!!, null)
|
||||||
ImageHelper.loadImage(video.thumbnailUrl, thumbnail)
|
ImageHelper.loadImage(video.thumbnailUrl, thumbnail)
|
||||||
ImageHelper.loadImage(video.uploaderAvatar, channelImage)
|
ImageHelper.loadImage(video.uploaderAvatar, channelImage)
|
||||||
|
|
||||||
|
@ -4,13 +4,10 @@ import android.text.format.DateUtils
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
|
|
||||||
fun TextView.setFormattedDuration(duration: Long) {
|
fun TextView.setFormattedDuration(duration: Long, isShort: Boolean?) {
|
||||||
val text = if (duration < 0L) {
|
this.text = when {
|
||||||
this.context.getString(R.string.live)
|
isShort == true -> context.getString(R.string.yt_shorts)
|
||||||
} else if (duration in 0L..60L) {
|
duration < 0L -> context.getString(R.string.live)
|
||||||
this.context.getString(R.string.yt_shorts)
|
else -> DateUtils.formatElapsedTime(duration)
|
||||||
} else {
|
|
||||||
DateUtils.formatElapsedTime(duration)
|
|
||||||
}
|
}
|
||||||
this.text = text
|
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,20 @@ import android.text.format.DateUtils
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.databinding.FragmentAudioPlayerBinding
|
import com.github.libretube.databinding.FragmentAudioPlayerBinding
|
||||||
|
import com.github.libretube.enums.ShareObjectType
|
||||||
import com.github.libretube.extensions.toID
|
import com.github.libretube.extensions.toID
|
||||||
|
import com.github.libretube.obj.ShareData
|
||||||
import com.github.libretube.services.BackgroundMode
|
import com.github.libretube.services.BackgroundMode
|
||||||
import com.github.libretube.ui.activities.MainActivity
|
import com.github.libretube.ui.activities.MainActivity
|
||||||
import com.github.libretube.ui.base.BaseFragment
|
import com.github.libretube.ui.base.BaseFragment
|
||||||
|
import com.github.libretube.ui.dialogs.ShareDialog
|
||||||
|
import com.github.libretube.ui.sheets.PlaybackOptionsSheet
|
||||||
import com.github.libretube.ui.sheets.PlayingQueueSheet
|
import com.github.libretube.ui.sheets.PlayingQueueSheet
|
||||||
|
import com.github.libretube.util.BackgroundHelper
|
||||||
import com.github.libretube.util.ImageHelper
|
import com.github.libretube.util.ImageHelper
|
||||||
import com.github.libretube.util.NavigationHelper
|
import com.github.libretube.util.NavigationHelper
|
||||||
import com.github.libretube.util.PlayingQueue
|
import com.github.libretube.util.PlayingQueue
|
||||||
@ -32,7 +38,7 @@ class AudioPlayerFragment : BaseFragment() {
|
|||||||
private var handler = Handler(Looper.getMainLooper())
|
private var handler = Handler(Looper.getMainLooper())
|
||||||
private var isPaused: Boolean = false
|
private var isPaused: Boolean = false
|
||||||
|
|
||||||
private lateinit var playerService: BackgroundMode
|
private var playerService: BackgroundMode? = null
|
||||||
|
|
||||||
/** Defines callbacks for service binding, passed to bindService() */
|
/** Defines callbacks for service binding, passed to bindService() */
|
||||||
private val connection = object : ServiceConnection {
|
private val connection = object : ServiceConnection {
|
||||||
@ -86,16 +92,42 @@ class AudioPlayerFragment : BaseFragment() {
|
|||||||
PlayingQueue.onQueueItemSelected(currentIndex + 1)
|
PlayingQueue.onQueueItemSelected(currentIndex + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.thumbnail.setOnClickListener {
|
binding.openQueue.setOnClickListener {
|
||||||
PlayingQueueSheet().show(childFragmentManager)
|
PlayingQueueSheet().show(childFragmentManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.playbackOptions.setOnClickListener {
|
||||||
|
playerService?.player?.let {
|
||||||
|
PlaybackOptionsSheet(it)
|
||||||
|
.show(childFragmentManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.openVideo.setOnClickListener {
|
||||||
|
NavigationHelper.navigateVideo(
|
||||||
|
context = requireContext(),
|
||||||
|
videoId = PlayingQueue.getCurrent()?.url?.toID(),
|
||||||
|
keepQueue = true,
|
||||||
|
forceVideo = true
|
||||||
|
)
|
||||||
|
BackgroundHelper.stopBackgroundPlay(requireContext())
|
||||||
|
findNavController().popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.share.setOnClickListener {
|
||||||
|
val currentVideo = PlayingQueue.getCurrent() ?: return@setOnClickListener
|
||||||
|
ShareDialog(
|
||||||
|
id = currentVideo.url!!.toID(),
|
||||||
|
shareObjectType = ShareObjectType.VIDEO,
|
||||||
|
shareData = ShareData(currentVideo = currentVideo.title)
|
||||||
|
).show(childFragmentManager, null)
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for track changes due to autoplay or the notification
|
// Listen for track changes due to autoplay or the notification
|
||||||
PlayingQueue.addOnTrackChangedListener(onTrackChangeListener)
|
PlayingQueue.addOnTrackChangedListener(onTrackChangeListener)
|
||||||
|
|
||||||
binding.playPause.setOnClickListener {
|
binding.playPause.setOnClickListener {
|
||||||
if (!this::playerService.isInitialized) return@setOnClickListener
|
if (isPaused) playerService?.play() else playerService?.pause()
|
||||||
if (isPaused) playerService.play() else playerService.pause()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// load the stream info into the UI
|
// load the stream info into the UI
|
||||||
@ -121,10 +153,8 @@ class AudioPlayerFragment : BaseFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeSeekBar() {
|
private fun initializeSeekBar() {
|
||||||
if (!this::playerService.isInitialized) return
|
|
||||||
|
|
||||||
binding.timeBar.addOnChangeListener { _, value, fromUser ->
|
binding.timeBar.addOnChangeListener { _, value, fromUser ->
|
||||||
if (fromUser) playerService.seekToPosition(value.toLong() * 1000)
|
if (fromUser) playerService?.seekToPosition(value.toLong() * 1000)
|
||||||
}
|
}
|
||||||
updateSeekBar()
|
updateSeekBar()
|
||||||
}
|
}
|
||||||
@ -133,7 +163,7 @@ class AudioPlayerFragment : BaseFragment() {
|
|||||||
* Update the position, duration and text views belonging to the seek bar
|
* Update the position, duration and text views belonging to the seek bar
|
||||||
*/
|
*/
|
||||||
private fun updateSeekBar() {
|
private fun updateSeekBar() {
|
||||||
val duration = playerService.getDuration()?.toFloat() ?: return
|
val duration = playerService?.getDuration()?.toFloat() ?: return
|
||||||
|
|
||||||
// when the video is not loaded yet, retry in 100 ms
|
// when the video is not loaded yet, retry in 100 ms
|
||||||
if (duration <= 0) {
|
if (duration <= 0) {
|
||||||
@ -142,7 +172,7 @@ class AudioPlayerFragment : BaseFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get the current position from the player service
|
// get the current position from the player service
|
||||||
val currentPosition = playerService.getCurrentPosition()?.toFloat() ?: 0f
|
val currentPosition = playerService?.getCurrentPosition()?.toFloat() ?: 0f
|
||||||
|
|
||||||
// set the text for the indicators
|
// set the text for the indicators
|
||||||
binding.duration.text = DateUtils.formatElapsedTime((duration / 1000).toLong())
|
binding.duration.text = DateUtils.formatElapsedTime((duration / 1000).toLong())
|
||||||
@ -161,7 +191,7 @@ class AudioPlayerFragment : BaseFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleServiceConnection() {
|
private fun handleServiceConnection() {
|
||||||
playerService.onIsPlayingChanged = { isPlaying ->
|
playerService?.onIsPlayingChanged = { isPlaying ->
|
||||||
binding.playPause.setIconResource(
|
binding.playPause.setIconResource(
|
||||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play
|
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play
|
||||||
)
|
)
|
||||||
@ -172,7 +202,7 @@ class AudioPlayerFragment : BaseFragment() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
// unregister all listeners and the connected [playerService]
|
// unregister all listeners and the connected [playerService]
|
||||||
playerService.onIsPlayingChanged = null
|
playerService?.onIsPlayingChanged = null
|
||||||
activity?.unbindService(connection)
|
activity?.unbindService(connection)
|
||||||
PlayingQueue.removeOnTrackChangedListener(onTrackChangeListener)
|
PlayingQueue.removeOnTrackChangedListener(onTrackChangeListener)
|
||||||
|
|
||||||
|
@ -94,6 +94,7 @@ import com.github.libretube.util.LinkHandler
|
|||||||
import com.github.libretube.util.NavigationHelper
|
import com.github.libretube.util.NavigationHelper
|
||||||
import com.github.libretube.util.NowPlayingNotification
|
import com.github.libretube.util.NowPlayingNotification
|
||||||
import com.github.libretube.util.PlayerHelper
|
import com.github.libretube.util.PlayerHelper
|
||||||
|
import com.github.libretube.util.PlayerHelper.loadPlaybackParams
|
||||||
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.SeekbarPreviewListener
|
||||||
@ -1343,6 +1344,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
.setHandleAudioBecomingNoisy(true)
|
.setHandleAudioBecomingNoisy(true)
|
||||||
.setAudioAttributes(PlayerHelper.getAudioAttributes(), true)
|
.setAudioAttributes(PlayerHelper.getAudioAttributes(), true)
|
||||||
.build()
|
.build()
|
||||||
|
.loadPlaybackParams()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +11,7 @@ import com.github.libretube.util.PreferenceHelper
|
|||||||
import com.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
import com.google.android.exoplayer2.PlaybackParameters
|
import com.google.android.exoplayer2.PlaybackParameters
|
||||||
|
|
||||||
class PlaybackSpeedSheet(
|
class PlaybackOptionsSheet(
|
||||||
private val player: ExoPlayer
|
private val player: ExoPlayer
|
||||||
) : ExpandedBottomSheet() {
|
) : ExpandedBottomSheet() {
|
||||||
private lateinit var binding: PlaybackBottomSheetBinding
|
private lateinit var binding: PlaybackBottomSheetBinding
|
@ -16,7 +16,6 @@ import android.widget.TextView
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
|
||||||
import com.github.libretube.databinding.DoubleTapOverlayBinding
|
import com.github.libretube.databinding.DoubleTapOverlayBinding
|
||||||
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
|
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
|
||||||
import com.github.libretube.databinding.PlayerGestureControlsViewBinding
|
import com.github.libretube.databinding.PlayerGestureControlsViewBinding
|
||||||
@ -30,15 +29,13 @@ import com.github.libretube.ui.interfaces.PlayerGestureOptions
|
|||||||
import com.github.libretube.ui.interfaces.PlayerOptions
|
import com.github.libretube.ui.interfaces.PlayerOptions
|
||||||
import com.github.libretube.ui.models.PlayerViewModel
|
import com.github.libretube.ui.models.PlayerViewModel
|
||||||
import com.github.libretube.ui.sheets.BaseBottomSheet
|
import com.github.libretube.ui.sheets.BaseBottomSheet
|
||||||
import com.github.libretube.ui.sheets.PlaybackSpeedSheet
|
import com.github.libretube.ui.sheets.PlaybackOptionsSheet
|
||||||
import com.github.libretube.util.AudioHelper
|
import com.github.libretube.util.AudioHelper
|
||||||
import com.github.libretube.util.BrightnessHelper
|
import com.github.libretube.util.BrightnessHelper
|
||||||
import com.github.libretube.util.PlayerGestureController
|
import com.github.libretube.util.PlayerGestureController
|
||||||
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.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
import com.google.android.exoplayer2.PlaybackParameters
|
|
||||||
import com.google.android.exoplayer2.Player
|
import com.google.android.exoplayer2.Player
|
||||||
import com.google.android.exoplayer2.text.Cue
|
import com.google.android.exoplayer2.text.Cue
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector
|
import com.google.android.exoplayer2.trackselection.TrackSelector
|
||||||
@ -92,9 +89,6 @@ internal class CustomExoPlayerView(
|
|||||||
if (isControllerFullyVisible) hideController() else showController()
|
if (isControllerFullyVisible) hideController() else showController()
|
||||||
}
|
}
|
||||||
|
|
||||||
// saved to only load the playback speed once (for the first video)
|
|
||||||
private var playbackPrefSet = false
|
|
||||||
|
|
||||||
private val hideControllerRunnable = Runnable {
|
private val hideControllerRunnable = Runnable {
|
||||||
hideController()
|
hideController()
|
||||||
}
|
}
|
||||||
@ -127,17 +121,6 @@ internal class CustomExoPlayerView(
|
|||||||
// don't let the player view hide its controls automatically
|
// don't let the player view hide its controls automatically
|
||||||
controllerShowTimeoutMs = -1
|
controllerShowTimeoutMs = -1
|
||||||
|
|
||||||
if (!playbackPrefSet) {
|
|
||||||
player?.playbackParameters = PlaybackParameters(
|
|
||||||
PlayerHelper.playbackSpeed.toFloat(),
|
|
||||||
1.0f
|
|
||||||
)
|
|
||||||
PreferenceHelper.getBoolean(PreferenceKeys.SKIP_SILENCE, false).let {
|
|
||||||
(player as ExoPlayer).skipSilenceEnabled = it
|
|
||||||
}
|
|
||||||
playbackPrefSet = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// locking the player
|
// locking the player
|
||||||
binding.lockPlayer.setOnClickListener {
|
binding.lockPlayer.setOnClickListener {
|
||||||
// change the locked/unlocked icon
|
// change the locked/unlocked icon
|
||||||
@ -218,9 +201,15 @@ internal class CustomExoPlayerView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun cancelHideControllerTask() {
|
||||||
|
runCatching {
|
||||||
|
handler.removeCallbacks(hideControllerRunnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun hideController() {
|
override fun hideController() {
|
||||||
// remove the callback to hide the controller
|
// remove the callback to hide the controller
|
||||||
handler.removeCallbacks(hideControllerRunnable)
|
cancelHideControllerTask()
|
||||||
super.hideController()
|
super.hideController()
|
||||||
|
|
||||||
// hide system bars if in fullscreen
|
// hide system bars if in fullscreen
|
||||||
@ -228,14 +217,15 @@ internal class CustomExoPlayerView(
|
|||||||
if (it.isFullscreen.value == true) {
|
if (it.isFullscreen.value == true) {
|
||||||
windowHelper?.setFullscreen()
|
windowHelper?.setFullscreen()
|
||||||
}
|
}
|
||||||
|
updateTopBarMargin()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showController() {
|
override fun showController() {
|
||||||
// remove the previous callback from the queue to prevent a flashing behavior
|
// remove the previous callback from the queue to prevent a flashing behavior
|
||||||
handler.removeCallbacks(hideControllerRunnable)
|
cancelHideControllerTask()
|
||||||
// automatically hide the controller after 2 seconds
|
// automatically hide the controller after 2 seconds
|
||||||
handler.postDelayed(hideControllerRunnable, 2000)
|
handler.postDelayed(hideControllerRunnable, AUTO_HIDE_CONTROLLER_DELAY)
|
||||||
super.showController()
|
super.showController()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,8 +382,8 @@ internal class CustomExoPlayerView(
|
|||||||
doubleTapOverlayBinding?.apply {
|
doubleTapOverlayBinding?.apply {
|
||||||
animateSeeking(rewindBTN, rewindIV, rewindTV, true)
|
animateSeeking(rewindBTN, rewindIV, rewindTV, true)
|
||||||
|
|
||||||
runnableHandler.removeCallbacks(hideRewindButtonRunnable)
|
|
||||||
// start callback to hide the button
|
// start callback to hide the button
|
||||||
|
runnableHandler.removeCallbacks(hideRewindButtonRunnable)
|
||||||
runnableHandler.postDelayed(hideRewindButtonRunnable, 700)
|
runnableHandler.postDelayed(hideRewindButtonRunnable, 700)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -530,7 +520,7 @@ internal class CustomExoPlayerView(
|
|||||||
|
|
||||||
override fun onPlaybackSpeedClicked() {
|
override fun onPlaybackSpeedClicked() {
|
||||||
player?.let {
|
player?.let {
|
||||||
PlaybackSpeedSheet(it as ExoPlayer).show(supportFragmentManager)
|
PlaybackOptionsSheet(it as ExoPlayer).show(supportFragmentManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -591,15 +581,7 @@ internal class CustomExoPlayerView(
|
|||||||
it.layoutParams = params
|
it.layoutParams = params
|
||||||
}
|
}
|
||||||
|
|
||||||
// add padding to the top bar to not overlap the status bar
|
updateTopBarMargin()
|
||||||
binding.topBar.let {
|
|
||||||
setPadding(
|
|
||||||
it.paddingLeft,
|
|
||||||
(if (newConfig?.orientation == Configuration.ORIENTATION_LANDSCAPE) 25 else 5).toPixel().toInt(),
|
|
||||||
it.paddingRight,
|
|
||||||
it.paddingBottom
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't add extra padding if there's no cutout
|
// don't add extra padding if there's no cutout
|
||||||
if ((context as? MainActivity)?.windowHelper?.hasCutout() == false) return
|
if ((context as? MainActivity)?.windowHelper?.hasCutout() == false) return
|
||||||
@ -632,6 +614,19 @@ internal class CustomExoPlayerView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add extra margin to the top bar to not overlap the status bar
|
||||||
|
*/
|
||||||
|
private fun updateTopBarMargin() {
|
||||||
|
val isFullscreen = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE ||
|
||||||
|
playerViewModel?.isFullscreen?.value == true
|
||||||
|
binding.topBar.let {
|
||||||
|
it.layoutParams = (it.layoutParams as MarginLayoutParams).apply {
|
||||||
|
topMargin = (if (isFullscreen) 25 else 5).toPixel().toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSingleTap() {
|
override fun onSingleTap() {
|
||||||
toggleController()
|
toggleController()
|
||||||
}
|
}
|
||||||
@ -706,9 +701,22 @@ internal class CustomExoPlayerView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for all child touch events
|
||||||
|
*/
|
||||||
|
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
||||||
|
// when a control is clicked, restart the countdown to hide the controller
|
||||||
|
if (isControllerFullyVisible) {
|
||||||
|
cancelHideControllerTask()
|
||||||
|
handler.postDelayed(hideControllerRunnable, AUTO_HIDE_CONTROLLER_DELAY)
|
||||||
|
}
|
||||||
|
return super.onInterceptTouchEvent(ev)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SUBTITLE_BOTTOM_PADDING_FRACTION = 0.158f
|
private const val SUBTITLE_BOTTOM_PADDING_FRACTION = 0.158f
|
||||||
private const val ANIMATION_DURATION = 100L
|
private const val ANIMATION_DURATION = 100L
|
||||||
|
private const val AUTO_HIDE_CONTROLLER_DELAY = 2000L
|
||||||
private val LANDSCAPE_MARGIN_HORIZONTAL = (20).toPixel().toInt()
|
private val LANDSCAPE_MARGIN_HORIZONTAL = (20).toPixel().toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,11 +60,12 @@ object NavigationHelper {
|
|||||||
playlistId: String? = null,
|
playlistId: String? = null,
|
||||||
channelId: String? = null,
|
channelId: String? = null,
|
||||||
keepQueue: Boolean = false,
|
keepQueue: Boolean = false,
|
||||||
timeStamp: Long? = null
|
timeStamp: Long? = null,
|
||||||
|
forceVideo: Boolean = false
|
||||||
) {
|
) {
|
||||||
if (videoId == null) return
|
if (videoId == null) return
|
||||||
|
|
||||||
if (PreferenceHelper.getBoolean(PreferenceKeys.AUDIO_ONLY_MODE, false)) {
|
if (PreferenceHelper.getBoolean(PreferenceKeys.AUDIO_ONLY_MODE, false) && !forceVideo) {
|
||||||
BackgroundHelper.stopBackgroundPlay(context)
|
BackgroundHelper.stopBackgroundPlay(context)
|
||||||
BackgroundHelper.playOnBackground(
|
BackgroundHelper.playOnBackground(
|
||||||
context,
|
context,
|
||||||
|
@ -38,6 +38,7 @@ class NowPlayingNotification(
|
|||||||
) {
|
) {
|
||||||
private var videoId: String? = null
|
private var videoId: String? = null
|
||||||
private var streams: Streams? = null
|
private var streams: Streams? = null
|
||||||
|
private var bitmap: Bitmap? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [MediaSessionCompat] for the [streams].
|
* The [MediaSessionCompat] for the [streams].
|
||||||
@ -105,24 +106,7 @@ class NowPlayingNotification(
|
|||||||
): Bitmap? {
|
): Bitmap? {
|
||||||
if (DataSaverMode.isEnabled(context)) return null
|
if (DataSaverMode.isEnabled(context)) return null
|
||||||
|
|
||||||
var bitmap: Bitmap? = null
|
if (bitmap == null) enqueueThumbnailRequest(callback)
|
||||||
|
|
||||||
val request = ImageRequest.Builder(context)
|
|
||||||
.data(streams?.thumbnailUrl)
|
|
||||||
.target { result ->
|
|
||||||
val bm = (result as BitmapDrawable).bitmap
|
|
||||||
// returns the bitmap on Android 13+, for everything below scaled down to a square
|
|
||||||
bitmap = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
ImageHelper.getSquareBitmap(bm)
|
|
||||||
} else {
|
|
||||||
bm
|
|
||||||
}
|
|
||||||
callback.onBitmap(bitmap!!)
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
|
|
||||||
// enqueue the thumbnail loading request
|
|
||||||
ImageHelper.imageLoader.enqueue(request)
|
|
||||||
|
|
||||||
return bitmap
|
return bitmap
|
||||||
}
|
}
|
||||||
@ -132,6 +116,25 @@ class NowPlayingNotification(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun enqueueThumbnailRequest(callback: PlayerNotificationManager.BitmapCallback) {
|
||||||
|
val request = ImageRequest.Builder(context)
|
||||||
|
.data(streams?.thumbnailUrl)
|
||||||
|
.target { result ->
|
||||||
|
val bm = (result as BitmapDrawable).bitmap
|
||||||
|
// returns the bitmap on Android 13+, for everything below scaled down to a square
|
||||||
|
bitmap = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
ImageHelper.getSquareBitmap(bm)
|
||||||
|
} else {
|
||||||
|
bm
|
||||||
|
}
|
||||||
|
callback.onBitmap(bitmap!!)
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// enqueue the thumbnail loading request
|
||||||
|
ImageHelper.imageLoader.enqueue(request)
|
||||||
|
}
|
||||||
|
|
||||||
private val customActionReceiver = object : CustomActionReceiver {
|
private val customActionReceiver = object : CustomActionReceiver {
|
||||||
override fun createCustomActions(
|
override fun createCustomActions(
|
||||||
context: Context,
|
context: Context,
|
||||||
@ -257,6 +260,8 @@ class NowPlayingNotification(
|
|||||||
) {
|
) {
|
||||||
this.videoId = videoId
|
this.videoId = videoId
|
||||||
this.streams = streams
|
this.streams = streams
|
||||||
|
// reset the thumbnail bitmap in order to become reloaded for the new video
|
||||||
|
this.bitmap = null
|
||||||
|
|
||||||
if (playerNotification == null) {
|
if (playerNotification == null) {
|
||||||
createMediaSession()
|
createMediaSession()
|
||||||
|
@ -18,7 +18,9 @@ import com.github.libretube.enums.AudioQuality
|
|||||||
import com.github.libretube.enums.PlayerEvent
|
import com.github.libretube.enums.PlayerEvent
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl
|
import com.google.android.exoplayer2.DefaultLoadControl
|
||||||
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
import com.google.android.exoplayer2.LoadControl
|
import com.google.android.exoplayer2.LoadControl
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes
|
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||||
import com.google.android.exoplayer2.ui.CaptionStyleCompat
|
import com.google.android.exoplayer2.ui.CaptionStyleCompat
|
||||||
import com.google.android.exoplayer2.video.VideoSize
|
import com.google.android.exoplayer2.video.VideoSize
|
||||||
@ -342,6 +344,12 @@ object PlayerHelper {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val skipSilence: Boolean
|
||||||
|
get() = PreferenceHelper.getBoolean(
|
||||||
|
PreferenceKeys.SKIP_SILENCE,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
fun getDefaultResolution(context: Context): String {
|
fun getDefaultResolution(context: Context): String {
|
||||||
return if (NetworkHelper.isNetworkMobile(context)) {
|
return if (NetworkHelper.isNetworkMobile(context)) {
|
||||||
PreferenceHelper.getString(
|
PreferenceHelper.getString(
|
||||||
@ -459,4 +467,16 @@ object PlayerHelper {
|
|||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load playback parameters such as speed and skip silence
|
||||||
|
*/
|
||||||
|
fun ExoPlayer.loadPlaybackParams(): ExoPlayer {
|
||||||
|
skipSilenceEnabled = skipSilence
|
||||||
|
playbackParameters = PlaybackParameters(
|
||||||
|
playbackSpeed.toFloat(),
|
||||||
|
1.0f
|
||||||
|
)
|
||||||
|
return this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package com.github.libretube.util
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
@ -48,10 +49,6 @@ class WindowHelper(private val activity: BaseActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun hasCutout(): Boolean {
|
fun hasCutout(): Boolean {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
return ViewCompat.getRootWindowInsets(activity.window.decorView)?.displayCutout != null
|
||||||
activity.window.decorView.rootWindowInsets.displayCutout != null
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
app/src/main/res/drawable/ic_video.xml
Normal file
10
app/src/main/res/drawable/ic_video.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v12c0,1.1 0.89,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.11 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12zM16,11l-7,4L9,7z" />
|
||||||
|
</vector>
|
@ -47,8 +47,8 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:textSize="18sp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_marginTop="10dp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@ -56,8 +56,8 @@
|
|||||||
android:id="@+id/time_bar"
|
android:id="@+id/time_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:labelBehavior="gone"
|
android:layout_marginHorizontal="20dp"
|
||||||
android:layout_marginHorizontal="20dp" />
|
app:labelBehavior="gone" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -69,14 +69,14 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="start|center"
|
android:layout_gravity="start|center"
|
||||||
tools:text="00:00"/>
|
tools:text="00:00" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/duration"
|
android:id="@+id/duration"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end|center"
|
android:layout_gravity="end|center"
|
||||||
tools:text="10:15"/>
|
tools:text="10:15" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
@ -84,7 +84,8 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_marginVertical="50dp">
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="36dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/prev"
|
android:id="@+id/prev"
|
||||||
@ -118,4 +119,44 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
style="@style/Widget.Material3.CardView.Elevated"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
app:cardCornerRadius="18dp"
|
||||||
|
android:layout_marginBottom="30dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:padding="15dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/open_queue"
|
||||||
|
style="@style/AudioPlayerButton"
|
||||||
|
android:src="@drawable/ic_queue" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/playback_options"
|
||||||
|
style="@style/AudioPlayerButton"
|
||||||
|
android:layout_width="27dp"
|
||||||
|
android:layout_height="27dp"
|
||||||
|
android:src="@drawable/ic_speed" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/open_video"
|
||||||
|
style="@style/AudioPlayerButton"
|
||||||
|
android:src="@drawable/ic_video" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/share"
|
||||||
|
style="@style/AudioPlayerButton"
|
||||||
|
android:src="@drawable/ic_share" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -238,4 +238,12 @@
|
|||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="AudioPlayerButton">
|
||||||
|
<item name="android:layout_width">30dp</item>
|
||||||
|
<item name="android:layout_height">30dp</item>
|
||||||
|
<item name="android:background">?attr/selectableItemBackgroundBorderless</item>
|
||||||
|
<item name="android:layout_marginStart">10dp</item>
|
||||||
|
<item name="android:layout_marginEnd">10dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
@ -78,17 +78,4 @@
|
|||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/background_mode">
|
|
||||||
|
|
||||||
<com.github.libretube.ui.views.SliderPreference
|
|
||||||
android:icon="@drawable/ic_speed"
|
|
||||||
app:defValue="1.0"
|
|
||||||
app:key="background_playback_speed"
|
|
||||||
app:stepSize="0.1"
|
|
||||||
app:title="@string/playback_speed"
|
|
||||||
app:valueFrom="0.2"
|
|
||||||
app:valueTo="4.0" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
Loading…
x
Reference in New Issue
Block a user