diff --git a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt index e87a00604..8565f98af 100644 --- a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt @@ -22,6 +22,7 @@ object PreferenceKeys { const val BREAK_REMINDER = "break_reminder" const val SAVE_FEED = "save_feed" const val NAVBAR_ITEMS = "navbar_items" + const val START_FRAGMENT = "start_fragment" /** * Appearance @@ -82,6 +83,9 @@ object PreferenceKeys { const val ALTERNATIVE_PLAYER_LAYOUT = "alternative_player_layout" const val USE_HLS_OVER_DASH = "use_hls" const val QUEUE_AUTO_INSERT_RELATED = "queue_insert_related_videos" + const val PLAYER_SWIPE_CONTROLS = "player_swipe_controls" + const val PLAYER_SCREEN_BRIGHTNESS = "player_screen_brightness" + const val SHOW_OPEN_WITH = "show_open_with" /** * Background mode diff --git a/app/src/main/java/com/github/libretube/extensions/Normalize.kt b/app/src/main/java/com/github/libretube/extensions/Normalize.kt new file mode 100644 index 000000000..0550a62c0 --- /dev/null +++ b/app/src/main/java/com/github/libretube/extensions/Normalize.kt @@ -0,0 +1,15 @@ +package com.github.libretube.extensions + +fun Int.normalize(oldMin: Int, oldMax: Int, newMin: Int, newMax: Int): Int { + val oldRange = oldMax - oldMin + val newRange = newMax - newMin + + return (this - oldMin) * newRange / oldRange + newMin +} + +fun Float.normalize(oldMin: Float, oldMax: Float, newMin: Float, newMax: Float): Float { + val oldRange = oldMax - oldMin + val newRange = newMax - newMin + + return (this - oldMin) * newRange / oldRange + newMin +} diff --git a/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt index 08a00e62a..a942b9204 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/MainActivity.kt @@ -53,7 +53,7 @@ class MainActivity : BaseActivity() { val autoRotationEnabled = PreferenceHelper.getBoolean(PreferenceKeys.AUTO_ROTATION, false) lateinit var searchView: SearchView - lateinit var searchItem: MenuItem + private lateinit var searchItem: MenuItem override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -131,9 +131,20 @@ class MainActivity : BaseActivity() { setupSubscriptionsBadge() + val playerViewModel = ViewModelProvider(this)[PlayerViewModel::class.java] + // new way of handling back presses onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { + if (playerViewModel.isFullscreen.value == true) { + for (fragment in supportFragmentManager.fragments) { + if (fragment is PlayerFragment) { + fragment.unsetFullscreen() + return + } + } + } + if (binding.mainMotionLayout.progress == 0F) { try { minimizePlayer() diff --git a/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt index 4f6d26638..1bdd22255 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt @@ -70,6 +70,7 @@ class OfflinePlayerActivity : BaseActivity() { binding.player.initialize( null, binding.doubleTapOverlay.binding, + binding.playerGestureControlsView.binding, null ) } diff --git a/app/src/main/java/com/github/libretube/ui/adapters/NavBarOptionsAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/NavBarOptionsAdapter.kt index cdb366228..a58ad475d 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/NavBarOptionsAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/NavBarOptionsAdapter.kt @@ -10,7 +10,8 @@ import com.github.libretube.databinding.NavOptionsItemBinding import com.github.libretube.ui.viewholders.NavBarOptionsViewHolder class NavBarOptionsAdapter( - val items: MutableList + val items: MutableList, + var selectedHomeTabId: Int ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NavBarOptionsViewHolder { @@ -31,7 +32,28 @@ class NavBarOptionsAdapter( holder.binding.apply { title.text = item.title checkbox.isChecked = item.isVisible + home.setImageResource( + if (item.itemId == selectedHomeTabId) R.drawable.ic_home else R.drawable.ic_home_outlined + ) + home.setOnClickListener { + if (selectedHomeTabId == item.itemId) { + return@setOnClickListener + } + if (!item.isVisible) { + Toast.makeText(root.context, R.string.not_enabled, Toast.LENGTH_SHORT).show() + return@setOnClickListener + } + val oldSelection = items.indexOfFirst { it.itemId == selectedHomeTabId } + selectedHomeTabId = item.itemId + listOf(position, oldSelection).forEach { + notifyItemChanged(it) + } + } checkbox.setOnClickListener { + if (item.itemId == selectedHomeTabId) { + Toast.makeText(root.context, R.string.select_other_start_tab, Toast.LENGTH_SHORT).show() + return@setOnClickListener + } if (!checkbox.isChecked && getVisibleItemsCount() < 2) { checkbox.isChecked = true Toast.makeText( @@ -41,7 +63,7 @@ class NavBarOptionsAdapter( ).show() return@setOnClickListener } - items[position].isVisible = checkbox.isChecked + item.isVisible = checkbox.isChecked } } } diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/NavBarOptionsDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/NavBarOptionsDialog.kt index 391766a6d..686ac218e 100644 --- a/app/src/main/java/com/github/libretube/ui/dialogs/NavBarOptionsDialog.kt +++ b/app/src/main/java/com/github/libretube/ui/dialogs/NavBarOptionsDialog.kt @@ -20,7 +20,7 @@ class NavBarOptionsDialog : DialogFragment() { val options = NavBarHelper.getNavBarItems(requireContext()) - val adapter = NavBarOptionsAdapter(options.toMutableList()) + val adapter = NavBarOptionsAdapter(options.toMutableList(), NavBarHelper.getStartFragmentId(requireContext())) val itemTouchCallback = object : ItemTouchHelper.Callback() { override fun getMovementFlags( @@ -63,6 +63,7 @@ class NavBarOptionsDialog : DialogFragment() { .setView(binding.root) .setPositiveButton(R.string.okay) { _, _ -> NavBarHelper.setNavBarItems(adapter.items, requireContext()) + NavBarHelper.setStartFragment(requireContext(), adapter.selectedHomeTabId) RequireRestartDialog() .show(requireParentFragment().childFragmentManager, null) } diff --git a/app/src/main/java/com/github/libretube/ui/extensions/SetInvisible.kt b/app/src/main/java/com/github/libretube/ui/extensions/SetInvisible.kt new file mode 100644 index 000000000..b5cb0f836 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/extensions/SetInvisible.kt @@ -0,0 +1,7 @@ +package com.github.libretube.ui.extensions + +import android.view.View + +fun View.setInvisible(value: Boolean) { + this.visibility = if (value) View.INVISIBLE else View.VISIBLE +} diff --git a/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt index 99051b126..a38494316 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt @@ -21,6 +21,7 @@ import com.github.libretube.ui.adapters.PlaylistsAdapter import com.github.libretube.ui.base.BaseFragment import com.github.libretube.ui.dialogs.CreatePlaylistDialog import com.github.libretube.ui.models.PlayerViewModel +import com.github.libretube.util.NavBarHelper import com.github.libretube.util.PreferenceHelper class LibraryFragment : BaseFragment() { @@ -68,6 +69,11 @@ class LibraryFragment : BaseFragment() { findNavController().navigate(R.id.downloadsFragment) } + val navBarItems = NavBarHelper.getNavBarItems(requireContext()) + if (navBarItems.filter { it.isVisible }.any { it.itemId == R.id.downloadsFragment }) { + binding.downloads.visibility = View.GONE + } + fetchPlaylists() binding.playlistRefresh.isEnabled = true diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index 50fde12b9..0eab64d17 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -7,7 +7,6 @@ import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo import android.content.res.Configuration -import android.graphics.Rect import android.media.session.PlaybackState import android.net.Uri import android.os.Build @@ -25,6 +24,7 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.annotation.RequiresApi import androidx.constraintlayout.motion.widget.MotionLayout import androidx.core.net.toUri import androidx.core.os.bundleOf @@ -49,6 +49,7 @@ import com.github.libretube.constants.PreferenceKeys import com.github.libretube.databinding.DoubleTapOverlayBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding import com.github.libretube.databinding.FragmentPlayerBinding +import com.github.libretube.databinding.PlayerGestureControlsViewBinding import com.github.libretube.db.DatabaseHelper import com.github.libretube.db.DatabaseHolder.Companion.Database import com.github.libretube.db.obj.WatchPosition @@ -72,6 +73,7 @@ import com.github.libretube.ui.base.BaseFragment import com.github.libretube.ui.dialogs.AddToPlaylistDialog import com.github.libretube.ui.dialogs.DownloadDialog import com.github.libretube.ui.dialogs.ShareDialog +import com.github.libretube.ui.extensions.setInvisible import com.github.libretube.ui.extensions.setupSubscriptionButton import com.github.libretube.ui.interfaces.OnlinePlayerOptions import com.github.libretube.ui.models.PlayerViewModel @@ -117,6 +119,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { lateinit var binding: FragmentPlayerBinding private lateinit var playerBinding: ExoStyledPlayerControlViewBinding private lateinit var doubleTapOverlayBinding: DoubleTapOverlayBinding + private lateinit var playerGestureControlsViewBinding: PlayerGestureControlsViewBinding private val viewModel: PlayerViewModel by activityViewModels() /** @@ -158,7 +161,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { /** * user preferences */ - private val token = PreferenceHelper.getToken() private var videoShownInExternalPlayer = false /** @@ -183,6 +185,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { exoPlayerView = binding.player playerBinding = binding.player.binding doubleTapOverlayBinding = binding.doubleTapOverlay.binding + playerGestureControlsViewBinding = binding.playerGestureControlsView.binding // Inflate the layout for this fragment return binding.root @@ -283,6 +286,8 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { binding.playerMotionLayout.setTransitionDuration(300) binding.playerMotionLayout.transitionToStart() } + + if (usePiP()) (activity as MainActivity).setPictureInPictureParams(getPipParams()) } // actions that don't depend on video information @@ -398,6 +403,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { LinearLayoutManager.HORIZONTAL, false ) + + if (!PreferenceHelper.getBoolean(PreferenceKeys.SHOW_OPEN_WITH, false)) { + binding.relPlayerOpen.visibility = View.GONE + binding.optionsLL.weightSum = 4f + } } private fun setFullscreen() { @@ -422,7 +432,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } @SuppressLint("SourceLockedOrientationActivity") - private fun unsetFullscreen() { + fun unsetFullscreen() { // leave fullscreen mode with(binding.playerMotionLayout) { getConstraintSet(R.id.start).constrainHeight(R.id.player, 0) @@ -501,6 +511,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { override fun onDestroy() { super.onDestroy() try { + // disable the auto PiP mode for SDK >= 32 + disableAutoPiP() + saveWatchPosition() // clear the playing queue and release the player @@ -518,16 +531,18 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } } + private fun disableAutoPiP() { + if (SDK_INT < Build.VERSION_CODES.S) { + return + } + activity?.setPictureInPictureParams( + PictureInPictureParams.Builder().setAutoEnterEnabled(false).build() + ) + } + // save the watch position if video isn't finished and option enabled private fun saveWatchPosition() { if (!PlayerHelper.watchPositionsEnabled) return - Log.e( - "watchpositions", - PreferenceHelper.getBoolean( - PreferenceKeys.WATCH_POSITION_TOGGLE, - true - ).toString() - ) val watchPosition = WatchPosition(videoId!!, exoPlayer.currentPosition) query { Database.watchPositionDao().insertAll(watchPosition) @@ -626,7 +641,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { if (binding.playerMotionLayout.progress != 1.0f) { // show controllers when not in picture in picture mode - if (!(SDK_INT >= Build.VERSION_CODES.O && activity?.isInPictureInPictureMode!!)) { + if (!(usePiP() && activity?.isInPictureInPictureMode!!)) { exoPlayerView.useController = true } } @@ -645,6 +660,13 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } } + /** + * Detect whether PiP is supported and enabled + */ + private fun usePiP(): Boolean { + return SDK_INT >= Build.VERSION_CODES.O && PlayerHelper.pipEnabled + } + /** * fetch the segments for SponsorBlock */ @@ -766,6 +788,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { binding.player.initialize( this, doubleTapOverlayBinding, + playerGestureControlsViewBinding, trackSelector ) @@ -830,7 +853,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { ) { transitioning = true // check whether autoplay is enabled - if (binding.player.autoplayEnabled) playNextVideo() + playNextVideo() } when (playbackState) { @@ -855,10 +878,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { query { Database.watchPositionDao().insertAll(watchPosition) } + disableAutoPiP() } // listen for the stop button in the notification - if (playbackState == PlaybackState.STATE_STOPPED && SDK_INT >= Build.VERSION_CODES.O) { + if (playbackState == PlaybackState.STATE_STOPPED && usePiP()) { // finish PiP by finishing the activity if (activity?.isInPictureInPictureMode!!) activity?.finish() } @@ -952,19 +976,13 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } // next and previous buttons - playerBinding.skipPrev.visibility = if ( - PlayerHelper.skipButtonsEnabled && PlayingQueue.hasPrev() - ) { - View.VISIBLE - } else { - View.INVISIBLE + if (PlayerHelper.skipButtonsEnabled) { + playerBinding.skipPrev.setInvisible(!PlayingQueue.hasPrev()) + playerBinding.skipNext.setInvisible(!PlayingQueue.hasNext()) } - playerBinding.skipNext.visibility = - if (PlayerHelper.skipButtonsEnabled) View.VISIBLE else View.INVISIBLE playerBinding.skipPrev.setOnClickListener { - videoId = PlayingQueue.getPrev() - playVideo() + playNextVideo(PlayingQueue.getPrev()) } playerBinding.skipNext.setOnClickListener { @@ -1388,13 +1406,18 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { super.onPictureInPictureModeChanged(isInPictureInPictureMode) if (isInPictureInPictureMode) { - // set portrait mode - unsetFullscreen() - // hide and disable exoPlayer controls exoPlayerView.hideController() exoPlayerView.useController = false + // set portrait mode + unsetFullscreen() + + if (viewModel.isMiniPlayerVisible.value == true) { + binding.playerMotionLayout.transitionToStart() + viewModel.isMiniPlayerVisible.value = false + } + with(binding.playerMotionLayout) { getConstraintSet(R.id.start).constrainHeight(R.id.player, -1) enableTransition(R.id.yt_transition, false) @@ -1421,15 +1444,21 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } fun onUserLeaveHint() { - if (SDK_INT >= Build.VERSION_CODES.O && shouldStartPiP()) { - activity?.enterPictureInPictureMode( - PictureInPictureParams.Builder() - .setActions(emptyList()) - .build() - ) + if (usePiP() && shouldStartPiP()) { + activity?.enterPictureInPictureMode(getPipParams()) } } + @RequiresApi(Build.VERSION_CODES.O) + fun getPipParams(): PictureInPictureParams = PictureInPictureParams.Builder() + .setActions(emptyList()) + .apply { + if (SDK_INT >= Build.VERSION_CODES.S) { + setAutoEnterEnabled(true) + } + } + .build() + private fun shouldStartPiP(): Boolean { if (!PlayerHelper.pipEnabled || exoPlayer.playbackState == PlaybackState.STATE_PAUSED || @@ -1438,15 +1467,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { return false } - val bounds = Rect() - binding.playerScrollView.getHitRect(bounds) - val backgroundModeRunning = isServiceRunning(requireContext(), BackgroundMode::class.java) - return ( - binding.playerScrollView.getLocalVisibleRect(bounds) || - viewModel.isFullscreen.value == true - ) && (exoPlayer.isPlaying || !backgroundModeRunning) + return exoPlayer.isPlaying && !backgroundModeRunning } private fun isServiceRunning(context: Context, serviceClass: Class<*>): Boolean { diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt index 168926a41..8205dafdc 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt @@ -6,6 +6,8 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.updatePadding +import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager @@ -21,10 +23,12 @@ import com.github.libretube.enums.PlaylistType import com.github.libretube.extensions.TAG import com.github.libretube.extensions.awaitQuery import com.github.libretube.extensions.query +import com.github.libretube.extensions.toDp import com.github.libretube.extensions.toID import com.github.libretube.ui.adapters.PlaylistAdapter import com.github.libretube.ui.base.BaseFragment import com.github.libretube.ui.extensions.serializable +import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet import com.github.libretube.util.ImageHelper import com.github.libretube.util.NavigationHelper @@ -43,6 +47,8 @@ class PlaylistFragment : BaseFragment() { private var isLoading = true private var isBookmarked = false + private val playerViewModel: PlayerViewModel by activityViewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { @@ -73,6 +79,12 @@ class PlaylistFragment : BaseFragment() { } updateBookmarkRes() + playerViewModel.isMiniPlayerVisible.observe(viewLifecycleOwner) { + binding.playlistRecView.updatePadding( + bottom = if (it) (64).toDp(resources).toInt() else 0 + ) + } + fetchPlaylist() } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/WatchHistoryFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/WatchHistoryFragment.kt index a9ae97a9a..69a06d390 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/WatchHistoryFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/WatchHistoryFragment.kt @@ -4,19 +4,25 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.updatePadding +import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.libretube.databinding.FragmentWatchHistoryBinding import com.github.libretube.db.DatabaseHolder.Companion.Database import com.github.libretube.extensions.awaitQuery +import com.github.libretube.extensions.toDp import com.github.libretube.ui.adapters.WatchHistoryAdapter import com.github.libretube.ui.base.BaseFragment +import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.util.ProxyHelper class WatchHistoryFragment : BaseFragment() { private lateinit var binding: FragmentWatchHistoryBinding + private val playerViewModel: PlayerViewModel by activityViewModels() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -29,6 +35,12 @@ class WatchHistoryFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + playerViewModel.isMiniPlayerVisible.observe(viewLifecycleOwner) { + binding.watchHistoryRecView.updatePadding( + bottom = if (it) (64).toDp(resources).toInt() else 0 + ) + } + val watchHistory = awaitQuery { Database.watchHistoryDao().getAll() } diff --git a/app/src/main/java/com/github/libretube/ui/interfaces/DoubleTapListener.kt b/app/src/main/java/com/github/libretube/ui/interfaces/DoubleTapListener.kt deleted file mode 100644 index 24fd9b1eb..000000000 --- a/app/src/main/java/com/github/libretube/ui/interfaces/DoubleTapListener.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.github.libretube.ui.interfaces - -import android.os.Handler -import android.os.Looper -import android.os.SystemClock -import android.view.View - -abstract class DoubleTapListener : View.OnClickListener { - - private val handler = Handler(Looper.getMainLooper()) - - private var lastClick = 0L - private var lastDoubleClick = 0L - - abstract fun onDoubleClick() - abstract fun onSingleClick() - - override fun onClick(v: View?) { - if (isSecondClick()) { - handler.removeCallbacks(runnable) - lastDoubleClick = elapsedTime() - onDoubleClick() - } else { - if (recentDoubleClick()) return - handler.removeCallbacks(runnable) - handler.postDelayed(runnable, MAX_TIME_DIFF) - lastClick = elapsedTime() - } - } - - private val runnable = Runnable { - if (isSecondClick()) return@Runnable - onSingleClick() - } - - private fun isSecondClick(): Boolean { - return elapsedTime() - lastClick < MAX_TIME_DIFF - } - - private fun recentDoubleClick(): Boolean { - return elapsedTime() - lastDoubleClick < MAX_TIME_DIFF / 2 - } - - fun elapsedTime() = SystemClock.elapsedRealtime() - - companion object { - private const val MAX_TIME_DIFF = 400L - } -} diff --git a/app/src/main/java/com/github/libretube/ui/interfaces/PlayerGestureOptions.kt b/app/src/main/java/com/github/libretube/ui/interfaces/PlayerGestureOptions.kt new file mode 100644 index 000000000..ee6e60dd1 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/interfaces/PlayerGestureOptions.kt @@ -0,0 +1,18 @@ +package com.github.libretube.ui.interfaces + +interface PlayerGestureOptions { + + fun onSingleTap() + + fun onDoubleTapCenterScreen() + + fun onDoubleTapLeftScreen() + + fun onDoubleTapRightScreen() + + fun onSwipeLeftScreen(distanceY: Float) + + fun onSwipeRightScreen(distanceY: Float) + + fun onSwipeEnd() +} diff --git a/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt b/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt index dd51ca16f..ccb198f29 100644 --- a/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt +++ b/app/src/main/java/com/github/libretube/ui/views/CustomExoPlayerView.kt @@ -1,6 +1,7 @@ package com.github.libretube.ui.views import android.annotation.SuppressLint +import android.app.Activity import android.content.Context import android.content.res.Configuration import android.os.Handler @@ -11,15 +12,20 @@ import android.view.View import com.github.libretube.R import com.github.libretube.databinding.DoubleTapOverlayBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding +import com.github.libretube.databinding.PlayerGestureControlsViewBinding +import com.github.libretube.extensions.normalize import com.github.libretube.extensions.toDp import com.github.libretube.obj.BottomSheetItem import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.base.BaseActivity -import com.github.libretube.ui.interfaces.DoubleTapListener import com.github.libretube.ui.interfaces.OnlinePlayerOptions +import com.github.libretube.ui.interfaces.PlayerGestureOptions import com.github.libretube.ui.interfaces.PlayerOptions import com.github.libretube.ui.sheets.BaseBottomSheet import com.github.libretube.ui.sheets.PlaybackSpeedSheet +import com.github.libretube.util.AudioHelper +import com.github.libretube.util.BrightnessHelper +import com.github.libretube.util.PlayerGestureController import com.github.libretube.util.PlayerHelper import com.github.libretube.util.PlayingQueue import com.google.android.exoplayer2.PlaybackParameters @@ -33,8 +39,16 @@ import com.google.android.exoplayer2.util.RepeatModeUtil internal class CustomExoPlayerView( context: Context, attributeSet: AttributeSet? = null -) : StyledPlayerView(context, attributeSet), PlayerOptions { +) : StyledPlayerView(context, attributeSet), PlayerOptions, PlayerGestureOptions { val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this) + + /** + * Objects for player tap and swipe gesture + */ + private lateinit var gestureViewBinding: PlayerGestureControlsViewBinding + private lateinit var playerGestureController: PlayerGestureController + private lateinit var brightnessHelper: BrightnessHelper + private lateinit var audioHelper: AudioHelper private var doubleTapOverlayBinding: DoubleTapOverlayBinding? = null /** @@ -45,16 +59,12 @@ internal class CustomExoPlayerView( private val runnableHandler = Handler(Looper.getMainLooper()) - // the x-position of where the user clicked - private var xPos = 0F - var isPlayerLocked: Boolean = false /** * Preferences */ var autoplayEnabled = PlayerHelper.autoPlayEnabled - private var doubleTapAllowed = true private var resizeModePref = PlayerHelper.resizeModePref @@ -65,42 +75,23 @@ internal class CustomExoPlayerView( if (isControllerFullyVisible) hideController() else showController() } - private val doubleTouchListener = object : DoubleTapListener() { - override fun onDoubleClick() { - if (!doubleTapAllowed) return - val eventPositionPercentageX = xPos / width - when { - eventPositionPercentageX < 0.4 -> rewind() - eventPositionPercentageX > 0.6 -> forward() - else -> { - player?.let { player -> - if (player.isPlaying) { - player.pause() - } else { - player.play() - } - } - } - } - } - - override fun onSingleClick() { - toggleController() - } - } - fun initialize( playerViewInterface: OnlinePlayerOptions?, doubleTapOverlayBinding: DoubleTapOverlayBinding, + playerGestureControlsViewBinding: PlayerGestureControlsViewBinding, trackSelector: TrackSelector? ) { this.playerOptionsInterface = playerViewInterface this.doubleTapOverlayBinding = doubleTapOverlayBinding this.trackSelector = trackSelector + this.gestureViewBinding = playerGestureControlsViewBinding + this.playerGestureController = PlayerGestureController(context, this) + this.brightnessHelper = BrightnessHelper(context as Activity) + this.audioHelper = AudioHelper(context) - // set the double click listener for rewind/forward - setOnClickListener(doubleTouchListener) - + // Set touch listner for tap and swipe gestures. + setOnTouchListener(playerGestureController) + initializeGestureProgress() enableDoubleTapToSeek() initializeAdvancedOptions(context) @@ -144,10 +135,6 @@ internal class CustomExoPlayerView( } override fun onTouchEvent(event: MotionEvent): Boolean { - // save the x position of the touch event - xPos = event.x - // listen for a double touch - doubleTouchListener.onClick(this) return false } @@ -261,8 +248,8 @@ internal class CustomExoPlayerView( binding.exoBottomBar.visibility = visibility binding.closeImageButton.visibility = visibility - // disable double tap to seek if the player is locked - doubleTapAllowed = !isLocked + // disable tap and swipe gesture if the player is locked + playerGestureController.isEnabled = isLocked } private fun enableDoubleTapToSeek() { @@ -331,6 +318,53 @@ internal class CustomExoPlayerView( } } + private fun initializeGestureProgress() { + gestureViewBinding.brightnessProgressBar.let { bar -> + bar.progress = brightnessHelper.getBrightnessWithScale(bar.max.toFloat(), saved = true).toInt() + } + gestureViewBinding.volumeProgressBar.let { bar -> + bar.progress = audioHelper.getVolumeWithScale(bar.max) + } + } + + private fun updateBrightness(distance: Float) { + gestureViewBinding.brightnessControlView.visibility = View.VISIBLE + val bar = gestureViewBinding.brightnessProgressBar + + if (bar.progress == 0) { + // If brightness progress goes to below 0, set to system brightness + if (distance <= 0) { + brightnessHelper.resetToSystemBrightness() + gestureViewBinding.brightnessImageView.setImageResource(R.drawable.ic_brightness_auto) + gestureViewBinding.brightnessTextView.text = resources.getString(R.string.auto) + return + } + gestureViewBinding.brightnessImageView.setImageResource(R.drawable.ic_brightness) + } + + bar.incrementProgressBy(distance.toInt()) + gestureViewBinding.brightnessTextView.text = "${bar.progress.normalize(0, bar.max, 0, 100)}" + brightnessHelper.setBrightnessWithScale(bar.progress.toFloat(), bar.max.toFloat()) + } + + private fun updateVolume(distance: Float) { + gestureViewBinding.volumeControlView.visibility = View.VISIBLE + val bar = gestureViewBinding.volumeProgressBar + + if (bar.progress == 0) { + gestureViewBinding.volumeImageView.setImageResource( + when { + distance > 0 -> R.drawable.ic_volume_up + else -> R.drawable.ic_volume_off + } + ) + } + bar.incrementProgressBy(distance.toInt()) + audioHelper.setVolumeWithScale(bar.progress, bar.max) + + gestureViewBinding.volumeTextView.text = "${bar.progress.normalize(0, bar.max, 0, 100)}" + } + override fun onAutoplayClicked() { // autoplay options dialog BaseBottomSheet() @@ -407,5 +441,55 @@ internal class CustomExoPlayerView( params.bottomMargin = offset.toInt() it.layoutParams = params } + + if (PlayerHelper.swipeGestureEnabled) { + when (newConfig?.orientation) { + Configuration.ORIENTATION_LANDSCAPE -> brightnessHelper.restoreSavedBrightness() + else -> brightnessHelper.resetToSystemBrightness(false) + } + } + } + + override fun onSingleTap() { + toggleController() + } + + override fun onDoubleTapCenterScreen() { + player?.let { player -> + if (player.isPlaying) { + player.pause() + if (!isControllerFullyVisible) showController() + } else { + player.play() + if (isControllerFullyVisible) hideController() + } + } + } + + override fun onDoubleTapLeftScreen() { + rewind() + } + + override fun onDoubleTapRightScreen() { + forward() + } + + override fun onSwipeLeftScreen(distanceY: Float) { + if (!PlayerHelper.swipeGestureEnabled || resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) return + + if (isControllerFullyVisible) hideController() + updateBrightness(distanceY) + } + + override fun onSwipeRightScreen(distanceY: Float) { + if (!PlayerHelper.swipeGestureEnabled || resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) return + + if (isControllerFullyVisible) hideController() + updateVolume(distanceY) + } + + override fun onSwipeEnd() { + gestureViewBinding.brightnessControlView.visibility = View.GONE + gestureViewBinding.volumeControlView.visibility = View.GONE } } diff --git a/app/src/main/java/com/github/libretube/ui/views/PlayerGestureControlsView.kt b/app/src/main/java/com/github/libretube/ui/views/PlayerGestureControlsView.kt new file mode 100644 index 000000000..8e3ac3fd7 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/views/PlayerGestureControlsView.kt @@ -0,0 +1,26 @@ +package com.github.libretube.ui.views + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.constraintlayout.widget.ConstraintLayout +import com.github.libretube.databinding.PlayerGestureControlsViewBinding + +class PlayerGestureControlsView( + context: Context, + attrs: AttributeSet? = null +) : ConstraintLayout(context, attrs) { + var binding: PlayerGestureControlsViewBinding + + init { + val layoutInflater = LayoutInflater.from(context) + binding = PlayerGestureControlsViewBinding.inflate(layoutInflater, this, true) + } + + override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { + super.onSizeChanged(width, height, oldHeight, oldHeight) + + binding.brightnessProgressBar.max = (height * 0.7).toInt() + binding.volumeProgressBar.max = (height * 0.7).toInt() + } +} diff --git a/app/src/main/java/com/github/libretube/ui/views/SliderPreference.kt b/app/src/main/java/com/github/libretube/ui/views/SliderPreference.kt index 239df8dd6..a9ff71664 100644 --- a/app/src/main/java/com/github/libretube/ui/views/SliderPreference.kt +++ b/app/src/main/java/com/github/libretube/ui/views/SliderPreference.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import androidx.preference.Preference import com.github.libretube.R import com.github.libretube.databinding.DialogSliderBinding +import com.github.libretube.extensions.round import com.github.libretube.util.PreferenceHelper import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -20,11 +21,33 @@ class SliderPreference( attributeSet ) { private lateinit var sliderBinding: DialogSliderBinding + private var defValue = 0f - val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.SliderPreference) + private var prefValue: Float + get() = PreferenceHelper.getString( + key, + defValue.toString() + ).toFloat() + set(value) { + PreferenceHelper.putString( + key, + value.toString() + ) + } + + private val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.SliderPreference) + + override fun onAttached() { + super.onAttached() + + defValue = typedArray.getFloat(R.styleable.SliderPreference_defValue, 1.0f) + } + + override fun getSummary(): CharSequence { + return prefValue.toString() + } override fun onClick() { - val defValue = typedArray.getFloat(R.styleable.SliderPreference_defValue, 1.0f) val valueFrom = typedArray.getFloat(R.styleable.SliderPreference_valueFrom, 1.0f) val valueTo = typedArray.getFloat(R.styleable.SliderPreference_valueTo, 10.0f) val stepSize = typedArray.getFloat(R.styleable.SliderPreference_stepSize, 1.0f) @@ -34,26 +57,46 @@ class SliderPreference( ) sliderBinding.slider.apply { - value = PreferenceHelper.getString( - key, - defValue.toString() - ).toFloat() + this.value = prefValue this.valueFrom = valueFrom this.valueTo = valueTo this.stepSize = stepSize } + sliderBinding.minus.setOnClickListener { + sliderBinding.slider.value = maxOf(valueFrom, sliderBinding.slider.value - stepSize) + } + + sliderBinding.plus.setOnClickListener { + sliderBinding.slider.value = minOf(valueTo, sliderBinding.slider.value + stepSize) + } + + sliderBinding.slider.addOnChangeListener { slider, _, _ -> + listOf(sliderBinding.minus, sliderBinding.plus).forEach { + it.alpha = 1f + } + when (slider.value) { + slider.valueFrom -> sliderBinding.minus.alpha = 0.5f + slider.valueTo -> sliderBinding.plus.alpha = 0.5f + } + updateCurrentValueText() + } + + updateCurrentValueText() + MaterialAlertDialogBuilder(context) .setTitle(title) .setView(sliderBinding.root) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.okay) { _, _ -> - PreferenceHelper.putString( - key, - sliderBinding.slider.value.toString() - ) + prefValue = sliderBinding.slider.value + summary = sliderBinding.slider.value.toString() } .show() super.onClick() } + + private fun updateCurrentValueText() { + sliderBinding.currentValue.text = sliderBinding.slider.value.round(2).toString() + } } diff --git a/app/src/main/java/com/github/libretube/util/AudioHelper.kt b/app/src/main/java/com/github/libretube/util/AudioHelper.kt new file mode 100644 index 000000000..603933cd5 --- /dev/null +++ b/app/src/main/java/com/github/libretube/util/AudioHelper.kt @@ -0,0 +1,50 @@ +package com.github.libretube.util + +import android.content.Context +import android.media.AudioManager +import android.os.Build +import androidx.core.math.MathUtils +import com.github.libretube.extensions.normalize + +class AudioHelper( + context: Context, + private val stream: Int = AudioManager.STREAM_MUSIC +) { + + private lateinit var audioManager: AudioManager + private var minimumVolumeIndex = 0 + private var maximumVolumeIndex = 16 + + init { + (context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.let { + audioManager = it + maximumVolumeIndex = it.getStreamMaxVolume(stream) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + minimumVolumeIndex = it.getStreamMinVolume(stream) + } + } + } + + var volume: Int + get() { + return if (this::audioManager.isInitialized) { + audioManager.getStreamVolume(stream) - minimumVolumeIndex + } else { + 0 + } + } + set(value) { + if (this::audioManager.isInitialized) { + val vol = MathUtils.clamp(value, minimumVolumeIndex, maximumVolumeIndex) + audioManager.setStreamVolume(stream, vol, 0) + } + } + + fun setVolumeWithScale(value: Int, maxValue: Int, minValue: Int = 0) { + volume = value.normalize(minValue, maxValue, minimumVolumeIndex, maximumVolumeIndex) + } + + fun getVolumeWithScale(maxValue: Int, minValue: Int = 0): Int { + return volume.normalize(minimumVolumeIndex, maximumVolumeIndex, minValue, maxValue) + } +} diff --git a/app/src/main/java/com/github/libretube/util/BrightnessHelper.kt b/app/src/main/java/com/github/libretube/util/BrightnessHelper.kt new file mode 100644 index 000000000..02aec92a1 --- /dev/null +++ b/app/src/main/java/com/github/libretube/util/BrightnessHelper.kt @@ -0,0 +1,75 @@ +package com.github.libretube.util + +import android.app.Activity +import android.os.Build +import android.view.WindowManager +import com.github.libretube.constants.PreferenceKeys +import com.github.libretube.extensions.normalize + +class BrightnessHelper(private val activity: Activity) { + + private val window = activity.window + private val minBrightness = 0.0f + private val maxBrightness = 1.0f + + /** + * Wrapper for the current screen brightness + */ + private var brightness: Float + get() = window.attributes.screenBrightness + set(value) { + val lp = window.attributes + lp.screenBrightness = value + window.attributes = lp + } + + /** + * Wrapper for the brightness persisted in the shared preferences. + */ + private var savedBrightness: Float + get() = PreferenceHelper.getFloat(PreferenceKeys.PLAYER_SCREEN_BRIGHTNESS, brightness) + set(value) = PreferenceHelper.putFloat(PreferenceKeys.PLAYER_SCREEN_BRIGHTNESS, value) + + /** + * Restore screen brightness to device system brightness. + * if [forced] is false then value will be stored only if it's not + * [WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE] value. + */ + fun resetToSystemBrightness(forced: Boolean = true) { + if (forced || brightness != WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE) { + savedBrightness = brightness + } + brightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE + } + + /** + * Set current screen brightness to saved brightness value. + */ + fun restoreSavedBrightness() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInPictureInPictureMode) { + return + } + brightness = savedBrightness + } + + /** + * Set current brightness value with scaling to given range. + * [shouldSave] determines whether the value should be persisted. + */ + fun setBrightnessWithScale(value: Float, maxValue: Float, minValue: Float = 0.0f, shouldSave: Boolean = false) { + brightness = value.normalize(minValue, maxValue, minBrightness, maxBrightness) + if (shouldSave) savedBrightness = brightness + } + + /** + * Get scaled brightness with given range. if [saved] is + * ture value will be retrived from shared preferences. + */ + fun getBrightnessWithScale(maxValue: Float, minValue: Float = 0.0f, saved: Boolean = false): Float { + return if (saved) { + savedBrightness.normalize(minBrightness, maxBrightness, minValue, maxValue) + } else { + brightness.normalize(minBrightness, maxBrightness, minValue, maxValue) + } + } +} diff --git a/app/src/main/java/com/github/libretube/util/NavBarHelper.kt b/app/src/main/java/com/github/libretube/util/NavBarHelper.kt index b1dc00330..f95a2f5fe 100644 --- a/app/src/main/java/com/github/libretube/util/NavBarHelper.kt +++ b/app/src/main/java/com/github/libretube/util/NavBarHelper.kt @@ -106,6 +106,21 @@ object NavBarHelper { ).icon = menuItem.icon } } - return navBarItems.first { it.isVisible }.itemId + return getStartFragmentId(bottomNav.context) + } + + fun getStartFragmentId(context: Context): Int { + val pref = PreferenceHelper.getInt(PreferenceKeys.START_FRAGMENT, Int.MAX_VALUE) + val defaultNavItems = getDefaultNavBarItems(context) + return if (pref == Int.MAX_VALUE) { + getNavBarItems(context).first { it.isVisible }.itemId + } else { + defaultNavItems.get(pref).itemId + } + } + + fun setStartFragment(context: Context, itemId: Int) { + val index = getDefaultNavBarItems(context).indexOfFirst { it.itemId == itemId } + PreferenceHelper.putInt(PreferenceKeys.START_FRAGMENT, index) } } diff --git a/app/src/main/java/com/github/libretube/util/PlayerGestureController.kt b/app/src/main/java/com/github/libretube/util/PlayerGestureController.kt new file mode 100644 index 000000000..d647306b8 --- /dev/null +++ b/app/src/main/java/com/github/libretube/util/PlayerGestureController.kt @@ -0,0 +1,130 @@ +package com.github.libretube.util + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View +import com.github.libretube.ui.interfaces.PlayerGestureOptions +import kotlin.math.abs + +class PlayerGestureController(context: Context, private val listner: PlayerGestureOptions) : + View.OnTouchListener { + + // width and height should be obtained each time using getter to adopt layout size changes. + private val width get() = Resources.getSystem().displayMetrics.widthPixels + private val height get() = Resources.getSystem().displayMetrics.heightPixels + private val orientation get() = Resources.getSystem().configuration.orientation + private val elapsedTime get() = SystemClock.elapsedRealtime() + + private val handler: Handler = Handler(Looper.getMainLooper()) + private val gestureDetector: GestureDetector + private var isMoving = false + var isEnabled = true + + init { + gestureDetector = GestureDetector(context, GestureListener(), handler) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(v: View, event: MotionEvent): Boolean { + if (event.action == MotionEvent.ACTION_UP && isMoving) { + isMoving = false + listner.onSwipeEnd() + } + + // Event can be already consumed by some view which may lead to NPE. + try { + gestureDetector.onTouchEvent(event) + } catch (_: Exception) { } + + // If orientation is landscape then allow `onScroll` to consume event and return true. + return orientation == Configuration.ORIENTATION_LANDSCAPE + } + + private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { + private var lastClick = 0L + private var lastDoubleClick = 0L + private var xPos = 0.0F + + override fun onDown(e: MotionEvent): Boolean { + if (isMoving) return false + + if (isEnabled && isSecondClick()) { + handler.removeCallbacks(runnable) + lastDoubleClick = elapsedTime + val eventPositionPercentageX = xPos / width + + when { + eventPositionPercentageX < 0.4 -> listner.onDoubleTapLeftScreen() + eventPositionPercentageX > 0.6 -> listner.onDoubleTapRightScreen() + else -> listner.onDoubleTapCenterScreen() + } + } else { + if (recentDoubleClick()) return true + handler.removeCallbacks(runnable) + handler.postDelayed(runnable, MAX_TIME_DIFF) + lastClick = elapsedTime + xPos = e.x + } + return true + } + + override fun onScroll( + e1: MotionEvent, + e2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + if (!isEnabled) return false + + val insideThreshHold = abs(e2.y - e1.y) <= MOVEMENT_THRESHOLD + val insideBorder = (e1.x < BORDER_THRESHOLD || e1.y < BORDER_THRESHOLD || e1.x > width - BORDER_THRESHOLD || e1.y > height - BORDER_THRESHOLD) + + // If the movement is inside threshold or scroll is horizontal then return false + if ( + !isMoving && ( + insideThreshHold || insideBorder || + abs(distanceX) > abs( + distanceY + ) + ) + ) { + return false + } + + isMoving = true + + when { + width * 0.5 > e1.x -> listner.onSwipeLeftScreen(distanceY) + width * 0.5 < e1.x -> listner.onSwipeRightScreen(distanceY) + } + return true + } + + private val runnable = Runnable { + // If user is scrolling then avoid single tap call + if (isMoving || isSecondClick()) return@Runnable + listner.onSingleTap() + } + + private fun isSecondClick(): Boolean { + return elapsedTime - lastClick < MAX_TIME_DIFF + } + + private fun recentDoubleClick(): Boolean { + return elapsedTime - lastDoubleClick < MAX_TIME_DIFF / 2 + } + } + + companion object { + private const val MAX_TIME_DIFF = 400L + private const val MOVEMENT_THRESHOLD = 30 + private const val BORDER_THRESHOLD = 90 + } +} diff --git a/app/src/main/java/com/github/libretube/util/PlayerHelper.kt b/app/src/main/java/com/github/libretube/util/PlayerHelper.kt index 346bb854d..177665efc 100644 --- a/app/src/main/java/com/github/libretube/util/PlayerHelper.kt +++ b/app/src/main/java/com/github/libretube/util/PlayerHelper.kt @@ -294,6 +294,12 @@ object PlayerHelper { true ) + val swipeGestureEnabled: Boolean + get() = PreferenceHelper.getBoolean( + PreferenceKeys.PLAYER_SWIPE_CONTROLS, + true + ) + fun getDefaultResolution(context: Context): String { return if (NetworkHelper.isNetworkMobile(context)) { PreferenceHelper.getString( diff --git a/app/src/main/java/com/github/libretube/util/PlayingQueue.kt b/app/src/main/java/com/github/libretube/util/PlayingQueue.kt index 7f2cfdaf1..e6171bb5b 100644 --- a/app/src/main/java/com/github/libretube/util/PlayingQueue.kt +++ b/app/src/main/java/com/github/libretube/util/PlayingQueue.kt @@ -45,12 +45,15 @@ object PlayingQueue { } fun getPrev(): String? { - val index = queue.indexOf(currentStream) - return if (index > 0) queue[index - 1].url?.toID() else null + return if (currentIndex() > 0) queue[currentIndex() - 1].url?.toID() else null } fun hasPrev(): Boolean { - return queue.indexOf(currentStream) > 0 + return currentIndex() > 0 + } + + fun hasNext(): Boolean { + return currentIndex() + 1 < size() } fun updateCurrent(streamItem: StreamItem) { diff --git a/app/src/main/java/com/github/libretube/util/PreferenceHelper.kt b/app/src/main/java/com/github/libretube/util/PreferenceHelper.kt index e9ac81e0d..f5f378134 100644 --- a/app/src/main/java/com/github/libretube/util/PreferenceHelper.kt +++ b/app/src/main/java/com/github/libretube/util/PreferenceHelper.kt @@ -37,6 +37,14 @@ object PreferenceHelper { editor.putBoolean(key, value).commit() } + fun putInt(key: String, value: Int) { + editor.putInt(key, value).commit() + } + + fun putFloat(key: String, value: Float) { + editor.putFloat(key, value).commit() + } + fun getString(key: String?, defValue: String): String { return settings.getString(key, defValue) ?: defValue } @@ -49,6 +57,10 @@ object PreferenceHelper { return settings.getInt(key, defValue) } + fun getFloat(key: String?, defValue: Float): Float { + return settings.getFloat(key, defValue) + } + fun clearPreferences() { editor.clear().apply() } diff --git a/app/src/main/res/drawable/controls_layout_bg.xml b/app/src/main/res/drawable/controls_layout_bg.xml new file mode 100644 index 000000000..9e7c439b7 --- /dev/null +++ b/app/src/main/res/drawable/controls_layout_bg.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml index 9460f4eaa..0553ae300 100644 --- a/app/src/main/res/drawable/ic_add.xml +++ b/app/src/main/res/drawable/ic_add.xml @@ -1,7 +1,7 @@ + + diff --git a/app/src/main/res/drawable/ic_brightness_auto.xml b/app/src/main/res/drawable/ic_brightness_auto.xml new file mode 100644 index 000000000..aa74c4915 --- /dev/null +++ b/app/src/main/res/drawable/ic_brightness_auto.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_home_outlined.xml b/app/src/main/res/drawable/ic_home_outlined.xml new file mode 100644 index 000000000..e1b9248ab --- /dev/null +++ b/app/src/main/res/drawable/ic_home_outlined.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_remove.xml b/app/src/main/res/drawable/ic_remove.xml new file mode 100644 index 000000000..6432fdeed --- /dev/null +++ b/app/src/main/res/drawable/ic_remove.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_swipe_gesture.xml b/app/src/main/res/drawable/ic_swipe_gesture.xml new file mode 100644 index 000000000..f10107b2e --- /dev/null +++ b/app/src/main/res/drawable/ic_swipe_gesture.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_volume_off.xml b/app/src/main/res/drawable/ic_volume_off.xml new file mode 100644 index 000000000..767b08878 --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_off.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_up.xml b/app/src/main/res/drawable/ic_volume_up.xml new file mode 100644 index 000000000..cdd4aac9e --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_up.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/vertical_progressbar.xml b/app/src/main/res/drawable/vertical_progressbar.xml new file mode 100644 index 000000000..c4350b1f8 --- /dev/null +++ b/app/src/main/res/drawable/vertical_progressbar.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_offline_player.xml b/app/src/main/res/layout/activity_offline_player.xml index f7c5345ec..963c69bf7 100644 --- a/app/src/main/res/layout/activity_offline_player.xml +++ b/app/src/main/res/layout/activity_offline_player.xml @@ -19,6 +19,13 @@ android:layout_gravity="center" android:gravity="center" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_slider.xml b/app/src/main/res/layout/dialog_slider.xml index 2a79fc766..491afe1ba 100644 --- a/app/src/main/res/layout/dialog_slider.xml +++ b/app/src/main/res/layout/dialog_slider.xml @@ -1,17 +1,51 @@ + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingHorizontal="10dp" + android:paddingVertical="10dp"> - + + + android:orientation="horizontal"> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_player.xml b/app/src/main/res/layout/fragment_player.xml index 775e8af8c..dd944b6e9 100644 --- a/app/src/main/res/layout/fragment_player.xml +++ b/app/src/main/res/layout/fragment_player.xml @@ -140,6 +140,7 @@ app:cardCornerRadius="27dp"> + + + android:nestedScrollingEnabled="false" /> diff --git a/app/src/main/res/layout/fragment_watch_history.xml b/app/src/main/res/layout/fragment_watch_history.xml index f8328f484..2cb061f89 100644 --- a/app/src/main/res/layout/fragment_watch_history.xml +++ b/app/src/main/res/layout/fragment_watch_history.xml @@ -32,7 +32,6 @@ android:layout_height="wrap_content" android:clipToPadding="false" android:nestedScrollingEnabled="false" - android:paddingBottom="64dp" android:visibility="gone" /> \ No newline at end of file diff --git a/app/src/main/res/layout/nav_options_item.xml b/app/src/main/res/layout/nav_options_item.xml index b0acb177a..a20ceacab 100644 --- a/app/src/main/res/layout/nav_options_item.xml +++ b/app/src/main/res/layout/nav_options_item.xml @@ -11,6 +11,14 @@ android:paddingEnd="0dp" tools:gravity="start|center_vertical"> + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index ac5293c28..e6ae09fe9 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -391,4 +391,8 @@ الاشاره المرجعيه مسح الإشارات المرجعية لا توجد إشارات مرجعية حتى الآن! + قوائم التشغيل المحلية + إدراج مقاطع فيديو ذات صلة + لم يتم تمكين عنصر القائمة! + يرجى تحديد علامة تبويب بدء أخرى أولا! \ No newline at end of file diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 31794dfc1..3ff3f2dfd 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -392,4 +392,15 @@ Əlfəcinləri təmizlə Hələ əlfəcin yoxdur! Əlaqədar videoları yerləşdir + Yerli pleylistlər + Lütfən, əvvəla başqa başlanğıc paneli seç! + Menyu elementi aktiv deyil! + Parlaqlıq + Avtomatik + Parlaqlığı və səs səviyyəsini nizamlamaq üçün sürüşdürmə jesti istifadə et. + Səs səviyyəsi + Sürüşdürmə nəzarətləri + İlə aç + Defolt + Videonu oynadıcıda 3-cü tərəf tətbiq ilə açmaq üçün düymə göstər. \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 7b85f697c..c97d2cbc2 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -392,4 +392,7 @@ Vymazat záložky Zatím žádné záložky! Vložit související videa + Místní playlisty + Položka nabídky není povolena! + Nejprve vyberte jinou kartu spuštění! \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ab762826d..f1ce82221 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -23,7 +23,7 @@ Abonniert Abonniere erst einige Kanäle. Video kann nicht heruntergeladen werden. - Eine weitere Datenübertragung ist bereits im Gange, bitte warten Sie, bis sie beendet ist. + Ein weiterer Download läuft bereits, bitte warte, bis er beendet ist. Herunterladen fehlgeschlagen. Konnte nicht in VLC geöffnet werden, möglicherweise ist es nicht installiert. Design @@ -65,7 +65,7 @@ System Kommentare Erneut versuchen - Stelle zunächst eine Verbindung zum Internet her. + Stelle zuerst eine Verbindung zum Internet her. %1$s Videos Einstellungen Standort @@ -128,7 +128,7 @@ Der Ort, an dem deine heruntergeladenen Medien gespeichert werden. Spenden Auf neue Version überprüfen - Klicke hier, um herauszufinden, ob die Anwendung auf dem neuesten Stand ist. + Nach Update suchen Es läuft die neueste Version. Du verwendest die neueste Version. Wiedergabegeschwindigkeit @@ -152,9 +152,9 @@ Instanzname URL zur Instanz-API Instanz hinzufügen - Geben Sie den Namen und die API URL ein. + Gebe den Namen und die API-URL ein. Benutzerdefinierte Instanzen löschen - Bitte geben Sie eine gültige URL ein + Bitte eine funktionierende URL angeben Piped, Anmeldung & Abonnements Hinzufügen… Musik: Nicht-musikalische Sektion @@ -166,10 +166,10 @@ Nur zur Verwendung in Musikvideos. Es sollte Teile des Videos abdecken, die nicht Teil der offiziellen Abmischungen sind. Am Ende sollte das Video der Spotify- oder einer anderen abgemischten Version so nahe wie möglich kommen oder das Sprechen und andere Ablenkungen reduzieren. Videoformat für Player Version %1$s - Lernen Sie das LibreTube-Team kennen und erfahren Sie, wie das alles abläuft. + Lerne das LibreTube-Team kennen und erfahre, wie alles abläuft. Für Segmente, die auf kommende Inhalte in diesem oder zukünftigen Videos der Serie hinweisen, aber keine zusätzlichen Informationen liefern. Wenn es Clips enthält, die nur hier erscheinen, ist dies sehr wahrscheinlich die falsche Kategorie. Automatische Wiedergabe - Zeigen Sie ähnliche Streams neben dem, was Sie sehen. + Zeige ähnliche Streams neben dem, was du siehst. Verwandte Inhalte Kein Ton Kein Video @@ -194,16 +194,16 @@ Das nächste Video automatisch abspielen, wenn das aktuelle beendet ist. Position merken Authentifizierungsinstanz - Verwenden Sie für authentifizierte Anrufe eine andere Instanz. - Wählen Sie eine Autorisierungsinstanz + Verwende für authentifizierte Aufrufe eine andere Instanz. + Wähle eine Authentifizierungsinstanz GitHub - Auto - Bitte stellen Sie eine Internetverbindung her, indem Sie WLAN oder mobile Daten aktivieren. + HLS + Bitte stelle eine Internetverbindung her, indem du WLAN oder mobile Daten aktivierst. Öffnen… Kapitel Wiedergabegeschwindigkeit Neustart der App erforderlich - Diese Änderung erfordert einen Neustart der App. Drücken Sie \'Ok\', um jetzt neu zu starten. + Starte die App neu, um die Änderungen anzuwenden. Audio und Video Ausrichtung im Vollbildmodus Video-Seitenverhältnis @@ -219,13 +219,13 @@ Verlorenes Vermächtnis Füllungstangente/Witze Keine - Wollen Sie die Anwendung jetzt aktualisieren\? + Möchtest du die neue LibreTube-Version jetzt installieren\? Für tangentiale Szenen, die nur als Füllmaterial oder für den Humor hinzugefügt wurden und für das Verständnis des Hauptinhalts des Videos nicht erforderlich sind. Ausgewählt Automatischer Vollbildmodus Nie Vollbild-Modus des Players wird aktiviert, wenn das Gerät gedreht wird. - Kein externer Player gefunden. Bitte stellen Sie sicher, dass Sie einen installiert haben. + Kein externer Player gefunden. Stelle bitte sicher, dass du einen installiert hast. Modisches Feuer Trendige Fackel Albern geformt @@ -245,7 +245,7 @@ Beste Qualität Video-Vorschau Von letzter Position weiter abspielen - Navigationsleistensichtbarkeit + Label-Sichtbarkeit Angesehene Videos lokal speichern Wiedergabe- und Suchverlauf Untertitel @@ -258,11 +258,11 @@ System Untertitel Audioformat für Player Keine Ergebnisse. - In Zwischenablage kopiert! 👌 + In Zwischenablage kopiert Untertitelsprache - Erhalten Sie Benachrichtigungen, wenn ihre Abonnierten Kanäle Videos veröffentlichen. + Erhalte Benachrichtigungen, wenn abonnierte Kanäle Videos veröffentlichen. Benachrichtigungen bei neuen Videos - Sind Sie sicher\? Dies kann nicht rückgängig gemacht werden! + Bist du sicher\? Dies kann nicht rückgängig gemacht werden! Untertitel %1$s haben neue Videos hochgeladen… Es sind %1$s neue Videos verfügbar @@ -285,7 +285,7 @@ Kanalnamen (Z-A) Fehler :( Mobiles Internet - Kopiert! + Kopiert Pausenerinnerung Geringste Aufrufe Internetverbindung erforderlich @@ -303,7 +303,7 @@ Benachrichtigungs-dienst Überspringungs Knöpfe Maximale Verlaufsgröße - Du guckst schon seit %1$s Minuten Videos, gönn dir \'ne Pause! + Du hast bereits %1$s Minuten in der App verbracht, Zeit für eine Pause. Ausfüllen Qualität und Format Aktuelles Video @@ -323,7 +323,7 @@ Mit Zeitstempel teilen Abos exportieren HLS auf 1080p beschränken - Laden Sie den Abo-Feed im Hintergrund und verhindern Sie, dass er automatisch aktualisiert wird. + Lade den Abo-Feed im Hintergrund und verhindere, dass er automatisch aktualisiert wird. Aus Downloads löschen Aufrufe Knöpfe um zum nächsten oder letzten Video zu kommen anzeigen. @@ -351,4 +351,46 @@ Wiedergabelistenname (umgekehrt) Kürzlich aktualisiert Kürzlich aktualisiert (umgekehrt) + Playlist dupliziert + Deabonnieren bestätigen + Markiere die Abschnitte auf der Zeitleiste. + Standard hell + Zeige Bestätigungsdialog vor dem Deabonnieren. + Bist du sicher, dass du %1$s deabbonieren möchtest\? + Lokale Playlists + Alles abspielen + Warteschlange wird abgespielt + Warteschlange + Zeit + Startzeit + Endzeit + Benachrichtigungszeit + Zeitspanne, in der Benachrichtigungen angezeigt werden dürfen. + Alternatives Trending-Layout + Anordnung + Layout + Alternatives Player-Layout + Zeige verwandte Videos in einer Reihe über den Kommentaren an, anstatt darunter. + Zur Playlist hinzugefügt + Markierungen + Tonspur + Standard + Livestreams + Alternatives Video-Layout + Nicht unterstütztes Dateiformat! + Verwende HLS + Verwende HLS statt DASH (wird langsamer sein, nicht empfohlen) + Automatisch + Auf Laufzeit beschränken + Öffne Warteschlange aus Benachrichtigung + Mehr anzeigen + Zeitstempel (Sekunden) + Trends + Vorgestellt + Was jetzt angesagt ist + Verwandte Videos einfügen + Lesezeichen + Lesezeichen + Lesezeichen löschen + Noch keine Lesezeichen vorhanden! \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 62964e200..a3f7a6382 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -266,7 +266,7 @@ Mejor Peor calidad Idioma subtítulo - Norificaciones para nuevos directos + Notificaciones para nuevos directos Comprobando todo … %1$s nuevo directo disponible Novedades @@ -392,4 +392,15 @@ ¡Aún no hay marcadores! Borrar marcadores Insertar videos relacionados + Listas de reproducción locales + ¡Seleccione otra pestaña para el inicio primero! + ¡Elemento del menú no habilitado! + Volumen + Automático + Controles del deslizamiento + Brillo + Use el gesto de deslizar para ajustar el brillo y el volumen. + Por defecto + Abrir con + Mostrar un botón para abrir el vídeo con una aplicación de terceros en el reproductor. \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 5d55f2b5a..74fc31e86 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1,59 +1,406 @@ हाँ - सर्च - सदस्यता ले + खोज करें + सब्सक्राइब अनसब्सक्राइब - शेयर + सांझा करें डाउनलोड - सेव + स॔भालें यूज़रनेम पासवर्ड लॉगिन रजिस्टर लॉगआउट - कैंसल - सफलतापूर्वक लॉग इन हो चुका है! - सफलतापूर्वक लॉग आउट किया! + रद्द करें + सफलतापूर्वक लॉग इन हो चुका है। + सफलतापूर्वक लॉग आउट किया। सफलतापूर्वक रजिस्टर हो गया! अब आप अपने मनचाहे चैनल को सब्सक्राइब कर सकते हैं। आप पहले ही लॉग इन हैं, आप अपने खाते से लॉगआउट कर सकते हैं। - कृपया पुनः लॉगिन करें और पुनः प्रयास करें! - इंस्टेंस चुनें + कृपया पुनः लॉगिन करें और पुनः प्रयास करें। + इंस्टेंस चुनें… अलग इंस्टेंस जोड़ें लॉग इन/रजिस्टर - सफलतापूर्वक सब्सक्राइब ही गया! - इस स्ट्रीम को डाउनलोड नहीं कर सकते! - डाउनलोड विफल! + सफलतापूर्वक सब्सक्राइब हो गया + इस स्ट्रीम को डाउनलोड नहीं कर सकते। + डाउनलोड विफल। वीएलसी में खोलें - वीएलसी में नहीं खुल सका। शायद यह अभी तक इंस्टॉल्ड नहीं है\? - यूट्यूब से सब्सक्रिप्शन आयात करें + वीएलसी में नहीं खुल सका। शायद यह अभी तक इंस्टॉल्ड नहीं है। + सब्सक्रिप्शन आयात करें ऐप थीम सर्वर में समस्या है। शायद कोई और इंस्टेंस चुनकर प्रयास करें\? - कुछ गलत हो गया! - उपयोगकर्ता नाम और पासवर्ड खाली नहीं हो सकता! - हे Piped अकाउंटसाठी आहे. + कुछ गलत हो गया। + उपयोगकर्ता नाम और पासवर्ड खाली नहीं हो सकता। + यह एक पाइप्ड खाते के लिए है। वीडियो रेजोल्यूशन ग्रिड कॉलम - यहाँ कुछ भी नहीं है! + यहाँ कुछ भी नहीं है। प्लेलिस्ट डिलीट करें क्या आप वाकई इस प्लेलिस्ट को हटाना चाहते हैं\? प्लेलिस्ट बनायें - प्लेलिस्ट बनाई गई! + प्लेलिस्ट बन गई। प्लेलिस्ट का नाम प्लेलिस्ट में जोड़ें - सफलता! + सफलता। अनुत्तीर्ण होना :( - नेटवर्क त्रुटि! - पहले कुछ चैनलों को सब्सक्राइब करें! + नेटवर्क त्रुटि। + पहले कुछ चैनलों को सब्सक्राइब करें। क्षेत्र चुनें कृपया पहले सेटिंग में लॉग इन या रजिस्टर करें! - डाउनलोड पूरा हो गया है! - एक और डाउनलोड पहले से ही हो रहा है कृपया इसके समाप्त होने तक प्रतीक्षा करें! - क्वालिटी चुनें: + डाउनलोड पूरा हो गया है। + एक और डाउनलोड पहले से ही हो रहा है कृपया इसके समाप्त होने तक प्रतीक्षा करें। + गुणवत्ता चुनें: के बारे में - होम - सदस्यता - लायब्ररी + घर + सब्सक्रिप्शनें + लायब्रेरी वीडियो - YouTube किंवा NewPipe वरून + यूट्यूब या न्यूपाइप से + समायोजन + पहले इंटरनेट से कनेक्ट करें। + प्लेलिस्ट का नाम खाली नहीं छोड़ा जा सकता + भाषा + सिस्टम + सिस्टम डिफ़ॉल्ट + हलका + गहरा + %1$s सब्सक्राइब्रस + सेटिंग्स + स्थान + इंस्टेंस + वैबसाइट + %1$s वीडियोज + फिर से कोशिश करें + टिॅपणीयां + प्लेलिस्ट क्लोन की गई + क्या आप वाकई %1$s को अनसब्सक्राईब करना चाहते हैं\? + अनसब्सक्राईब करने की पुष्टि करें + अनसब्सक्राईब करने से पहले एक पुष्टिकरण संवाद दिखाएं। + पाइप्ड + यूट्यूब + सभी चलाएं + जब सामग्री के बीच में लाइक, सब्सक्राइब या फॉलो करने का एक छोटा सा रिमाइंडर आता है। यदि लंबा है या किसी विशिष्ट चीज के बारे में है, तो इसके बजाय यह स्वयं प्रचार होना चाहिए। + आन + वाईटी संगीत पलेलिसटें + वाईटी संगीत गानें + अवैतनिक या स्वयं प्रचार को छोड़कर \"प्रायोजक\" के समान। इसमें मर्चेंडाइज, दान या उन लोगों के बारे में जानकारी शामिल है जिनके साथ उन्होंने सहयोग किया है। + इंटरेक्शन रिमाइंडर (लाइक और सब्सक्राइब) + केवल संगीत वीडियो में उपयोग के लिए। इसमें वीडियो के कुछ हिस्से शामिल होने चाहिए जो आधिकारिक मिक्स का हिस्सा नहीं हैं। अंत में, वीडियो को Spotify या किसी अन्य मिश्रित संस्करण के जितना संभव हो उतना करीब से मिलना चाहिए, या बात करने या अन्य विकर्षणों को कम करना चाहिए। + नए संस्करण की तलाश करें + आप नवीनतम संस्करण चला रहे हैं। + इस टिप्पणी का कोई उत्तर नहीं है। + डाउनलोड किए गए मीडिया के फ़ोल्डर का नाम इसमें संग्रहीत है। + मीटर किए गए + अगला चलाएं + लेखक + खोज फ़िल्टर चुनें + चैनल + सभी + प्लेलिस्ट + ठीक है + इतिहास + खोज इतिहास + इतिहास को मिटाएं + वाईटी संगीत वीडियोज + वाईटी संगीत एलबमें + डिफ़ॉल्ट टैब + प्रायोजक ब्लॉक + छोड़ा गया खंड + प्रायोजक + पेड प्रमोशन, पेड रेफरल और सीधे विज्ञापन। स्व-प्रचार या कारणों, रचनाकारों, वेबसाइटों और उत्पादों के लिए मुफ्त वास्तविक चिल्लाहट के लिए नहीं। + अवैतनिक / स्वयं प्रचार + इंटरमिशन/इंट्रो एनिमेशन + समाप्ति के बाद की जानकारी। जानकारी के साथ निष्कर्ष के लिए नहीं। + खंड + वास्तविक सामग्री के बिना एक अंतराल। एक ठहराव, स्थिर फ्रेम, दोहराए जाने वाला एनीमेशन हो सकता है। जानकारी वाले संक्रमणों के लिए उपयोग नहीं किया जाना चाहिए। + आंतरिक स्टोरेज + एसडी कार्ड + %1$s व्यूज + डिफॉल्ट + https://sponsor.ajay.app API का उपयोग करता है + आन + अंत कार्ड और क्रेडिट + लहजे + आराम लाल + पीला पीला + नवीनतम संस्करण चला रहा है। + उड़ती हुई लौ + सिली आकार की + बजने वाली कतार + कतार + समय + समय शुरू + समय समाप्त + सूचना का समय + समय अवधि जिसमें सूचनाओं को दिखाने की अनुमति है। + व्यूज + बूसटड पक्षी + चैनल का नाम (ए-जेड) + चैनल का नाम (जेड-ए) + छंटाई + कनेक्शन आवश्यक + समय कोड के साथ साझा करें + सब्सक्रिप्शनें निर्यात करें + अधिकतम इतिहास आकार + असीमित + पृष्ठभूमि मोड + क़तार में जोड़ें + विविध + ब्रेक रिमाइंडर + ब्रेक लेने का समय + आप पहले ही ऐप में %1$s मिनट बिता चुके हैं, ब्रेक लेने का समय आ गया है। + शॉर्ट + कोई उपशीर्षक उपलब्ध नहीं है + रिपीट मोड + फिॅट + फिल + जूम + कोई भी नहीं + मौजूदा + बैकअप और बहाली + बैकअप + चित्र में चित्र + आकार बदलें मोड + अधिकतम इमेज कैश आकार + क्लिपबोर्ड पर कापी हुआ + खोलें + याद दिलाने से पहले मिनट + लीगेसी सब्सक्रिप्शन दृश्य + गुणवत्ता और प्रारूप + डाउनलोडस से हटाएं + वैकल्पिक ट्रेंडिंग लेआउट + प्लेलिस्ट का नाम बदलें + वाई - फाई + मोबाइल डाटा + नए वीडियो के लिए संकेतक + यदि कुछ हैं तो नए वीडियो की मात्रा के साथ बैज दिखाएं। + खंड छोड़ें + मैन्युअली छोड़ें + खंडों को स्वचालित रूप से न छोड़ें, हमेशा पहले संकेत दें। + स्थानीय सब्सक्रिप्शनें + तरजीहें + कस्टम इंस्टैंस + पृष्ठभूमि में फ़ीड लोड करें + सब्सक्रिप्शन फीड को बैकग्राउंड में लोड करें और इसे ऑटो-रीफ्रेश होने से रोकें। + नेविगेशन पट्टी + कृपया कम से कम एक आइटम चुनें + प्रगतिशील लोड अंतराल आकार + एक कम मान आरंभिक वीडियो लोडिंग को गति दे सकता है। + डिफॉल्ट + पिॅच + ख़राबी + कॉपी किया गया + वैकल्पिक ट्रेंडिंग लेआउट + क्रम + लेआउट + वैकल्पिक प्लेयर लेआउट + संबंधित वीडियो को नीचे की बजाय टिप्पणियों के ऊपर एक पंक्ति के रूप में दिखाएं। + वर्तमान क्षेत्र के लिए रुझान अनुपलब्ध प्रतीत होता है। कृपया सेटिंग में दूसरे का चयन करें। + डिवाइस की जानकारी + आफ + प्लेलिस्ट में जोड़ा गया + भराव स्पर्शरेखा/चुटकुले + संगीत: गैर-संगीत अनुभाग + पूर्वावलोकन/रिकैप + लाइसेंस + आनंदमय नीला + ग्रूवी हरा + सुखद बैंगनी + मैटेरियल यू + सूचनाएं + आइकन + पृष्ठभूमि में चलाएं + इसे डाउनलोड करने के लिए GitHub पर रिलीज़ पर जाएं\? + दिखावट + व्‍यवहार + डाउनलोड करें + जहां डाउनलोड किया गया मीडिया स्टोर होता है। + मूवी फ़ोल्डर + URL को साझा करें + खोई हुई विरासत + ग्लिब ग्रेडिएंट + फैशनेबल आग + ट्रेंडी मशाल + स्किप बटन + अगले या पिछले वीडियो पर जाने के लिए बटन दिखाएं। + HLS को 1080p तक सीमित करें + फ़ाइल का नाम + अमान्य फ़ाइल नाम! + प्लेलिस्ट का क्रम + प्लेलिस्ट का नाम (उलट) + हाल ही में अपडेट हुए + हाल ही में अपडेट हुए (उलट) + इस श्रृंखला में आगामी सामग्री या भविष्य के वीडियो का विवरण देने वाले खंडों के लिए, लेकिन अतिरिक्त जानकारी प्रदान न करें। यदि इसमें क्लिप शामिल हैं जो केवल यहां दिखाई देती हैं, तो बहुत संभव है कि यह गलत श्रेणी हो। + काला + केवल फिलर या हास्य के लिए जोड़े गए स्पर्शरेखा दृश्यों के लिए वीडियो की मुख्य सामग्री को समझने की आवश्यकता नहीं है। + मार्करज + समय पट्टी पर खंडों को चिह्नित करें। + संस्करण %1$s उपलब्ध है + ऑडियो ट्रैक + डिफ़ॉल्ट + डाउनलोडस + वीडियो फार्मेट + ऑडियो और वीडियो दोनों डाउनलोड होने पर फाइलों का रूपांतरण। + लाइव स्ट्रीम + वैकल्पिक वीडियो लेआउट + डिफ़ॉल्ट हलका + असमर्थित फ़ाइल स्वरूप! + योगदान + दान + अपडेट के लिये जांचें + प्लेबैक गति + विकसित + पलेयर + अपनी पसंद के हिसाब से ऐप को एडजस्ट करें। + डाउनलोडस, और रीसेट + लाईव्ह + नाम + डाउनलोड फ़ोल्डर + संगीत फ़ोल्डर + सभी + केवल वाई-फाई पर + अनुवाद + कोई परिणाम नहीं। + डाउनलोड सफल रहा + एचएलएस का प्रयोग करें + डीएएसएच के बजाय एचएलएस का प्रयोग करें (धीमा होगा, अनुशंसित नहीं) + ऑटो + रनटाइम तक सीमित करें + सूचना से कतार खोलें + प्रवृत्तियां + विशेष रुप से प्रदर्शित + अभी क्या चलन में है + और दिखाओ + समय कोड (सेकंडस) + बुकमार्क + बुकमार्कस + बुकमार्कस मिटाएं + अभी तक कोई बुकमार्क नहीं! + पृष्ठभूमि मोड + स्थानीय प्लेलिस्ट + ध्वनि + ऑटो + सूचना कार्यकर्ता + कृपया पहले कोई दूसरा स्टार्ट टैब चुनें! + मेनू आइटम सक्षम नहीं! + चमक + स्वाइप नियंत्रण + चमक और ध्वनि को समायोजित करने के लिए स्वाइप जेस्चर का उपयोग करें। + ऑडियो प्लेयर को नियंत्रित करने के लिए बटनों के साथ सूचना दिखाता है। + डाउनलोड सेवा + मीडिया डाउनलोड करते समय एक सूचना दिखाता है। + संबंधित वीडियो डालें + नई स्ट्रीमें उपलब्ध होने पर एक सूचना दिखाता है। + आप जो देखते हैं उसके साथ मिलती-जुलती स्ट्रीम दिखाएं. + नाम और एपीआई यूआरएल भरें। + कृपया वह URL दर्ज करें जो काम करता हो + अध्याय छुपाएं + प्रीलोडिंग + बफ़र करने के लिए वीडियो की सेकंडस में उच्चतम मात्रा। + पाइप्ड, लॉगिन और सब्सक्रिप्शन + जोड़े गये हटाएं + वर्शन %1$s + टीम लिब्रेट्यूब के बारे में जानें और जानें कि यह सब कैसे होता है। + अध्याय दिखाएँ + जोड़ें… + इंस्टेंस का नाम + इंस्टेंस API के लिए URL + इंस्टेंस जोड़ें + संबंधित सामग्री + उपशीर्षक भाषा + सभी सेटिंग रीसेट करें और लॉग आउट करें\? + खाता + डिवाइस घूमाने पर फ़ुलस्क्रीन प्लेबैक। + नए परिवर्तनों का उपयोग करने के लिए ऐप को पुनरारंभ करें। + कोई बाहरी प्लेयर नहीं मिला। कृपया सुनिश्चित करें कि आपके पास एक इन्स्टाल है। + डिफ़ॉल्टस + के साथ खोलें + सबसे पुराने + प्लेयर के लिए विडिओ फॉर्मेट + विडियो नहीं + ऑडियो + ऑडियो नहीं + विडिओ + डाउनलोड हो रहा है… + आटो-प्ले + ट्रेंडिंग पेज छुपाएं + गुणवत्ता + व्‍यवहार + डिफ़ॉल्टस और व्यवहार + URL से इंस्टेंस का फर॔टऐंड + सीक में वृद्धि + ऑटो ठहराव + स्क्रीन बंद होने पर प्लेबैक रोकें। + मौजूदा वीडियो के बाद अगला वीडियो ऑटो-प्ले करें। + क्लोन प्लेलिस्ट + डिफॉल्ट्स का पुनःस्थापन + खाता डिलीट करें + अपना पाइप्ड खाता डिलीट करें + पुनर्स्थापित + पोजीशन याद रखें + पिछली प्लेबैक स्थिति से जारी रखें + प्रमाणीकरण इंस्टेंस + प्रमाणीकृत कॉल के लिए एक अलग इंस्टेंस का उपयोग करें। + एक प्रमाणीकरण इंस्टेंस चुनें + ऑडियो और वीडियो + गिटहब + फुलस्क्रीन ओरिएंटेशन + समुदाय + डिसकौरड + मैट्रिक्स + टेलीग्राम + रैडिट + ट्विटर + खोलें… + अध्याय + प्लेबैक गति + ऐप को पुनरारंभ करना आवश्यक है + लेबल दृश्यता + हमेशा + सिलेक्टिड + कभी नहीँ + डेटा-सेवर मोड + थंमनेल और अन्य छवियों को छोड़ें। + खोजें याद रखें + देखे गए वीडियो की स्थानीय रूप से सूची रखें + खोज और देखा इतिहास + याद प्लेबैक स्थितियां + रीसेट + सिस्टम कैप्शन शैली + कैप्शनज + कोई भी नहीं + नया लिब्रेट्यूब संस्करण अभी इंस्टाल करें\? + वीडियो पूर्वावलोकन + प्लेबैक सूचक को खींचते समय एक स्नैपशॉट दिखाएँ। + भाषा और क्षेत्र + बैकग्राउंड में चल रहा है… + कैप्शनज + एपीके डाउनलोड हो रहा है… + सूचनाएं + नई स्ट्रीमों के लिए सूचनाएं + आपके द्वारा अनुसरण किए जाने वाले रचनाकारों की ताज़ा सामग्री के बारे में सूचनाएं। + क्या आपको यकीन है\? इसे पूर्ववत नहीं किया जा सकता! + अभी तक कोई इतिहास नहीं है। + नवीनतम + सबसे ज्यादा देखे गए + सबसे कम देखे गए + कृपया इंटरनेट से कनेक्ट करने के लिए वाई-फ़ाई या मोबाइल डेटा चालू करें. + %1$s नई स्ट्रीम उपलब्ध हैं + %1$s द्वारा नई स्ट्रीम… + चेकिंग हर… + देखा इतिहास + एचएलएस + वीडियो पहलू अनुपात + ऑटो रोटेशन + लैंडस्केप + पोट्रेट + शुद्ध थीम + शुद्ध सफेद / काला थीम + ऑटो-फुलस्क्रीन + सामान्य + प्लेयर के लिए ऑडियो फार्मेट + ऑडियो गुणवत्ता + श्रेष्ठ + न्यूनतम + वीडियो को तीसरे पक्ष के ऐप्लिकेशन के साथ प्लेयर पर खोलने के लिए बटन दिखाएं। \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index ce739c19f..700db8959 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -225,7 +225,7 @@ Töltse be a nevet és az API URL-t. Maximális másodpercnyi videó a puffereléshez. Léptetés időköze - Ehhez a módosításhoz az alkalmazás újraindítása szükséges. Nyomja meg az \'OK\' gombot az újraindításhoz. + Indítsa újra az alkalmazást a változtatások használatához. Tiszta fehér/fekete téma Adattakarékos üzemmód Videó előnézet @@ -390,4 +390,9 @@ Könyvjelző Nincsenek még könyvjelzők! Könyvjelzők törlése + Helyi lejátszási listák + Kapcsolódó videók beillesztése + Felkapottak + Válasszon egy másik kezdőlapot először! + Menüelem nincs engedélyezve! \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 6098846b9..3e9405ff8 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -392,4 +392,15 @@ Hapus markah Belum ada markah! Masukkan video terkait + Daftar putar lokal + Item menu tidak diaktifkan! + Mohon pilih tab mulai yang lain terlebih dahulu! + Volume + Otomatis + Kendali usap + Gunakan gestur usap untuk mengatur kecerahan dan volume. + Buka Dengan + Bawaan + Tampilkan tombol dalam pemain untuk membuka video dengan aplikasi pihak ketiga. + Kecerahan \ No newline at end of file diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 1f983572f..37f579273 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -392,4 +392,7 @@ פינוי סימניות אין סימניות עדיין! הוספת סרטונים קשורים + רשימות נגינה מקומיות + פריט התפריט לא פעיל! + נא לבחור לשונית התחלה אחרת תחילה! \ No newline at end of file diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 7820fcea3..d09416692 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -81,7 +81,7 @@ Autoriai Aplanko, kuriame saugoma atsisiųsta medija, pavadinimas. Vidinė atmintis - Numatytoji + Numatyta Piped, prisijungimas ir prenumeratos Pridėti… Perdavimo šaltinio pavadinimas @@ -106,7 +106,7 @@ Automatinis pristabdymas Pristabdyti atkūrimą, kai ekranas išjungtas. Automatiškai paleisti kitą vaizdo įrašą po dabartinio. - Atkurti numatytuosius nustatymus + Atkurti numatytus nustatymus Paskyra Tęsti nuo paskutinės buvusios atkūrimo pozicijos Autentifikavimo perdavimo šaltinis @@ -179,7 +179,7 @@ Atrodo, kad dabartiniame regione tendencijos yra neprieinamos. Nustatymuose pasirinkite kitą. Apriboti HLS iki 1080p Mažesnė vertė gali pagreitinti pradinį vaizdo įrašų įkėlimą. - Numatytoji + Numatyta Pikinė Failo pavadinimas Netinkamas failo pavadinimas! @@ -295,7 +295,7 @@ Pritaikymas %1$s vaizdo įrašai Bandyti dar kartą - Numatytoji kortelė + Numatyta kortelė Apmokama reklama, apmokamos rekomendacijos ir tiesioginė reklama. Ne savireklama ar neapmokamas nuoširdus, kūrėjų, svetainių ir produktų reklamavimas. Sąveikos priminimas (skatinimas pamėgti ir prenumeruoti) Pertrauka / įžanginė animacija @@ -326,7 +326,7 @@ Ištrinti pridėtą Rodyti panašias transliacijas šalia to, ką žiūrite. Didžiausias užkrauto vaizdo įrašo sekundžių skaičius atmintyje. - Numatytieji nustatymai ir elgsena + Numatyti nustatymai ir elgsena Prasukimo žingsnio dydis HLS Viso ekrano padėtis @@ -360,7 +360,7 @@ Žymėti segmentus laiko juostoje. Tiesioginės transliacijos Alternatyvus vaizdo įrašų išdėstymas - Numatytoji šviesi + Numatyta šviesi Grojaraštis klonuotas Ar tikrai norite atšaukti %1$s prenumeratą\? Patvirtinkite prenumeratos atšaukimą @@ -377,7 +377,7 @@ Alternatyvus tendencijų išdėstymas Alternatyvus grotuvo išdėstymas Garso takelis - Numatytasis + Numatytas Nepalaikomas failo formatas! Automatinė Naudoti HLS @@ -392,4 +392,15 @@ Išvalyti žymes Dar nėra jokių žymų! Įterpti susijusius vaizdo įrašus + Vietiniai grojaraščiai + Meniu elementas neįjungtas! + Pirmiausia pasirinkite kitą pradžios kortelę! + Automatinis + Perbraukimo valdikliai + Ryškumas + Garsas + Braukimo gestu sureguliuokite ryškumą ir garsumą. + Numatyti + Atidaryti naudojant + Rodyti mygtuką grotuve, kad galima būtų atidaryti vaizdo įrašą naudojant trečiosios šalies programėlę. \ No newline at end of file diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 26b05f455..a3c4b9d40 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -387,4 +387,20 @@ På vei opp Alternativ avspillertilpasning Framhevet + Lokale spillelister + Legg til relaterte videoer + Bokmerke + Bokmerke + Tøm bokmerker + Ingen bokmerker enda. + Menyelementet er ikke påskrudd. + Velg en annen startfane først. + Dragningskontroller + Bruk dragnings-håndvendinger for å justere lys- og lydstyrke. + Åpne med + Forvalg + Lysstyrke + Lydstyrke + Auto + Vis en knapp i avspilleren for å åpne video med tredjepartsprogram. \ No newline at end of file diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index cb715bd1b..5094172de 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -392,4 +392,12 @@ ଏବେ ଯାଏଁ କୌଣସି ବୁକ୍ ମାର୍କ୍ ନାହିଁ! ବୁକ୍ ମାର୍କ୍ ଗୁଡ଼ିକୁ ଖାଲି କରନ୍ତୁ ସମ୍ପର୍କୀୟ ଭିଡିଓ ଯୋଡ଼ନ୍ତୁ + ସ୍ଥାନୀୟ ପ୍ଲେଲିଷ୍ଟଗୁଡିକ + ମେନୁ ଆଇଟମ୍ ସକ୍ଷମ ନୁହେଁ! + ଦୟାକରି ପ୍ରଥମେ ଅନ୍ୟ ଏକ ଆରମ୍ଭ ଟ୍ୟାବ୍ ଚୟନ କରନ୍ତୁ! + ଉଜ୍ଜଳତା + ଶବ୍ଦ + ସ୍ଵତଃ + ସ୍ୱାଇପ୍ ନିୟନ୍ତ୍ରଣ + ଉଜ୍ଜ୍ୱଳତା ଏବଂ ଭଲ୍ୟୁମ୍ ସଜାଡିବା ପାଇଁ ସ୍ୱାଇପ୍ ଅଙ୍ଗଭଙ୍ଗୀ ବ୍ୟବହାର କରନ୍ତୁ । \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index e9a5ff8ac..ee4130974 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -183,7 +183,7 @@ Przywróć kopię zapasową Naciśnij „OK”, aby zastosować zmiany. Aplikacja zostanie zrestartowana. Powiadomienia - Nowe treści + Przypomnij o nowych treściach Informuj o nowych treściach od twórców, których śledzisz. Czy na pewno\? Tego nie da się cofnąć! Wymagany restart aplikacji @@ -210,11 +210,11 @@ Pobieranie ukończone Przyciski pomijania Pokaż przyciski umożliwiające przejście do następnego lub poprzedniego filmu. - Zapamiętane pozycje - Kontynuuj odtwarzanie od ostatniej pozycji + Zapamiętaj pozycje + Kontynuuj odtwarzanie od ostatnio zapamiętanej pozycji HLS GitHub - Pozioma + Wymuś tryb poziomy Dodaj do kolejki Różne Stos kart @@ -236,7 +236,7 @@ Wybierz instancję autoryzacyjną Dźwięk i wideo Wyświetlanie pełnoekranowe - Pionowa + Wymuś tryb pionowy Społeczność Discord Matrix @@ -246,7 +246,7 @@ Nie znaleziono zewnętrznego odtwarzacza. Upewnij się, że masz takowy zainstalowany. Tryb oszczędzania danych Pomiń miniatury i inne obrazy. - Zapamiętane wyszukiwania + Zapamiętaj wyszukiwania Przechowuj lokalnie historię obejrzanych filmów Historia oglądania i wyszukiwania Zapamiętane pozycje odtwarzania @@ -392,4 +392,15 @@ Usuń zakładki Nie dodano zakładek. Wstaw powiązane filmy + Lokalne playlisty + Najpierw włącz kartę! + Najpierw wybierz inną kartę główną! + Jasność + Głośność + Auto + Sterowanie gestami + Steruj jasnością i głośnością za pomocą gestów. + Domyślne + Przycisk „Otwórz w…” + Pozwala pzekierować odtwarzanie do zewnętrznej aplikacji. \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d6386f144..b50ab3f2e 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -391,4 +391,13 @@ Favorito Limpar favoritos Ainda não há favoritos! + Playlists locais + Inserir vídeos relacionados + Item de menu não ativado! + Auto + Controles deslizantes + Use o gesto de deslizar para ajustar o brilho e o volume. + Selecione outra aba inicial primeiro! + Brilho + Volume \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 2e7e75cba..551c3d147 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -183,7 +183,7 @@ Instância de autenticação Utilize outra instância para invocações autenticadas. Escolha uma instância - Automático + HLS GitHub Pré-carregamento Número máximo de segundos para colocar em memória. @@ -224,9 +224,9 @@ Sempre Seleção Velocidade de reprodução - Esta alteração exige que a aplicação seja reiniciada. Prima \'Ok\' para reiniciar agora. + Esta alteração exige que a aplicação seja reiniciada. Tem que reiniciar a aplicação - Visibilidade da barra de navegação + Visibilidade do rótulo Ativar modo de ecrã completo ao ligar o ecrã. Tema Branco/preto puro @@ -358,7 +358,41 @@ Esquema alternativo de vídeos Claro por omissão Mostrar mais - Código de tempo + Código de tempo (segundos) Para cenas tangenciais adicionadas apenas para preencher ou humor não necessário para compreender o conteúdo do vídeo. Apenas para utilização em vídeos musicais. Deve abranger partes do vídeo que não façam parte das misturas oficiais. No final, o vídeo deve-se assemelhar ao Spotify ou a qualquer outra versão mista o mais próximo possível ou reduzir a conversa e outras distrações. + Lista de reprodução clonada + Confirmar a anulação da subscrição + Listas de jogos locais + Reproduzir tudo + Tem a certeza de que quer anular a subscrição %1$s\? + Mostrar um diálogo de confirmação antes de cancelar a inscrição. + Por favor, seleccione primeiro um outro separador de arranque! + Item do menu não activado! + Tempo + Hora de início + Tempo final + Hora de notificação + Período de tempo em que é permitida a apresentação de notificações. + Ordem + Traçado alternativo de tendências + Layout + Layout alternativo do reprodutor + Mostrar os vídeos relacionados como uma linha acima dos comentários em vez de abaixo. + Pista de áudio + Padrão + Formato de ficheiro não suportado! + Usar HLS + Usar HLS em vez de DASH (será mais lento, não recomendado) + Auto + Limite de tempo de execução + Fila aberta de notificação + Tendências + Destaque + O que está nas tendências agora + Inserir vídeos relacionados + Marcadores + Marcadores + Limpar Marcadores + Ainda sem marcadores! \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d2fd1041a..9da42b4d8 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -158,7 +158,7 @@ URL-адрес API экземпляра Максимальное количество секунд видео для буферизации Очистить добавленные - Показывать похожие видео рядом с тем, что вы смотрите. + Показывать связанные видео рядом с тем, что вы смотрите. Пожалуйста, введите рабочий URL Предзагрузка Загрузки @@ -391,4 +391,8 @@ Закладок пока нет! Рекомендации Закладка + Сначала выберите другую начальную вкладку! + Локальные плейлисты + Пункт меню не включен! + Вставить связанные видео \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 1c8172d46..8c38eff52 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -392,4 +392,12 @@ Yer imlerini temizle Henüz yer imi yok! İlgili videoları ekle + Yerel oynatma listeleri + Menü ögesi etkinleştirilmedi! + Lütfen önce başka bir başlangıç sekmesi seçin! + Ses + Otomatik + Kaydırma kontrolleri + Parlaklık + Parlaklığı ve sesi ayarlamak için kaydırma hareketini kullan. \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index daee26081..807e9aaec 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -392,4 +392,15 @@ Очистити закладки Ще немає закладок! Вставити пов\'язані відео + Локальні добірки + Пункт меню не увімкнено! + Спочатку виберіть іншу вкладку для запуску! + Яскравість + Авто + Керування посуваннями + Для регулювання яскравості та гучності використовувати жест посування. + Гучність + Типові + Відкрити в + Показувати на програвачі кнопку для відкриття відео за допомогою стороннього застосунку. \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 4f31bfd1a..cf0a7bf27 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -6,7 +6,7 @@ Chất lượng Tìm kiếm - Videos + Các video Đăng kí Hủy đăng kí Chia sẻ @@ -50,7 +50,7 @@ Ngôn ngữ Ngôn ngữ hệ thống Server - %1$s videos + %1$s các video Website Kết nối với mạng trước. Thử lại @@ -82,7 +82,7 @@ Màu xanh lá vui vẻ Màu đỏ resting Thông báo - Piped + Đã thông qua YouTube Phiên bản %1$s đã có sẵn Tải xuống @@ -131,7 +131,7 @@ Filler Tangent / Trò đùa Xem trước/Tóm tắt Cho các phân đoạn mà thể hiện điều sắp diễn ra hay ở videos tới ở trong cùng series đó, nhưng không có cung cấp thông tin thêm. Nếu nó bao gồm các clips mà chỉ xuất hiện ở đây thì có thể không phải là một phân loại thích hợp. - Material You + Tài liệu bạn Biểu tượng app Kích hoạt Vô hiệu hóa @@ -311,7 +311,7 @@ Phóng to Không Dung lượng cache ảnh tối đa - Dùng instance khác để xác minh. + Dùng một yêu cầu khác cho cuộc gọi được xác minh. Chỉ trên Wi-Fi Đã sao chép Xuất mục đăng ký @@ -324,7 +324,7 @@ Nhắc nhở nghỉ giải lao Đến lúc nghỉ ngơi Bạn đã dùng app được %1$s phút rồi, nghỉ ngơi nhá bạn yêu. - Shorts + Các đoạn video short Sao lưu và phục hồi Sao lưu Ảnh trong ảnh @@ -388,7 +388,19 @@ Tính năng Xu hướng hiện tại Chèn thêm các video liên quan - Bookmarks + Đánh dấu trang Xóa tất cả bookmark Chưa có bookmark! + Danh sách phát cục bộ + Mục menu không được kích hoạt! + Độ sáng + Âm lượng + Tự động + Mở với + Mặc định + Vui lòng chọn một tab bắt đầu khác trước tiên! + Điều khiển vuốt + Sử dụng thao tác vuốt để điều chỉnh độ sáng và âm lượng. + Hiển thị một nút trong trình phát để mở video bằng ứng dụng của bên thứ ba. + Đánh dấu trang \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ccd8c49b1..7e5f9cd27 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -392,4 +392,7 @@ 清除书签 尚无书签! 插入相关视频 + 本地播放列表 + 菜单项未启用! + 请先选择其他启动选项卡! \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f1a232ebe..f87d400bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -387,7 +387,16 @@ No bookmarks yet! Insert related videos Local playlists - + Menu item not enabled! + Please select an other start tab first! + Brightness + Volume + Auto + Swipe controls + Use swipe gesture to adjust the brightness and volume. + Defaults + Open With + Show a button in the player to open the video with a third-party app. Download Service Shows a notification when downloading media. diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml index c4c8be643..72cbd129c 100644 --- a/app/src/main/res/values/style.xml +++ b/app/src/main/res/values/style.xml @@ -149,6 +149,18 @@ @android:color/white + +