diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 06d4a6c8f..0843a522c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,6 +47,21 @@ android:name=".ui.activities.CommunityActivity" android:label="@string/settings" /> + + + + + + + + + + - - - - - - - - - - + diff --git a/app/src/main/java/com/github/libretube/LibreTubeApp.kt b/app/src/main/java/com/github/libretube/LibreTubeApp.kt index 2fab6a40f..791d5771d 100644 --- a/app/src/main/java/com/github/libretube/LibreTubeApp.kt +++ b/app/src/main/java/com/github/libretube/LibreTubeApp.kt @@ -17,6 +17,7 @@ import com.github.libretube.util.ImageHelper import com.github.libretube.util.NotificationHelper import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.ProxyHelper +import com.github.libretube.util.ShortcutHelper class LibreTubeApp : Application() { override fun onCreate() { @@ -69,6 +70,11 @@ class LibreTubeApp : Application() { val defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler() val exceptionHandler = ExceptionHandler(defaultExceptionHandler) Thread.setDefaultUncaughtExceptionHandler(exceptionHandler) + + /** + * Dynamically create App Shortcuts + */ + ShortcutHelper.createShortcuts(this) } /** diff --git a/app/src/main/java/com/github/libretube/constants/IntentData.kt b/app/src/main/java/com/github/libretube/constants/IntentData.kt index 6d9352f1c..c10d30549 100644 --- a/app/src/main/java/com/github/libretube/constants/IntentData.kt +++ b/app/src/main/java/com/github/libretube/constants/IntentData.kt @@ -17,4 +17,5 @@ object IntentData { const val subtitleCode = "subtitleCode" const val downloading = "downloading" const val openAudioPlayer = "openAudioPlayer" + const val fragmentToOpen = "fragmentToOpen" } 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 56d9070d4..486a858d7 100644 --- a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt @@ -18,8 +18,8 @@ object PreferenceKeys { const val LANGUAGE = "language" const val REGION = "region" const val AUTO_ROTATION = "auto_rotation" - const val BREAK_REMINDER_TOGGLE = "break_reminder_toggle" - const val BREAK_REMINDER = "break_reminder" + const val SLEEP_TIMER = "sleep_timer_toggle" + const val SLEEP_TIMER_DELAY = "sleep_timer_delay" const val SAVE_FEED = "save_feed" const val NAVBAR_ITEMS = "navbar_items" const val START_FRAGMENT = "start_fragment" @@ -88,6 +88,7 @@ object PreferenceKeys { const val DOUBLE_TAP_TO_SEEK = "double_tap_seek" const val PAUSE_ON_QUIT = "pause_on_quit" const val ALTERNATIVE_PIP_CONTROLS = "alternative_pip_controls" + const val SKIP_SILENCE = "skip_silence" /** * Background mode diff --git a/app/src/main/java/com/github/libretube/obj/AppShortcut.kt b/app/src/main/java/com/github/libretube/obj/AppShortcut.kt new file mode 100644 index 000000000..b268ea381 --- /dev/null +++ b/app/src/main/java/com/github/libretube/obj/AppShortcut.kt @@ -0,0 +1,10 @@ +package com.github.libretube.obj + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes + +data class AppShortcut( + val action: String, + @StringRes val label: Int, + @DrawableRes val drawable: Int +) diff --git a/app/src/main/java/com/github/libretube/services/BackgroundMode.kt b/app/src/main/java/com/github/libretube/services/BackgroundMode.kt index 7ad013a21..dd9e0dbb8 100644 --- a/app/src/main/java/com/github/libretube/services/BackgroundMode.kt +++ b/app/src/main/java/com/github/libretube/services/BackgroundMode.kt @@ -169,6 +169,9 @@ class BackgroundMode : Service() { /** * Gets the video data and prepares the [player]. + * @param videoId The id of the video to play + * @param seekToPosition The position of the video to seek to + * @param keepQueue Whether to keep the queue or clear it instead */ private fun loadAudio( videoId: String, @@ -298,7 +301,7 @@ class BackgroundMode : Service() { this.videoId = nextVideo this.streams = null this.segmentData = null - loadAudio(videoId) + loadAudio(videoId, keepQueue = true) } /** diff --git a/app/src/main/java/com/github/libretube/ui/activities/AddToQueueActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/AddToQueueActivity.kt new file mode 100644 index 000000000..e1f1ca436 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/activities/AddToQueueActivity.kt @@ -0,0 +1,39 @@ +package com.github.libretube.ui.activities + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import com.github.libretube.ui.base.BaseActivity +import com.github.libretube.util.PlayingQueue + +/** + * Receives a text by the intent and attempts to add it to the playing queue + * If no video is playing currently, the queue will be left unchanged and the the main activity is being resumed + */ +class AddToQueueActivity : BaseActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val uri = Uri.parse(intent.getStringExtra(Intent.EXTRA_TEXT)!!) + var videoId: String? = null + listOf("/shorts/", "/v/", "/embed/").forEach { + if (uri.path!!.contains(it)) { + videoId = uri.path!!.replace(it, "") + } + } + if ( + uri.path!!.contains("/watch") && uri.query != null + ) { + videoId = uri.getQueryParameter("v") + } + + if (videoId == null) videoId = uri.path!!.replace("/", "") + + // if playing a video currently, the playing queue is not empty + if (PlayingQueue.isNotEmpty()) PlayingQueue.insertByVideoId(videoId!!) + + val intent = packageManager.getLaunchIntentForPackage(packageName) + startActivity(intent) + finishAndRemoveTask() + } +} 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 66d3a6174..cbd3d7239 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 @@ -6,15 +6,10 @@ import android.content.pm.ActivityInfo import android.content.res.Configuration import android.os.Build import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.view.WindowInsets -import android.view.WindowInsetsController -import android.view.WindowManager import android.widget.ScrollView import androidx.activity.OnBackPressedCallback import androidx.appcompat.widget.SearchView @@ -40,12 +35,13 @@ import com.github.libretube.ui.fragments.PlayerFragment import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.SearchViewModel import com.github.libretube.ui.models.SubscriptionsViewModel -import com.github.libretube.ui.tools.BreakReminder +import com.github.libretube.ui.tools.SleepTimer import com.github.libretube.util.NavBarHelper import com.github.libretube.util.NavigationHelper import com.github.libretube.util.NetworkHelper import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.ThemeHelper +import com.github.libretube.util.WindowHelper import com.google.android.material.elevation.SurfaceColors class MainActivity : BaseActivity() { @@ -60,7 +56,7 @@ class MainActivity : BaseActivity() { lateinit var searchView: SearchView private lateinit var searchItem: MenuItem - private val handler = Handler(Looper.getMainLooper()) + val windowHelper = WindowHelper(this) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -136,13 +132,12 @@ class MainActivity : BaseActivity() { binding.toolbar.title = ThemeHelper.getStyledAppName(this) - /** - * handle error logs - */ - val log = PreferenceHelper.getErrorLog() - if (log != "") ErrorDialog().show(supportFragmentManager, null) + // handle error logs + PreferenceHelper.getErrorLog().ifBlank { null }?.let { + ErrorDialog().show(supportFragmentManager, null) + } - BreakReminder.setupBreakReminder(applicationContext) + SleepTimer.setup(this) setupSubscriptionsBadge() @@ -161,11 +156,9 @@ class MainActivity : BaseActivity() { } if (binding.mainMotionLayout.progress == 0F) { - try { + runCatching { minimizePlayer() return - } catch (e: Exception) { - // current fragment isn't the player fragment } } @@ -247,6 +240,7 @@ class MainActivity : BaseActivity() { binding.bottomNav.getOrCreateBadge(R.id.subscriptionsFragment).apply { number = lastSeenVideoIndex backgroundColor = ThemeHelper.getThemeColor(this@MainActivity, R.attr.colorPrimary) + badgeTextColor = ThemeHelper.getThemeColor(this@MainActivity, R.attr.colorOnPrimary) } } } @@ -467,68 +461,8 @@ class MainActivity : BaseActivity() { super.onConfigurationChanged(newConfig) when (newConfig.orientation) { - Configuration.ORIENTATION_PORTRAIT -> unsetFullscreen() - Configuration.ORIENTATION_LANDSCAPE -> setFullscreen() - } - } - - fun setFullscreen() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - window.attributes.layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - window.setDecorFitsSystemWindows(false) - window.insetsController?.apply { - hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()) - systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - } - } else { - @Suppress("DEPRECATION") - window.decorView.systemUiVisibility = ( - View.SYSTEM_UI_FLAG_FULLSCREEN - or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_IMMERSIVE - or View.SYSTEM_UI_FLAG_LAYOUT_STABLE - or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - ) - } - - window.setFlags( - WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, - WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS - ) - } - - private fun unsetFullscreen() { - window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - @Suppress("DEPRECATION") - window.clearFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN - ) - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - window.attributes.layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - window.setDecorFitsSystemWindows(true) - window.insetsController?.apply { - show(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - systemBarsBehavior = WindowInsetsController.BEHAVIOR_DEFAULT - } - } - } else { - @Suppress("DEPRECATION") - window.decorView.systemUiVisibility = - (View.SYSTEM_UI_FLAG_VISIBLE or View.SYSTEM_UI_FLAG_LAYOUT_STABLE) + Configuration.ORIENTATION_PORTRAIT -> windowHelper.unsetFullscreen() + Configuration.ORIENTATION_LANDSCAPE -> windowHelper.setFullscreen() } } 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 4a4762fb8..253e6a662 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 @@ -2,18 +2,13 @@ package com.github.libretube.ui.activities import android.app.PictureInPictureParams import android.content.pm.ActivityInfo -import android.graphics.Color import android.media.session.PlaybackState import android.net.Uri import android.os.Build import android.os.Bundle import android.text.format.DateUtils import android.view.View -import android.view.WindowManager import androidx.activity.viewModels -import androidx.core.view.WindowCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.WindowInsetsControllerCompat import com.github.libretube.constants.IntentData import com.github.libretube.databinding.ActivityOfflinePlayerBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding @@ -25,6 +20,7 @@ import com.github.libretube.ui.base.BaseActivity import com.github.libretube.ui.extensions.setAspectRatio import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.util.PlayerHelper +import com.github.libretube.util.WindowHelper import com.google.android.exoplayer2.C import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem @@ -49,7 +45,7 @@ class OfflinePlayerActivity : BaseActivity() { private val playerViewModel: PlayerViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { - hideSystemBars() + WindowHelper(this).setFullscreen() requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE @@ -179,32 +175,6 @@ class OfflinePlayerActivity : BaseActivity() { } } - @Suppress("DEPRECATION") - private fun hideSystemBars() { - window?.decorView?.systemUiVisibility = ( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - ) - window.statusBarColor = Color.TRANSPARENT - - window.setFlags( - WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - ) - - val windowInsetsController = - WindowCompat.getInsetsController(window, window.decorView) - windowInsetsController.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - windowInsetsController.hide(WindowInsetsCompat.Type.statusBars()) - - supportActionBar?.hide() - - windowInsetsController.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) - } - override fun onResume() { playerViewModel.isFullscreen.value = true super.onResume() diff --git a/app/src/main/java/com/github/libretube/ui/activities/RouterActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/RouterActivity.kt index a0ff62adf..898e35557 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/RouterActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/RouterActivity.kt @@ -84,10 +84,8 @@ class RouterActivity : BaseActivity() { val pm: PackageManager = this.packageManager val intent = pm.getLaunchIntentForPackage(this.packageName) intent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK - this.startActivity( - resolveType(intent!!, uri) - ) - this.finishAndRemoveTask() + startActivity(resolveType(intent!!, uri)) + finishAndRemoveTask() } private fun parseTimestamp(t: String): Long? { 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 62609636f..e9f041245 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 @@ -29,6 +29,7 @@ import android.widget.Toast import androidx.annotation.RequiresApi import androidx.constraintlayout.motion.widget.MotionLayout import androidx.core.net.toUri +import androidx.core.os.ConfigurationCompat import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels @@ -252,8 +253,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { super.onViewCreated(view, savedInstanceState) context?.hideKeyboard(view) + // reset the callbacks of the playing queue + PlayingQueue.resetToDefaults() + // clear the playing queue - if (!keepQueue) PlayingQueue.resetToDefaults() + if (!keepQueue) PlayingQueue.clear() changeOrientationMode() @@ -342,15 +346,13 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { // actions that don't depend on video information private fun initializeOnClickActions() { binding.closeImageView.setOnClickListener { - viewModel.isMiniPlayerVisible.value = false - binding.playerMotionLayout.transitionToEnd() - val mainActivity = activity as MainActivity - mainActivity.supportFragmentManager.beginTransaction() - .remove(this) - .commit() + PlayingQueue.clear() BackgroundHelper.stopBackgroundPlay(requireContext()) + killPlayerFragment() } playerBinding.closeImageButton.setOnClickListener { + PlayingQueue.clear() + BackgroundHelper.stopBackgroundPlay(requireContext()) killPlayerFragment() } playerBinding.autoPlay.visibility = View.VISIBLE @@ -832,11 +834,8 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { } private fun localizedDate(date: String?): String? { - return if (SDK_INT >= Build.VERSION_CODES.N) { - TextUtils.localizeDate(date, resources.configuration.locales[0]) - } else { - TextUtils.localizeDate(date) - } + val locale = ConfigurationCompat.getLocales(resources.configuration)[0]!! + return TextUtils.localizeDate(date, locale) } private fun handleLiveVideo() { @@ -1319,8 +1318,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { }.flatten() .filter { it > 0 } .sortedDescending() - .toSet() - .toList() + .distinct() return resolutions.map { VideoResolution( @@ -1469,8 +1467,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { override fun onCaptionsClicked() { if (!this@PlayerFragment::streams.isInitialized || - streams.subtitles == null || - streams.subtitles!!.isEmpty() + streams.subtitles.isNullOrEmpty() ) { Toast.makeText(context, R.string.no_subtitles_available, Toast.LENGTH_SHORT).show() return @@ -1485,7 +1482,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { BaseBottomSheet() .setSimpleItems(subtitlesNamesList) { index -> - val language = if (index > 0) subtitleCodesList[index] else null + val language = subtitleCodesList.getOrNull(index) updateCaptionsLanguage(language) this.captionLanguage = language } @@ -1634,6 +1631,8 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions { mainActivity.supportFragmentManager.beginTransaction() .remove(this) .commit() + + onDestroy() } override fun onConfigurationChanged(newConfig: Configuration) { diff --git a/app/src/main/java/com/github/libretube/ui/preferences/GeneralSettings.kt b/app/src/main/java/com/github/libretube/ui/preferences/GeneralSettings.kt index 4c4b58e3f..5d28cf875 100644 --- a/app/src/main/java/com/github/libretube/ui/preferences/GeneralSettings.kt +++ b/app/src/main/java/com/github/libretube/ui/preferences/GeneralSettings.kt @@ -30,21 +30,26 @@ class GeneralSettings : BasePreferenceFragment() { val autoRotation = findPreference(PreferenceKeys.AUTO_ROTATION) autoRotation?.setOnPreferenceChangeListener { _, _ -> - val restartDialog = RequireRestartDialog() - restartDialog.show(childFragmentManager, RequireRestartDialog::class.java.name) + RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name) true } val breakReminder = - findPreference(PreferenceKeys.BREAK_REMINDER_TOGGLE) - val breakReminderTime = findPreference(PreferenceKeys.BREAK_REMINDER) + findPreference(PreferenceKeys.SLEEP_TIMER) + val breakReminderTime = findPreference(PreferenceKeys.SLEEP_TIMER_DELAY) breakReminderTime?.isEnabled = PreferenceHelper.getBoolean( - PreferenceKeys.BREAK_REMINDER_TOGGLE, + PreferenceKeys.SLEEP_TIMER, false ) breakReminder?.setOnPreferenceChangeListener { _, newValue -> breakReminderTime?.isEnabled = newValue as Boolean + RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name) + true + } + + breakReminderTime?.setOnPreferenceChangeListener { _, _ -> + RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name) true } } diff --git a/app/src/main/java/com/github/libretube/ui/preferences/PlayerSettings.kt b/app/src/main/java/com/github/libretube/ui/preferences/PlayerSettings.kt index ac228defc..f77cb80a7 100644 --- a/app/src/main/java/com/github/libretube/ui/preferences/PlayerSettings.kt +++ b/app/src/main/java/com/github/libretube/ui/preferences/PlayerSettings.kt @@ -73,7 +73,7 @@ class PlayerSettings : BasePreferenceFragment() { } private fun setupSubtitlePref(preference: ListPreference) { - val locales = LocaleHelper.getAvailableLocales().sortedBy { it.name } + val locales = LocaleHelper.getAvailableLocales() val localeNames = locales.map { it.name } .toMutableList() localeNames.add(0, requireContext().getString(R.string.none)) diff --git a/app/src/main/java/com/github/libretube/ui/sheets/PlaybackSpeedSheet.kt b/app/src/main/java/com/github/libretube/ui/sheets/PlaybackSpeedSheet.kt index dc0d670b3..4bf141c9e 100644 --- a/app/src/main/java/com/github/libretube/ui/sheets/PlaybackSpeedSheet.kt +++ b/app/src/main/java/com/github/libretube/ui/sheets/PlaybackSpeedSheet.kt @@ -4,13 +4,15 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import com.github.libretube.constants.PreferenceKeys import com.github.libretube.databinding.PlaybackBottomSheetBinding import com.github.libretube.extensions.round +import com.github.libretube.util.PreferenceHelper +import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.PlaybackParameters -import com.google.android.exoplayer2.Player class PlaybackSpeedSheet( - private val player: Player + private val player: ExoPlayer ) : ExpandedBottomSheet() { private lateinit var binding: PlaybackBottomSheetBinding @@ -28,6 +30,9 @@ class PlaybackSpeedSheet( binding.speed.value = player.playbackParameters.speed binding.pitch.value = player.playbackParameters.pitch + PreferenceHelper.getBoolean(PreferenceKeys.SKIP_SILENCE, false).let { + binding.skipSilence.isChecked = it + } binding.speed.addOnChangeListener { _, _, _ -> onChange() @@ -46,6 +51,11 @@ class PlaybackSpeedSheet( binding.pitch.value = 1f onChange() } + + binding.skipSilence.setOnCheckedChangeListener { _, isChecked -> + player.skipSilenceEnabled = isChecked + PreferenceHelper.putBoolean(PreferenceKeys.SKIP_SILENCE, isChecked) + } } private fun onChange() { diff --git a/app/src/main/java/com/github/libretube/ui/tools/BreakReminder.kt b/app/src/main/java/com/github/libretube/ui/tools/BreakReminder.kt deleted file mode 100644 index 7e38e059f..000000000 --- a/app/src/main/java/com/github/libretube/ui/tools/BreakReminder.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.github.libretube.ui.tools - -import android.content.Context -import android.os.Handler -import android.os.Looper -import android.widget.Toast -import com.github.libretube.R -import com.github.libretube.constants.PreferenceKeys -import com.github.libretube.util.PreferenceHelper -import com.google.android.material.dialog.MaterialAlertDialogBuilder - -object BreakReminder { - /** - * Show a break reminder when watched too long - */ - fun setupBreakReminder(context: Context) { - if (!PreferenceHelper.getBoolean( - PreferenceKeys.BREAK_REMINDER_TOGGLE, - false - ) - ) { - return - } - val breakReminderPref = PreferenceHelper.getString( - PreferenceKeys.BREAK_REMINDER, - "0" - ) - if (!breakReminderPref.all { Character.isDigit(it) } || - breakReminderPref == "" || breakReminderPref == "0" - ) { - return - } - Handler(Looper.getMainLooper()).postDelayed( - { - try { - MaterialAlertDialogBuilder(context) - .setTitle(R.string.take_a_break) - .setMessage( - context.getString( - R.string.already_spent_time, - breakReminderPref - ) - ) - .setPositiveButton(R.string.okay, null) - .show() - } catch (e: Exception) { - runCatching { - Toast.makeText(context, R.string.take_a_break, Toast.LENGTH_LONG).show() - } - } - }, - breakReminderPref.toLong() * 60 * 1000 - ) - } -} diff --git a/app/src/main/java/com/github/libretube/ui/tools/SleepTimer.kt b/app/src/main/java/com/github/libretube/ui/tools/SleepTimer.kt new file mode 100644 index 000000000..5fe8c2b74 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/tools/SleepTimer.kt @@ -0,0 +1,62 @@ +package com.github.libretube.ui.tools + +import android.content.Context +import android.os.Handler +import android.os.Looper +import com.github.libretube.R +import com.github.libretube.constants.PreferenceKeys +import com.github.libretube.ui.activities.MainActivity +import com.github.libretube.util.PreferenceHelper +import com.google.android.material.snackbar.Snackbar + +object SleepTimer { + private val handler = Handler(Looper.getMainLooper()) + private const val REACTION_INTERVAL = 5L + + /** + * Kill the app after showing a warning after a certain amount of time + * @param context This must not be the applicationContext! + */ + fun setup(context: Context) { + if (!PreferenceHelper.getBoolean(PreferenceKeys.SLEEP_TIMER, false)) return + + val breakReminderPref = PreferenceHelper.getString( + PreferenceKeys.SLEEP_TIMER_DELAY, + "" + ).ifEmpty { return } + + handler.postDelayed( + { + var killApp = true + val mainActivity = context as? MainActivity ?: return@postDelayed + val snackBar = Snackbar.make( + mainActivity.binding.root, + R.string.take_a_break, + Snackbar.LENGTH_INDEFINITE + ) + .setAction(R.string.cancel) { + killApp = false + } + snackBar.show() + (0..REACTION_INTERVAL).forEach { + handler.postDelayed({ + val remainingTime = " (${REACTION_INTERVAL - it})" + snackBar.setText(context.getString(R.string.take_a_break) + remainingTime) + }, it * 1000) + } + handler.postDelayed( + killApp@{ + if (!killApp) return@killApp + + // kill the application + mainActivity.finishAffinity() + mainActivity.finish() + android.os.Process.killProcess(android.os.Process.myPid()) + }, + REACTION_INTERVAL * 1000 + ) + }, + breakReminderPref.toLong() * 60 * 1000 + ) + } +} 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 9b33f4354..0ab60b343 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 @@ -15,6 +15,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.core.content.ContextCompat import com.github.libretube.R +import com.github.libretube.constants.PreferenceKeys import com.github.libretube.databinding.DoubleTapOverlayBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding import com.github.libretube.databinding.PlayerGestureControlsViewBinding @@ -33,6 +34,8 @@ 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.github.libretube.util.PreferenceHelper +import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.PlaybackParameters import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.trackselection.TrackSelector @@ -111,6 +114,9 @@ internal class CustomExoPlayerView( PlayerHelper.playbackSpeed.toFloat(), 1.0f ) + PreferenceHelper.getBoolean(PreferenceKeys.SKIP_SILENCE, false).let { + (player as ExoPlayer).skipSilenceEnabled = true + } playbackPrefSet = true } @@ -189,7 +195,7 @@ internal class CustomExoPlayerView( override fun hideController() { if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { // hide all the navigation bars that potentially could have been reopened manually ba the user - (context as? MainActivity)?.setFullscreen() + (context as? MainActivity)?.windowHelper?.setFullscreen() } super.hideController() } @@ -484,7 +490,9 @@ internal class CustomExoPlayerView( } override fun onPlaybackSpeedClicked() { - player?.let { PlaybackSpeedSheet(it).show(supportFragmentManager) } + player?.let { + PlaybackSpeedSheet(it as ExoPlayer).show(supportFragmentManager) + } } override fun onResizeModeClicked() { diff --git a/app/src/main/java/com/github/libretube/ui/views/TimePickerPreference.kt b/app/src/main/java/com/github/libretube/ui/views/TimePickerPreference.kt index 3bb1830a2..f319b2ac5 100644 --- a/app/src/main/java/com/github/libretube/ui/views/TimePickerPreference.kt +++ b/app/src/main/java/com/github/libretube/ui/views/TimePickerPreference.kt @@ -6,60 +6,39 @@ import android.util.AttributeSet import androidx.appcompat.app.AppCompatActivity import androidx.preference.Preference import com.github.libretube.util.PreferenceHelper -import com.github.libretube.util.TextUtils import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.TimeFormat +import java.time.LocalTime class TimePickerPreference( context: Context, attributeSet: AttributeSet ) : Preference(context, attributeSet) { override fun getSummary(): CharSequence { - val prefStr = PreferenceHelper.getString(key, "") - return if (prefStr != "") prefStr else DEFAULT_VALUE + return PreferenceHelper.getString(key, DEFAULT_VALUE) } override fun onClick() { + val prefTime = LocalTime.parse(PreferenceHelper.getString(key, DEFAULT_VALUE)) val picker = MaterialTimePicker.Builder() .setInputMode(MaterialTimePicker.INPUT_MODE_CLOCK) - .setTimeFormat(getTimeFormat()) - .setHour(getHour()) - .setMinute(getMinutes()) + .setTimeFormat(timeFormat) + .setHour(prefTime.hour) + .setMinute(prefTime.minute) .build() picker.addOnPositiveButtonClickListener { - val timeStr = getTimeStr(picker) + val timeStr = LocalTime.of(picker.hour, picker.minute).toString() PreferenceHelper.putString(key, timeStr) summary = timeStr } picker.show((context as AppCompatActivity).supportFragmentManager, null) } - private fun getTimeFormat(): Int { - return if (is24HourFormat(context)) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H - } - - private fun getPrefStringPart(index: Int): String? { - val prefStr = PreferenceHelper.getString(key, "").split(SEPARATOR).getOrNull(index) - return if (prefStr != "") prefStr else null - } - - private fun getHour(): Int { - return getPrefStringPart(0)?.toInt() ?: 0 - } - - private fun getMinutes(): Int { - return getPrefStringPart(1)?.toInt() ?: 0 - } - - private fun getTimeStr(picker: MaterialTimePicker): String { - val hour = TextUtils.toTwoDecimalsString(picker.hour) - val minute = TextUtils.toTwoDecimalsString(picker.minute) - return "$hour$SEPARATOR$minute" - } + private val timeFormat: Int + get() = if (is24HourFormat(context)) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H companion object { - const val SEPARATOR = ":" const val DEFAULT_VALUE = "12:00" } } diff --git a/app/src/main/java/com/github/libretube/util/ImageHelper.kt b/app/src/main/java/com/github/libretube/util/ImageHelper.kt index f5aaca9b6..ca07d5865 100644 --- a/app/src/main/java/com/github/libretube/util/ImageHelper.kt +++ b/app/src/main/java/com/github/libretube/util/ImageHelper.kt @@ -88,4 +88,20 @@ object ImageHelper { } return null } + + /** + * Get a squared bitmap with the same width and height from a bitmap + * @param bitmap The bitmap to resize + */ + fun getSquareBitmap(bitmap: Bitmap?): Bitmap? { + bitmap ?: return null + val newSize = minOf(bitmap.width, bitmap.height) + return Bitmap.createBitmap( + bitmap, + (bitmap.width - newSize) / 2, + (bitmap.height - newSize) / 2, + newSize, + newSize + ) + } } diff --git a/app/src/main/java/com/github/libretube/util/LocaleHelper.kt b/app/src/main/java/com/github/libretube/util/LocaleHelper.kt index 017a96022..9f28e1915 100644 --- a/app/src/main/java/com/github/libretube/util/LocaleHelper.kt +++ b/app/src/main/java/com/github/libretube/util/LocaleHelper.kt @@ -2,9 +2,10 @@ package com.github.libretube.util import android.content.Context import android.content.res.Configuration -import android.content.res.Resources import android.os.Build import android.telephony.TelephonyManager +import androidx.core.content.getSystemService +import androidx.core.os.ConfigurationCompat import com.github.libretube.constants.PreferenceKeys import com.github.libretube.obj.Country import java.util.* @@ -15,7 +16,7 @@ object LocaleHelper { val languageName = PreferenceHelper.getString(PreferenceKeys.LANGUAGE, "sys") val locale = when { languageName == "sys" -> Locale.getDefault() - languageName.contains("-") == true -> { + languageName.contains("-") -> { val languageParts = languageName.split("-") Locale( languageParts[0], @@ -38,93 +39,43 @@ object LocaleHelper { @Suppress("DEPRECATION") private fun updateResourcesLegacy(context: Context, locale: Locale) { Locale.setDefault(locale) - val resources: Resources = context.resources - val configuration: Configuration = resources.getConfiguration() + val resources = context.resources + val configuration = resources.configuration configuration.locale = locale - resources.updateConfiguration(configuration, resources.getDisplayMetrics()) + resources.updateConfiguration(configuration, resources.displayMetrics) } - fun getDetectedCountry(context: Context, defaultCountryIsoCode: String): String { - detectSIMCountry(context)?.let { - if (it != "") return it - } - - detectNetworkCountry(context)?.let { - if (it != "") return it - } - - detectLocaleCountry(context)?.let { - if (it != "") return it - } - - return defaultCountryIsoCode + private fun getDetectedCountry(context: Context): String { + return detectSIMCountry(context) + ?: detectNetworkCountry(context) + ?: detectLocaleCountry(context) + ?: "UK" } private fun detectSIMCountry(context: Context): String? { - try { - val telephonyManager = - context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager - return telephonyManager.simCountryIso - } catch (e: Exception) { - e.printStackTrace() - } - return null + return context.getSystemService()?.simCountryIso?.ifEmpty { null } } private fun detectNetworkCountry(context: Context): String? { - try { - val telephonyManager = - context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager - return telephonyManager.networkCountryIso - } catch (e: Exception) { - e.printStackTrace() - } - return null + return context.getSystemService()?.networkCountryIso?.ifEmpty { null } } private fun detectLocaleCountry(context: Context): String? { - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return context.resources.configuration.locales[0].country - } - } catch (e: Exception) { - e.printStackTrace() - } - return null + return ConfigurationCompat.getLocales(context.resources.configuration)[0]!!.country + .ifEmpty { null } } fun getAvailableCountries(): List { - val isoCountries = Locale.getISOCountries() - val countries = mutableListOf() - isoCountries.forEach { countryCode -> - val locale = Locale("", countryCode) - val countryName = locale.displayCountry - countries.add( - Country( - countryName, - countryCode - ) - ) - } - countries.sortBy { it.name } - return countries + return Locale.getISOCountries() + .map { Country(Locale("", it).displayCountry, it) } + .sortedBy { it.name } } fun getAvailableLocales(): List { - val availableLocales: Array = Locale.getAvailableLocales() - val locales = mutableListOf() - - availableLocales.forEach { locale -> - if (locales.filter { it.code == locale.language }.isEmpty()) { - locales.add( - Country( - locale.displayLanguage, - locale.language - ) - ) - } - } - return locales + return Locale.getAvailableLocales() + .distinctBy { it.language } + .map { Country(it.displayLanguage, it.language) } + .sortedBy { it.name } } fun getTrendingRegion(context: Context): String { @@ -132,8 +83,7 @@ object LocaleHelper { // get the system default country if auto region selected return if (regionPref == "sys") { - getDetectedCountry(context, "UK") - .uppercase() + getDetectedCountry(context).uppercase() } else { regionPref } diff --git a/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt b/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt index 471addf10..c9295f314 100644 --- a/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt +++ b/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt @@ -5,7 +5,6 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.content.res.Resources import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable @@ -14,9 +13,13 @@ import android.os.Bundle import android.support.v4.media.MediaDescriptionCompat import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.session.MediaSessionCompat +import android.support.v4.media.session.PlaybackStateCompat +import androidx.annotation.DrawableRes +import androidx.core.app.NotificationCompat import coil.request.ImageRequest import com.github.libretube.R import com.github.libretube.api.obj.Streams +import com.github.libretube.compat.PendingIntentCompat import com.github.libretube.constants.BACKGROUND_CHANNEL_ID import com.github.libretube.constants.IntentData import com.github.libretube.constants.PLAYER_NOTIFICATION_ID @@ -26,6 +29,7 @@ import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator import com.google.android.exoplayer2.ui.PlayerNotificationManager +import com.google.android.exoplayer2.ui.PlayerNotificationManager.CustomActionReceiver class NowPlayingNotification( private val context: Context, @@ -51,11 +55,10 @@ class NowPlayingNotification( private var playerNotification: PlayerNotificationManager? = null /** - * The [DescriptionAdapter] is used to show title, uploaderName and thumbnail of the video in the notification + * The [descriptionAdapter] is used to show title, uploaderName and thumbnail of the video in the notification * Basic example [here](https://github.com/AnthonyMarkD/AudioPlayerSampleTest) */ - inner class DescriptionAdapter : - PlayerNotificationManager.MediaDescriptionAdapter { + private val descriptionAdapter = object : PlayerNotificationManager.MediaDescriptionAdapter { /** * sets the title of the notification */ @@ -71,23 +74,19 @@ class NowPlayingNotification( // starts a new MainActivity Intent when the player notification is clicked // it doesn't start a completely new MainActivity because the MainActivity's launchMode // is set to "singleTop" in the AndroidManifest (important!!!) - // that's the only way to launch back into the previous activity (e.g. the player view + // that's the only way to launch back into the previous activity (e.g. the player view val intent = Intent(context, MainActivity::class.java).apply { if (isBackgroundPlayerNotification) { putExtra(IntentData.openAudioPlayer, true) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } } - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.getActivity( - context, - 0, - intent, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) - } else { - PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) - } + return PendingIntent.getActivity( + context, + 0, + intent, + PendingIntentCompat.updateCurrentFlags + ) } /** @@ -111,27 +110,71 @@ class NowPlayingNotification( val request = ImageRequest.Builder(context) .data(streams?.thumbnailUrl) .target { result -> - bitmap = (result as BitmapDrawable).bitmap + 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) - // returns the bitmap on Android 13+, for everything below scaled down to a square - return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) getSquareBitmap(bitmap) else bitmap + return bitmap + } + + override fun getCurrentSubText(player: Player): CharSequence? { + return streams?.uploader } } - private fun getSquareBitmap(bitmap: Bitmap?): Bitmap? { - bitmap ?: return null - val newSize = minOf(bitmap.width, bitmap.height) - return Bitmap.createBitmap( - bitmap, - (bitmap.width - newSize) / 2, - (bitmap.height - newSize) / 2, - newSize, - newSize + private val customActionReceiver = object : CustomActionReceiver { + override fun createCustomActions( + context: Context, + instanceId: Int + ): MutableMap { + return mutableMapOf( + PREV to createNotificationAction(R.drawable.ic_prev_outlined, PREV, instanceId), + NEXT to createNotificationAction(R.drawable.ic_next_outlined, NEXT, instanceId), + REWIND to createNotificationAction(R.drawable.ic_rewind_md, REWIND, instanceId), + FORWARD to createNotificationAction(R.drawable.ic_forward_md, FORWARD, instanceId) + ) + } + + override fun getCustomActions(player: Player): MutableList { + return mutableListOf(PREV, NEXT, REWIND, FORWARD) + } + + override fun onCustomAction(player: Player, action: String, intent: Intent) { + handlePlayerAction(action) + } + } + + private fun createNotificationAction(drawableRes: Int, actionName: String, instanceId: Int): NotificationCompat.Action { + val intent: Intent = Intent(actionName).setPackage(context.packageName) + val pendingIntent = PendingIntent.getBroadcast( + context, + instanceId, + intent, + PendingIntentCompat.cancelCurrentFlags ) + return NotificationCompat.Action.Builder(drawableRes, actionName, pendingIntent).build() + } + + private fun createMediaSessionAction(@DrawableRes drawableRes: Int, actionName: String): MediaSessionConnector.CustomActionProvider { + return object : MediaSessionConnector.CustomActionProvider { + override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? { + return PlaybackStateCompat.CustomAction.Builder(actionName, actionName, drawableRes).build() + } + + override fun onCustomAction(player: Player, action: String, extras: Bundle?) { + handlePlayerAction(action) + } + } } /** @@ -139,32 +182,70 @@ class NowPlayingNotification( */ private fun createMediaSession() { if (this::mediaSession.isInitialized) return - mediaSession = MediaSessionCompat(context, this.javaClass.name) - mediaSession.isActive = true + mediaSession = MediaSessionCompat(context, this.javaClass.name).apply { + isActive = true + } - mediaSessionConnector = MediaSessionConnector(mediaSession) - mediaSessionConnector.setQueueNavigator(object : TimelineQueueNavigator(mediaSession) { - override fun getMediaDescription( - player: Player, - windowIndex: Int - ): MediaDescriptionCompat { - return MediaDescriptionCompat.Builder().apply { - setTitle(streams?.title!!) - setSubtitle(streams?.uploader) - val extras = Bundle() - val appIcon = BitmapFactory.decodeResource( - Resources.getSystem(), - R.drawable.ic_launcher_monochrome + mediaSessionConnector = MediaSessionConnector(mediaSession).apply { + setPlayer(player) + setQueueNavigator(object : TimelineQueueNavigator(mediaSession) { + override fun getMediaDescription( + player: Player, + windowIndex: Int + ): MediaDescriptionCompat { + return MediaDescriptionCompat.Builder().apply { + setTitle(streams?.title!!) + setSubtitle(streams?.uploader) + val appIcon = BitmapFactory.decodeResource( + context.resources, + R.drawable.ic_launcher_monochrome + ) + val extras = Bundle().apply { + putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, appIcon) + putString(MediaMetadataCompat.METADATA_KEY_TITLE, streams?.title!!) + putString(MediaMetadataCompat.METADATA_KEY_ARTIST, streams?.uploader) + } + setIconBitmap(appIcon) + setExtras(extras) + }.build() + } + + override fun getSupportedQueueNavigatorActions(player: Player): Long { + return PlaybackStateCompat.ACTION_PLAY_PAUSE + } + }) + setCustomActionProviders( + createMediaSessionAction(R.drawable.ic_prev_outlined, PREV), + createMediaSessionAction(R.drawable.ic_next_outlined, NEXT), + createMediaSessionAction(R.drawable.ic_rewind_md, REWIND), + createMediaSessionAction(R.drawable.ic_forward_md, FORWARD) + ) + } + } + + private fun handlePlayerAction(action: String) { + when (action) { + NEXT -> { + if (PlayingQueue.hasNext()) { + PlayingQueue.onQueueItemSelected( + PlayingQueue.currentIndex() + 1 ) - extras.putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, appIcon) - extras.putString(MediaMetadataCompat.METADATA_KEY_TITLE, streams?.title!!) - extras.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, streams?.uploader) - setIconBitmap(appIcon) - setExtras(extras) - }.build() + } } - }) - mediaSessionConnector.setPlayer(player) + PREV -> { + if (PlayingQueue.hasPrev()) { + PlayingQueue.onQueueItemSelected( + PlayingQueue.currentIndex() - 1 + ) + } + } + REWIND -> { + player.seekTo(player.currentPosition - PlayerHelper.seekIncrement) + } + FORWARD -> { + player.seekTo(player.currentPosition + PlayerHelper.seekIncrement) + } + } } /** @@ -190,21 +271,18 @@ class NowPlayingNotification( playerNotification = PlayerNotificationManager .Builder(context, PLAYER_NOTIFICATION_ID, BACKGROUND_CHANNEL_ID) // set the description of the notification - .setMediaDescriptionAdapter( - DescriptionAdapter() - ) - .build() - playerNotification?.apply { - setPlayer(player) - setUseNextAction(false) - setUsePreviousAction(false) - setUseStopAction(true) - setColorized(true) - setMediaSessionToken(mediaSession.sessionToken) - setSmallIcon(R.drawable.ic_launcher_lockscreen) - setUseFastForwardActionInCompactView(true) - setUseRewindActionInCompactView(true) - } + .setMediaDescriptionAdapter(descriptionAdapter) + // register the receiver for custom actions, doesn't seem to change anything + .setCustomActionReceiver(customActionReceiver) + .build().apply { + setPlayer(player) + setColorized(true) + setMediaSessionToken(mediaSession.sessionToken) + setSmallIcon(R.drawable.ic_launcher_lockscreen) + setUseNextAction(false) + setUsePreviousAction(false) + setUseStopAction(true) + } } /** @@ -224,4 +302,11 @@ class NowPlayingNotification( ) as NotificationManager notificationManager.cancel(PLAYER_NOTIFICATION_ID) } + + companion object { + private const val PREV = "prev" + private const val NEXT = "next" + private const val REWIND = "rewind" + private const val FORWARD = "forward" + } } 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 560021075..008e92963 100644 --- a/app/src/main/java/com/github/libretube/util/PlayerHelper.kt +++ b/app/src/main/java/com/github/libretube/util/PlayerHelper.kt @@ -397,52 +397,47 @@ object PlayerHelper { */ @RequiresApi(Build.VERSION_CODES.O) fun getPiPModeActions(activity: Activity, isPlaying: Boolean, isOfflinePlayer: Boolean = false): ArrayList { - val actions: ArrayList = ArrayList() - actions.add( - if (!isOfflinePlayer && alternativePiPControls) { - getRemoteAction( - activity, - R.drawable.ic_headphones, - R.string.background_mode, - PlayerEvent.Background - ) - } else { - getRemoteAction( - activity, - R.drawable.ic_rewind, - R.string.rewind, - PlayerEvent.Rewind - ) - } + val audioModeAction = getRemoteAction( + activity, + R.drawable.ic_headphones, + R.string.background_mode, + PlayerEvent.Background ) - actions.add( - getRemoteAction( - activity, - if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play, - R.string.pause, - if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play - ) + val rewindAction = getRemoteAction( + activity, + R.drawable.ic_rewind, + R.string.rewind, + PlayerEvent.Rewind ) - actions.add( - if (!isOfflinePlayer && alternativePiPControls) { - getRemoteAction( - activity, - R.drawable.ic_next, - R.string.play_next, - PlayerEvent.Next - ) - } else { - getRemoteAction( - activity, - R.drawable.ic_forward, - R.string.forward, - PlayerEvent.Forward - ) - } + val playPauseAction = getRemoteAction( + activity, + if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play, + R.string.pause, + if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play ) - return actions + + val skipNextAction = getRemoteAction( + activity, + R.drawable.ic_next, + R.string.play_next, + PlayerEvent.Next + ) + + val forwardAction = getRemoteAction( + activity, + R.drawable.ic_forward, + R.string.forward, + PlayerEvent.Forward + ) + return if ( + !isOfflinePlayer && alternativePiPControls + ) { + arrayListOf(audioModeAction, playPauseAction, skipNextAction) + } else { + arrayListOf(rewindAction, playPauseAction, forwardAction) + } } /** 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 995f45bfd..31769d384 100644 --- a/app/src/main/java/com/github/libretube/util/PlayingQueue.kt +++ b/app/src/main/java/com/github/libretube/util/PlayingQueue.kt @@ -6,6 +6,7 @@ import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.obj.StreamItem import com.github.libretube.extensions.move import com.github.libretube.extensions.toID +import com.github.libretube.extensions.toStreamItem import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -13,6 +14,7 @@ import kotlinx.coroutines.launch object PlayingQueue { private val queue = mutableListOf() private var currentStream: StreamItem? = null + private val scope = CoroutineScope(Dispatchers.IO) /** * Listener that gets called when the user selects an item from the queue @@ -28,11 +30,11 @@ object PlayingQueue { fun clear() = queue.clear() fun add(vararg streamItem: StreamItem) { - streamItem.forEach { - if (currentStream != it) { - if (queue.contains(it)) queue.remove(it) - queue.add(it) - } + for (stream in streamItem) { + if (currentStream?.url?.toID() == stream.url?.toID()) continue + // remove if already present + queue.remove(stream) + queue.add(stream) } } @@ -111,7 +113,7 @@ object PlayingQueue { private fun fetchMoreFromPlaylist(playlistId: String, nextPage: String?) { var playlistNextPage: String? = nextPage - CoroutineScope(Dispatchers.IO).launch { + scope.launch { while (playlistNextPage != null) { RetrofitInstance.authApi.getPlaylistNextPage( playlistId, @@ -127,7 +129,7 @@ object PlayingQueue { } fun insertPlaylist(playlistId: String, newCurrentStream: StreamItem) { - CoroutineScope(Dispatchers.IO).launch { + scope.launch { try { val playlist = PlaylistsHelper.getPlaylist(playlistId) add(*playlist.relatedStreams.orEmpty().toTypedArray()) @@ -142,7 +144,7 @@ object PlayingQueue { private fun fetchMoreFromChannel(channelId: String, nextPage: String?) { var channelNextPage: String? = nextPage - CoroutineScope(Dispatchers.IO).launch { + scope.launch { while (channelNextPage != null) { RetrofitInstance.api.getChannelNextPage(channelId, nextPage!!).apply { add(*relatedStreams.orEmpty().toTypedArray()) @@ -153,15 +155,22 @@ object PlayingQueue { } fun insertChannel(channelId: String, newCurrentStream: StreamItem) { - CoroutineScope(Dispatchers.IO).launch { - try { + scope.launch { + runCatching { val channel = RetrofitInstance.api.getChannel(channelId) add(*channel.relatedStreams.orEmpty().toTypedArray()) updateCurrent(newCurrentStream) if (channel.nextpage == null) return@launch fetchMoreFromChannel(channelId, channel.nextpage) - } catch (e: Exception) { - e.printStackTrace() + } + } + } + + fun insertByVideoId(videoId: String) { + scope.launch { + runCatching { + val streams = RetrofitInstance.api.getStreams(videoId.toID()) + add(streams.toStreamItem(videoId)) } } } diff --git a/app/src/main/java/com/github/libretube/util/ShortcutHelper.kt b/app/src/main/java/com/github/libretube/util/ShortcutHelper.kt new file mode 100644 index 000000000..ea1b14279 --- /dev/null +++ b/app/src/main/java/com/github/libretube/util/ShortcutHelper.kt @@ -0,0 +1,45 @@ +package com.github.libretube.util + +import android.content.Context +import android.content.Intent +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat +import com.github.libretube.R +import com.github.libretube.constants.IntentData +import com.github.libretube.obj.AppShortcut +import com.github.libretube.ui.activities.MainActivity + +object ShortcutHelper { + private val shortcuts = listOf( + AppShortcut("home", R.string.startpage, R.drawable.ic_home), + AppShortcut("trends", R.string.trends, R.drawable.ic_trending), + AppShortcut("subscriptions", R.string.subscriptions, R.drawable.ic_subscriptions), + AppShortcut("library", R.string.library, R.drawable.ic_library) + ).reversed() + + private fun createShortcut(context: Context, action: String, label: String, icon: IconCompat) { + val shortcut = ShortcutInfoCompat.Builder(context, action) + .setShortLabel(label) + .setLongLabel(label) + .setIcon(icon) + .setIntent( + Intent(context, MainActivity::class.java).apply { + this.action = Intent.ACTION_VIEW + putExtra(IntentData.fragmentToOpen, action) + } + ) + .build() + + ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) + } + + fun createShortcuts(context: Context) { + ShortcutManagerCompat.getDynamicShortcuts(context).takeIf { it.isEmpty() } ?: return + + shortcuts.forEach { + val icon = IconCompat.createWithResource(context, it.drawable) + createShortcut(context, it.action, context.getString(it.label), icon) + } + } +} diff --git a/app/src/main/java/com/github/libretube/util/TextUtils.kt b/app/src/main/java/com/github/libretube/util/TextUtils.kt index 4d5f2f9e3..6d100523d 100644 --- a/app/src/main/java/com/github/libretube/util/TextUtils.kt +++ b/app/src/main/java/com/github/libretube/util/TextUtils.kt @@ -22,10 +22,6 @@ object TextUtils { */ const val RESERVED_CHARS = "?:\"*|/\\<>\u0000" - fun toTwoDecimalsString(num: Int): String { - return if (num >= 10) num.toString() else "0$num" - } - /** * Check whether an Url is valid * @param url The url to test @@ -45,7 +41,7 @@ object TextUtils { * @param locale The locale to use, otherwise uses system default * return Localized date string */ - fun localizeDate(date: String?, locale: Locale? = null): String? { + fun localizeDate(date: String?, locale: Locale): String? { date ?: return null // relative time span diff --git a/app/src/main/java/com/github/libretube/util/WindowHelper.kt b/app/src/main/java/com/github/libretube/util/WindowHelper.kt new file mode 100644 index 000000000..e75c6855d --- /dev/null +++ b/app/src/main/java/com/github/libretube/util/WindowHelper.kt @@ -0,0 +1,49 @@ +package com.github.libretube.util + +import android.os.Build +import android.view.WindowManager +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import com.github.libretube.ui.base.BaseActivity + +class WindowHelper(private val activity: BaseActivity) { + fun setFullscreen() = activity.apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + window.attributes.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + } + + WindowCompat.setDecorFitsSystemWindows(window, false) + WindowInsetsControllerCompat(window, window.decorView).let { controller -> + controller.hide( + WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.navigationBars() + ) + controller.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + + window.setFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + ) + } + + fun unsetFullscreen() = activity.apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + window.attributes.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + } + + WindowCompat.setDecorFitsSystemWindows(window, true) + WindowInsetsControllerCompat(window, window.decorView).let { controller -> + controller.show( + WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.navigationBars() + ) + controller.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH + } + + window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) + } +} diff --git a/app/src/main/res/drawable/ic_forward_md.xml b/app/src/main/res/drawable/ic_forward_md.xml new file mode 100644 index 000000000..bf1528a53 --- /dev/null +++ b/app/src/main/res/drawable/ic_forward_md.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml index 20c5ae8c5..68e02a616 100644 --- a/app/src/main/res/drawable/ic_home.xml +++ b/app/src/main/res/drawable/ic_home.xml @@ -6,6 +6,6 @@ android:viewportHeight="48"> diff --git a/app/src/main/res/drawable/ic_library.xml b/app/src/main/res/drawable/ic_library.xml index e623e5d3c..8588f8bb2 100644 --- a/app/src/main/res/drawable/ic_library.xml +++ b/app/src/main/res/drawable/ic_library.xml @@ -6,6 +6,6 @@ android:viewportHeight="48"> diff --git a/app/src/main/res/drawable/ic_next_outlined.xml b/app/src/main/res/drawable/ic_next_outlined.xml new file mode 100644 index 000000000..1a8af1d37 --- /dev/null +++ b/app/src/main/res/drawable/ic_next_outlined.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_prev_outlined.xml b/app/src/main/res/drawable/ic_prev_outlined.xml new file mode 100644 index 000000000..9152dd34c --- /dev/null +++ b/app/src/main/res/drawable/ic_prev_outlined.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_rewind_md.xml b/app/src/main/res/drawable/ic_rewind_md.xml new file mode 100644 index 000000000..1e3b79b2c --- /dev/null +++ b/app/src/main/res/drawable/ic_rewind_md.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_subscriptions.xml b/app/src/main/res/drawable/ic_subscriptions.xml index 10574772e..72d819812 100644 --- a/app/src/main/res/drawable/ic_subscriptions.xml +++ b/app/src/main/res/drawable/ic_subscriptions.xml @@ -6,6 +6,6 @@ android:viewportHeight="48"> diff --git a/app/src/main/res/drawable/ic_trending.xml b/app/src/main/res/drawable/ic_trending.xml index 233823167..b69f9c14c 100644 --- a/app/src/main/res/drawable/ic_trending.xml +++ b/app/src/main/res/drawable/ic_trending.xml @@ -4,7 +4,8 @@ android:tint="?attr/colorControlNormal" android:viewportWidth="52" android:viewportHeight="52"> + diff --git a/app/src/main/res/layout/playback_bottom_sheet.xml b/app/src/main/res/layout/playback_bottom_sheet.xml index ab34782c3..b99366ce8 100644 --- a/app/src/main/res/layout/playback_bottom_sheet.xml +++ b/app/src/main/res/layout/playback_bottom_sheet.xml @@ -92,6 +92,29 @@ + + + + + + + + diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 24a0777dd..0921627cc 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -434,9 +434,16 @@ İrəli Fasilə ver Alternativ PiP nəzarətləri - Yalnız səsi göstər və irəli və geri çevirmək əvəzinə PiP-də idarəetmələri ötür + Yalnız səsi göstər və irəli və geri çevirmək əvəzində PiP-də idarəetmələri ötür Geri sar Səs oynadıcı Yalnız səs rejimi LibreTube-u musiqi oynadıcıya dəyiş. + Altyazı yoxdur + Endirməyə fasilə verildi + Endirmə tamamlandı + Maksimal paralel endirmələr + Maksimal paralel endirmə limitinə çatıldı. + Naməlum + Davam et \ 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 e3adf5813..504d160a7 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -437,4 +437,13 @@ Vpřed Alternativní ovládací prvky PiP Přehrávač zvuku + Bez titulků + Stahování pozastaveno + Stahování dokončeno + Maximální počet souběžných stahování + Dosažen limit maximálního počtu souběžných stahování. + Neznámý + Pokračovat + Režim pouze zvuku + Proměňte LibreTube v hudební přehrávač. \ 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 538b6a70e..16fd501e5 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -435,4 +435,10 @@ Vorspulen Pause Alternative BiB-Steuerungen + Nur-Audio-Modus + Kein Untertitel + Herunterladen pausiert + Herunterladen abgeschlossen + Unbekannt + Fortsetzen \ 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 47c0bf47c..316d57b91 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -437,4 +437,13 @@ Rebobinar Controles alternativos de imagen en imagen Reproductor de audio + Convierte LibreTube en un reproductor de música. + Sin subtítulos + Descarga pausada + Descarga completa + Número máximo de descargas simultáneas + Límite máximo de descargas simultáneas alcanzado. + Reanudar + Sólo audio + Desconocido \ 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 fe2d579e3..da6a3c30e 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -439,4 +439,11 @@ ऑडियो प्लेयर ऑडियो केवल मोड LibreTube को म्यूजिक प्लेयर में बदलें। + कोई उपशीर्षक नहीं + डाउनलोड रोका गया + डाउनलोड पूरा हुआ + अधिकतम समवर्ती डाउनलोड + अनजान + अधिकतम समवर्ती डाउनलोड सीमा पूरी हो गई है। + फिर शुरू करें \ 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 1e4992beb..532b151c2 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -436,4 +436,14 @@ Visszatekerés Alternatív PiP irányítás Csak hang és átlépés gombok megjelenítése a PiP-ben az előretekerés és a visszatekerés helyett + Hanglejátszó + Nincs felirat + Letöltés szüneteltetve + Letöltés befejezve + Maximális egyidejű letöltések + Ismeretlen + Folytatás + Az egyidejű letöltések maximális száma elérve. + Csak hang mód + Változtassa a LibreTube-ot zenelejátszóvá. \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 50aaa4283..9062c2193 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -436,4 +436,14 @@ Pausa Controlli PiP alternativi Mostra solo l\'audio e salta i controlli nel PiP, invece di avanti e riavvolgi + Riproduttore audio + Nessun sottotitolo + Scaricamento in pausa + Scaricamento completato + Max download simultanei + Limite massimo di download simultanei raggiunto. + Sconosciuto + Riprendi + Modalità solo audio + Trasforma LibreTube in un riproduttore musicale. \ 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 d258e575c..99c367f59 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -436,4 +436,15 @@ השהיה בקרים חלופיים לתמונה בתוך תמונה להציג בקרי שמע בלבד ודילוג בתמונה בתוך תמונה במקום קדימה ואחורה + נגן שמע + ההורדה הושלמה + כמות ההורדות המרבית במקביל + אין כתובית + ההורדה מושהית + הגעת למגבלת ההורדות במקביל. + לא ידוע + המשך + מצב שמע בלבד + הפיכת LibreTube לנגן מוזיקה. + מתזמן שינה \ 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 3daae90e2..204cffe04 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -437,4 +437,13 @@ ବିକଳ୍ପ PIP ନିୟନ୍ତ୍ରଣ କେବଳ ଅଡିଓ ଦେଖାନ୍ତୁ ଏବଂ ଆଗକୁ ଏବଂ ପଛକୁ ନେବା ପରିବର୍ତ୍ତେ PiP ରେ କଣ୍ଟ୍ରୋଲ୍ ଛାଡିଦିଅ ଅଡିଓ ପ୍ଲେୟାର + କୌଣସି ଉପ-ଆଖ୍ୟା ନାହିଁ + ଡାଉନଲୋଡ୍ ବିରତ ହୋଇଛି + ଡାଉନଲୋଡ୍ ସମାପ୍ତ ହୋଇଛି + ସର୍ବାଧିକ ଏକକାଳୀନ ଡାଉନଲୋଡ୍ + ସର୍ବାଧିକ ଏକକାଳୀନ ଡାଉନଲୋଡ୍ ସୀମା ପହଞ୍ଚିଲା । + ଅଜ୍ଞାତ + ପୁଣି ଆରମ୍ଭ କରନ୍ତୁ + କେବଳ ଅଡିଓ ମୋଡ୍ + LibreTube କୁ ଏକ ମ୍ୟୁଜିକ୍ ପ୍ଲେୟାରରେ ପରିଣତ କର । \ No newline at end of file diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index ee84c03c8..da813dbdb 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -439,4 +439,11 @@ ਆਡੀਓ ਪਲੇਅਰ ਸਿਰਫ ਆਡੀਓ ਮੋਡ LibreTube ਨੂੰ ਇੱਕ ਸੰਗੀਤ ਪਲੇਅਰ ਵਿੱਚ ਬਦਲੋ। + ਕੋਈ ਉਪਸਿਰਲੇਖ ਨਹੀਂ + ਅਗਿਆਤ + ਡਾਊਨਲੋਡ ਰੋਕਿਆ ਗਿਆ + ਡਾਊਨਲੋਡ ਪੂਰਾ ਹੋਇਆ + ਮੁੜ ਸ਼ੁਰੂ ਕਰੋ + ਅਧਿਕਤਮ ਸਮਕਾਲੀ ਡਾਊਨਲੋਡ + ਅਧਿਕਤਮ ਸਮਕਾਲੀ ਡਾਊਨਲੋਡ ਸੀਮਾ ਪੂਰੀ ਹੋ ਗਈ ਹੈ। \ 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 a59513cca..71437027e 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -435,6 +435,15 @@ Do przodu Wstrzymaj Alternatywne sterowanie PiP - Zastąp przyciski „przewiń do tyłu” i „przwiń do przodu” na „tylko dźwięk” i „pomiń film”. + Wyświetl sterowanie odtwarzaniem dźwięku w tle i pomijania filmu zamiast przycisków przewijania do tyłu i przodu. Odtwarzacz dźwięku + Pobieranie wstrzymane + Pobieranie ukończone + Maks. równoczesnych pobierań + Osiągnięto limit maksymalnego jednoczesnego pobierania. + Nieznane + Wznów + Bez napisów + Zmień LibreTube w odtwarzacz muzyczny. + Tryb „tylko dźwięk” \ 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 17614cd78..035698ccc 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -437,4 +437,15 @@ Controles PiP alternativos Mostrar controles de apenas áudio e pular no PiP em vez de avançar e retroceder Player de Áudio + Sem legenda + Download pausado + Download concluído + Máximo de downloads simultâneos + Limite máximo de downloads simultâneos atingido. + Desconhecido + Retomar + Transforme o LibreTube em um reprodutor de música. + Modo somente áudio + Temporizador + Pular silêncio \ 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 2da1c80e3..091a7fd4a 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -437,4 +437,13 @@ Controlos PiP alternativos Mostrar apenas áudio e saltar controlos em PiP em vez de avançar e rebobinar Reprodutor de áudio + Sem legendas + Descarga em pausa + Apenas áudio + Transforme LibreTube num reprodutor de músicas. + Número de transferências simultâneas + Desconhecido + Continuar + Descarga terminada + Atingiu o limite máximo de descargas em simultâneo. \ 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 36b391fb1..2820776f4 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -436,4 +436,14 @@ Пауза Добавить в закладки Удалить закладку + Аудиоплеер + Без субтитров + Загрузка приостановлена + Загрузка завершена + Максимальное количество одновременных загрузок + Достигнут максимальный предел одновременных загрузок. + Неизвестно + Продолжить + Режим только аудио + Превратите LibreTube в музыкальный проигрыватель. \ No newline at end of file diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml new file mode 100644 index 000000000..06ad6fef3 --- /dev/null +++ b/app/src/main/res/values-si/strings.xml @@ -0,0 +1,449 @@ + + + ලේඛනාලය + ඔව් + ගුණත්වය + සොයන්න + වීඩියෝ + දායකවන්න + බෙදාගන්න + බාගත කරන්න + සුරකින්න + පරිශීලක නාමය + ඇතුළු වන්න + ලියාපදිංචි වන්න + වරනය වන්න + අවලංගු කරන්න + වරනය වුනා. + දැනටමත් පුරනය වී ඇත. ඔබට ඔබගේ ගිණුමෙන් වරනය විය හැක. + කරුණාකර පුරනය වී නැවත උත්සාහ කරන්න. + අභිරුචිය පරිදි + කලාපය + කරුණාකර පළමුව පුරනය වන්න හෝ සැකසීම් තුළ ලියාපදිංචි වන්න. + දායක විය + මෙම ප්‍රවාහය බාගත කළ නොහැක. + බාගත කිරීම සම්පූර්ණයි. + බාගත කිරීම අසමත් විය. + VLC හි විවෘත කරන්න + දායකත්වයන් ආනයනය කරන්න + YouTube හෝ NewPipe වෙතින් + පෙනුම + ජාල දෝෂයකි. + මොකක්හරි වැරැද්දක් වෙලා. + ජාලකයේ තීරු ගණන + මෙහි කිසිවක් නැහැ. + කළා. + අසාර්ථකයි :( + වාදන ලැයිස්තුව මකන්න + වාදන ලැයිස්තුව සාදන්න + ගැන + භාෂාව + පද්ධතිය + පද්ධතිය + එළිය + අඳුරු + %1$s දායක වන්නන් + සැකසුම් + ස්ථානය + සේවාදායකය + ගැලපීම් + වෙබ් අඩවිය + %1$s වීඩියෝ + මුලින්ම අන්තර්ජාලයට සම්බන්ධ වෙන්න. + නැවත උත්සාහ කරන්න + අදහස් + සෙවුම් පෙරහන තෝරන්න + නාලිකා + සියලුම + හරි + සෙවුම් ඉතිහාසය + ඉතිහාසය හිස් කරන්න + YT Music ගීත + YT Music වීඩියෝ + YT Music ඇල්බම + පෙරනිමි පිම්ම + කොටස මඟ හැරිය + සක්‍රිය + අනුග්‍රාහකයා + නොගෙවූ/ස්වයං ප්‍රවර්ධන + අන්තර්ක්‍රියා මතක් කිරීම (කැමති සහ දායක වන්න) + විරාම/හඳුන්වාදීමේ සජීවිකරණය + අවසන් කාඩ්පත් සහ දායක ලැයිස්තුව + අවසානයට පසුව තොරතුරු. තොරතුරු සහිත නිගමන සඳහා නොවේ. + අදාළ නොවන කොටස්/විහිළු + SponsorBlock + වාදන ලැයිස්තු + YT Music වාදන ලැයිස්තු + නොගෙවූ හෝ ස්වයං ප්‍රවර්ධනය හැර \"අනුග්‍රාහකයා\" හා සමානයි. මෙයට වෙළඳ භාණ්ඩ, පරිත්‍යාග, හෝ ඔවුන් සහයෝගීව කටයුතු කළ අය පිළිබඳ තොරතුරු පිළිබඳ කොටස් ඇතුළත් වේ. + පෙරදසුන/සාරාංශය + බලපත්රය + Material You + දැනුම්දීම් + නිරූපකය + සක්‍රිය + අක්‍රිය + Piped + YouTube + නිකුතු අංකය %1$s ඇත + එය බාගත කිරීමට GitHub හි නිකුතු වෙත යන්නද\? + පෙනුම + බාගත කිරීම් + වීඩියෝ ආකෘතිය + බාගත කරන ස්ථානය + බාගත කළ මාධ්‍ය ගබඩා කරන තැන. + දායක වන්න + නව අනුවාදයක් සොයන්න + යාවත්කාලීන කිරීමට ඹබන්න + නවතම අනුවාදය ධාවනය කරමින් සිටියි. + උසස් + වාදකය + ඔබගේ අභිමතය පරිදි යෙදුම සකසන්න. + බාගැනීම්, සහ යළි පිහිටුවන්න + සජීවි + කතුවරුන් + බාගත කළ මාධ්‍ය ගබඩා කර ඇති ෆෝල්ඩරයේ නම. + අභ්යන්තර ගබඩාව + ෆෝල්ඩරය බාගත කරන්න + SD කාඩ් + URL එක බෙදාගන්න + බැලීම් + %1$s බැලීම් + පෙරනිමිය + අවධාරණ + කහ පාට + කොළ පාට + දම් පාට + කළු පාට + නිල් පාට + රතු පාට + වාදන වේගය + විලාසිතාමය ගින්න + නවීන පන්දම + පියාඹන දැල්ල + Piped, පුරනය සහ දායකත්ව + සේවාදායකයේ නම + සේවාදායක API වෙත URL + සේවාදායකයක් එකතු කරන්න + එකතු කිරීම් මකන්න + අනුවාදය %1$s + LibreTube කණ්ඩායම සහ ඒ සියල්ල සිදුවන්නේ කෙසේදැයි දැන ගන්න. + අදාළ අන්තර්ගත + පරිච්ඡේද පෙන්වන්න + පරිච්ඡේද සඟවන්න + Glib ශ්‍රේණියේ වර්ණය + පියාඹන කුරුල්ලා + පූර්ව ප්‍රෙව්ශය වෙමින් + වාදකය සඳහා වීඩියෝ ආකෘතිය + ශ්‍රව්‍ය නැත + උපසිරැසි නැත + ශ්‍රව්‍ය + බාගත වෙමින්… + බාගත කිරීම නවතා ඇත + උපරිම සමකාලීන බාගත කිරීම් + උපරිම සමකාලීන බාගත කිරීම් සීමාව ළඟා විය. + නවත්වන්න + අරඹන්න + නැගී එන පිටුව සඟවන්න + සේවාදායක ඉදිරිපස URL + ගුණත්වය + පෙරනිමි සහ හැසිරීම + වර්ධක අන්වේෂණය කරන්න + ස්වයංක්‍රීය වාදනය + පෙරනිමි ප්‍රත්‍යර්පණය කරන්න + ගිණුම මකන්න + ඔබගේ Piped ගිණුම මකන්න + ගිණුම + ප්‍රත්‍යර්පණය කරන්න + නැරඹුම් ඉතිහාසය + සත්‍යතාව තහවුරු කිරීමේ සේවාදායකය + සත්‍යතාව තහවුරු කිරීමට සේවාදායකයක් තෝරන්න + HLS + GitHub + ශ්රව්ය සහ වීඩියෝ + සම්පූර්ණ තිර නැඹුරුව + භූ දර්ශනය + ප්‍රජාව + Discord + Matrix + Telegram + Reddit + Twitter + කරුණාකර අන්තර්ජාලයට සම්බන්ධ වීමට Wi-Fi හෝ ජංගම දත්ත ක්‍රියාත්මක කරන්න. + විවෘත කරන්න… + පරිච්ඡේද + යෙදුම නැවත පටන් ගැනීම අවශ්‍යයි + ලේබල් දෘශ්‍යතාව + තෝරා ගන්නා ලදී + කවදාවත් නැහැ + ස්වයං-පූර්ණ තිරය + තනි පෙනුම + තනි සුදු/කළු පෙනුම + දත්ත සුරැකීමේ ප්‍රකාරය + සිඟිති රූ සහ අනෙකුත් පින්තූර මඟ හරින්න. + නැරඹූ වීඩියෝ දේශීයව මතක තබා ගන්න + නැරඹුම් සහ සෙවුම් ඉතිහාසය + ප්‍රත්‍යාරම්භ කරන්න + වාදන වේගය + උපාංගය හැරවූ විට සම්පූර්ණ තිර වාදනය. + මතක වාදන ස්ථාන + නව LibreTube අනුවාදය දැන් පිහිටු වන්නද\? + භාෂාව සහ ප්‍රදේශය + සිරස්තල + වාදකය සඳහා ශ්‍රව්‍ය ආකෘතිය + ශ්‍රව්‍ය ගුණත්වය + විශිෂ්ට + නරකම + නව ප්‍රවාහ සඳහා දැනුම්දීම් + බලන වාර ගණන… + %1$s නව ප්‍රවාහ තිබේ + %1$s විසින් නව ප්‍රවාහ… + ඔබට විශ්වාසද\? මෙය පසුගමනය කළ නොහැක! + අලුත්ම + පැරණිම + බොහෝ බැලීම් + අඩුම බැලීම් + නාලිකාවේ නම (A-Z) + නාලිකාවේ නම (Z-A) + තේරීම + සියලුම + සීමිත + Wi-Fi මත පමණි + පරිවර්තනය + ප්‍රතිපල නැත. + දෝෂයකි + පිටපත් කර ඇත + කාල කේතය සමඟ බෙදා ගන්න + දායකත්වයන් අපනයනය කරන්න + ඊළඟ හෝ පෙර වීඩියෝවට යාමට බොත්තම් පෙන්වන්න. + උපරිම ඉතිහාස ප්‍රමාණය + පසුබිම් ප්‍රකාරය + වෙනත් + විවේක මතක් කිරීම + විවේකයක් ගැනීමට කාලයයි + කෙටි වීඩියෝ + උපසිරැසි නොමැත + නැවත ධාවනය කිරීම් ප්‍රකාරය + සුදුසු + පුරවන්න + පසුබිමේ වාදනය වෙමින්… + දැන් + උපස්ථ සහ ප්‍රත‍්‍යර්පණ කිරීම + උපස්ථ කිරීම + පින්තූරයේ පින්තූරය + උපරිම පින්තූර නිහිත ප්‍රමාණය + විවෘත කරන්න + මතක් කිරීමට පෙර මිනිත්තු ගණන + උපාංග තොරතුරු + ගුණත්වය සහ ආකෘතිය + බාගත කිරීම් වලින් මකන්න + විකල්ප නැගී එන පිරිසැලසුම + Wi-Fi + ජංගම දත්ත + අලුත් වීඩියෝ තිබේ නම් ප්‍රමාණය සමඟ ලාංඡනයක් පෙන්වන්න. + කොටස මඟ හරින්න + අතින් මඟ හරින්න + දේශීයව ගබඩා කර ඇති දායකත්වයන් + සැකසුම් + වෙනත් සේවාදායකයක් + පසුබිමේ නවතම වීඩියෝ පූරණය කරන්න + මං සෙවුම් තීරුව + කරුණාකර අවම වශයෙන් එක් අයිතමයක් තෝරන්න + ප්‍රගතිශීලීව ප්‍රවේශනය වීමේ විරාම ප්‍රමාණය + අඩු අගයක් මුල් වීඩියෝ ප්‍රවේශනය වේගවත් කළ හැක. + ස්වරය + ගොනු නාමය + වලංගු නොවන ගොනු නාමයකි! + මෑතකදී යාවත්කාලීන කරන ලද + මෑතකදී යාවත්කාලීන කරන ලද (ප්‍රතිලෝම) + තව පෙන්වන්න + ඊළඟ වීඩියෝව වාදනය කරන්න + වාදන ලැයිස්තුවට එක් කරන ලදී + පෝලිම + සජීවී ප්‍රවාහ + විකල්ප වීඩියෝ පිරිසැලසුම + පෙරනිමි එළිය + ඔබට %1$s ගෙ දායකත්වයෙන් ඉවත් වීමට අවශ්‍ය බව විශ්වාසද\? + දායකත්වයෙන් ඉවත් වීම තහවුරු කරන්න + කාලය + දැනුම්දීමේ කාලය + දැනුම්දීම් පෙන්වීමට අවසර දී ඇති කාල පරාසය. + විකල්ප නැගී එන පිරිසැලසුම + පිළිවෙල + පිරිසැලසුම + ශ්‍රව්‍ය පථය + පෙරනිමිය + සහාය නොදක්වන ගොනු ආකෘතියකි! + HLS භාවිතා කරන්න + ස්වයං + ධාවන කාලයට සීමා කරන්න + දැනුම්දීමෙන් පෝලිම විවෘත කරන්න + ප්‍රවණතා + විශේෂාංගගත + දැන් නැගී එන්නේ කුමක්ද + පොත් සලකුණු + පොත් සලකුණු හිස් කරන්න + තවමත් පොත් සලකුණු නොමැත! + කරුණාකර පළමුව වෙනත් ආරම්භක පිම්මක් තෝරන්න! + දීප්තිය + ශබ්ද හඩ + ස්වයං + ස්වයිප් පාලන + දීප්තිය සහ ශබ්ද හඩ සීරුමාරු කිරීමට ස්වයිප් අභිනය භාවිත කරන්න. + කෙනිත්තීම අභිනය පාලනය + විශාලනය කිරීමට/පිටතට කිරීමට කෙනිත්තීම අභිනය භාවිත කරන්න. + පෙරනිමි + උත්පතන + අවම ඒකවර්ණ + සිරස්තල ප්‍රමාණය + සියල්ල වාදනය කරන්න + ඔබ නව වීඩියෝ සියල්ලම දැක ඇත + යෙදුම් උපස්ථ + අපනයනය කරන ලදී. + ඇවිරීම් ඇඟවීම + නිර්දේශ නොකළ විද්‍යුත් තැපැල් ලිපිනයක් සමඟ ඉදිරියට යන්නද\? + ඉදිරියට යන්න + නවතම වීඩියෝ වාදනය කරන්න + කිසිවක් තෝරා නැත! + ජම්බූල පාට + වාදන ලැයිස්තු ආනයනය කරන්න + වාදන ලැයිස්තු URL + ශ්‍රව්‍ය වාදකය + LibreTube සංගීත වාදකයක් බවට පත් කරන්න. + දායකත්ව පිම්මෙහි 90%කට වඩා නරඹන වීඩියෝ නොපෙන්වන්න. + ඉවත් වීමේදී නවත්වන්න + කලවම් කරන්න + පොත් සලකුණු වලට එකතු කරන්න + ආපස්සට යන්න + ඉදිරියට යන්න + විකල්ප PiP පාලන + ශ්‍රව්‍ය පමණක් ප්‍රකාරය + මාධ්‍ය බාගත කිරීමේදී දැනුම්දීමක් පෙන්වයි. + පසුබිම් ප්‍රකාරය + නව ප්‍රවාහ පවතින විට දැනුම්දීමක් පෙන්වයි. + ඹබ සියල්ල බලා ඇත + මුල් + මුරපදය + දායකත්ව + දායක නොවන්න + පුරනය වී ඇත. + ලියාපදිංචි. දැන් ඔබට නාලිකා වලට දායක විය හැක. + තෝරන්න… + පුරනය වන්න / ලියාපදිංචි වන්න + මුලින්ම නාලිකා කිහිපයකට දායක වෙන්න. + තවත් බාගත කිරීමක් දැනටමත් සිදු වෙමින් පවතී, කරුණාකර එය අවසන් වන තෙක් රැඳී සිටින්න. + VLC හි විවෘත කළ නොහැක. එය පිහිටුවා නැති විය හැකිය. + ඔබ පරිශීලක නාමයක් සහ මුරපදයක් ඇතුළත් කළ යුතුය. + සේවාදායකයේ ගැටලුවක් තිබේ. වෙනත් සේවාදායකයක් උත්සාහ කරන්නද\? + මෙය Piped ගිණුමක් සඳහා ය. + වීඩියෝ විභේදනය + වාදන ලැයිස්තුවේ නම + ඉතිහාසය + https://sponsor.ajay.app API භාවිත කරයි + කොටස් + අතරතුර කැමති වීමට, දායක වීමට හෝ අනුගමනය කිරීමට කෙටි මතක් කිරීමක් ඇති විට. දිගු හෝ විශේෂිත දෙයක් ගැන නම්, ඒ වෙනුවට එය ස්වයං ප්‍රවර්ධනයකි. + සැබෑ අන්තර්ගතයක් නොමැති විරාමයක්. නැවතීමක්, ස්ථිතික රාමුවක්, පුනරාවර්තන සජීවිකරණයක් විය හැකිය. තොරතුරු අඩංගු සංක්‍රාන්ති සඳහා භාවිතා නොකළ යුතුය. + සංගීත වීඩියෝවල භාවිතය සඳහා පමණි. එය නිල මිශ්‍රණවල කොටසක් නොව වීඩියෝවේ කොටස් ආවරණය කළ යුතුය. අවසානයේදී, වීඩියෝව Spotify හෝ වෙනත් ඕනෑම මිශ්‍ර අනුවාදයක් හැකිතාක් සමීපව සමාන විය යුතුය, නැතහොත් කතා කිරීම හෝ වෙනත් අවධානය වෙනතකට යොමු කිරීම අඩු කළ යුතුය. + වීඩියෝවේ ප්‍රධාන අන්තර්ගතය තේරුම් ගැනීමට අවශ්‍ය නොවන ෆිලර් හෝ හාස්‍යය සඳහා පමණක් එක් කළ කොටස් සඳහා. + ගීත: ගීතය නොවන කොටස + හැසිරීම + අහිමි වූ උරුමය + මෝඩ හැඩැති + එකතු කරන්න… + නම සහ API URL එක පුරවන්න. + පසුබිමේ වාදනය කරන්න + ශ්‍රව්‍ය සහ දෘශ්‍ය යන දෙකම බාගත කර ඇත්නම් ගොනු පරිවර්තනය කිරීම. + ඔබ නවතම අනුවාදය ධාවනය කරමින් සිටියි. + මෙම අදහසට පිළිතුරු නොමැත. + සංගීත ෆෝල්ඩරය + පරිත්‍යාග කරන්න + නම + චිත්‍රපට ෆෝල්ඩරය + කරුණාකර වැඩ කරන URL එකක් ඇතුළු කරන්න + ඔබ නරඹන දේ හා සමාන ප්‍රවාහ පෙන්වන්න. + සලකුණු + වීඩියෝ + වීඩියෝව බෆර කිරීමට දෙන උපරිම තත්පර ගණන. + වීඩියෝ නැත + බාගත කිරීම සම්පූර්ණයි + නොදන්නා + හැසිරීම + ස්වයං නැවැත්වීම + වාදන ලැයිස්තුව ක්ලෝනය කරන්න + වත්මන් වීඩියෝවෙන් පසුව ඊළඟ වීඩියෝව ස්වයංක්‍රීයව වාදනය කරන්න. + සියලු සැකසුම් යළි සකසා වරනය වන්නද\? + නව වෙනස්කම් භාවිතා කිරීමට යෙදුම නැවත පටන් ගන්න. + අවසාන වාදන ස්ථානයෙන් ඉදිරියට යන්න + ස්ථානය මතක තබා ගන්න + සත්‍යතාව තහවුරු කිරීම සඳහා වෙනත් සේවාදායකයක් භාවිතා කරන්න. + වීඩියෝ දර්ශන අනුපාතය + ස්වයංක්‍රීය භ්‍රමණය + සිරස් + සැමවිටම + අසීමිත + බාහිර වාදකයක් හමු නොවීය. කරුණාකර ඔබ එකක් පිහිටුවා ඇති බවට වග බලා ගන්න. + වීඩියෝ පෙරදසුන + පොදු + සෙවුම් මතක තබා ගන්න + සිරස්තල + කිසිවක් නැත + පද්ධති සිරස්තල විලාසය + APK බාගත කරමින්… + දැනුම්දීම් + උපසිරැසි භාෂාව + ඔබ අනුගමනය කරන නිර්මාණකරුවන්ගෙන් නැවුම් අන්තර්ගත පිළිබඳ දැනුම්දීම්. + තවමත් ඉතිහාසයක් නැත. + ඔබ දැනටමත් යෙදුම තුළ මිනිත්තු %1$s ගත කර ඇත, විවේකයක් ගැනීමට කාලයයි. + සම්බන්ධතාවයක් අවශ්‍ය + බාගත කිරීම සාර්ථක විය + මඟ හරින බොත්තම් + නව වීඩියෝ සඳහා දර්ශකය + නව දායකත්ව වීඩියෝ පසුබිමේ පූරණය කර එය ස්වයංක්‍රීයව නැවුම් වීම වළක්වන්න. + විශාලනය කරන්න + කිසිවක් නැත + පෝලිමට එකතු කරන්න + වත්මන් ප්‍රදේශය සඳහා නැගී එන වීඩියෝ නොමැති බව පෙනේ. කරුණාකර සැකසීම් තුළ වෙනත් එකක් තෝරන්න. + ප්‍රමාණය වෙනස් කිරීමේ ප්‍රකාරය + පසුරු පුවරුවට පිටපත් කර ඇත + HLS 1080p දක්වා සීමා කරන්න + උරුම දායකත්ව දසුන + වාදන ලැයිස්තුව නැවත නම් කරන්න + කොටස් ස්වයංක්‍රීයව මඟ නොහරින්න, සෑම විටම පෙර විමසන්න. + පෙරනිමිය + වාදන ලැයිස්තු අනුපිළිවෙල + කාල කේතය (තත්පර) + කාල තීරුවේ කොටස් සලකුණු කරන්න. + වාදනය කරන පෝලිම + දායකත්වයෙන් ඉවත් වීමට පෙර තහවුරු කිරීමේ සංවාදයක් පෙන්වන්න. + වාදන ලැයිස්තුව ක්ලෝන කරන ලදී + විකල්ප වාදක පිරිසැලසුම + අවසන් කාලය + ආරම්භක කාලය + අදාළ වීඩියෝ පහතින් වෙනුවට අදහස්වලට ඉහළින් පේළියක් ලෙස පෙන්වන්න. + DASH වෙනුවට HLS භාවිතා කරන්න (මන්දගාමී වනු ඇත, නිර්දේශ නොකරයි) + පොත් සලකුණ + මෙනු අයිතමය සබල කර නැත! + දේශීය වාදන ලැයිස්තු + අදාළ වීඩියෝ ඇතුළු කරන්න + මෙම වීඩියෝවට අදහස් නොමැත. + උඩුගත කරන්නා විසින් අදහස් අබල කර ඇත. + අන්වේෂණයට දෙවරක් තට්ටු කරන්න + වාදකයේ ස්ථානය රිවයින්ඩ් කිරීමට හෝ ඉදිරියට යාමට වමේ හෝ දකුණේ දෙවරක් තට්ටු කරන්න. + වාදන ලැයිස්තු අපනයනය කරන්න + දායකත්වයන්, වාදන ලැයිස්තු,… ආනයනය සහ අපනයනය කරන්න + පවතින සේවාදායකයන් ලබා ගැනීමට නොහැකි විය. + නවතම කොටසෙහි නැරඹූ වීඩියෝ සඟවන්න + මෙම වාදන ලැයිස්තුව මකන්නද\? + වාදන ලැයිස්තුවේ නම හිස් විය නොහැක + තිරය ක්‍රියාවිරහිත වූ විට වාදනය නවත්වන්න. + වාදන දර්ශකය ඇදගෙන යන විට සැණරුවක් පෙන්වන්න. + වාදන ලැයිස්තුවේ නම (ප්‍රතිලෝම) + වාදන ලැයිස්තුව සාදන ලදී. + වාදන ලැයිස්තුවට එක් කරන්න + ශ්‍රව්‍ය ධාවකය පාලනය කිරීමට බොත්තම් සහිත දැනුම්දීමක් පෙන්වයි. + පොත් සලකුණ ඉවත් කරන්න + බාගත කිරීමේ සේවාව + දැනුම්දීම් සේවකයා + ගෙවුම් ප්‍රවර්ධනය, ගෙවුම් යොමු කිරීම් සහ සෘජු දැන්වීම්. ස්වයං ප්‍රවර්ධනය හෝ නොමිලේ නිර්මාණකරුවන්, වෙබ් අඩවි සහ නිෂ්පාදන සඳහා සත්‍ය ප්‍රචාරක කටයුතු, සඳහා නොවේ. + මෙම හෝ එහි මාලාවේ අනාගත වීඩියෝවල ඉදිරියට එන අන්තර්ගතය විස්තර කරන කොටස් සඳහා, නමුත් අමතර තොරතුරු ලබා නොදේ. මෙහි පමණක් පෙනෙන ක්ලිප් ඇතුළත් නම්, මෙය බොහෝ දුරට වැරදි කාණ්ඩයකි. + ඉදිරියට සහ පසුපසට යැවීම වෙනුවට PiP හි ශබ්ද පමණක් සහ මඟහරින පාලක පෙන්වන්න + \ No newline at end of file diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 5d8bc415f..39329ad9b 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -389,4 +389,61 @@ Шта је сада у тренду Обележивачи Обележивач + Локалне листе + Свестрана љубичаста + Ставка менија није омогућена! + Прво изаберите другу почетну картицу! + Осветлење екрана + Јачина звука + Аутоматски + Брзо превлачење контрола + Користите покрет брзог превлачења да бисте подесили светлину екрана и јачину звука. + Подразумевано + Искачући + Величина натписа + Пауза + Премотај уназад + Додирните двапут да бисте тражили + Додирните двапут са леве или десне стране да бисте премотали или проследили позицију плејера. + Није могуће добити доступне инстанце. + Не приказуј видео записе који се гледају више од 90% на картици са праћењима. + Ништа изабрано! + Штипање + Извези листе + Прављење резервне копије апликације + Репродукуј најновије видео записе + Аудио плејер + URL листе + Нема превода + Преузимање је паузирано + Преузимање је завршено + Максимално истовремених преузимања + Овај видео нема доступних коментара. + Минималистички монохром + Видели сте све нове снимке + Све прегледано + Увези листе + Увоз & извоз праћења, листа,… + Извезено. + Упозорење о приватности + Желите ли да наставите са е-адресом која се не препоручује\? + Настави + Сакриј гледане видео записе са фида + Премотај унапред + Алтернативне PiP контроле + Приказивање само звука и прескакање контрола у PiP-у уместо унапред и уназад + Режим само звука + Претвори LibreTube у музички плејер. + Максимално истовремених преузимања је достигнуто. + Користите покрет штипања за увећавање/умањивање. + Непознато + Настави + Отпремилац је онемогућио коментаре. + Пауза приликом изласка + Уметање сродних видео записа + Брисање обележивача + Нема обележивача! + Насумично + Обриши обележивач + Додај у обележиваче \ 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 fa6d3d3dc..ae9e58a4d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -437,4 +437,13 @@ Альтернативні елементи керування PiP Показувати лише аудіо та пропускати елементи керування в PiP замість перемотування вперед та назад Аудіопрогравач + Режим лише аудіо + Перетворіть LibreTube на музичний програвач. + Субтитрів немає + Завантаження призупинено + Максимальна кількість одночасних завантажень + Досягнуто максимальної кількості одночасних завантажень. + Невідомо + Завантаження завершено + Продовжити \ No newline at end of file diff --git a/app/src/main/res/values-yue/strings.xml b/app/src/main/res/values-yue/strings.xml index 6b6a972dd..9c65b68bb 100644 --- a/app/src/main/res/values-yue/strings.xml +++ b/app/src/main/res/values-yue/strings.xml @@ -83,4 +83,5 @@ YT Music 歌 Sponsor + 較嘢 \ 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 df7f8d650..7b53dd164 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -437,4 +437,13 @@ 备选 PiP (画中画)操控 仅显示音频并跳过画中画操控而非前进和后退 音频播放器 + 将 LibreTube 作为音乐播放器。 + 无字幕 + 下载已暂停 + 下载已完成 + 最大并发下载 + 已达到最大并发下载限制。 + 未知 + 恢复 + 仅音频模式 \ No newline at end of file diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index 9e8bbd4fa..ce2c0f36f 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -28,7 +28,7 @@ @string/systemLanguage العربية - Azərbaycan dili + Azərbaycanca Euskara বাংলা Català diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index dc758a619..4886b7498 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,6 +2,7 @@ #AA000000 #EEFFFFFF + #0061A6 #0058CB #FFFFFF diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2d656d3c3..f97725a59 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -440,6 +440,8 @@ Audio player Audio only mode Turn LibreTube into a music player. + Sleep timer + Skip silence Download Service diff --git a/app/src/main/res/xml/general_settings.xml b/app/src/main/res/xml/general_settings.xml index 67b585be7..e98d056ff 100644 --- a/app/src/main/res/xml/general_settings.xml +++ b/app/src/main/res/xml/general_settings.xml @@ -50,14 +50,14 @@ + app:key="sleep_timer_toggle" + app:title="@string/sleep_timer" /> diff --git a/app/src/main/res/xml/shortcuts.xml b/app/src/main/res/xml/shortcuts.xml deleted file mode 100644 index 54387f153..000000000 --- a/app/src/main/res/xml/shortcuts.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file