1
0
Fork 0
mirror of https://github.com/TeamNewPipe/NewPipe.git synced 2025-10-03 09:49:21 +02:00

Merge branch 'refactor' into Playlist-Compose

This commit is contained in:
Isira Seneviratne 2025-09-07 09:48:41 +05:30
commit 582b10fc04
235 changed files with 5855 additions and 4999 deletions

View file

@ -72,8 +72,8 @@ jobs:
- api-level: 21 - api-level: 21
target: default target: default
arch: x86 arch: x86
- api-level: 33 - api-level: 35
target: google_apis # emulator API 33 only exists with Google APIs target: default
arch: x86_64 arch: x86_64
permissions: permissions:

View file

@ -9,12 +9,8 @@
<p align="center"> <p align="center">
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub NewPipe releases"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a> <a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub NewPipe releases"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
<a href="https://github.com/TeamNewPipe/NewPipe-nightly/releases" alt="GitHub NewPipe nightly releases"> <a href="https://github.com/TeamNewPipe/NewPipe-nightly/releases" alt="GitHub NewPipe nightly releases"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe-nightly.svg?labelColor=purple&label=dev%20nightly"></a>
<img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe-nightly.svg?labelColor=purple&label=dev%20nightly" > <a href="https://github.com/TeamNewPipe/NewPipe-refactor-nightly/releases" alt="GitHub NewPipe refactor nightly releases"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe-refactor-nightly.svg?labelColor=purple&label=refactor%20nightly"></a>
</a>
<a href="https://github.com/TeamNewPipe/NewPipe-refactor-nightly/releases" alt="GitHub NewPipe refactor nightly releases">
<img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe-refactor-nightly.svg?labelColor=purple&label=refactor%20nightly" >
</a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a> <a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/actions/workflows/ci.yml/badge.svg?branch=dev&event=push"></a> <a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/actions/workflows/ci.yml/badge.svg?branch=dev&event=push"></a>
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a> <a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>

View file

@ -17,20 +17,20 @@ plugins {
} }
android { android {
compileSdk 35 compileSdk 36
namespace 'org.schabi.newpipe' namespace 'org.schabi.newpipe'
defaultConfig { defaultConfig {
applicationId "org.schabi.newpipe" applicationId "org.schabi.newpipe"
resValue "string", "app_name", "NewPipe" resValue "string", "app_name", "NewPipe"
minSdk 21 minSdk 21
targetSdk 33 targetSdk 35
if (System.properties.containsKey('versionCodeOverride')) { if (System.properties.containsKey('versionCodeOverride')) {
versionCode System.getProperty('versionCodeOverride') as Integer versionCode System.getProperty('versionCodeOverride') as Integer
} else { } else {
versionCode 1004 versionCode 1005
} }
versionName "0.27.7" versionName "0.28.0"
if (System.properties.containsKey('versionNameSuffix')) { if (System.properties.containsKey('versionNameSuffix')) {
versionNameSuffix System.getProperty('versionNameSuffix') versionNameSuffix System.getProperty('versionNameSuffix')
} }

View file

@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- We need to be able to open links in the browser on API 30+ --> <!-- We need to be able to open links in the browser on API 30+ -->
@ -58,6 +59,15 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
<service <service
android:name=".player.PlayerService" android:name=".player.PlayerService"
android:exported="true" android:exported="true"
@ -95,7 +105,10 @@
android:name="androidx.work.impl.foreground.SystemForegroundService" android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync" android:foregroundServiceType="dataSync"
tools:node="merge" /> tools:node="merge" />
<service android:name=".local.feed.service.FeedLoadService" />
<service
android:name=".local.feed.service.FeedLoadService"
android:foregroundServiceType="dataSync" />
<activity <activity
android:name=".PanicResponderActivity" android:name=".PanicResponderActivity"
@ -127,7 +140,9 @@
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTask" /> android:launchMode="singleTask" />
<service android:name="us.shandian.giga.service.DownloadManagerService" /> <service
android:name="us.shandian.giga.service.DownloadManagerService"
android:foregroundServiceType="dataSync" />
<activity <activity
android:name=".util.FilePickerActivityHelper" android:name=".util.FilePickerActivityHelper"

View file

@ -97,7 +97,7 @@ open class App :
Localization.getPreferredLocalization(this), Localization.getPreferredLocalization(this),
Localization.getPreferredContentCountry(this), Localization.getPreferredContentCountry(this),
) )
Localization.initPrettyTime(Localization.resolvePrettyTime(this)) Localization.initPrettyTime(Localization.resolvePrettyTime())
BridgeStateSaverInitializer.init(this) BridgeStateSaverInitializer.init(this)
StateSaver.init(this) StateSaver.init(this)

View file

@ -29,7 +29,7 @@ import okhttp3.ResponseBody;
public final class DownloaderImpl extends Downloader { public final class DownloaderImpl extends Downloader {
public static final String USER_AGENT = public static final String USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0"; "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0";
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY = public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY =
"youtube_restricted_mode_key"; "youtube_restricted_mode_key";
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000"; public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";

View file

@ -20,8 +20,6 @@
package org.schabi.newpipe; package org.schabi.newpipe;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -49,6 +47,7 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat; import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@ -77,6 +76,7 @@ import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.settings.UpdateSettingsFragment; import org.schabi.newpipe.settings.UpdateSettingsFragment;
import org.schabi.newpipe.settings.migration.MigrationManager;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.KioskTranslator;
@ -137,6 +137,7 @@ public class MainActivity extends AppCompatActivity {
+ "savedInstanceState = [" + savedInstanceState + "]"); + "savedInstanceState = [" + savedInstanceState + "]");
} }
Localization.migrateAppLanguageSettingIfNecessary(getApplicationContext());
ThemeHelper.setDayNightMode(this); ThemeHelper.setDayNightMode(this);
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
@ -153,7 +154,6 @@ public class MainActivity extends AppCompatActivity {
} }
} }
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
sharedPrefEditor = sharedPreferences.edit(); sharedPrefEditor = sharedPreferences.edit();
@ -192,7 +192,7 @@ public class MainActivity extends AppCompatActivity {
UpdateSettingsFragment.askForConsentToUpdateChecks(this); UpdateSettingsFragment.askForConsentToUpdateChecks(this);
} }
Localization.migrateAppLanguageSettingIfNecessary(getApplicationContext()); MigrationManager.showUserInfoIfPresent(this);
} }
@Override @Override
@ -260,19 +260,6 @@ public class MainActivity extends AppCompatActivity {
*/ */
private void addDrawerMenuForCurrentService() throws ExtractionException { private void addDrawerMenuForCurrentService() throws ExtractionException {
//Tabs //Tabs
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
final StreamingService service = NewPipe.getService(currentServiceId);
int kioskMenuItemId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, kioskMenuItemId, 0, KioskTranslator
.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcon(ks));
kioskMenuItemId++;
}
drawerLayoutBinding.navigation.getMenu() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, .add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
R.string.tab_subscriptions) R.string.tab_subscriptions)
@ -290,6 +277,20 @@ public class MainActivity extends AppCompatActivity {
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history) .add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
.setIcon(R.drawable.ic_history); .setIcon(R.drawable.ic_history);
//Kiosks
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
final StreamingService service = NewPipe.getService(currentServiceId);
int kioskMenuItemId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_kiosks_group, kioskMenuItemId, 0, KioskTranslator
.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcon(ks));
kioskMenuItemId++;
}
//Settings and About //Settings and About
drawerLayoutBinding.navigation.getMenu() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings) .add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
@ -309,10 +310,13 @@ public class MainActivity extends AppCompatActivity {
changeService(item); changeService(item);
break; break;
case R.id.menu_tabs_group: case R.id.menu_tabs_group:
tabSelected(item);
break;
case R.id.menu_kiosks_group:
try { try {
tabSelected(item); kioskSelected(item);
} catch (final Exception e) { } catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(this, "Selecting main page tab", e); ErrorUtil.showUiErrorSnackbar(this, "Selecting drawer kiosk", e);
} }
break; break;
case R.id.menu_options_about_group: case R.id.menu_options_about_group:
@ -336,7 +340,7 @@ public class MainActivity extends AppCompatActivity {
.setChecked(true); .setChecked(true);
} }
private void tabSelected(final MenuItem item) throws ExtractionException { private void tabSelected(final MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case ITEM_ID_SUBSCRIPTIONS: case ITEM_ID_SUBSCRIPTIONS:
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager()); NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
@ -353,18 +357,19 @@ public class MainActivity extends AppCompatActivity {
case ITEM_ID_HISTORY: case ITEM_ID_HISTORY:
NavigationHelper.openStatisticFragment(getSupportFragmentManager()); NavigationHelper.openStatisticFragment(getSupportFragmentManager());
break; break;
default: }
final StreamingService currentService = ServiceHelper.getSelectedService(this); }
int kioskMenuItemId = 0;
for (final String kioskId : currentService.getKioskList().getAvailableKiosks()) { private void kioskSelected(final MenuItem item) throws ExtractionException {
if (kioskMenuItemId == item.getItemId()) { final StreamingService currentService = ServiceHelper.getSelectedService(this);
NavigationHelper.openKioskFragment(getSupportFragmentManager(), int kioskMenuItemId = 0;
currentService.getServiceId(), kioskId); for (final String kioskId : currentService.getKioskList().getAvailableKiosks()) {
break; if (kioskMenuItemId == item.getItemId()) {
} NavigationHelper.openKioskFragment(getSupportFragmentManager(),
kioskMenuItemId++; currentService.getServiceId(), kioskId);
}
break; break;
}
kioskMenuItemId++;
} }
} }
@ -405,6 +410,7 @@ public class MainActivity extends AppCompatActivity {
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_services_group); drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_services_group);
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_tabs_group); drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_tabs_group);
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_kiosks_group);
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_options_about_group); drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_options_about_group);
// Show up or down arrow // Show up or down arrow
@ -498,9 +504,8 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
protected void onResume() { protected void onResume() {
assureCorrectAppLanguage(this);
// Change the date format to match the selected language on resume // Change the date format to match the selected language on resume
Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext())); Localization.initPrettyTime(Localization.resolvePrettyTime());
super.onResume(); super.onResume();
// Close drawer on return, and don't show animation, // Close drawer on return, and don't show animation,
@ -849,7 +854,7 @@ public class MainActivity extends AppCompatActivity {
return; return;
} }
if (PlayerHolder.getInstance().isPlayerOpen()) { if (PlayerHolder.INSTANCE.isPlayerOpen()) {
// if the player is already open, no need for a broadcast receiver // if the player is already open, no need for a broadcast receiver
openMiniPlayerIfMissing(); openMiniPlayerIfMissing();
} else { } else {
@ -859,7 +864,7 @@ public class MainActivity extends AppCompatActivity {
public void onReceive(final Context context, final Intent intent) { public void onReceive(final Context context, final Intent intent) {
if (Objects.equals(intent.getAction(), if (Objects.equals(intent.getAction(),
VideoDetailFragment.ACTION_PLAYER_STARTED) VideoDetailFragment.ACTION_PLAYER_STARTED)
&& PlayerHolder.getInstance().isPlayerOpen()) { && PlayerHolder.INSTANCE.isPlayerOpen()) {
openMiniPlayerIfMissing(); openMiniPlayerIfMissing();
// At this point the player is added 100%, we can unregister. Other actions // At this point the player is added 100%, we can unregister. Other actions
// are useless since the fragment will not be removed after that. // are useless since the fragment will not be removed after that.
@ -870,11 +875,12 @@ public class MainActivity extends AppCompatActivity {
}; };
final IntentFilter intentFilter = new IntentFilter(); final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED); intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED);
registerReceiver(broadcastReceiver, intentFilter); ContextCompat.registerReceiver(this, broadcastReceiver, intentFilter,
ContextCompat.RECEIVER_EXPORTED);
// If the PlayerHolder is not bound yet, but the service is running, try to bind to it. // If the PlayerHolder is not bound yet, but the service is running, try to bind to it.
// Once the connection is established, the ACTION_PLAYER_STARTED will be sent. // Once the connection is established, the ACTION_PLAYER_STARTED will be sent.
PlayerHolder.getInstance().tryBindIfNeeded(this); PlayerHolder.INSTANCE.tryBindIfNeeded(this);
} }
} }

View file

@ -84,7 +84,6 @@ import org.schabi.newpipe.util.ChannelTabHelper;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
@ -132,7 +131,6 @@ public class RouterActivity extends AppCompatActivity {
ThemeHelper.setDayNightMode(this); ThemeHelper.setDayNightMode(this);
setTheme(ThemeHelper.isLightThemeSelected(this) setTheme(ThemeHelper.isLightThemeSelected(this)
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark); ? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
Localization.assureCorrectAppLanguage(this);
// Pass-through touch events to background activities // Pass-through touch events to background activities
// so that our transparent window won't lock UI in the mean time // so that our transparent window won't lock UI in the mean time
@ -701,7 +699,7 @@ public class RouterActivity extends AppCompatActivity {
} }
// ...the player is not running or in normal Video-mode/type // ...the player is not running or in normal Video-mode/type
final PlayerType playerType = PlayerHolder.getInstance().getType(); final PlayerType playerType = PlayerHolder.INSTANCE.getType();
return playerType == null || playerType == PlayerType.MAIN; return playerType == null || playerType == PlayerType.MAIN;
} }

View file

@ -9,11 +9,9 @@ import org.schabi.newpipe.R
import org.schabi.newpipe.ui.components.common.ScaffoldWithToolbar import org.schabi.newpipe.ui.components.common.ScaffoldWithToolbar
import org.schabi.newpipe.ui.screens.AboutScreen import org.schabi.newpipe.ui.screens.AboutScreen
import org.schabi.newpipe.ui.theme.AppTheme import org.schabi.newpipe.ui.theme.AppTheme
import org.schabi.newpipe.util.Localization
class AboutActivity : AppCompatActivity() { class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Localization.assureCorrectAppLanguage(this)
enableEdgeToEdge() enableEdgeToEdge()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View file

@ -20,8 +20,6 @@ import org.schabi.newpipe.views.FocusOverlayView;
import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.fragment.MissionsFragment; import us.shandian.giga.ui.fragment.MissionsFragment;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class DownloadActivity extends AppCompatActivity { public class DownloadActivity extends AppCompatActivity {
private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag"; private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag";
@ -33,7 +31,6 @@ public class DownloadActivity extends AppCompatActivity {
i.setClass(this, DownloadManagerService.class); i.setClass(this, DownloadManagerService.class);
startService(i); startService(i);
assureCorrectAppLanguage(this);
ThemeHelper.setTheme(this); ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);

View file

@ -2,7 +2,6 @@ package org.schabi.newpipe.download;
import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP; import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP;
import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery; import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Activity; import android.app.Activity;
import android.content.ComponentName; import android.content.ComponentName;
@ -751,7 +750,6 @@ public class DownloadDialog extends DialogFragment
} }
private void showFailedDialog(@StringRes final int msg) { private void showFailedDialog(@StringRes final int msg) {
assureCorrectAppLanguage(requireContext());
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setTitle(R.string.general_error) .setTitle(R.string.general_error)
.setMessage(msg) .setMessage(msg)

View file

@ -1,7 +1,5 @@
package org.schabi.newpipe.error; package org.schabi.newpipe.error;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
@ -79,7 +77,6 @@ public class ErrorActivity extends AppCompatActivity {
@Override @Override
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ThemeHelper.setDayNightMode(this); ThemeHelper.setDayNightMode(this);
@ -306,7 +303,7 @@ public class ErrorActivity extends AppCompatActivity {
} }
private String getAppLanguage() { private String getAppLanguage() {
return Localization.getAppLocale(getApplicationContext()).toString(); return Localization.getAppLocale().toString();
} }
private String getOsString() { private String getOsString() {

View file

@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit
class ErrorPanelHelper( class ErrorPanelHelper(
private val fragment: Fragment, private val fragment: Fragment,
rootView: View, rootView: View,
onRetry: Runnable onRetry: Runnable?,
) { ) {
private val context: Context = rootView.context!! private val context: Context = rootView.context!!
@ -56,12 +56,15 @@ class ErrorPanelHelper(
errorPanelRoot.findViewById(R.id.error_open_in_browser) errorPanelRoot.findViewById(R.id.error_open_in_browser)
private var errorDisposable: Disposable? = null private var errorDisposable: Disposable? = null
private var retryShouldBeShown: Boolean = (onRetry != null)
init { init {
errorDisposable = errorRetryButton.clicks() if (onRetry != null) {
.debounce(300, TimeUnit.MILLISECONDS) errorDisposable = errorRetryButton.clicks()
.observeOn(AndroidSchedulers.mainThread()) .debounce(300, TimeUnit.MILLISECONDS)
.subscribe { onRetry.run() } .observeOn(AndroidSchedulers.mainThread())
.subscribe { onRetry.run() }
}
} }
private fun ensureDefaultVisibility() { private fun ensureDefaultVisibility() {
@ -101,7 +104,7 @@ class ErrorPanelHelper(
errorActionButton.setOnClickListener(null) errorActionButton.setOnClickListener(null)
} }
errorRetryButton.isVisible = true errorRetryButton.isVisible = retryShouldBeShown
showAndSetOpenInBrowserButtonAction(errorInfo) showAndSetOpenInBrowserButtonAction(errorInfo)
} else if (errorInfo.throwable is AccountTerminatedException) { } else if (errorInfo.throwable is AccountTerminatedException) {
errorTextView.setText(R.string.account_terminated) errorTextView.setText(R.string.account_terminated)
@ -130,7 +133,7 @@ class ErrorPanelHelper(
errorInfo.throwable !is ContentNotSupportedException errorInfo.throwable !is ContentNotSupportedException
) { ) {
// show retry button only for content which is not unavailable or unsupported // show retry button only for content which is not unavailable or unsupported
errorRetryButton.isVisible = true errorRetryButton.isVisible = retryShouldBeShown
} }
showAndSetOpenInBrowserButtonAction(errorInfo) showAndSetOpenInBrowserButtonAction(errorInfo)
} }

View file

@ -10,6 +10,7 @@ import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -136,9 +137,11 @@ class ErrorUtil {
NotificationManagerCompat.from(context) NotificationManagerCompat.from(context)
.notify(ERROR_REPORT_NOTIFICATION_ID, notificationBuilder.build()) .notify(ERROR_REPORT_NOTIFICATION_ID, notificationBuilder.build())
// since the notification is silent, also show a toast, otherwise the user is confused ContextCompat.getMainExecutor(context).execute {
Toast.makeText(context, R.string.error_report_notification_toast, Toast.LENGTH_SHORT) // since the notification is silent, also show a toast, otherwise the user is confused
.show() Toast.makeText(context, R.string.error_report_notification_toast, Toast.LENGTH_SHORT)
.show()
}
} }
private fun getErrorActivityIntent(context: Context, errorInfo: ErrorInfo): Intent { private fun getErrorActivityIntent(context: Context, errorInfo: ErrorInfo): Intent {

View file

@ -32,7 +32,8 @@ public enum UserAction {
PREFERENCES_MIGRATION("migration of preferences"), PREFERENCES_MIGRATION("migration of preferences"),
SHARE_TO_NEWPIPE("share to newpipe"), SHARE_TO_NEWPIPE("share to newpipe"),
CHECK_FOR_NEW_APP_VERSION("check for new app version"), CHECK_FOR_NEW_APP_VERSION("check for new app version"),
OPEN_INFO_ITEM_DIALOG("open info item dialog"); OPEN_INFO_ITEM_DIALOG("open info item dialog"),
GETTING_MAIN_SCREEN_TAB("getting main screen tab");
private final String message; private final String message;

View file

@ -7,16 +7,57 @@ import android.view.ViewGroup;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.evernote.android.state.State;
import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorPanelHelper;
public class BlankFragment extends BaseFragment { public class BlankFragment extends BaseFragment {
@State
@Nullable
ErrorInfo errorInfo;
@Nullable
ErrorPanelHelper errorPanel = null;
/**
* Builds a blank fragment that just says the app name and suggests clicking on search.
*/
public BlankFragment() {
this(null);
}
/**
* @param errorInfo if null acts like {@link BlankFragment}, else shows an error panel.
*/
public BlankFragment(@Nullable final ErrorInfo errorInfo) {
this.errorInfo = errorInfo;
}
@Nullable @Nullable
@Override @Override
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
final Bundle savedInstanceState) { final Bundle savedInstanceState) {
setTitle("NewPipe"); setTitle("NewPipe");
return inflater.inflate(R.layout.fragment_blank, container, false); final View view = inflater.inflate(R.layout.fragment_blank, container, false);
if (errorInfo != null) {
errorPanel = new ErrorPanelHelper(this, view, null);
errorPanel.showError(errorInfo);
view.findViewById(R.id.blank_page_content).setVisibility(View.GONE);
}
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (errorPanel != null) {
errorPanel.dispose();
errorPanel = null;
}
} }
@Override @Override

View file

@ -36,8 +36,9 @@ import com.google.android.material.tabs.TabLayout;
import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentMainBinding; import org.schabi.newpipe.databinding.FragmentMainBinding;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
import org.schabi.newpipe.settings.tabs.Tab; import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager; import org.schabi.newpipe.settings.tabs.TabsManager;
@ -303,9 +304,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
final Fragment fragment; final Fragment fragment;
try { try {
fragment = tab.getFragment(context); fragment = tab.getFragment(context);
} catch (final ExtractionException e) { } catch (final Throwable t) {
ErrorUtil.showUiErrorSnackbar(context, "Getting fragment item", e); return new BlankFragment(new ErrorInfo(t, UserAction.GETTING_MAIN_SCREEN_TAB,
return new BlankFragment(); "Tab " + tab.getClass().getSimpleName() + ":" + tab.getTabName(context)));
} }
if (fragment instanceof BaseFragment) { if (fragment instanceof BaseFragment) {

View file

@ -93,7 +93,7 @@ public class DescriptionFragment extends BaseDescriptionFragment {
if (streamInfo.getLanguageInfo() != null) { if (streamInfo.getLanguageInfo() != null) {
addMetadataItem(inflater, layout, false, R.string.metadata_language, addMetadataItem(inflater, layout, false, R.string.metadata_language,
streamInfo.getLanguageInfo().getDisplayLanguage(getAppLocale(getContext()))); streamInfo.getLanguageInfo().getDisplayLanguage(getAppLocale()));
} }
addMetadataItem(inflater, layout, true, R.string.metadata_support, addMetadataItem(inflater, layout, true, R.string.metadata_support,

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@ import android.util.Log;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.evernote.android.state.State; import com.evernote.android.state.State;
@ -42,6 +43,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
private final UserAction errorUserAction; private final UserAction errorUserAction;
protected L currentInfo; protected L currentInfo;
@Nullable
protected Page currentNextPage; protected Page currentNextPage;
protected Disposable currentWorker; protected Disposable currentWorker;

View file

@ -81,9 +81,7 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
if (channelInfo.getSubscriberCount() != UNKNOWN_SUBSCRIBER_COUNT) { if (channelInfo.getSubscriberCount() != UNKNOWN_SUBSCRIBER_COUNT) {
addMetadataItem(inflater, layout, false, R.string.metadata_subscribers, addMetadataItem(inflater, layout, false, R.string.metadata_subscribers,
Localization.localizeNumber( Localization.localizeNumber(channelInfo.getSubscriberCount()));
requireContext(),
channelInfo.getSubscriberCount()));
} }
addImagesMetadataItem(inflater, layout, R.string.metadata_avatars, addImagesMetadataItem(inflater, layout, R.string.metadata_avatars,

View file

@ -146,6 +146,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
private final SparseArrayCompat<String> menuItemToFilterName = new SparseArrayCompat<>(); private final SparseArrayCompat<String> menuItemToFilterName = new SparseArrayCompat<>();
private StreamingService service; private StreamingService service;
@Nullable
private Page nextPage; private Page nextPage;
private boolean showLocalSuggestions = true; private boolean showLocalSuggestions = true;
private boolean showRemoteSuggestions = true; private boolean showRemoteSuggestions = true;
@ -221,6 +222,15 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
searchBinding = FragmentSearchBinding.bind(rootView); searchBinding = FragmentSearchBinding.bind(rootView);
super.onViewCreated(rootView, savedInstanceState); super.onViewCreated(rootView, savedInstanceState);
updateService();
// Add the service name to search string hint
// to make it more obvious which platform is being searched.
if (service != null) {
searchEditText.setHint(
getString(R.string.search_with_service_name,
service.getServiceInfo().getName()));
}
showSearchOnStart(); showSearchOnStart();
initSearchListeners(); initSearchListeners();
} }
@ -942,6 +952,20 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
filterItemCheckedId = item.getItemId(); filterItemCheckedId = item.getItemId();
item.setChecked(true); item.setChecked(true);
if (service != null) {
final boolean isNotFiltered = theContentFilter.isEmpty()
|| "all".equals(theContentFilter.get(0));
if (isNotFiltered) {
searchEditText.setHint(
getString(R.string.search_with_service_name,
service.getServiceInfo().getName()));
} else {
searchEditText.setHint(getString(R.string.search_with_service_name_and_filter,
service.getServiceInfo().getName(),
item.getTitle()));
}
}
contentFilter = theContentFilter.toArray(new String[0]); contentFilter = theContentFilter.toArray(new String[0]);
if (!TextUtils.isEmpty(searchString)) { if (!TextUtils.isEmpty(searchString)) {
@ -1071,15 +1095,25 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
public void handleNextItems(final ListExtractor.InfoItemsPage<?> result) { public void handleNextItems(final ListExtractor.InfoItemsPage<?> result) {
showListFooter(false); showListFooter(false);
infoListAdapter.addInfoItemList(result.getItems()); infoListAdapter.addInfoItemList(result.getItems());
nextPage = result.getNextPage();
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED, // nextPage should be non-null at this point, because it refers to the page
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", " // whose results are handled here, but let's check it anyway
+ "pageIds: " + nextPage.getIds() + ", " if (nextPage == null) {
+ "pageCookies: " + nextPage.getCookies(), showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
serviceId)); "\"" + searchString + "\" → nextPage == null", serviceId));
} else {
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
+ "pageIds: " + nextPage.getIds() + ", "
+ "pageCookies: " + nextPage.getCookies(),
serviceId));
}
} }
// keep the reassignment of nextPage after the error handling to ensure that nextPage
// still holds the correct value during the error handling
nextPage = result.getNextPage();
super.handleNextItems(result); super.handleNextItems(result);
} }

View file

@ -252,7 +252,7 @@ public final class InfoItemDialog {
* @return the current {@link Builder} instance * @return the current {@link Builder} instance
*/ */
public Builder addEnqueueEntriesIfNeeded() { public Builder addEnqueueEntriesIfNeeded() {
final PlayerHolder holder = PlayerHolder.getInstance(); final PlayerHolder holder = PlayerHolder.INSTANCE;
if (holder.isPlayQueueReady()) { if (holder.isPlayQueueReady()) {
addEntry(StreamDialogDefaultEntry.ENQUEUE); addEntry(StreamDialogDefaultEntry.ENQUEUE);

View file

@ -31,6 +31,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat import androidx.core.app.PendingIntentCompat
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
@ -200,7 +201,7 @@ class FeedLoadService : Service() {
} }
} }
} }
registerReceiver(broadcastReceiver, IntentFilter(ACTION_CANCEL)) ContextCompat.registerReceiver(this, broadcastReceiver, IntentFilter(ACTION_CANCEL), ContextCompat.RECEIVER_NOT_EXPORTED)
} }
// ///////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////

View file

@ -1,7 +1,5 @@
package org.schabi.newpipe.local.subscription; package org.schabi.newpipe.local.subscription;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Dialog; import android.app.Dialog;
import android.os.Bundle; import android.os.Bundle;
@ -39,7 +37,6 @@ public class ImportConfirmationDialog extends DialogFragment {
@Override @Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
final var context = requireContext(); final var context = requireContext();
assureCorrectAppLanguage(context);
return new AlertDialog.Builder(context) return new AlertDialog.Builder(context)
.setMessage(R.string.import_network_expensive_warning) .setMessage(R.string.import_network_expensive_warning)
.setCancelable(true) .setCancelable(true)

View file

@ -2,7 +2,6 @@ package org.schabi.newpipe.player;
import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
@ -84,7 +83,6 @@ public final class PlayQueueActivity extends AppCompatActivity
@Override @Override
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
@ -97,8 +95,48 @@ public final class PlayQueueActivity extends AppCompatActivity
getSupportActionBar().setTitle(R.string.title_activity_play_queue); getSupportActionBar().setTitle(R.string.title_activity_play_queue);
} }
serviceConnection = getServiceConnection(); serviceConnection = new ServiceConnection() {
bind(); @Override
public void onServiceDisconnected(final ComponentName name) {
Log.d(TAG, "Player service is disconnected");
}
@Override
public void onServiceConnected(final ComponentName name, final IBinder binder) {
Log.d(TAG, "Player service is connected");
if (binder instanceof PlayerService.LocalBinder) {
@Nullable final PlayerService s =
((PlayerService.LocalBinder) binder).getService();
if (s == null) {
throw new IllegalArgumentException(
"PlayerService.LocalBinder.getService() must never be"
+ "null after the service connects");
}
player = s.getPlayer();
}
if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) {
unbind();
} else {
onQueueUpdate(player.getPlayQueue());
buildComponents();
if (player != null) {
player.setActivityListener(PlayQueueActivity.this);
}
}
}
};
// Note: this code should not really exist, and PlayerHolder should be used instead, but
// it will be rewritten when NewPlayer will replace the current player.
final Intent bindIntent = new Intent(this, PlayerService.class);
bindIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION);
final boolean success = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);
if (!success) {
unbindService(serviceConnection);
}
serviceBound = success;
} }
@Override @Override
@ -180,19 +218,6 @@ public final class PlayQueueActivity extends AppCompatActivity
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Service Connection // Service Connection
////////////////////////////////////////////////////////////////////////////
private void bind() {
// Note: this code should not really exist, and PlayerHolder should be used instead, but
// it will be rewritten when NewPlayer will replace the current player.
final Intent bindIntent = new Intent(this, PlayerService.class);
bindIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION);
final boolean success = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);
if (!success) {
unbindService(serviceConnection);
}
serviceBound = success;
}
private void unbind() { private void unbind() {
if (serviceBound) { if (serviceBound) {
@ -212,41 +237,6 @@ public final class PlayQueueActivity extends AppCompatActivity
} }
} }
private ServiceConnection getServiceConnection() {
return new ServiceConnection() {
@Override
public void onServiceDisconnected(final ComponentName name) {
Log.d(TAG, "Player service is disconnected");
}
@Override
public void onServiceConnected(final ComponentName name, final IBinder binder) {
Log.d(TAG, "Player service is connected");
if (binder instanceof PlayerService.LocalBinder) {
@Nullable final PlayerService s =
((PlayerService.LocalBinder) binder).getService();
if (s == null) {
throw new IllegalArgumentException(
"PlayerService.LocalBinder.getService() must never be"
+ "null after the service connects");
}
player = s.getPlayer();
}
if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) {
unbind();
} else {
onQueueUpdate(player.getPlayQueue());
buildComponents();
if (player != null) {
player.setActivityListener(PlayQueueActivity.this);
}
}
}
};
}
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Component Building // Component Building
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////

View file

@ -44,7 +44,6 @@ import static org.schabi.newpipe.player.notification.NotificationConstants.ACTIO
import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE; import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE;
import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static coil3.Image_androidKt.toBitmap; import static coil3.Image_androidKt.toBitmap;
@ -61,6 +60,7 @@ import android.view.LayoutInflater;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.math.MathUtils; import androidx.core.math.MathUtils;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@ -133,6 +133,10 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.disposables.SerialDisposable; import io.reactivex.rxjava3.disposables.SerialDisposable;
/**
* The ExoPlayer wrapper & Player business logic.
* Only instantiated once, from {@link PlayerService}.
*/
public final class Player implements PlaybackListener, Listener { public final class Player implements PlaybackListener, Listener {
public static final boolean DEBUG = MainActivity.DEBUG; public static final boolean DEBUG = MainActivity.DEBUG;
public static final String TAG = Player.class.getSimpleName(); public static final String TAG = Player.class.getSimpleName();
@ -397,7 +401,7 @@ public final class Player implements PlaybackListener, Listener {
&& newQueue.size() == 1 && newQueue.getItem() != null && newQueue.size() == 1 && newQueue.getItem() != null
&& playQueue != null && playQueue.size() == 1 && playQueue.getItem() != null && playQueue != null && playQueue.size() == 1 && playQueue.getItem() != null
&& newQueue.getItem().getUrl().equals(playQueue.getItem().getUrl()) && newQueue.getItem().getUrl().equals(playQueue.getItem().getUrl())
&& newQueue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) { && newQueue.getItem().getRecoveryPosition() != Long.MIN_VALUE) {
// Player can have state = IDLE when playback is stopped or failed // Player can have state = IDLE when playback is stopped or failed
// and we should retry in this case // and we should retry in this case
if (simpleExoPlayer.getPlaybackState() if (simpleExoPlayer.getPlaybackState()
@ -425,7 +429,7 @@ public final class Player implements PlaybackListener, Listener {
&& !samePlayQueue && !samePlayQueue
&& !newQueue.isEmpty() && !newQueue.isEmpty()
&& newQueue.getItem() != null && newQueue.getItem() != null
&& newQueue.getItem().getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) { && newQueue.getItem().getRecoveryPosition() == Long.MIN_VALUE) {
databaseUpdateDisposable.add(recordManager.loadStreamState(newQueue.getItem()) databaseUpdateDisposable.add(recordManager.loadStreamState(newQueue.getItem())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
// Do not place initPlayback() in doFinally() because // Do not place initPlayback() in doFinally() because
@ -473,22 +477,23 @@ public final class Player implements PlaybackListener, Listener {
} }
private void initUIsForCurrentPlayerType() { private void initUIsForCurrentPlayerType() {
if ((UIs.getOpt(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) if ((UIs.get(MainPlayerUi.class) != null && playerType == PlayerType.MAIN)
|| (UIs.getOpt(PopupPlayerUi.class).isPresent() || (UIs.get(PopupPlayerUi.class) != null
&& playerType == PlayerType.POPUP)) { && playerType == PlayerType.POPUP)) {
// correct UI already in place // correct UI already in place
return; return;
} }
// try to reuse binding if possible // try to reuse binding if possible
final PlayerBinding binding = UIs.getOpt(VideoPlayerUi.class).map(VideoPlayerUi::getBinding) @Nullable final VideoPlayerUi ui = UIs.get(VideoPlayerUi.class);
.orElseGet(() -> { final PlayerBinding binding;
if (playerType == PlayerType.AUDIO) { if (ui != null) {
return null; binding = ui.getBinding();
} else { } else if (playerType == PlayerType.AUDIO) {
return PlayerBinding.inflate(LayoutInflater.from(context)); binding = null;
} } else {
}); binding = PlayerBinding.inflate(LayoutInflater.from(context));
}
switch (playerType) { switch (playerType) {
case MAIN: case MAIN:
@ -751,7 +756,6 @@ public final class Player implements PlaybackListener, Listener {
toggleShuffleModeEnabled(); toggleShuffleModeEnabled();
break; break;
case Intent.ACTION_CONFIGURATION_CHANGED: case Intent.ACTION_CONFIGURATION_CHANGED:
assureCorrectAppLanguage(service);
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received"); Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received");
} }
@ -764,7 +768,8 @@ public final class Player implements PlaybackListener, Listener {
private void registerBroadcastReceiver() { private void registerBroadcastReceiver() {
// Try to unregister current first // Try to unregister current first
unregisterBroadcastReceiver(); unregisterBroadcastReceiver();
context.registerReceiver(broadcastReceiver, intentFilter); ContextCompat.registerReceiver(context, broadcastReceiver, intentFilter,
ContextCompat.RECEIVER_EXPORTED);
} }
private void unregisterBroadcastReceiver() { private void unregisterBroadcastReceiver() {
@ -1588,7 +1593,7 @@ public final class Player implements PlaybackListener, Listener {
} }
// sync the player index with the queue index, and seek to the correct position // sync the player index with the queue index, and seek to the correct position
if (item.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) { if (item.getRecoveryPosition() != Long.MIN_VALUE) {
simpleExoPlayer.seekTo(playQueueIndex, item.getRecoveryPosition()); simpleExoPlayer.seekTo(playQueueIndex, item.getRecoveryPosition());
playQueue.unsetRecovery(playQueueIndex); playQueue.unsetRecovery(playQueueIndex);
} else { } else {

View file

@ -34,10 +34,8 @@ import org.schabi.newpipe.player.mediabrowser.MediaBrowserImpl
import org.schabi.newpipe.player.mediabrowser.MediaBrowserPlaybackPreparer import org.schabi.newpipe.player.mediabrowser.MediaBrowserPlaybackPreparer
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi
import org.schabi.newpipe.player.notification.NotificationPlayerUi import org.schabi.newpipe.player.notification.NotificationPlayerUi
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.ThemeHelper
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.function.BiConsumer
import java.util.function.Consumer import java.util.function.Consumer
/** /**
@ -47,13 +45,13 @@ class PlayerService : MediaBrowserServiceCompat() {
// These objects are used to cleanly separate the Service implementation (in this file) and the // These objects are used to cleanly separate the Service implementation (in this file) and the
// media browser and playback preparer implementations. At the moment the playback preparer is // media browser and playback preparer implementations. At the moment the playback preparer is
// only used in conjunction with the media browser. // only used in conjunction with the media browser.
private var mediaBrowserImpl: MediaBrowserImpl? = null private lateinit var mediaBrowserImpl: MediaBrowserImpl
private var mediaBrowserPlaybackPreparer: MediaBrowserPlaybackPreparer? = null private lateinit var mediaBrowserPlaybackPreparer: MediaBrowserPlaybackPreparer
// these are instantiated in onCreate() as per // these are instantiated in onCreate() as per
// https://developer.android.com/training/cars/media#browser_workflow // https://developer.android.com/training/cars/media#browser_workflow
private var mediaSession: MediaSessionCompat? = null private lateinit var mediaSession: MediaSessionCompat
private var sessionConnector: MediaSessionConnector? = null private lateinit var sessionConnector: MediaSessionConnector
/** /**
* @return the current active player instance. May be null, since the player service can outlive * @return the current active player instance. May be null, since the player service can outlive
@ -68,7 +66,7 @@ class PlayerService : MediaBrowserServiceCompat() {
* The parameter taken by this [Consumer] can be null to indicate the player is being * The parameter taken by this [Consumer] can be null to indicate the player is being
* stopped. * stopped.
*/ */
private var onPlayerStartedOrStopped: Consumer<Player?>? = null private var onPlayerStartedOrStopped: ((player: Player?) -> Unit)? = null
//region Service lifecycle //region Service lifecycle
override fun onCreate() { override fun onCreate() {
@ -77,17 +75,9 @@ class PlayerService : MediaBrowserServiceCompat() {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onCreate() called") Log.d(TAG, "onCreate() called")
} }
Localization.assureCorrectAppLanguage(this)
ThemeHelper.setTheme(this) ThemeHelper.setTheme(this)
mediaBrowserImpl = MediaBrowserImpl( mediaBrowserImpl = MediaBrowserImpl(this, this::notifyChildrenChanged)
this,
Consumer { parentId: String ->
this.notifyChildrenChanged(
parentId
)
}
)
// see https://developer.android.com/training/cars/media#browser_workflow // see https://developer.android.com/training/cars/media#browser_workflow
val session = MediaSessionCompat(this, "MediaSessionPlayerServ") val session = MediaSessionCompat(this, "MediaSessionPlayerServ")
@ -98,17 +88,10 @@ class PlayerService : MediaBrowserServiceCompat() {
connector.setMetadataDeduplicationEnabled(true) connector.setMetadataDeduplicationEnabled(true)
mediaBrowserPlaybackPreparer = MediaBrowserPlaybackPreparer( mediaBrowserPlaybackPreparer = MediaBrowserPlaybackPreparer(
this, context = this,
BiConsumer { message: String, code: Int -> setMediaSessionError = connector::setCustomErrorMessage,
connector.setCustomErrorMessage( clearMediaSessionError = { connector.setCustomErrorMessage(null) },
message, onPrepare = { player?.onPrepare() }
code
)
},
Runnable { connector.setCustomErrorMessage(null) },
Consumer { playWhenReady: Boolean? ->
player?.onPrepare()
}
) )
connector.setPlaybackPreparer(mediaBrowserPlaybackPreparer) connector.setPlaybackPreparer(mediaBrowserPlaybackPreparer)
@ -125,11 +108,8 @@ class PlayerService : MediaBrowserServiceCompat() {
if (DEBUG) { if (DEBUG) {
Log.d( Log.d(
TAG, TAG,
( "onStartCommand() called with: intent = [$intent], extras = [${
"onStartCommand() called with: intent = [" + intent + intent.extras.toDebugString()}], flags = [$flags], startId = [$startId]"
"], extras = [" + intent.extras.toDebugString() +
"], flags = [" + flags + "], startId = [" + startId + "]"
)
) )
} }
@ -140,7 +120,7 @@ class PlayerService : MediaBrowserServiceCompat() {
val playerWasNull = (player == null) val playerWasNull = (player == null)
if (playerWasNull) { if (playerWasNull) {
// make sure the player exists, in case the service was resumed // make sure the player exists, in case the service was resumed
player = Player(this, mediaSession!!, sessionConnector!!) player = Player(this, mediaSession, sessionConnector)
} }
// Be sure that the player notification is set and the service is started in foreground, // Be sure that the player notification is set and the service is started in foreground,
@ -150,35 +130,29 @@ class PlayerService : MediaBrowserServiceCompat() {
// no one already and starting the service in foreground should not create any issues. // no one already and starting the service in foreground should not create any issues.
// If the service is already started in foreground, requesting it to be started // If the service is already started in foreground, requesting it to be started
// shouldn't do anything. // shouldn't do anything.
player!!.UIs().get(NotificationPlayerUi::class.java) player?.UIs()?.get(NotificationPlayerUi::class)?.createNotificationAndStartForeground()
?.createNotificationAndStartForeground()
val startedOrStopped = onPlayerStartedOrStopped if (playerWasNull) {
if (playerWasNull && startedOrStopped != null) {
// notify that a new player was created (but do it after creating the foreground // notify that a new player was created (but do it after creating the foreground
// notification just to make sure we don't incur, due to slowness, in // notification just to make sure we don't incur, due to slowness, in
// "Context.startForegroundService() did not then call Service.startForeground()") // "Context.startForegroundService() did not then call Service.startForeground()")
startedOrStopped.accept(player) onPlayerStartedOrStopped?.invoke(player)
} }
} }
val p = player val p = player
if (Intent.ACTION_MEDIA_BUTTON == intent.action && if (Intent.ACTION_MEDIA_BUTTON == intent.action && p?.playQueue == null) {
(p == null || p.playQueue == null) // No need to process media button's actions if the player is not working, otherwise
) { // the player service would strangely start with nothing to play
/* // Stop the service in this case, which will be removed from the foreground and its
No need to process media button's actions if the player is not working, otherwise // notification cancelled in its destruction
the player service would strangely start with nothing to play
Stop the service in this case, which will be removed from the foreground and its
notification cancelled in its destruction
*/
destroyPlayerAndStopService() destroyPlayerAndStopService()
return START_NOT_STICKY return START_NOT_STICKY
} }
if (p != null) { if (p != null) {
p.handleIntent(intent) p.handleIntent(intent)
p.UIs().get(MediaSessionPlayerUi::class.java) p.UIs().get(MediaSessionPlayerUi::class)
?.handleMediaButtonIntent(intent) ?.handleMediaButtonIntent(intent)
} }
@ -218,22 +192,22 @@ class PlayerService : MediaBrowserServiceCompat() {
cleanup() cleanup()
mediaBrowserPlaybackPreparer?.dispose() mediaBrowserPlaybackPreparer.dispose()
mediaSession?.release() mediaSession.release()
mediaBrowserImpl?.dispose() mediaBrowserImpl.dispose()
} }
private fun cleanup() { private fun cleanup() {
val p = player val p = player
if (p != null) { if (p != null) {
// notify that the player is being destroyed // notify that the player is being destroyed
onPlayerStartedOrStopped?.accept(null) onPlayerStartedOrStopped?.invoke(null)
p.saveAndShutdown() p.saveAndShutdown()
player = null player = null
} }
// Should already be handled by MediaSessionPlayerUi, but just to be sure. // Should already be handled by MediaSessionPlayerUi, but just to be sure.
mediaSession?.setActive(false) mediaSession.setActive(false)
// Should already be handled by NotificationUtil.cancelNotificationAndStopForeground() in // Should already be handled by NotificationUtil.cancelNotificationAndStopForeground() in
// NotificationPlayerUi, but let's make sure that the foreground service is stopped. // NotificationPlayerUi, but let's make sure that the foreground service is stopped.
@ -273,29 +247,27 @@ class PlayerService : MediaBrowserServiceCompat() {
if (DEBUG) { if (DEBUG) {
Log.d( Log.d(
TAG, TAG,
( "onBind() called with: intent = [$intent], extras = [${
"onBind() called with: intent = [" + intent + intent.extras.toDebugString()}]"
"], extras = [" + intent.extras.toDebugString() + "]"
)
) )
} }
if (BIND_PLAYER_HOLDER_ACTION == intent.action) { return if (BIND_PLAYER_HOLDER_ACTION == intent.action) {
// Note that this binder might be reused multiple times while the service is alive, even // Note that this binder might be reused multiple times while the service is alive, even
// after unbind() has been called: https://stackoverflow.com/a/8794930 . // after unbind() has been called: https://stackoverflow.com/a/8794930 .
return mBinder mBinder
} else if (SERVICE_INTERFACE == intent.action) { } else if (SERVICE_INTERFACE == intent.action) {
// MediaBrowserService also uses its own binder, so for actions related to the media // MediaBrowserService also uses its own binder, so for actions related to the media
// browser service, pass the onBind to the superclass. // browser service, pass the onBind to the superclass.
return super.onBind(intent) super.onBind(intent)
} else { } else {
// This is an unknown request, avoid returning any binder to not leak objects. // This is an unknown request, avoid returning any binder to not leak objects.
return null null
} }
} }
class LocalBinder internal constructor(playerService: PlayerService) : Binder() { class LocalBinder internal constructor(playerService: PlayerService) : Binder() {
private val playerService = WeakReference<PlayerService?>(playerService) private val playerService = WeakReference(playerService)
val service: PlayerService? val service: PlayerService?
get() = playerService.get() get() = playerService.get()
@ -307,9 +279,9 @@ class PlayerService : MediaBrowserServiceCompat() {
* by the [Consumer] can be null to indicate that the player is stopping. * by the [Consumer] can be null to indicate that the player is stopping.
* @param listener the listener to set or unset * @param listener the listener to set or unset
*/ */
fun setPlayerListener(listener: Consumer<Player?>?) { fun setPlayerListener(listener: ((player: Player?) -> Unit)?) {
this.onPlayerStartedOrStopped = listener this.onPlayerStartedOrStopped = listener
listener?.accept(player) listener?.invoke(player)
} }
//endregion //endregion
@ -320,14 +292,14 @@ class PlayerService : MediaBrowserServiceCompat() {
rootHints: Bundle? rootHints: Bundle?
): BrowserRoot? { ): BrowserRoot? {
// TODO check if the accessing package has permission to view data // TODO check if the accessing package has permission to view data
return mediaBrowserImpl?.onGetRoot(clientPackageName, clientUid, rootHints) return mediaBrowserImpl.onGetRoot(clientPackageName, clientUid, rootHints)
} }
override fun onLoadChildren( override fun onLoadChildren(
parentId: String, parentId: String,
result: Result<List<MediaBrowserCompat.MediaItem>> result: Result<List<MediaBrowserCompat.MediaItem>>
) { ) {
mediaBrowserImpl?.onLoadChildren(parentId, result) mediaBrowserImpl.onLoadChildren(parentId, result)
} }
override fun onSearch( override fun onSearch(
@ -335,7 +307,7 @@ class PlayerService : MediaBrowserServiceCompat() {
extras: Bundle?, extras: Bundle?,
result: Result<List<MediaBrowserCompat.MediaItem>> result: Result<List<MediaBrowserCompat.MediaItem>>
) { ) {
mediaBrowserImpl?.onSearch(query, result) mediaBrowserImpl.onSearch(query, result)
} //endregion } //endregion
companion object { companion object {

View file

@ -2,7 +2,6 @@ package org.schabi.newpipe.player.helper;
import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; import static org.schabi.newpipe.ktx.ViewUtils.animateRotation;
import static org.schabi.newpipe.player.Player.DEBUG; import static org.schabi.newpipe.player.Player.DEBUG;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import static org.schabi.newpipe.util.ThemeHelper.resolveDrawable; import static org.schabi.newpipe.util.ThemeHelper.resolveDrawable;
import android.app.Dialog; import android.app.Dialog;
@ -145,7 +144,6 @@ public class PlaybackParameterDialog extends DialogFragment {
@NonNull @NonNull
@Override @Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
assureCorrectAppLanguage(getContext());
Bridge.restoreInstanceState(this, savedInstanceState); Bridge.restoreInstanceState(this, savedInstanceState);
binding = DialogPlaybackParameterBinding.inflate(getLayoutInflater()); binding = DialogPlaybackParameterBinding.inflate(getLayoutInflater());

View file

@ -33,11 +33,9 @@ import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode;
import com.google.android.exoplayer2.ui.CaptionStyleCompat; import com.google.android.exoplayer2.ui.CaptionStyleCompat;
import com.google.android.exoplayer2.util.MimeTypes;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.SubtitlesStream;
@ -47,13 +45,14 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.Localization;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Formatter;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -62,11 +61,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public final class PlayerHelper { public final class PlayerHelper {
private static final StringBuilder STRING_BUILDER = new StringBuilder(); private static final FormattersProvider FORMATTERS_PROVIDER = new FormattersProvider();
private static final Formatter STRING_FORMATTER =
new Formatter(STRING_BUILDER, Locale.getDefault());
private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x");
private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%");
@Retention(SOURCE) @Retention(SOURCE)
@IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI, @IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI,
@ -89,9 +84,11 @@ public final class PlayerHelper {
private PlayerHelper() { private PlayerHelper() {
} }
//////////////////////////////////////////////////////////////////////////// // region Exposed helpers
// Exposed helpers
//////////////////////////////////////////////////////////////////////////// public static void resetFormat() {
FORMATTERS_PROVIDER.reset();
}
@NonNull @NonNull
public static String getTimeString(final int milliSeconds) { public static String getTimeString(final int milliSeconds) {
@ -100,35 +97,24 @@ public final class PlayerHelper {
final int hours = (milliSeconds % 86400000) / 3600000; final int hours = (milliSeconds % 86400000) / 3600000;
final int days = (milliSeconds % (86400000 * 7)) / 86400000; final int days = (milliSeconds % (86400000 * 7)) / 86400000;
STRING_BUILDER.setLength(0); final Formatters formatters = FORMATTERS_PROVIDER.formatters();
return (days > 0 if (days > 0) {
? STRING_FORMATTER.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds) return formatters.stringFormat("%d:%02d:%02d:%02d", days, hours, minutes, seconds);
: hours > 0 }
? STRING_FORMATTER.format("%d:%02d:%02d", hours, minutes, seconds)
: STRING_FORMATTER.format("%02d:%02d", minutes, seconds) return hours > 0
).toString(); ? formatters.stringFormat("%d:%02d:%02d", hours, minutes, seconds)
: formatters.stringFormat("%02d:%02d", minutes, seconds);
} }
@NonNull @NonNull
public static String formatSpeed(final double speed) { public static String formatSpeed(final double speed) {
return SPEED_FORMATTER.format(speed); return FORMATTERS_PROVIDER.formatters().speed().format(speed);
} }
@NonNull @NonNull
public static String formatPitch(final double pitch) { public static String formatPitch(final double pitch) {
return PITCH_FORMATTER.format(pitch); return FORMATTERS_PROVIDER.formatters().pitch().format(pitch);
}
@NonNull
public static String subtitleMimeTypesOf(@NonNull final MediaFormat format) {
switch (format) {
case VTT:
return MimeTypes.TEXT_VTT;
case TTML:
return MimeTypes.APPLICATION_TTML;
default:
throw new IllegalArgumentException("Unrecognized mime type: " + format.name());
}
} }
@NonNull @NonNull
@ -219,9 +205,8 @@ public final class PlayerHelper {
? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0)); ? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
} }
//////////////////////////////////////////////////////////////////////////// // endregion
// Settings Resolution // region Resolution
////////////////////////////////////////////////////////////////////////////
public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) { public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) {
return getPreferences(context) return getPreferences(context)
@ -405,9 +390,8 @@ public final class PlayerHelper {
return Integer.parseInt(preferredIntervalBytes) * 1024; return Integer.parseInt(preferredIntervalBytes) * 1024;
} }
//////////////////////////////////////////////////////////////////////////// // endregion
// Private helpers // region Private helpers
////////////////////////////////////////////////////////////////////////////
@NonNull @NonNull
private static SharedPreferences getPreferences(@NonNull final Context context) { private static SharedPreferences getPreferences(@NonNull final Context context) {
@ -427,9 +411,8 @@ public final class PlayerHelper {
} }
//////////////////////////////////////////////////////////////////////////// // endregion
// Utils used by player // region Utils used by player
////////////////////////////////////////////////////////////////////////////
@RepeatMode @RepeatMode
public static int nextRepeatMode(@RepeatMode final int repeatMode) { public static int nextRepeatMode(@RepeatMode final int repeatMode) {
@ -503,4 +486,43 @@ public final class PlayerHelper {
player.getContext().getString(R.string.seek_duration_key), player.getContext().getString(R.string.seek_duration_key),
player.getContext().getString(R.string.seek_duration_default_value)))); player.getContext().getString(R.string.seek_duration_default_value))));
} }
// endregion
// region Format
static class FormattersProvider {
private Formatters formatters;
public Formatters formatters() {
if (formatters == null) {
formatters = Formatters.create();
}
return formatters;
}
public void reset() {
formatters = null;
}
}
record Formatters(
Locale locale,
NumberFormat speed,
NumberFormat pitch) {
static Formatters create() {
final Locale locale = Localization.getAppLocale();
final DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale);
return new Formatters(
locale,
new DecimalFormat("0.##x", dfs),
new DecimalFormat("##%", dfs));
}
String stringFormat(final String format, final Object... args) {
return String.format(locale, format, args);
}
}
// endregion
} }

View file

@ -1,385 +0,0 @@
package org.schabi.newpipe.player.helper;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
import org.schabi.newpipe.App;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.PlayerService;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.Optional;
import java.util.function.Consumer;
public final class PlayerHolder {
private PlayerHolder() {
}
private static PlayerHolder instance;
public static synchronized PlayerHolder getInstance() {
if (PlayerHolder.instance == null) {
PlayerHolder.instance = new PlayerHolder();
}
return PlayerHolder.instance;
}
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = PlayerHolder.class.getSimpleName();
@Nullable private PlayerServiceExtendedEventListener listener;
private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection();
private boolean bound;
@Nullable private PlayerService playerService;
private Optional<Player> getPlayer() {
return Optional.ofNullable(playerService)
.flatMap(s -> Optional.ofNullable(s.getPlayer()));
}
private Optional<PlayQueue> getPlayQueue() {
// player play queue might be null e.g. while player is starting
return getPlayer().flatMap(p -> Optional.ofNullable(p.getPlayQueue()));
}
/**
* Returns the current {@link PlayerType} of the {@link PlayerService} service,
* otherwise `null` if no service is running.
*
* @return Current PlayerType
*/
@Nullable
public PlayerType getType() {
return getPlayer().map(Player::getPlayerType).orElse(null);
}
public boolean isPlaying() {
return getPlayer().map(Player::isPlaying).orElse(false);
}
public boolean isPlayerOpen() {
return getPlayer().isPresent();
}
/**
* Use this method to only allow the user to manipulate the play queue (e.g. by enqueueing via
* the stream long press menu) when there actually is a play queue to manipulate.
* @return true only if the player is open and its play queue is ready (i.e. it is not null)
*/
public boolean isPlayQueueReady() {
return getPlayQueue().isPresent();
}
public boolean isBound() {
return bound;
}
public int getQueueSize() {
return getPlayQueue().map(PlayQueue::size).orElse(0);
}
public int getQueuePosition() {
return getPlayQueue().map(PlayQueue::getIndex).orElse(0);
}
public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) {
listener = newListener;
if (listener == null) {
return;
}
// Force reload data from service
if (playerService != null) {
listener.onServiceConnected(playerService);
startPlayerListener();
// ^ will call listener.onPlayerConnected() down the line if there is an active player
}
}
// helper to handle context in common place as using the same
// context to bind/unbind a service is crucial
private Context getCommonContext() {
return App.getInstance();
}
/**
* Connect to (and if needed start) the {@link PlayerService}
* and bind {@link PlayerServiceConnection} to it.
* If the service is already started, only set the listener.
* @param playAfterConnect If this holders service was already started,
* start playing immediately
* @param newListener set this listener
* */
public void startService(final boolean playAfterConnect,
final PlayerServiceExtendedEventListener newListener) {
if (DEBUG) {
Log.d(TAG, "startService() called with playAfterConnect=" + playAfterConnect);
}
final Context context = getCommonContext();
setListener(newListener);
if (bound) {
return;
}
// startService() can be called concurrently and it will give a random crashes
// and NullPointerExceptions inside the service because the service will be
// bound twice. Prevent it with unbinding first
unbind(context);
final Intent intent = new Intent(context, PlayerService.class);
intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true);
ContextCompat.startForegroundService(context, intent);
serviceConnection.doPlayAfterConnect(playAfterConnect);
bind(context);
}
public void stopService() {
if (DEBUG) {
Log.d(TAG, "stopService() called");
}
if (playerService != null) {
playerService.destroyPlayerAndStopService();
}
final Context context = getCommonContext();
unbind(context);
// destroyPlayerAndStopService() already runs the next line of code, but run it again just
// to make sure to stop the service even if playerService is null by any chance.
context.stopService(new Intent(context, PlayerService.class));
}
class PlayerServiceConnection implements ServiceConnection {
private boolean playAfterConnect = false;
/**
* @param playAfterConnection Sets the value of `playAfterConnect` to pass to the {@link
* PlayerServiceExtendedEventListener#onPlayerConnected(Player, boolean)} the next time it
* is called. The value of `playAfterConnect` will be reset to false after that.
*/
public void doPlayAfterConnect(final boolean playAfterConnection) {
this.playAfterConnect = playAfterConnection;
}
@Override
public void onServiceDisconnected(final ComponentName compName) {
if (DEBUG) {
Log.d(TAG, "Player service is disconnected");
}
final Context context = getCommonContext();
unbind(context);
}
@Override
public void onServiceConnected(final ComponentName compName, final IBinder service) {
if (DEBUG) {
Log.d(TAG, "Player service is connected");
}
final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service;
@Nullable final PlayerService s = localBinder.getService();
if (s == null) {
throw new IllegalArgumentException(
"PlayerService.LocalBinder.getService() must never be"
+ "null after the service connects");
}
playerService = s;
if (listener != null) {
listener.onServiceConnected(s);
getPlayer().ifPresent(p -> listener.onPlayerConnected(p, playAfterConnect));
}
startPlayerListener();
// ^ will call listener.onPlayerConnected() down the line if there is an active player
// notify the main activity that binding the service has completed, so that it can
// open the bottom mini-player
NavigationHelper.sendPlayerStartedEvent(s);
}
}
private void bind(final Context context) {
if (DEBUG) {
Log.d(TAG, "bind() called");
}
// BIND_AUTO_CREATE starts the service if it's not already running
bound = bind(context, Context.BIND_AUTO_CREATE);
if (!bound) {
context.unbindService(serviceConnection);
}
}
public void tryBindIfNeeded(final Context context) {
if (!bound) {
// flags=0 means the service will not be started if it does not already exist. In this
// case the return value is not useful, as a value of "true" does not really indicate
// that the service is going to be bound.
bind(context, 0);
}
}
private boolean bind(final Context context, final int flags) {
final Intent serviceIntent = new Intent(context, PlayerService.class);
serviceIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION);
return context.bindService(serviceIntent, serviceConnection, flags);
}
private void unbind(final Context context) {
if (DEBUG) {
Log.d(TAG, "unbind() called");
}
if (bound) {
context.unbindService(serviceConnection);
bound = false;
stopPlayerListener();
playerService = null;
if (listener != null) {
listener.onPlayerDisconnected();
listener.onServiceDisconnected();
}
}
}
private void startPlayerListener() {
if (playerService != null) {
// setting the player listener will take care of calling relevant callbacks if the
// player in the service is (not) already active, also see playerStateListener below
playerService.setPlayerListener(playerStateListener);
}
getPlayer().ifPresent(p -> p.setFragmentListener(internalListener));
}
private void stopPlayerListener() {
if (playerService != null) {
playerService.setPlayerListener(null);
}
getPlayer().ifPresent(p -> p.removeFragmentListener(internalListener));
}
/**
* This listener will be held by the players created by {@link PlayerService}.
*/
private final PlayerServiceEventListener internalListener =
new PlayerServiceEventListener() {
@Override
public void onViewCreated() {
if (listener != null) {
listener.onViewCreated();
}
}
@Override
public void onFullscreenStateChanged(final boolean fullscreen) {
if (listener != null) {
listener.onFullscreenStateChanged(fullscreen);
}
}
@Override
public void onScreenRotationButtonClicked() {
if (listener != null) {
listener.onScreenRotationButtonClicked();
}
}
@Override
public void onMoreOptionsLongClicked() {
if (listener != null) {
listener.onMoreOptionsLongClicked();
}
}
@Override
public void onPlayerError(final PlaybackException error,
final boolean isCatchableException) {
if (listener != null) {
listener.onPlayerError(error, isCatchableException);
}
}
@Override
public void hideSystemUiIfNeeded() {
if (listener != null) {
listener.hideSystemUiIfNeeded();
}
}
@Override
public void onQueueUpdate(final PlayQueue queue) {
if (listener != null) {
listener.onQueueUpdate(queue);
}
}
@Override
public void onPlaybackUpdate(final int state,
final int repeatMode,
final boolean shuffled,
final PlaybackParameters parameters) {
if (listener != null) {
listener.onPlaybackUpdate(state, repeatMode, shuffled, parameters);
}
}
@Override
public void onProgressUpdate(final int currentProgress,
final int duration,
final int bufferPercent) {
if (listener != null) {
listener.onProgressUpdate(currentProgress, duration, bufferPercent);
}
}
@Override
public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
if (listener != null) {
listener.onMetadataUpdate(info, queue);
}
}
@Override
public void onServiceStopped() {
if (listener != null) {
listener.onServiceStopped();
}
unbind(getCommonContext());
}
};
/**
* This listener will be held by bound {@link PlayerService}s to notify of the player starting
* or stopping. This is necessary since the service outlives the player e.g. to answer Android
* Auto media browser queries.
*/
private final Consumer<Player> playerStateListener = (@Nullable final Player player) -> {
if (listener != null) {
if (player == null) {
// player.fragmentListener=null is already done by player.stopActivityBinding(),
// which is called by player.destroy(), which is in turn called by PlayerService
// before setting its player to null
listener.onPlayerDisconnected();
} else {
listener.onPlayerConnected(player, serviceConnection.playAfterConnect);
// reset the value of playAfterConnect: if it was true before, it is now "consumed"
serviceConnection.playAfterConnect = false;
player.setFragmentListener(internalListener);
}
}
};
}

View file

@ -0,0 +1,316 @@
package org.schabi.newpipe.player.helper
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.util.Log
import androidx.core.content.ContextCompat
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.PlaybackParameters
import org.schabi.newpipe.App
import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.player.Player
import org.schabi.newpipe.player.PlayerService
import org.schabi.newpipe.player.PlayerService.LocalBinder
import org.schabi.newpipe.player.PlayerType
import org.schabi.newpipe.player.event.PlayerServiceEventListener
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener
import org.schabi.newpipe.player.playqueue.PlayQueue
import org.schabi.newpipe.util.NavigationHelper
private val DEBUG = MainActivity.DEBUG
private val TAG: String = PlayerHolder::class.java.getSimpleName()
/**
* Singleton that manages a `PlayerService`
* and can be used to control the player instance through the service.
*/
object PlayerHolder {
private var listener: PlayerServiceExtendedEventListener? = null
var isBound: Boolean = false
private set
private var playerService: PlayerService? = null
private val player: Player?
get() = playerService?.player
// player play queue might be null e.g. while player is starting
private val playQueue: PlayQueue?
get() = this.player?.playQueue
val type: PlayerType?
/**
* Returns the current [PlayerType] of the [PlayerService] service,
* otherwise `null` if no service is running.
*
* @return Current PlayerType
*/
get() = this.player?.playerType
val isPlaying: Boolean
get() = this.player?.isPlaying == true
val isPlayerOpen: Boolean
get() = this.player != null
val isPlayQueueReady: Boolean
/**
* Use this method to only allow the user to manipulate the play queue (e.g. by enqueueing via
* the stream long press menu) when there actually is a play queue to manipulate.
* @return true only if the player is open and its play queue is ready (i.e. it is not null)
*/
get() = this.playQueue != null
val queueSize: Int
get() = this.playQueue?.size() ?: 0
val queuePosition: Int
get() = this.playQueue?.index ?: 0
fun setListener(newListener: PlayerServiceExtendedEventListener?) {
listener = newListener
// Force reload data from service
newListener?.let { listener ->
playerService?.let { service ->
listener.onServiceConnected(service)
startPlayerListener()
// ^ will call listener.onPlayerConnected() down the line if there is an active player
}
}
}
private val commonContext: Context
// helper to handle context in common place as using the same
get() = App.instance
/**
* Connect to (and if needed start) the [PlayerService]
* and bind [PlayerServiceConnection] to it.
* If the service is already started, only set the listener.
* @param playAfterConnect If this holders service was already started,
* start playing immediately
* @param newListener set this listener
*/
fun startService(
playAfterConnect: Boolean,
newListener: PlayerServiceExtendedEventListener?
) {
if (DEBUG) {
Log.d(TAG, "startService() called with playAfterConnect=$playAfterConnect")
}
val context = this.commonContext
setListener(newListener)
if (this.isBound) {
return
}
// startService() can be called concurrently and it will give a random crashes
// and NullPointerExceptions inside the service because the service will be
// bound twice. Prevent it with unbinding first
unbind(context)
val intent = Intent(context, PlayerService::class.java)
intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true)
ContextCompat.startForegroundService(context, intent)
PlayerServiceConnection.doPlayAfterConnect(playAfterConnect)
bind(context)
}
fun stopService() {
if (DEBUG) {
Log.d(TAG, "stopService() called")
}
playerService?.destroyPlayerAndStopService()
val context = this.commonContext
unbind(context)
// destroyPlayerAndStopService() already runs the next line of code, but run it again just
// to make sure to stop the service even if playerService is null by any chance.
context.stopService(Intent(context, PlayerService::class.java))
}
internal object PlayerServiceConnection : ServiceConnection {
internal var playAfterConnect = false
/**
* @param playAfterConnection Sets the value of [playAfterConnect] to pass to the
* [PlayerServiceExtendedEventListener.onPlayerConnected] the next time it
* is called. The value of [playAfterConnect] will be reset to false after that.
*/
fun doPlayAfterConnect(playAfterConnection: Boolean) {
this.playAfterConnect = playAfterConnection
}
override fun onServiceDisconnected(compName: ComponentName?) {
if (DEBUG) {
Log.d(TAG, "Player service is disconnected")
}
val context: Context = this@PlayerHolder.commonContext
unbind(context)
}
override fun onServiceConnected(compName: ComponentName?, service: IBinder?) {
if (DEBUG) {
Log.d(TAG, "Player service is connected")
}
val localBinder = service as LocalBinder
val s = localBinder.service
requireNotNull(s) {
"PlayerService.LocalBinder.getService() must never be" +
"null after the service connects"
}
playerService = s
listener?.let { l ->
l.onServiceConnected(s)
player?.let { l.onPlayerConnected(it, playAfterConnect) }
}
startPlayerListener()
// ^ will call listener.onPlayerConnected() down the line if there is an active player
// notify the main activity that binding the service has completed, so that it can
// open the bottom mini-player
NavigationHelper.sendPlayerStartedEvent(s)
}
}
private fun bind(context: Context) {
if (DEBUG) {
Log.d(TAG, "bind() called")
}
// BIND_AUTO_CREATE starts the service if it's not already running
this.isBound = bind(context, Context.BIND_AUTO_CREATE)
if (!this.isBound) {
context.unbindService(PlayerServiceConnection)
}
}
fun tryBindIfNeeded(context: Context) {
if (!this.isBound) {
// flags=0 means the service will not be started if it does not already exist. In this
// case the return value is not useful, as a value of "true" does not really indicate
// that the service is going to be bound.
bind(context, 0)
}
}
private fun bind(context: Context, flags: Int): Boolean {
val serviceIntent = Intent(context, PlayerService::class.java)
serviceIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION)
return context.bindService(serviceIntent, PlayerServiceConnection, flags)
}
private fun unbind(context: Context) {
if (DEBUG) {
Log.d(TAG, "unbind() called")
}
if (this.isBound) {
context.unbindService(PlayerServiceConnection)
this.isBound = false
stopPlayerListener()
playerService = null
listener?.onPlayerDisconnected()
listener?.onServiceDisconnected()
}
}
private fun startPlayerListener() {
// setting the player listener will take care of calling relevant callbacks if the
// player in the service is (not) already active, also see playerStateListener below
playerService?.setPlayerListener(playerStateListener)
this.player?.setFragmentListener(HolderPlayerServiceEventListener)
}
private fun stopPlayerListener() {
playerService?.setPlayerListener(null)
this.player?.removeFragmentListener(HolderPlayerServiceEventListener)
}
/**
* This listener will be held by the players created by [PlayerService].
*/
private object HolderPlayerServiceEventListener : PlayerServiceEventListener {
override fun onViewCreated() {
listener?.onViewCreated()
}
override fun onFullscreenStateChanged(fullscreen: Boolean) {
listener?.onFullscreenStateChanged(fullscreen)
}
override fun onScreenRotationButtonClicked() {
listener?.onScreenRotationButtonClicked()
}
override fun onMoreOptionsLongClicked() {
listener?.onMoreOptionsLongClicked()
}
override fun onPlayerError(
error: PlaybackException?,
isCatchableException: Boolean
) {
listener?.onPlayerError(error, isCatchableException)
}
override fun hideSystemUiIfNeeded() {
listener?.hideSystemUiIfNeeded()
}
override fun onQueueUpdate(queue: PlayQueue?) {
listener?.onQueueUpdate(queue)
}
override fun onPlaybackUpdate(
state: Int,
repeatMode: Int,
shuffled: Boolean,
parameters: PlaybackParameters?
) {
listener?.onPlaybackUpdate(state, repeatMode, shuffled, parameters)
}
override fun onProgressUpdate(
currentProgress: Int,
duration: Int,
bufferPercent: Int
) {
listener?.onProgressUpdate(currentProgress, duration, bufferPercent)
}
override fun onMetadataUpdate(info: StreamInfo?, queue: PlayQueue?) {
listener?.onMetadataUpdate(info, queue)
}
override fun onServiceStopped() {
listener?.onServiceStopped()
unbind(this@PlayerHolder.commonContext)
}
}
/**
* This listener will be held by bound [PlayerService]s to notify of the player starting
* or stopping. This is necessary since the service outlives the player e.g. to answer Android
* Auto media browser queries.
*/
private val playerStateListener: (Player?) -> Unit = { player: Player? ->
listener?.let { l ->
if (player == null) {
// player.fragmentListener=null is already done by player.stopActivityBinding(),
// which is called by player.destroy(), which is in turn called by PlayerService
// before setting its player to null
l.onPlayerDisconnected()
} else {
l.onPlayerConnected(player, PlayerServiceConnection.playAfterConnect)
// reset the value of playAfterConnect: if it was true before, it is now "consumed"
PlayerServiceConnection.playAfterConnect = false
player.setFragmentListener(HolderPlayerServiceEventListener)
}
}
}
}

View file

@ -9,7 +9,9 @@ import android.support.v4.media.MediaDescriptionCompat
import android.util.Log import android.util.Log
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.os.bundleOf
import androidx.media.MediaBrowserServiceCompat import androidx.media.MediaBrowserServiceCompat
import androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT
import androidx.media.MediaBrowserServiceCompat.Result import androidx.media.MediaBrowserServiceCompat.Result
import androidx.media.utils.MediaConstants import androidx.media.utils.MediaConstants
import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Flowable
@ -36,7 +38,6 @@ import org.schabi.newpipe.local.playlist.RemotePlaylistManager
import org.schabi.newpipe.util.ExtractorHelper import org.schabi.newpipe.util.ExtractorHelper
import org.schabi.newpipe.util.ServiceHelper import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.image.ImageStrategy import org.schabi.newpipe.util.image.ImageStrategy
import java.util.function.Consumer
/** /**
* This class is used to cleanly separate the Service implementation (in * This class is used to cleanly separate the Service implementation (in
@ -46,16 +47,15 @@ import java.util.function.Consumer
*/ */
class MediaBrowserImpl( class MediaBrowserImpl(
private val context: Context, private val context: Context,
notifyChildrenChanged: Consumer<String>, // parentId notifyChildrenChanged: (parentId: String) -> Unit,
) { ) {
private val packageValidator = PackageValidator(context)
private val database = NewPipeDatabase.getInstance(context) private val database = NewPipeDatabase.getInstance(context)
private var disposables = CompositeDisposable() private var disposables = CompositeDisposable()
init { init {
// this will listen to changes in the bookmarks until this MediaBrowserImpl is dispose()d // this will listen to changes in the bookmarks until this MediaBrowserImpl is dispose()d
disposables.add( disposables.add(getMergedPlaylists().subscribe { notifyChildrenChanged(ID_BOOKMARKS) })
getMergedPlaylists().subscribe { notifyChildrenChanged.accept(ID_BOOKMARKS) }
)
} }
//region Cleanup //region Cleanup
@ -69,11 +69,22 @@ class MediaBrowserImpl(
clientPackageName: String, clientPackageName: String,
clientUid: Int, clientUid: Int,
rootHints: Bundle? rootHints: Bundle?
): MediaBrowserServiceCompat.BrowserRoot { ): MediaBrowserServiceCompat.BrowserRoot? {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onGetRoot($clientPackageName, $clientUid, $rootHints)") Log.d(TAG, "onGetRoot($clientPackageName, $clientUid, $rootHints)")
} }
if (!packageValidator.isKnownCaller(clientPackageName, clientUid)) {
// this is a caller we can't trust (see PackageValidator's rules taken from uamp)
return null
}
if (rootHints?.getBoolean(EXTRA_RECENT, false) == true) {
// the system is asking for a root to do media resumption, but we can't handle that yet,
// see https://developer.android.com/media/implement/surfaces/mobile#mediabrowserservice_implementation
return null
}
val extras = Bundle() val extras = Bundle()
extras.putBoolean( extras.putBoolean(
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true
@ -183,17 +194,16 @@ class MediaBrowserImpl(
private fun createPlaylistMediaItem(playlist: PlaylistLocalItem): MediaBrowserCompat.MediaItem { private fun createPlaylistMediaItem(playlist: PlaylistLocalItem): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder() val builder = MediaDescriptionCompat.Builder()
builder
.setMediaId(createMediaIdForInfoItem(playlist is PlaylistRemoteEntity, playlist.uid)) .setMediaId(createMediaIdForInfoItem(playlist is PlaylistRemoteEntity, playlist.uid))
.setTitle(playlist.orderingName) .setTitle(playlist.orderingName)
.setIconUri(imageUriOrNullIfDisabled(playlist.thumbnailUrl)) .setIconUri(imageUriOrNullIfDisabled(playlist.thumbnailUrl))
.setExtras(
bundleOf(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
to context.resources.getString(R.string.tab_bookmarks)
)
)
val extras = Bundle()
extras.putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
context.resources.getString(R.string.tab_bookmarks),
)
builder.setExtras(extras)
return MediaBrowserCompat.MediaItem( return MediaBrowserCompat.MediaItem(
builder.build(), builder.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE,
@ -202,8 +212,9 @@ class MediaBrowserImpl(
private fun createInfoItemMediaItem(item: InfoItem): MediaBrowserCompat.MediaItem? { private fun createInfoItemMediaItem(item: InfoItem): MediaBrowserCompat.MediaItem? {
val builder = MediaDescriptionCompat.Builder() val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(createMediaIdForInfoItem(item)) .setMediaId(createMediaIdForInfoItem(item))
.setTitle(item.name) .setTitle(item.name)
.setIconUri(ImageStrategy.choosePreferredImage(item.thumbnails)?.toUri())
when (item.infoType) { when (item.infoType) {
InfoType.STREAM -> builder.setSubtitle((item as StreamInfoItem).uploaderName) InfoType.STREAM -> builder.setSubtitle((item as StreamInfoItem).uploaderName)
@ -212,10 +223,6 @@ class MediaBrowserImpl(
else -> return null else -> return null
} }
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
builder.setIconUri(imageUriOrNullIfDisabled(it))
}
return MediaBrowserCompat.MediaItem( return MediaBrowserCompat.MediaItem(
builder.build(), builder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
@ -256,7 +263,7 @@ class MediaBrowserImpl(
index: Int, index: Int,
): MediaBrowserCompat.MediaItem { ): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder() val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(createMediaIdForPlaylistIndex(false, playlistId, index)) .setMediaId(createMediaIdForPlaylistIndex(false, playlistId, index))
.setTitle(item.streamEntity.title) .setTitle(item.streamEntity.title)
.setSubtitle(item.streamEntity.uploader) .setSubtitle(item.streamEntity.uploader)
.setIconUri(imageUriOrNullIfDisabled(item.streamEntity.thumbnailUrl)) .setIconUri(imageUriOrNullIfDisabled(item.streamEntity.thumbnailUrl))
@ -276,10 +283,7 @@ class MediaBrowserImpl(
builder.setMediaId(createMediaIdForPlaylistIndex(true, playlistId, index)) builder.setMediaId(createMediaIdForPlaylistIndex(true, playlistId, index))
.setTitle(item.name) .setTitle(item.name)
.setSubtitle(item.uploaderName) .setSubtitle(item.uploaderName)
.setIconUri(ImageStrategy.choosePreferredImage(item.thumbnails)?.toUri())
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
builder.setIconUri(imageUriOrNullIfDisabled(it))
}
return MediaBrowserCompat.MediaItem( return MediaBrowserCompat.MediaItem(
builder.build(), builder.build(),

View file

@ -6,6 +6,7 @@ import android.os.Bundle
import android.os.ResultReceiver import android.os.ResultReceiver
import android.support.v4.media.session.PlaybackStateCompat import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector.PlaybackPreparer import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector.PlaybackPreparer
@ -109,14 +110,14 @@ class MediaBrowserPlaybackPreparer(
//region Errors //region Errors
private fun onUnsupportedError() { private fun onUnsupportedError() {
setMediaSessionError.accept( setMediaSessionError.accept(
context.getString(R.string.content_not_supported), ContextCompat.getString(context, R.string.content_not_supported),
PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED
) )
} }
private fun onPrepareError() { private fun onPrepareError() {
setMediaSessionError.accept( setMediaSessionError.accept(
context.getString(R.string.error_snackbar_message), ContextCompat.getString(context, R.string.error_snackbar_message),
PlaybackStateCompat.ERROR_CODE_APP_ERROR PlaybackStateCompat.ERROR_CODE_APP_ERROR
) )
} }

View file

@ -0,0 +1,240 @@
/*
* Copyright 2018 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// THIS FILE WAS TAKEN FROM UAMP, EXCEPT FOR THINGS RELATED TO THE WHITELIST. UPDATE IT WHEN NEEDED.
// https://github.com/android/uamp/blob/329a21b63c247e9bd35f6858d4fc0e448fa38603/common/src/main/java/com/example/android/uamp/media/PackageValidator.kt
package org.schabi.newpipe.player.mediabrowser
import android.Manifest.permission.MEDIA_CONTENT_CONTROL
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED
import android.content.pm.PackageManager
import android.os.Process
import android.support.v4.media.session.MediaSessionCompat
import android.util.Log
import androidx.core.app.NotificationManagerCompat
import androidx.media.MediaBrowserServiceCompat
import org.schabi.newpipe.BuildConfig
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
/**
* Validates that the calling package is authorized to browse a [MediaBrowserServiceCompat].
*
* The list of allowed signing certificates and their corresponding package names is defined in
* res/xml/allowed_media_browser_callers.xml.
*
* If you want to add a new caller to allowed_media_browser_callers.xml and you don't know
* its signature, this class will print to logcat (INFO level) a message with the proper
* xml tags to add to allow the caller.
*
* For more information, see res/xml/allowed_media_browser_callers.xml.
*/
internal class PackageValidator(context: Context) {
private val context: Context = context.applicationContext
private val packageManager: PackageManager = this.context.packageManager
private val platformSignature: String = getSystemSignature()
private val callerChecked = mutableMapOf<String, Pair<Int, Boolean>>()
/**
* Checks whether the caller attempting to connect to a [MediaBrowserServiceCompat] is known.
* See [MusicService.onGetRoot] for where this is utilized.
*
* @param callingPackage The package name of the caller.
* @param callingUid The user id of the caller.
* @return `true` if the caller is known, `false` otherwise.
*/
fun isKnownCaller(callingPackage: String, callingUid: Int): Boolean {
// If the caller has already been checked, return the previous result here.
val (checkedUid, checkResult) = callerChecked[callingPackage] ?: Pair(0, false)
if (checkedUid == callingUid) {
return checkResult
}
/**
* Because some of these checks can be slow, we save the results in [callerChecked] after
* this code is run.
*
* In particular, there's little reason to recompute the calling package's certificate
* signature (SHA-256) each call.
*
* This is safe to do as we know the UID matches the package's UID (from the check above),
* and app UIDs are set at install time. Additionally, a package name + UID is guaranteed to
* be constant until a reboot. (After a reboot then a previously assigned UID could be
* reassigned.)
*/
// Build the caller info for the rest of the checks here.
val callerPackageInfo = buildCallerInfo(callingPackage)
?: throw IllegalStateException("Caller wasn't found in the system?")
// Verify that things aren't ... broken. (This test should always pass.)
if (callerPackageInfo.uid != callingUid) {
throw IllegalStateException("Caller's package UID doesn't match caller's actual UID?")
}
val callerSignature = callerPackageInfo.signature
val isCallerKnown = when {
// If it's our own app making the call, allow it.
callingUid == Process.myUid() -> true
// If the system is making the call, allow it.
callingUid == Process.SYSTEM_UID -> true
// If the app was signed by the same certificate as the platform itself, also allow it.
callerSignature == platformSignature -> true
/**
* [MEDIA_CONTENT_CONTROL] permission is only available to system applications, and
* while it isn't required to allow these apps to connect to a
* [MediaBrowserServiceCompat], allowing this ensures optimal compatability with apps
* such as Android TV and the Google Assistant.
*/
callerPackageInfo.permissions.contains(MEDIA_CONTENT_CONTROL) -> true
/**
* If the calling app has a notification listener it is able to retrieve notifications
* and can connect to an active [MediaSessionCompat].
*
* It's not required to allow apps with a notification listener to
* connect to your [MediaBrowserServiceCompat], but it does allow easy compatibility
* with apps such as Wear OS.
*/
NotificationManagerCompat.getEnabledListenerPackages(this.context)
.contains(callerPackageInfo.packageName) -> true
// If none of the previous checks succeeded, then the caller is unrecognized.
else -> false
}
if (!isCallerKnown) {
logUnknownCaller(callerPackageInfo)
}
// Save our work for next time.
callerChecked[callingPackage] = Pair(callingUid, isCallerKnown)
return isCallerKnown
}
/**
* Logs an info level message with details of how to add a caller to the allowed callers list
* when the app is debuggable.
*/
private fun logUnknownCaller(callerPackageInfo: CallerPackageInfo) {
if (BuildConfig.DEBUG) {
Log.w(TAG, "Unknown caller $callerPackageInfo")
}
}
/**
* Builds a [CallerPackageInfo] for a given package that can be used for all the
* various checks that are performed before allowing an app to connect to a
* [MediaBrowserServiceCompat].
*/
private fun buildCallerInfo(callingPackage: String): CallerPackageInfo? {
val packageInfo = getPackageInfo(callingPackage) ?: return null
val appName = packageInfo.applicationInfo?.loadLabel(packageManager).toString()
val uid = packageInfo.applicationInfo?.uid ?: -1
val signature = getSignature(packageInfo)
val requestedPermissions = packageInfo.requestedPermissions?.asSequence().orEmpty()
val permissionFlags = packageInfo.requestedPermissionsFlags?.asSequence().orEmpty()
val activePermissions = (requestedPermissions zip permissionFlags)
.filter { (permission, flag) -> flag and REQUESTED_PERMISSION_GRANTED != 0 }
.mapTo(mutableSetOf()) { (permission, flag) -> permission }
return CallerPackageInfo(appName, callingPackage, uid, signature, activePermissions.toSet())
}
/**
* Looks up the [PackageInfo] for a package name.
* This requests both the signatures (for checking if an app is on the allow list) and
* the app's permissions, which allow for more flexibility in the allow list.
*
* @return [PackageInfo] for the package name or null if it's not found.
*/
@Suppress("deprecation")
@SuppressLint("PackageManagerGetSignatures")
private fun getPackageInfo(callingPackage: String): PackageInfo? =
packageManager.getPackageInfo(
callingPackage,
PackageManager.GET_SIGNATURES or PackageManager.GET_PERMISSIONS
)
/**
* Gets the signature of a given package's [PackageInfo].
*
* The "signature" is a SHA-256 hash of the public key of the signing certificate used by
* the app.
*
* If the app is not found, or if the app does not have exactly one signature, this method
* returns `null` as the signature.
*/
@Suppress("deprecation")
private fun getSignature(packageInfo: PackageInfo): String? =
if (packageInfo.signatures == null || packageInfo.signatures!!.size != 1) {
// Security best practices dictate that an app should be signed with exactly one (1)
// signature. Because of this, if there are multiple signatures, reject it.
null
} else {
val certificate = packageInfo.signatures!![0].toByteArray()
getSignatureSha256(certificate)
}
/**
* Finds the Android platform signing key signature. This key is never null.
*/
private fun getSystemSignature(): String =
getPackageInfo(ANDROID_PLATFORM)?.let { platformInfo ->
getSignature(platformInfo)
} ?: throw IllegalStateException("Platform signature not found")
/**
* Creates a SHA-256 signature given a certificate byte array.
*/
private fun getSignatureSha256(certificate: ByteArray): String {
val md: MessageDigest
try {
md = MessageDigest.getInstance("SHA256")
} catch (noSuchAlgorithmException: NoSuchAlgorithmException) {
Log.e(TAG, "No such algorithm: $noSuchAlgorithmException")
throw RuntimeException("Could not find SHA256 hash algorithm", noSuchAlgorithmException)
}
md.update(certificate)
// This code takes the byte array generated by `md.digest()` and joins each of the bytes
// to a string, applying the string format `%02x` on each digit before it's appended, with
// a colon (':') between each of the items.
// For example: input=[0,2,4,6,8,10,12], output="00:02:04:06:08:0a:0c"
return md.digest().joinToString(":") { String.format("%02x", it) }
}
/**
* Convenience class to hold all of the information about an app that's being checked
* to see if it's a known caller.
*/
private data class CallerPackageInfo(
val name: String,
val packageName: String,
val uid: Int,
val signature: String?,
val permissions: Set<String>
)
}
private const val TAG = "PackageValidator"
private const val ANDROID_PLATFORM = "android"

View file

@ -124,8 +124,10 @@ public class MediaSessionPlayerUi extends PlayerUi
MediaButtonReceiver.handleIntent(mediaSession, intent); MediaButtonReceiver.handleIntent(mediaSession, intent);
} }
public Optional<MediaSessionCompat.Token> getSessionToken() {
return Optional.ofNullable(mediaSession).map(MediaSessionCompat::getSessionToken); @NonNull
public MediaSessionCompat.Token getSessionToken() {
return mediaSession.getSessionToken();
} }
@ -138,7 +140,10 @@ public class MediaSessionPlayerUi extends PlayerUi
public void play() { public void play() {
player.play(); player.play();
// hide the player controls even if the play command came from the media session // hide the player controls even if the play command came from the media session
player.UIs().getOpt(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); final VideoPlayerUi ui = player.UIs().get(VideoPlayerUi.class);
if (ui != null) {
ui.hideControls(0, 0);
}
} }
@Override @Override

View file

@ -101,10 +101,10 @@ public final class NotificationUtil {
final int[] compactSlots = initializeNotificationSlots(); final int[] compactSlots = initializeNotificationSlots();
mediaStyle.setShowActionsInCompactView(compactSlots); mediaStyle.setShowActionsInCompactView(compactSlots);
} }
player.UIs() @Nullable final MediaSessionPlayerUi ui = player.UIs().get(MediaSessionPlayerUi.class);
.getOpt(MediaSessionPlayerUi.class) if (ui != null) {
.flatMap(MediaSessionPlayerUi::getSessionToken) mediaStyle.setMediaSession(ui.getSessionToken());
.ifPresent(mediaStyle::setMediaSession); }
// setup notification builder // setup notification builder
builder.setStyle(mediaStyle) builder.setStyle(mediaStyle)

View file

@ -38,9 +38,9 @@ import io.reactivex.rxjava3.internal.subscriptions.EmptySubscription;
import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject; import io.reactivex.rxjava3.subjects.PublishSubject;
import static org.schabi.newpipe.BuildConfig.DEBUG;
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException; import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException;
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException; import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException;
import static org.schabi.newpipe.player.playqueue.PlayQueue.DEBUG;
import static org.schabi.newpipe.util.ServiceHelper.getCacheExpirationMillis; import static org.schabi.newpipe.util.ServiceHelper.getCacheExpirationMillis;
public class MediaSourceManager { public class MediaSourceManager {

View file

@ -3,6 +3,7 @@ package org.schabi.newpipe.player.playqueue;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
@ -23,6 +24,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo<? extends InfoItem>>
final int serviceId; final int serviceId;
final String baseUrl; final String baseUrl;
@Nullable
Page nextPage; Page nextPage;
private transient Disposable fetchReactor; private transient Disposable fetchReactor;

View file

@ -1,561 +0,0 @@
package org.schabi.newpipe.player.playqueue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
import org.schabi.newpipe.player.playqueue.events.InitEvent;
import org.schabi.newpipe.player.playqueue.events.MoveEvent;
import org.schabi.newpipe.player.playqueue.events.PlayQueueEvent;
import org.schabi.newpipe.player.playqueue.events.RecoveryEvent;
import org.schabi.newpipe.player.playqueue.events.RemoveEvent;
import org.schabi.newpipe.player.playqueue.events.ReorderEvent;
import org.schabi.newpipe.player.playqueue.events.SelectEvent;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
/**
* PlayQueue is responsible for keeping track of a list of streams and the index of
* the stream that should be currently playing.
* <p>
* This class contains basic manipulation of a playlist while also functions as a
* message bus, providing all listeners with new updates to the play queue.
* </p>
* <p>
* This class can be serialized for passing intents, but in order to start the
* message bus, it must be initialized.
* </p>
*/
public abstract class PlayQueue implements Serializable {
public static final boolean DEBUG = MainActivity.DEBUG;
@NonNull
private final AtomicInteger queueIndex;
private final List<PlayQueueItem> history = new ArrayList<>();
private List<PlayQueueItem> backup;
private List<PlayQueueItem> streams;
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
private transient Flowable<PlayQueueEvent> broadcastReceiver;
private transient boolean disposed = false;
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
streams = new ArrayList<>(startWith);
if (streams.size() > index) {
history.add(streams.get(index));
}
queueIndex = new AtomicInteger(index);
}
/*//////////////////////////////////////////////////////////////////////////
// Playlist actions
//////////////////////////////////////////////////////////////////////////*/
/**
* Initializes the play queue message buses.
* <p>
* Also starts a self reporter for logging if debug mode is enabled.
* </p>
*/
public void init() {
eventBroadcast = BehaviorSubject.create();
broadcastReceiver = eventBroadcast.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(AndroidSchedulers.mainThread())
.startWithItem(new InitEvent());
}
/**
* Dispose the play queue by stopping all message buses.
*/
public void dispose() {
if (eventBroadcast != null) {
eventBroadcast.onComplete();
}
eventBroadcast = null;
broadcastReceiver = null;
disposed = true;
}
/**
* Checks if the queue is complete.
* <p>
* A queue is complete if it has loaded all items in an external playlist
* single stream or local queues are always complete.
* </p>
*
* @return whether the queue is complete
*/
public abstract boolean isComplete();
/**
* Load partial queue in the background, does nothing if the queue is complete.
*/
public abstract void fetch();
/*//////////////////////////////////////////////////////////////////////////
// Readonly ops
//////////////////////////////////////////////////////////////////////////*/
/**
* @return the current index that should be played
*/
public int getIndex() {
return queueIndex.get();
}
/**
* Changes the current playing index to a new index.
* <p>
* This method is guarded using in a circular manner for index exceeding the play queue size.
* </p>
* <p>
* Will emit a {@link SelectEvent} if the index is not the current playing index.
* </p>
*
* @param index the index to be set
*/
public synchronized void setIndex(final int index) {
final int oldIndex = getIndex();
final int newIndex;
if (index < 0) {
newIndex = 0;
} else if (index < streams.size()) {
// Regular assignment for index in bounds
newIndex = index;
} else if (streams.isEmpty()) {
// Out of bounds from here on
// Need to check if stream is empty to prevent arithmetic error and negative index
newIndex = 0;
} else if (isComplete()) {
// Circular indexing
newIndex = index % streams.size();
} else {
// Index of last element
newIndex = streams.size() - 1;
}
queueIndex.set(newIndex);
if (oldIndex != newIndex) {
history.add(streams.get(newIndex));
}
/*
TODO: Documentation states that a SelectEvent will only be emitted if the new index is...
different from the old one but this is emitted regardless? Not sure what this what it does
exactly so I won't touch it
*/
broadcast(new SelectEvent(oldIndex, newIndex));
}
/**
* @return the current item that should be played, or null if the queue is empty
*/
@Nullable
public PlayQueueItem getItem() {
return getItem(getIndex());
}
/**
* @param index the index of the item to return
* @return the item at the given index, or null if the index is out of bounds
*/
@Nullable
public PlayQueueItem getItem(final int index) {
if (index < 0 || index >= streams.size()) {
return null;
}
return streams.get(index);
}
/**
* Returns the index of the given item using referential equality.
* May be null despite play queue contains identical item.
*
* @param item the item to find the index of
* @return the index of the given item
*/
public int indexOf(@NonNull final PlayQueueItem item) {
return streams.indexOf(item);
}
/**
* @return the current size of play queue.
*/
public int size() {
return streams.size();
}
/**
* Checks if the play queue is empty.
*
* @return whether the play queue is empty
*/
public boolean isEmpty() {
return streams.isEmpty();
}
/**
* Determines if the current play queue is shuffled.
*
* @return whether the play queue is shuffled
*/
public boolean isShuffled() {
return backup != null;
}
/**
* @return an immutable view of the play queue
*/
@NonNull
public List<PlayQueueItem> getStreams() {
return Collections.unmodifiableList(streams);
}
/*//////////////////////////////////////////////////////////////////////////
// Write ops
//////////////////////////////////////////////////////////////////////////*/
/**
* Returns the play queue's update broadcast.
* May be null if the play queue message bus is not initialized.
*
* @return the play queue's update broadcast
*/
@Nullable
public Flowable<PlayQueueEvent> getBroadcastReceiver() {
return broadcastReceiver;
}
/**
* Changes the current playing index by an offset amount.
* <p>
* Will emit a {@link SelectEvent} if offset is non-zero.
* </p>
*
* @param offset the offset relative to the current index
*/
public synchronized void offsetIndex(final int offset) {
setIndex(getIndex() + offset);
}
/**
* Notifies that a change has occurred.
*/
public synchronized void notifyChange() {
broadcast(new AppendEvent(0));
}
/**
* Appends the given {@link PlayQueueItem}s to the current play queue.
* <p>
* If the play queue is shuffled, then append the items to the backup queue as is and
* append the shuffle items to the play queue.
* </p>
* <p>
* Will emit a {@link AppendEvent} on any given context.
* </p>
*
* @param items {@link PlayQueueItem}s to append
*/
public synchronized void append(@NonNull final List<PlayQueueItem> items) {
final List<PlayQueueItem> itemList = new ArrayList<>(items);
if (isShuffled()) {
backup.addAll(itemList);
Collections.shuffle(itemList);
}
if (!streams.isEmpty() && streams.get(streams.size() - 1).isAutoQueued()
&& !itemList.get(0).isAutoQueued()) {
streams.remove(streams.size() - 1);
}
streams.addAll(itemList);
broadcast(new AppendEvent(itemList.size()));
}
/**
* Removes the item at the given index from the play queue.
* <p>
* The current playing index will decrement if it is greater than the index being removed.
* On cases where the current playing index exceeds the playlist range, it is set to 0.
* </p>
* <p>
* Will emit a {@link RemoveEvent} if the index is within the play queue index range.
* </p>
*
* @param index the index of the item to remove
*/
public synchronized void remove(final int index) {
if (index >= streams.size() || index < 0) {
return;
}
removeInternal(index);
broadcast(new RemoveEvent(index, getIndex()));
}
/**
* Report an exception for the item at the current index in order and skip to the next one
* <p>
* This is done as a separate event as the underlying manager may have
* different implementation regarding exceptions.
* </p>
*/
public synchronized void error() {
final int oldIndex = getIndex();
queueIndex.incrementAndGet();
if (streams.size() > queueIndex.get()) {
history.add(streams.get(queueIndex.get()));
}
broadcast(new ErrorEvent(oldIndex, getIndex()));
}
private synchronized void removeInternal(final int removeIndex) {
final int currentIndex = queueIndex.get();
final int size = size();
if (currentIndex > removeIndex) {
queueIndex.decrementAndGet();
} else if (currentIndex >= size) {
queueIndex.set(currentIndex % (size - 1));
} else if (currentIndex == removeIndex && currentIndex == size - 1) {
queueIndex.set(0);
}
if (backup != null) {
backup.remove(getItem(removeIndex));
}
history.remove(streams.remove(removeIndex));
if (streams.size() > queueIndex.get()) {
history.add(streams.get(queueIndex.get()));
}
}
/**
* Moves a queue item at the source index to the target index.
* <p>
* If the item being moved is the currently playing, then the current playing index is set
* to that of the target.
* If the moved item is not the currently playing and moves to an index <b>AFTER</b> the
* current playing index, then the current playing index is decremented.
* Vice versa if the an item after the currently playing is moved <b>BEFORE</b>.
* </p>
*
* @param source the original index of the item
* @param target the new index of the item
*/
public synchronized void move(final int source, final int target) {
if (source < 0 || target < 0) {
return;
}
if (source >= streams.size() || target >= streams.size()) {
return;
}
final int current = getIndex();
if (source == current) {
queueIndex.set(target);
} else if (source < current && target >= current) {
queueIndex.decrementAndGet();
} else if (source > current && target <= current) {
queueIndex.incrementAndGet();
}
final PlayQueueItem playQueueItem = streams.remove(source);
playQueueItem.setAutoQueued(false);
streams.add(target, playQueueItem);
broadcast(new MoveEvent(source, target));
}
/**
* Sets the recovery record of the item at the index.
* <p>
* Broadcasts a recovery event.
* </p>
*
* @param index index of the item
* @param position the recovery position
*/
public synchronized void setRecovery(final int index, final long position) {
if (index < 0 || index >= streams.size()) {
return;
}
streams.get(index).setRecoveryPosition(position);
broadcast(new RecoveryEvent(index, position));
}
/**
* Revoke the recovery record of the item at the index.
* <p>
* Broadcasts a recovery event.
* </p>
*
* @param index index of the item
*/
public synchronized void unsetRecovery(final int index) {
setRecovery(index, PlayQueueItem.RECOVERY_UNSET);
}
/**
* Shuffles the current play queue
* <p>
* This method first backs up the existing play queue and item being played. Then a newly
* shuffled play queue will be generated along with currently playing item placed at the
* beginning of the queue. This item will also be added to the history.
* </p>
* <p>
* Will emit a {@link ReorderEvent} if shuffled.
* </p>
*
* @implNote Does nothing if the queue has a size <= 2 (the currently playing video must stay on
* top, so shuffling a size-2 list does nothing)
*/
public synchronized void shuffle() {
// Create a backup if it doesn't already exist
// Note: The backup-list has to be created at all cost (even when size <= 2).
// Otherwise it's not possible to enter shuffle-mode!
if (backup == null) {
backup = new ArrayList<>(streams);
}
// Can't shuffle a list that's empty or only has one element
if (size() <= 2) {
return;
}
final int originalIndex = getIndex();
final PlayQueueItem currentItem = getItem();
Collections.shuffle(streams);
// Move currentItem to the head of the queue
streams.remove(currentItem);
streams.add(0, currentItem);
queueIndex.set(0);
history.add(currentItem);
broadcast(new ReorderEvent(originalIndex, 0));
}
/**
* Unshuffles the current play queue if a backup play queue exists.
* <p>
* This method undoes shuffling and index will be set to the previously playing item if found,
* otherwise, the index will reset to 0.
* </p>
* <p>
* Will emit a {@link ReorderEvent} if a backup exists.
* </p>
*/
public synchronized void unshuffle() {
if (backup == null) {
return;
}
final int originIndex = getIndex();
final PlayQueueItem current = getItem();
streams = backup;
backup = null;
final int newIndex = streams.indexOf(current);
if (newIndex != -1) {
queueIndex.set(newIndex);
} else {
queueIndex.set(0);
}
if (streams.size() > queueIndex.get()) {
history.add(streams.get(queueIndex.get()));
}
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
}
/**
* Selects previous played item.
*
* This method removes currently playing item from history and
* starts playing the last item from history if it exists
*
* @return true if history is not empty and the item can be played
* */
public synchronized boolean previous() {
if (history.size() <= 1) {
return false;
}
history.remove(history.size() - 1);
final PlayQueueItem last = history.remove(history.size() - 1);
setIndex(indexOf(last));
return true;
}
/*
* Compares two PlayQueues. Useful when a user switches players but queue is the same so
* we don't have to do anything with new queue.
* This method also gives a chance to track history of items in a queue in
* VideoDetailFragment without duplicating items from two identical queues
*/
public boolean equalStreams(@Nullable final PlayQueue other) {
if (other == null) {
return false;
}
if (size() != other.size()) {
return false;
}
for (int i = 0; i < size(); i++) {
final PlayQueueItem stream = streams.get(i);
final PlayQueueItem otherStream = other.streams.get(i);
// Check is based on serviceId and URL
if (stream.getServiceId() != otherStream.getServiceId()
|| !stream.getUrl().equals(otherStream.getUrl())) {
return false;
}
}
return true;
}
public boolean equalStreamsAndIndex(@Nullable final PlayQueue other) {
if (equalStreams(other)) {
//noinspection ConstantConditions
return other.getIndex() == getIndex(); //NOSONAR: other is not null
}
return false;
}
public boolean isDisposed() {
return disposed;
}
/*//////////////////////////////////////////////////////////////////////////
// Rx Broadcast
//////////////////////////////////////////////////////////////////////////*/
private void broadcast(@NonNull final PlayQueueEvent event) {
if (eventBroadcast != null) {
eventBroadcast.onNext(event);
}
}
}

View file

@ -0,0 +1,497 @@
package org.schabi.newpipe.player.playqueue
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.BehaviorSubject
import org.schabi.newpipe.player.playqueue.events.AppendEvent
import org.schabi.newpipe.player.playqueue.events.ErrorEvent
import org.schabi.newpipe.player.playqueue.events.InitEvent
import org.schabi.newpipe.player.playqueue.events.MoveEvent
import org.schabi.newpipe.player.playqueue.events.PlayQueueEvent
import org.schabi.newpipe.player.playqueue.events.RecoveryEvent
import org.schabi.newpipe.player.playqueue.events.RemoveEvent
import org.schabi.newpipe.player.playqueue.events.ReorderEvent
import org.schabi.newpipe.player.playqueue.events.SelectEvent
import java.io.Serializable
import java.util.Collections
import java.util.concurrent.atomic.AtomicInteger
/**
* PlayQueue is responsible for keeping track of a list of streams and the index of
* the stream that should be currently playing.
*
* This class contains basic manipulation of a playlist while also functions as a
* message bus, providing all listeners with new updates to the play queue.
*
* This class can be serialized for passing intents, but in order to start the
* message bus, it must be initialized.
*/
abstract class PlayQueue internal constructor(
index: Int,
startWith: List<PlayQueueItem>,
) : Serializable {
private val queueIndex = AtomicInteger(index)
private val history = mutableListOf<PlayQueueItem>()
private var backup = mutableListOf<PlayQueueItem>()
private var streams = startWith.toMutableList()
@Transient
private var eventBroadcast: BehaviorSubject<PlayQueueEvent>? = null
/**
* Returns the play queue's update broadcast.
* May be null if the play queue message bus is not initialized.
*
* @return the play queue's update broadcast
*/
@Transient
var broadcastReceiver: Flowable<PlayQueueEvent>? = null
private set
@Transient
var isDisposed: Boolean = false
private set
init {
if (streams.size > index) {
history.add(streams[index])
}
}
/*//////////////////////////////////////////////////////////////////////////
// Playlist actions
////////////////////////////////////////////////////////////////////////// */
/**
* Initializes the play queue message buses.
*
* Also starts a self reporter for logging if debug mode is enabled.
*/
fun init() {
eventBroadcast = BehaviorSubject.create()
broadcastReceiver =
eventBroadcast!!
.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(AndroidSchedulers.mainThread())
.startWithItem(InitEvent())
}
/**
* Dispose the play queue by stopping all message buses.
*/
open fun dispose() {
eventBroadcast?.onComplete()
eventBroadcast = null
broadcastReceiver = null
this.isDisposed = true
}
/**
* Checks if the queue is complete.
*
* A queue is complete if it has loaded all items in an external playlist
* single stream or local queues are always complete.
*
* @return whether the queue is complete
*/
abstract val isComplete: Boolean
/**
* Load partial queue in the background, does nothing if the queue is complete.
*/
abstract fun fetch()
/*//////////////////////////////////////////////////////////////////////////
// Readonly ops
////////////////////////////////////////////////////////////////////////// */
@set:Synchronized
var index: Int = 0
/**
* @return the current index that should be played
*/
get() = queueIndex.get()
/**
* Changes the current playing index to a new index.
*
* This method is guarded using in a circular manner for index exceeding the play queue size.
*
* Will emit a [SelectEvent] if the index is not the current playing index.
*
* @param index the index to be set
*/
set(index) {
val oldIndex = field
val newIndex: Int
if (index < 0) {
newIndex = 0
} else if (index < streams.size) {
// Regular assignment for index in bounds
newIndex = index
} else if (streams.isEmpty()) {
// Out of bounds from here on
// Need to check if stream is empty to prevent arithmetic error and negative index
newIndex = 0
} else if (this.isComplete) {
// Circular indexing
newIndex = index % streams.size
} else {
// Index of last element
newIndex = streams.size - 1
}
queueIndex.set(newIndex)
if (oldIndex != newIndex) {
history.add(streams[newIndex])
}
/*
TODO: Documentation states that a SelectEvent will only be emitted if the new index is...
different from the old one but this is emitted regardless? Not sure what this what it does
exactly so I won't touch it
*/
broadcast(SelectEvent(oldIndex, newIndex))
}
/**
* @return the current item that should be played, or null if the queue is empty
*/
val item get() = getItem(this.index)
/**
* @param index the index of the item to return
* @return the item at the given index, or null if the index is out of bounds
*/
fun getItem(index: Int) = streams.getOrNull(index)
/**
* Returns the index of the given item using referential equality.
* May be null despite play queue contains identical item.
*
* @param item the item to find the index of
* @return the index of the given item
*/
fun indexOf(item: PlayQueueItem): Int = streams.indexOf(item)
/**
* @return the current size of play queue.
*/
fun size(): Int = streams.size
/**
* Checks if the play queue is empty.
*
* @return whether the play queue is empty
*/
val isEmpty: Boolean
get() = streams.isEmpty()
/**
* Determines if the current play queue is shuffled.
*
* @return whether the play queue is shuffled
*/
val isShuffled: Boolean
get() = backup.isNotEmpty()
/**
* @return an immutable view of the play queue
*/
fun getStreams(): List<PlayQueueItem> = Collections.unmodifiableList(streams)
/*//////////////////////////////////////////////////////////////////////////
// Write ops
////////////////////////////////////////////////////////////////////////// */
/**
* Changes the current playing index by an offset amount.
*
* Will emit a [SelectEvent] if offset is non-zero.
*
* @param offset the offset relative to the current index
*/
@Synchronized
fun offsetIndex(offset: Int) {
this.index += offset
}
/**
* Notifies that a change has occurred.
*/
@Synchronized
fun notifyChange() {
broadcast(AppendEvent(0))
}
/**
* Appends the given [PlayQueueItem]s to the current play queue.
*
* If the play queue is shuffled, then append the items to the backup queue as is and
* append the shuffle items to the play queue.
*
* Will emit a [AppendEvent] on any given context.
*
* @param items [PlayQueueItem]s to append
*/
@Synchronized
fun append(items: List<PlayQueueItem>) {
val itemList = items.toMutableList()
if (this.isShuffled) {
backup.addAll(itemList)
itemList.shuffle()
}
if (!streams.isEmpty() && streams.last().isAutoQueued && !itemList[0].isAutoQueued) {
streams.removeAt(streams.lastIndex)
}
streams.addAll(itemList)
broadcast(AppendEvent(itemList.size))
}
/**
* Removes the item at the given index from the play queue.
*
* The current playing index will decrement if it is greater than the index being removed.
* On cases where the current playing index exceeds the playlist range, it is set to 0.
*
* Will emit a [RemoveEvent] if the index is within the play queue index range.
*
* @param index the index of the item to remove
*/
@Synchronized
fun remove(index: Int) {
if (index >= streams.size || index < 0) {
return
}
removeInternal(index)
broadcast(RemoveEvent(index, this.index))
}
/**
* Report an exception for the item at the current index in order and skip to the next one
*
* This is done as a separate event as the underlying manager may have
* different implementation regarding exceptions.
*/
@Synchronized
fun error() {
val oldIndex = this.index
queueIndex.incrementAndGet()
if (streams.size > queueIndex.get()) {
history.add(streams[queueIndex.get()])
}
broadcast(ErrorEvent(oldIndex, this.index))
}
@Synchronized
private fun removeInternal(removeIndex: Int) {
val currentIndex = queueIndex.get()
val size = size()
if (currentIndex > removeIndex) {
queueIndex.decrementAndGet()
} else if (currentIndex >= size) {
queueIndex.set(currentIndex % (size - 1))
} else if (currentIndex == removeIndex && currentIndex == size - 1) {
queueIndex.set(0)
}
backup.remove(getItem(removeIndex)!!)
history.remove(streams.removeAt(removeIndex))
if (streams.size > queueIndex.get()) {
history.add(streams[queueIndex.get()])
}
}
/**
* Moves a queue item at the source index to the target index.
*
* If the item being moved is the currently playing, then the current playing index is set
* to that of the target.
* If the moved item is not the currently playing and moves to an index **AFTER** the
* current playing index, then the current playing index is decremented.
* Vice versa if the an item after the currently playing is moved **BEFORE**.
*
* @param source the original index of the item
* @param target the new index of the item
*/
@Synchronized
fun move(
source: Int,
target: Int,
) {
if (source < 0 || target < 0) {
return
}
if (source >= streams.size || target >= streams.size) {
return
}
val current = this.index
if (source == current) {
queueIndex.set(target)
} else if (source < current && target >= current) {
queueIndex.decrementAndGet()
} else if (source > current && target <= current) {
queueIndex.incrementAndGet()
}
val playQueueItem = streams.removeAt(source)
playQueueItem.isAutoQueued = false
streams.add(target, playQueueItem)
broadcast(MoveEvent(source, target))
}
/**
* Sets the recovery record of the item at the index.
*
* Broadcasts a recovery event.
*
* @param index index of the item
* @param position the recovery position
*/
@Synchronized
fun setRecovery(
index: Int,
position: Long,
) {
streams.getOrNull(index)?.let {
it.recoveryPosition = position
broadcast(RecoveryEvent(index, position))
}
}
/**
* Revoke the recovery record of the item at the index.
*
* Broadcasts a recovery event.
*
* @param index index of the item
*/
@Synchronized
fun unsetRecovery(index: Int) {
setRecovery(index, Long.Companion.MIN_VALUE)
}
/**
* Shuffles the current play queue
*
* This method first backs up the existing play queue and item being played. Then a newly
* shuffled play queue will be generated along with currently playing item placed at the
* beginning of the queue. This item will also be added to the history.
*
* Will emit a [ReorderEvent] if shuffled.
*
* @implNote Does nothing if the queue has a size <= 2 (the currently playing video must stay on
* top, so shuffling a size-2 list does nothing)
*/
@Synchronized
fun shuffle() {
// Create a backup if it doesn't already exist
// Note: The backup-list has to be created at all cost (even when size <= 2).
// Otherwise it's not possible to enter shuffle-mode!
if (backup.isEmpty()) {
backup = streams.toMutableList()
}
// Can't shuffle a list that's empty or only has one element
if (size() <= 2) {
return
}
val originalIndex = this.index
val currentItem = this.item
streams.shuffle()
// Move currentItem to the head of the queue
streams.remove(currentItem!!)
streams.add(0, currentItem)
queueIndex.set(0)
history.add(currentItem)
broadcast(ReorderEvent(originalIndex, 0))
}
/**
* Unshuffles the current play queue if a backup play queue exists.
*
* This method undoes shuffling and index will be set to the previously playing item if found,
* otherwise, the index will reset to 0.
*
* Will emit a [ReorderEvent] if a backup exists.
*/
@Synchronized
fun unshuffle() {
if (backup.isEmpty()) {
return
}
val originIndex = this.index
val current = this.item
streams = backup
backup = mutableListOf()
val newIndex = streams.indexOf(current!!)
if (newIndex != -1) {
queueIndex.set(newIndex)
} else {
queueIndex.set(0)
}
if (streams.size > queueIndex.get()) {
history.add(streams[queueIndex.get()])
}
broadcast(ReorderEvent(originIndex, queueIndex.get()))
}
/**
* Selects previous played item.
*
* This method removes currently playing item from history and
* starts playing the last item from history if it exists
*
* @return true if history is not empty and the item can be played
*/
@Synchronized
fun previous(): Boolean {
if (history.size <= 1) {
return false
}
history.removeAt(history.size - 1)
val last = history.removeAt(history.size - 1)
this.index = indexOf(last)
return true
}
/*
* Compares two PlayQueues. Useful when a user switches players but queue is the same so
* we don't have to do anything with new queue.
* This method also gives a chance to track history of items in a queue in
* VideoDetailFragment without duplicating items from two identical queues
*/
override fun equals(o: Any?): Boolean = o is PlayQueue && streams == o.streams
override fun hashCode(): Int = streams.hashCode()
fun equalStreamsAndIndex(other: PlayQueue?): Boolean {
return equals(other) && other!!.index == this.index // NOSONAR: other is not null
}
/*//////////////////////////////////////////////////////////////////////////
// Rx Broadcast
////////////////////////////////////////////////////////////////////////// */
private fun broadcast(event: PlayQueueEvent) {
eventBroadcast?.onNext(event)
}
}

View file

@ -1,142 +0,0 @@
package org.schabi.newpipe.player.playqueue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.util.ExtractorHelper;
import java.io.Serializable;
import java.util.List;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class PlayQueueItem implements Serializable {
public static final long RECOVERY_UNSET = Long.MIN_VALUE;
private static final String EMPTY_STRING = "";
@NonNull
private final String title;
@NonNull
private final String url;
private final int serviceId;
private final long duration;
@NonNull
private final List<Image> thumbnails;
@NonNull
private final String uploader;
private final String uploaderUrl;
@NonNull
private final StreamType streamType;
private boolean isAutoQueued;
private long recoveryPosition;
private Throwable error;
PlayQueueItem(@NonNull final StreamInfo info) {
this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(),
info.getThumbnails(), info.getUploaderName(),
info.getUploaderUrl(), info.getStreamType());
if (info.getStartPosition() > 0) {
setRecoveryPosition(info.getStartPosition() * 1000);
}
}
PlayQueueItem(@NonNull final StreamInfoItem item) {
this(item.getName(), item.getUrl(), item.getServiceId(), item.getDuration(),
item.getThumbnails(), item.getUploaderName(),
item.getUploaderUrl(), item.getStreamType());
}
@SuppressWarnings("ParameterNumber")
private PlayQueueItem(@Nullable final String name, @Nullable final String url,
final int serviceId, final long duration,
final List<Image> thumbnails, @Nullable final String uploader,
final String uploaderUrl, @NonNull final StreamType streamType) {
this.title = name != null ? name : EMPTY_STRING;
this.url = url != null ? url : EMPTY_STRING;
this.serviceId = serviceId;
this.duration = duration;
this.thumbnails = thumbnails;
this.uploader = uploader != null ? uploader : EMPTY_STRING;
this.uploaderUrl = uploaderUrl;
this.streamType = streamType;
this.recoveryPosition = RECOVERY_UNSET;
}
@NonNull
public String getTitle() {
return title;
}
@NonNull
public String getUrl() {
return url;
}
public int getServiceId() {
return serviceId;
}
public long getDuration() {
return duration;
}
@NonNull
public List<Image> getThumbnails() {
return thumbnails;
}
@NonNull
public String getUploader() {
return uploader;
}
public String getUploaderUrl() {
return uploaderUrl;
}
@NonNull
public StreamType getStreamType() {
return streamType;
}
public long getRecoveryPosition() {
return recoveryPosition;
}
/*package-private*/ void setRecoveryPosition(final long recoveryPosition) {
this.recoveryPosition = recoveryPosition;
}
@Nullable
public Throwable getError() {
return error;
}
@NonNull
public Single<StreamInfo> getStream() {
return ExtractorHelper.getStreamInfo(this.serviceId, this.url, false)
.subscribeOn(Schedulers.io())
.doOnError(throwable -> error = throwable);
}
public boolean isAutoQueued() {
return isAutoQueued;
}
////////////////////////////////////////////////////////////////////////////
// Item States, keep external access out
////////////////////////////////////////////////////////////////////////////
public void setAutoQueued(final boolean autoQueued) {
isAutoQueued = autoQueued;
}
}

View file

@ -0,0 +1,71 @@
package org.schabi.newpipe.player.playqueue
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.extractor.Image
import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.util.ExtractorHelper
import java.io.Serializable
import java.util.Objects
class PlayQueueItem private constructor(
val title: String,
val url: String,
val serviceId: Int,
val duration: Long,
val thumbnails: List<Image>,
val uploader: String,
val uploaderUrl: String?,
val streamType: StreamType,
) : Serializable {
//
// ////////////////////////////////////////////////////////////////////// */
// Item States, keep external access out
//
// ////////////////////////////////////////////////////////////////////// */
var isAutoQueued: Boolean = false
// package-private
var recoveryPosition = Long.Companion.MIN_VALUE
var error: Throwable? = null
private set
constructor(info: StreamInfo) : this(
info.name.orEmpty(),
info.url.orEmpty(),
info.serviceId,
info.duration,
info.thumbnails,
info.uploaderName.orEmpty(),
info.uploaderUrl,
info.streamType,
) {
if (info.startPosition > 0) {
this.recoveryPosition = info.startPosition * 1000
}
}
constructor(item: StreamInfoItem) : this(
item.name.orEmpty(),
item.url.orEmpty(),
item.serviceId,
item.duration,
item.thumbnails,
item.uploaderName.orEmpty(),
item.uploaderUrl,
item.streamType,
)
val stream: Single<StreamInfo>
get() =
ExtractorHelper
.getStreamInfo(serviceId, url, false)
.subscribeOn(Schedulers.io())
.doOnError { throwable -> error = throwable }
override fun equals(o: Any?) = o is PlayQueueItem && serviceId == o.serviceId && url == o.url
override fun hashCode() = Objects.hash(url, serviceId)
}

View file

@ -1,25 +1,20 @@
package org.schabi.newpipe.player.ui package org.schabi.newpipe.player.ui
import org.schabi.newpipe.util.GuardedByMutex import org.schabi.newpipe.util.GuardedByMutex
import java.util.Optional import kotlin.reflect.KClass
import kotlin.reflect.safeCast
/**
* Creates a [PlayerUiList] starting with the provided player uis. The provided player uis
* will not be prepared like those passed to [.addAndPrepare], because when
* the [PlayerUiList] constructor is called, the player is still not running and it
* wouldn't make sense to initialize uis then. Instead the player will initialize them by doing
* proper calls to [.call].
*
* @param initialPlayerUis the player uis this list should start with; the order will be kept
*/
class PlayerUiList(vararg initialPlayerUis: PlayerUi) { class PlayerUiList(vararg initialPlayerUis: PlayerUi) {
private val playerUis = GuardedByMutex(mutableListOf<PlayerUi>()) private val playerUis = GuardedByMutex(mutableListOf(*initialPlayerUis))
/**
* Creates a [PlayerUiList] starting with the provided player uis. The provided player uis
* will not be prepared like those passed to [.addAndPrepare], because when
* the [PlayerUiList] constructor is called, the player is still not running and it
* wouldn't make sense to initialize uis then. Instead the player will initialize them by doing
* proper calls to [.call].
*
* @param initialPlayerUis the player uis this list should start with; the order will be kept
*/
init {
playerUis.runWithLockSync {
lockData.addAll(listOf(*initialPlayerUis))
}
}
/** /**
* Adds the provided player ui to the list and calls on it the initialization functions that * Adds the provided player ui to the list and calls on it the initialization functions that
@ -83,30 +78,22 @@ class PlayerUiList(vararg initialPlayerUis: PlayerUi) {
* @param T the class type parameter * @param T the class type parameter
* @return the first player UI of the required type found in the list, or null * @return the first player UI of the required type found in the list, or null
</T> */ </T> */
fun <T : PlayerUi> get(playerUiType: Class<T>): T? = fun <T : PlayerUi> get(playerUiType: KClass<T>): T? =
playerUis.runWithLockSync { playerUis.runWithLockSync {
for (ui in lockData) { for (ui in lockData) {
if (playerUiType.isInstance(ui)) { if (playerUiType.isInstance(ui)) {
when (val r = playerUiType.cast(ui)) { // try all UIs before returning null
// try all UIs before returning null playerUiType.safeCast(ui)?.let { return@runWithLockSync it }
null -> continue
else -> return@runWithLockSync r
}
} }
} }
return@runWithLockSync null return@runWithLockSync null
} }
/** /**
* @param playerUiType the class of the player UI to return; * See [get] above
* the [Class.isInstance] method will be used, so even subclasses could be returned */
* @param T the class type parameter fun <T : PlayerUi> get(playerUiType: Class<T>): T? =
* @return the first player UI of the required type found in the list, or an empty get(playerUiType.kotlin)
* [Optional] otherwise
</T> */
@Deprecated("use get", ReplaceWith("get(playerUiType)"))
fun <T : PlayerUi> getOpt(playerUiType: Class<T>): Optional<T> =
Optional.ofNullable(get(playerUiType))
/** /**
* Calls the provided consumer on all player UIs in the list, in order of addition. * Calls the provided consumer on all player UIs in the list, in order of addition.

View file

@ -1,7 +1,6 @@
package org.schabi.newpipe.settings; package org.schabi.newpipe.settings;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -17,7 +16,6 @@ import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@ -35,12 +33,10 @@ import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ZipHelper; import org.schabi.newpipe.util.ZipHelper;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
public class BackupRestoreSettingsFragment extends BasePreferenceFragment { public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
@ -61,13 +57,10 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
@Override @Override
public void onCreatePreferences(@Nullable final Bundle savedInstanceState, public void onCreatePreferences(@Nullable final Bundle savedInstanceState,
@Nullable final String rootKey) { @Nullable final String rootKey) {
final File homeDir = ContextCompat.getDataDir(requireContext()); manager = new ImportExportManager(new BackupFileLocator(requireContext()));
Objects.requireNonNull(homeDir);
manager = new ImportExportManager(new BackupFileLocator(homeDir));
importExportDataPathKey = getString(R.string.import_export_data_path); importExportDataPathKey = getString(R.string.import_export_data_path);
addPreferencesFromResourceRegistry(); addPreferencesFromResourceRegistry();
final Preference importDataPreference = requirePreference(R.string.import_data); final Preference importDataPreference = requirePreference(R.string.import_data);
@ -126,7 +119,6 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
} }
private void requestExportPathResult(final ActivityResult result) { private void requestExportPathResult(final ActivityResult result) {
assureCorrectAppLanguage(requireContext());
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
// will be saved only on success // will be saved only on success
final Uri lastExportDataUri = result.getData().getData(); final Uri lastExportDataUri = result.getData().getData();
@ -139,7 +131,6 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
} }
private void requestImportPathResult(final ActivityResult result) { private void requestImportPathResult(final ActivityResult result) {
assureCorrectAppLanguage(requireContext());
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
// will be saved only on success // will be saved only on success
final Uri lastImportDataUri = result.getData().getData(); final Uri lastImportDataUri = result.getData().getData();
@ -183,9 +174,7 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
} }
try { try {
if (!manager.ensureDbDirectoryExists()) { manager.ensureDbDirectoryExists();
throw new IOException("Could not create databases dir");
}
// replace the current database // replace the current database
if (!manager.extractDb(file)) { if (!manager.extractDb(file)) {

View file

@ -10,13 +10,14 @@ import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.os.LocaleListCompat;
import androidx.preference.Preference; import androidx.preference.Preference;
import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.extractor.localization.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.image.ImageStrategy; import org.schabi.newpipe.util.image.ImageStrategy;
import org.schabi.newpipe.util.image.PreferredImageQuality; import org.schabi.newpipe.util.image.PreferredImageQuality;
@ -27,26 +28,27 @@ import coil3.SingletonImageLoader;
public class ContentSettingsFragment extends BasePreferenceFragment { public class ContentSettingsFragment extends BasePreferenceFragment {
private String youtubeRestrictedModeEnabledKey; private String youtubeRestrictedModeEnabledKey;
private String initialLanguage;
@Override @Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled); youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
addPreferencesFromResourceRegistry(); addPreferencesFromResourceRegistry();
initialLanguage = defaultPreferences.getString(getString(R.string.app_language_key), "en"); setupAppLanguagePreferences();
setupImageQualityPref();
}
private void setupAppLanguagePreferences() {
final Preference appLanguagePref = requirePreference(R.string.app_language_key);
// Android 13+ allows to set app specific languages
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
appLanguagePref.setVisible(false);
if (Build.VERSION.SDK_INT >= 33) {
requirePreference(R.string.app_language_key).setVisible(false);
final Preference newAppLanguagePref = final Preference newAppLanguagePref =
requirePreference(R.string.app_language_android_13_and_up_key); requirePreference(R.string.app_language_android_13_and_up_key);
newAppLanguagePref.setSummaryProvider(preference -> { newAppLanguagePref.setSummaryProvider(preference -> {
final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0); final Locale loc = AppCompatDelegate.getApplicationLocales().get(0);
if (customLocale != null) { return loc != null ? loc.getDisplayName() : getString(R.string.systems_language);
return customLocale.getDisplayName();
}
return getString(R.string.systems_language);
}); });
newAppLanguagePref.setOnPreferenceClickListener(preference -> { newAppLanguagePref.setOnPreferenceClickListener(preference -> {
final Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS) final Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS)
@ -55,22 +57,32 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
return true; return true;
}); });
newAppLanguagePref.setVisible(true); newAppLanguagePref.setVisible(true);
return;
} }
final Preference imageQualityPreference = requirePreference(R.string.image_quality_key); appLanguagePref.setOnPreferenceChangeListener((preference, newValue) -> {
imageQualityPreference.setOnPreferenceChangeListener( final String language = (String) newValue;
(preference, newValue) -> { final String systemLang = getString(R.string.default_localization_key);
ImageStrategy.setPreferredImageQuality(PreferredImageQuality final String tag = systemLang.equals(language) ? null : language;
.fromPreferenceKey(requireContext(), (String) newValue)); AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(tag));
final var loader = SingletonImageLoader.get(preference.getContext()); return true;
loader.getMemoryCache().clear(); });
loader.getDiskCache().clear(); }
Toast.makeText(preference.getContext(),
R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT)
.show();
return true; private void setupImageQualityPref() {
}); requirePreference(R.string.image_quality_key).setOnPreferenceChangeListener(
(preference, newValue) -> {
ImageStrategy.setPreferredImageQuality(PreferredImageQuality
.fromPreferenceKey(requireContext(), (String) newValue));
final var loader = SingletonImageLoader.get(preference.getContext());
loader.getMemoryCache().clear();
loader.getDiskCache().clear();
Toast.makeText(preference.getContext(),
R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT)
.show();
return true;
});
} }
@Override @Override
@ -91,22 +103,10 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
final String selectedLanguage = final Context context = requireContext();
defaultPreferences.getString(getString(R.string.app_language_key), "en"); NewPipe.setupLocalization(
Localization.getPreferredLocalization(context),
if (!selectedLanguage.equals(initialLanguage)) { Localization.getPreferredContentCountry(context));
if (Build.VERSION.SDK_INT < 33) { PlayerHelper.resetFormat();
Toast.makeText(
requireContext(),
R.string.localization_changes_requires_app_restart,
Toast.LENGTH_LONG
).show();
}
final Localization selectedLocalization = org.schabi.newpipe.util.Localization
.getPreferredLocalization(requireContext());
final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization
.getPreferredContentCountry(requireContext());
NewPipe.setupLocalization(selectedLocalization, selectedContentCountry);
}
} }
} }

View file

@ -1,7 +1,5 @@
package org.schabi.newpipe.settings; package org.schabi.newpipe.settings;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Activity; import android.app.Activity;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
@ -209,8 +207,6 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
} }
private void requestDownloadPathResult(final ActivityResult result, final String key) { private void requestDownloadPathResult(final ActivityResult result, final String key) {
assureCorrectAppLanguage(getContext());
if (result.getResultCode() != Activity.RESULT_OK) { if (result.getResultCode() != Activity.RESULT_OK) {
return; return;
} }

View file

@ -13,6 +13,7 @@ import androidx.preference.PreferenceManager;
import org.schabi.newpipe.App; import org.schabi.newpipe.App;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.migration.MigrationManager;
import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.DeviceUtils;
import java.io.File; import java.io.File;
@ -46,7 +47,7 @@ public final class NewPipeSettings {
public static void initSettings(final Context context) { public static void initSettings(final Context context) {
// first run migrations, then setDefaultValues, since the latter requires the correct types // first run migrations, then setDefaultValues, since the latter requires the correct types
SettingMigrations.runMigrationsIfNeeded(context); MigrationManager.runMigrationsIfNeeded(context);
// readAgain is true so that if new settings are added their default value is set // readAgain is true so that if new settings are added their default value is set
PreferenceManager.setDefaultValues(context, R.xml.main_settings, true); PreferenceManager.setDefaultValues(context, R.xml.main_settings, true);

View file

@ -1,7 +1,5 @@
package org.schabi.newpipe.settings; package org.schabi.newpipe.settings;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
@ -89,7 +87,6 @@ public class SettingsActivity extends AppCompatActivity implements
@Override @Override
protected void onCreate(final Bundle savedInstanceBundle) { protected void onCreate(final Bundle savedInstanceBundle) {
setTheme(ThemeHelper.getSettingsThemeStyle(this)); setTheme(ThemeHelper.getSettingsThemeStyle(this));
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceBundle); super.onCreate(savedInstanceBundle);
Bridge.restoreInstanceState(this, savedInstanceBundle); Bridge.restoreInstanceState(this, savedInstanceBundle);
@ -228,7 +225,6 @@ public class SettingsActivity extends AppCompatActivity implements
// Build search items // Build search items
final Context searchContext = getApplicationContext(); final Context searchContext = getApplicationContext();
assureCorrectAppLanguage(searchContext);
final PreferenceParser parser = new PreferenceParser(searchContext, config); final PreferenceParser parser = new PreferenceParser(searchContext, config);
final PreferenceSearcher searcher = new PreferenceSearcher(config); final PreferenceSearcher searcher = new PreferenceSearcher(config);

View file

@ -1,11 +1,13 @@
package org.schabi.newpipe.settings.export package org.schabi.newpipe.settings.export
import java.io.File import android.content.Context
import java.nio.file.Path
import kotlin.io.path.div
/** /**
* Locates specific files of NewPipe based on the home directory of the app. * Locates specific files of NewPipe based on the home directory of the app.
*/ */
class BackupFileLocator(private val homeDir: File) { class BackupFileLocator(context: Context) {
companion object { companion object {
const val FILE_NAME_DB = "newpipe.db" const val FILE_NAME_DB = "newpipe.db"
@Deprecated( @Deprecated(
@ -16,13 +18,8 @@ class BackupFileLocator(private val homeDir: File) {
const val FILE_NAME_JSON_PREFS = "preferences.json" const val FILE_NAME_JSON_PREFS = "preferences.json"
} }
val dbDir by lazy { File(homeDir, "/databases") } val db: Path = context.getDatabasePath(FILE_NAME_DB).toPath()
val dbJournal: Path = db.resolveSibling("$FILE_NAME_DB-journal")
val db by lazy { File(dbDir, FILE_NAME_DB) } val dbShm: Path = db.resolveSibling("$FILE_NAME_DB-shm")
val dbWal: Path = db.resolveSibling("$FILE_NAME_DB-wal")
val dbJournal by lazy { File(dbDir, "$FILE_NAME_DB-journal") }
val dbShm by lazy { File(dbDir, "$FILE_NAME_DB-shm") }
val dbWal by lazy { File(dbDir, "$FILE_NAME_DB-wal") }
} }

View file

@ -12,6 +12,8 @@ import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.ObjectOutputStream import java.io.ObjectOutputStream
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
import kotlin.io.path.createParentDirectories
import kotlin.io.path.deleteIfExists
class ImportExportManager(private val fileLocator: BackupFileLocator) { class ImportExportManager(private val fileLocator: BackupFileLocator) {
companion object { companion object {
@ -28,11 +30,8 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) {
// previous file size, the file will retain part of the previous content and be corrupted // previous file size, the file will retain part of the previous content and be corrupted
ZipOutputStream(SharpOutputStream(file.openAndTruncateStream()).buffered()).use { outZip -> ZipOutputStream(SharpOutputStream(file.openAndTruncateStream()).buffered()).use { outZip ->
// add the database // add the database
ZipHelper.addFileToZip( val name = BackupFileLocator.FILE_NAME_DB
outZip, ZipHelper.addFileToZip(outZip, name, fileLocator.db)
BackupFileLocator.FILE_NAME_DB,
fileLocator.db.path,
)
// add the legacy vulnerable serialized preferences (will be removed in the future) // add the legacy vulnerable serialized preferences (will be removed in the future)
ZipHelper.addFileToZip( ZipHelper.addFileToZip(
@ -61,11 +60,10 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) {
/** /**
* Tries to create database directory if it does not exist. * Tries to create database directory if it does not exist.
*
* @return Whether the directory exists afterwards.
*/ */
fun ensureDbDirectoryExists(): Boolean { @Throws(IOException::class)
return fileLocator.dbDir.exists() || fileLocator.dbDir.mkdir() fun ensureDbDirectoryExists() {
fileLocator.db.createParentDirectories()
} }
/** /**
@ -75,16 +73,13 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) {
* @return true if the database was successfully extracted, false otherwise * @return true if the database was successfully extracted, false otherwise
*/ */
fun extractDb(file: StoredFileHelper): Boolean { fun extractDb(file: StoredFileHelper): Boolean {
val success = ZipHelper.extractFileFromZip( val name = BackupFileLocator.FILE_NAME_DB
file, val success = ZipHelper.extractFileFromZip(file, name, fileLocator.db)
BackupFileLocator.FILE_NAME_DB,
fileLocator.db.path,
)
if (success) { if (success) {
fileLocator.dbJournal.delete() fileLocator.dbJournal.deleteIfExists()
fileLocator.dbWal.delete() fileLocator.dbWal.deleteIfExists()
fileLocator.dbShm.delete() fileLocator.dbShm.deleteIfExists()
} }
return success return success

View file

@ -0,0 +1,103 @@
package org.schabi.newpipe.settings.migration;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.util.Consumer;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorUtil;
import java.util.ArrayList;
import java.util.List;
/**
* MigrationManager is responsible for running migrations and showing the user information about
* the migrations that were applied.
*/
public final class MigrationManager {
private static final String TAG = MigrationManager.class.getSimpleName();
/**
* List of UI actions that are performed after the UI is initialized (e.g. showing alert
* dialogs) to inform the user about changes that were applied by migrations.
*/
private static final List<Consumer<Context>> MIGRATION_INFO = new ArrayList<>();
private MigrationManager() {
// MigrationManager is a utility class that is completely static
}
/**
* Run all migrations that are needed for the current version of NewPipe.
* This method should be called at the start of the application, before any other operations
* that depend on the settings.
*
* @param context Context that can be used to run migrations
*/
public static void runMigrationsIfNeeded(@NonNull final Context context) {
SettingMigrations.runMigrationsIfNeeded(context);
}
/**
* Perform UI actions informing about migrations that took place if they are present.
* @param context Context that can be used to show dialogs/snackbars/toasts
*/
public static void showUserInfoIfPresent(@NonNull final Context context) {
if (MIGRATION_INFO.isEmpty()) {
return;
}
try {
MIGRATION_INFO.get(0).accept(context);
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(context, "Showing migration info to the user", e);
// Remove the migration that caused the error and continue with the next one
MIGRATION_INFO.remove(0);
showUserInfoIfPresent(context);
}
}
/**
* Add a migration info action that will be executed after the UI is initialized.
* This can be used to show dialogs/snackbars/toasts to inform the user about changes that
* were applied by migrations.
*
* @param info the action to be executed
*/
public static void addMigrationInfo(final Consumer<Context> info) {
MIGRATION_INFO.add(info);
}
/**
* This method should be called when the user dismisses the migration info
* to check if there are any more migration info actions to be shown.
* @param context Context that can be used to show dialogs/snackbars/toasts
*/
public static void onMigrationInfoDismissed(@NonNull final Context context) {
MIGRATION_INFO.remove(0);
showUserInfoIfPresent(context);
}
/**
* Creates a dialog to inform the user about the migration.
* @param uiContext Context that can be used to show dialogs/snackbars/toasts
* @param title the title of the dialog
* @param message the message of the dialog
* @return the dialog that can be shown to the user with a custom dismiss listener
*/
static AlertDialog createMigrationInfoDialog(@NonNull final Context uiContext,
@NonNull final String title,
@NonNull final String message) {
return new AlertDialog.Builder(uiContext)
.setTitle(title)
.setMessage(message)
.setPositiveButton(R.string.ok, null)
.setOnDismissListener(dialog ->
MigrationManager.onMigrationInfoDismissed(uiContext))
.setCancelable(false) // prevents the dialog from being dismissed accidentally
.create();
}
}

View file

@ -1,12 +1,15 @@
package org.schabi.newpipe.settings; package org.schabi.newpipe.settings.migration;
import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.MainActivity.DEBUG;
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import org.schabi.newpipe.App; import org.schabi.newpipe.App;
@ -14,18 +17,32 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager;
import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.DeviceUtils;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
/** /**
* In order to add a migration, follow these steps, given P is the previous version:<br> * This class contains the code to migrate the settings from one version to another.
* - in the class body add a new {@code MIGRATION_P_P+1 = new Migration(P, P+1) { ... }} and put in * Migrations are run automatically when the app is started and the settings version changed.
* the {@code migrate()} method the code that need to be run when migrating from P to P+1<br> * <br>
* - add {@code MIGRATION_P_P+1} at the end of {@link SettingMigrations#SETTING_MIGRATIONS}<br> * In order to add a migration, follow these steps, given {@code P} is the previous version:
* - increment {@link SettingMigrations#VERSION}'s value by 1 (so it should become P+1) * <ul>
* <li>in the class body add a new {@code MIGRATION_P_P+1 = new Migration(P, P+1) { ... }} and put
* in the {@code migrate()} method the code that need to be run
* when migrating from {@code P} to {@code P+1}</li>
* <li>add {@code MIGRATION_P_P+1} at the end of {@link SettingMigrations#SETTING_MIGRATIONS}</li>
* <li>increment {@link SettingMigrations#VERSION}'s value by 1
* (so it becomes {@code P+1})</li>
* </ul>
* Migrations can register UI actions using {@link MigrationManager#addMigrationInfo(Consumer)}
* that will be performed after the UI is initialized to inform the user about changes
* that were applied by migrations.
*/ */
public final class SettingMigrations { public final class SettingMigrations {
@ -129,7 +146,7 @@ public final class SettingMigrations {
} }
}; };
public static final Migration MIGRATION_5_6 = new Migration(5, 6) { private static final Migration MIGRATION_5_6 = new Migration(5, 6) {
@Override @Override
protected void migrate(@NonNull final Context context) { protected void migrate(@NonNull final Context context) {
final boolean loadImages = sp.getBoolean("download_thumbnail_key", true); final boolean loadImages = sp.getBoolean("download_thumbnail_key", true);
@ -143,6 +160,67 @@ public final class SettingMigrations {
} }
}; };
private static final Migration MIGRATION_6_7 = new Migration(6, 7) {
@Override
protected void migrate(@NonNull final Context context) {
// The SoundCloud Top 50 Kiosk was removed in the extractor,
// so we remove the corresponding tab if it exists.
final TabsManager tabsManager = TabsManager.getManager(context);
final List<Tab> tabs = tabsManager.getTabs();
final List<Tab> cleanedTabs = tabs.stream()
.filter(tab -> !(tab instanceof Tab.KioskTab kioskTab
&& kioskTab.getKioskServiceId() == SoundCloud.getServiceId()
&& kioskTab.getKioskId().equals("Top 50")))
.collect(Collectors.toUnmodifiableList());
if (tabs.size() != cleanedTabs.size()) {
tabsManager.saveTabs(cleanedTabs);
// create an AlertDialog to inform the user about the change
MigrationManager.addMigrationInfo(uiContext ->
MigrationManager.createMigrationInfoDialog(
uiContext,
uiContext.getString(R.string.migration_info_6_7_title),
uiContext.getString(R.string.migration_info_6_7_message))
.show());
}
}
};
private static final Migration MIGRATION_7_8 = new Migration(7, 8) {
@Override
protected void migrate(@NonNull final Context context) {
// YouTube remove the combined Trending kiosk, see
// https://github.com/TeamNewPipe/NewPipe/discussions/12445 for more information.
// If the user has a dedicated YouTube/Trending kiosk tab,
// it is removed and replaced with the new live kiosk tab.
// The default trending kiosk tab is not touched
// because it uses the default kiosk provided by the extractor
// and is thus updated automatically.
final TabsManager tabsManager = TabsManager.getManager(context);
final List<Tab> tabs = tabsManager.getTabs();
final List<Tab> cleanedTabs = tabs.stream()
.filter(tab -> !(tab instanceof Tab.KioskTab kioskTab
&& kioskTab.getKioskServiceId() == YouTube.getServiceId()
&& kioskTab.getKioskId().equals("Trending")))
.collect(Collectors.toUnmodifiableList());
if (tabs.size() != cleanedTabs.size()) {
tabsManager.saveTabs(cleanedTabs);
}
final boolean hasDefaultTrendingTab = tabs.stream()
.anyMatch(tab -> tab instanceof Tab.DefaultKioskTab);
if (tabs.size() != cleanedTabs.size() || hasDefaultTrendingTab) {
// User is informed about the change
MigrationManager.addMigrationInfo(uiContext ->
MigrationManager.createMigrationInfoDialog(
uiContext,
uiContext.getString(R.string.migration_info_7_8_title),
uiContext.getString(R.string.migration_info_7_8_message))
.show());
}
}
};
/** /**
* List of all implemented migrations. * List of all implemented migrations.
* <p> * <p>
@ -156,15 +234,17 @@ public final class SettingMigrations {
MIGRATION_3_4, MIGRATION_3_4,
MIGRATION_4_5, MIGRATION_4_5,
MIGRATION_5_6, MIGRATION_5_6,
MIGRATION_6_7,
MIGRATION_7_8,
}; };
/** /**
* Version number for preferences. Must be incremented every time a migration is necessary. * Version number for preferences. Must be incremented every time a migration is necessary.
*/ */
private static final int VERSION = 6; private static final int VERSION = 8;
public static void runMigrationsIfNeeded(@NonNull final Context context) { static void runMigrationsIfNeeded(@NonNull final Context context) {
// setup migrations and check if there is something to do // setup migrations and check if there is something to do
sp = PreferenceManager.getDefaultSharedPreferences(context); sp = PreferenceManager.getDefaultSharedPreferences(context);
final String lastPrefVersionKey = context.getString(R.string.last_used_preferences_version); final String lastPrefVersionKey = context.getString(R.string.last_used_preferences_version);

View file

@ -28,10 +28,9 @@ fun StreamMenu(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val streamViewModel = viewModel<StreamViewModel>() val streamViewModel = viewModel<StreamViewModel>()
val playerHolder = PlayerHolder.getInstance()
DropdownMenu(expanded = expanded, onDismissRequest = onDismissRequest) { DropdownMenu(expanded = expanded, onDismissRequest = onDismissRequest) {
if (playerHolder.isPlayQueueReady) { if (PlayerHolder.isPlayQueueReady) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(R.string.enqueue_stream)) }, text = { Text(text = stringResource(R.string.enqueue_stream)) },
onClick = { onClick = {
@ -42,7 +41,7 @@ fun StreamMenu(
} }
) )
if (playerHolder.queuePosition < playerHolder.queueSize - 1) { if (PlayerHolder.queuePosition < PlayerHolder.queueSize - 1) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(R.string.enqueue_next_stream)) }, text = { Text(text = stringResource(R.string.enqueue_next_stream)) },
onClick = { onClick = {

View file

@ -81,12 +81,11 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, onCommentAuthorOpened: () ->
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
) )
Text( Localization.relativeTimeOrTextual(
text = Localization.relativeTimeOrTextual( context, comment.uploadDate, comment.textualUploadDate
context, comment.uploadDate, comment.textualUploadDate )?.let {
), Text(text = it, style = MaterialTheme.typography.bodySmall)
style = MaterialTheme.typography.bodySmall, }
)
} }
} }

View file

@ -24,6 +24,7 @@ public final class ChannelTabHelper {
switch (tab) { switch (tab) {
case ChannelTabs.VIDEOS: case ChannelTabs.VIDEOS:
case ChannelTabs.TRACKS: case ChannelTabs.TRACKS:
case ChannelTabs.LIKES:
case ChannelTabs.SHORTS: case ChannelTabs.SHORTS:
case ChannelTabs.LIVESTREAMS: case ChannelTabs.LIVESTREAMS:
return true; return true;
@ -62,6 +63,8 @@ public final class ChannelTabHelper {
return R.string.show_channel_tabs_playlists; return R.string.show_channel_tabs_playlists;
case ChannelTabs.ALBUMS: case ChannelTabs.ALBUMS:
return R.string.show_channel_tabs_albums; return R.string.show_channel_tabs_albums;
case ChannelTabs.LIKES:
return R.string.show_channel_tabs_likes;
default: default:
return -1; return -1;
} }
@ -78,6 +81,8 @@ public final class ChannelTabHelper {
return R.string.fetch_channel_tabs_shorts; return R.string.fetch_channel_tabs_shorts;
case ChannelTabs.LIVESTREAMS: case ChannelTabs.LIVESTREAMS:
return R.string.fetch_channel_tabs_livestreams; return R.string.fetch_channel_tabs_livestreams;
case ChannelTabs.LIKES:
return R.string.fetch_channel_tabs_likes;
default: default:
return -1; return -1;
} }
@ -100,6 +105,8 @@ public final class ChannelTabHelper {
return R.string.channel_tab_playlists; return R.string.channel_tab_playlists;
case ChannelTabs.ALBUMS: case ChannelTabs.ALBUMS:
return R.string.channel_tab_albums; return R.string.channel_tab_albums;
case ChannelTabs.LIKES:
return R.string.channel_tab_likes;
default: default:
return R.string.unknown_content; return R.string.unknown_content;
} }

View file

@ -52,6 +52,14 @@ public final class KioskTranslator {
return c.getString(R.string.featured); return c.getString(R.string.featured);
case "Radio": case "Radio":
return c.getString(R.string.radio); return c.getString(R.string.radio);
case "trending_gaming":
return c.getString(R.string.trending_gaming);
case "trending_music":
return c.getString(R.string.trending_music);
case "trending_movies_and_shows":
return c.getString(R.string.trending_movies);
case "trending_podcasts_episodes":
return c.getString(R.string.trending_podcasts);
default: default:
return kioskId; return kioskId;
} }
@ -77,6 +85,14 @@ public final class KioskTranslator {
return R.drawable.ic_stars; return R.drawable.ic_stars;
case "Radio": case "Radio":
return R.drawable.ic_radio; return R.drawable.ic_radio;
case "trending_gaming":
return R.drawable.ic_videogame_asset;
case "trending_music":
return R.drawable.ic_music_note;
case "trending_movies_and_shows":
return R.drawable.ic_movie;
case "trending_podcasts_episodes":
return R.drawable.ic_podcasts;
default: default:
return 0; return 0;
} }

View file

@ -322,7 +322,7 @@ public final class ListHelper {
} }
// Sort collected streams by name // Sort collected streams by name
return collectedStreams.values().stream().sorted(getAudioTrackNameComparator(context)) return collectedStreams.values().stream().sorted(getAudioTrackNameComparator())
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -359,7 +359,7 @@ public final class ListHelper {
} }
// Sort tracks alphabetically, sort track streams by quality // Sort tracks alphabetically, sort track streams by quality
final Comparator<AudioStream> nameCmp = getAudioTrackNameComparator(context); final Comparator<AudioStream> nameCmp = getAudioTrackNameComparator();
final Comparator<AudioStream> formatCmp = getAudioFormatComparator(context); final Comparator<AudioStream> formatCmp = getAudioFormatComparator(context);
return collectedStreams.values().stream() return collectedStreams.values().stream()
@ -867,12 +867,10 @@ public final class ListHelper {
* Get a {@link Comparator} to compare {@link AudioStream}s by their languages and track types * Get a {@link Comparator} to compare {@link AudioStream}s by their languages and track types
* for alphabetical sorting. * for alphabetical sorting.
* *
* @param context app context for localization
* @return Comparator * @return Comparator
*/ */
private static Comparator<AudioStream> getAudioTrackNameComparator( private static Comparator<AudioStream> getAudioTrackNameComparator() {
@NonNull final Context context) { final Locale appLoc = Localization.getAppLocale();
final Locale appLoc = Localization.getAppLocale(context);
return Comparator.comparing(AudioStream::getAudioLocale, Comparator.nullsLast( return Comparator.comparing(AudioStream::getAudioLocale, Comparator.nullsLast(
Comparator.comparing(locale -> locale.getDisplayName(appLoc)))) Comparator.comparing(locale -> locale.getDisplayName(appLoc))))

View file

@ -5,14 +5,12 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.icu.text.CompactDecimalFormat; import android.icu.text.CompactDecimalFormat;
import android.os.Build; import android.os.Build;
import android.text.BidiFormatter;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.text.BidiFormatter;
import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -43,7 +41,6 @@ import java.time.format.FormatStyle;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -120,39 +117,34 @@ public final class Localization {
return getLocaleFromPrefs(context, R.string.content_language_key); return getLocaleFromPrefs(context, R.string.content_language_key);
} }
public static Locale getAppLocale(@NonNull final Context context) { public static Locale getAppLocale() {
if (Build.VERSION.SDK_INT >= 33) { final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0);
final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0); return customLocale != null ? customLocale : Locale.getDefault();
return Objects.requireNonNullElseGet(customLocale, Locale::getDefault);
}
return getLocaleFromPrefs(context, R.string.app_language_key);
} }
public static String localizeNumber(@NonNull final Context context, final long number) { public static String localizeNumber(final long number) {
return localizeNumber(context, (double) number); return localizeNumber((double) number);
} }
public static String localizeNumber(@NonNull final Context context, final double number) { public static String localizeNumber(final double number) {
final NumberFormat nf = NumberFormat.getInstance(getAppLocale(context)); return NumberFormat.getInstance(getAppLocale()).format(number);
return nf.format(number);
} }
public static String formatDate(@NonNull final Context context, public static String formatDate(@NonNull final OffsetDateTime offsetDateTime) {
@NonNull final OffsetDateTime offsetDateTime) {
return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(getAppLocale(context)).format(offsetDateTime .withLocale(getAppLocale())
.atZoneSameInstant(ZoneId.systemDefault())); .format(offsetDateTime.atZoneSameInstant(ZoneId.systemDefault()));
} }
@SuppressLint("StringFormatInvalid") @SuppressLint("StringFormatInvalid")
public static String localizeUploadDate(@NonNull final Context context, public static String localizeUploadDate(@NonNull final Context context,
@NonNull final OffsetDateTime offsetDateTime) { @NonNull final OffsetDateTime offsetDateTime) {
return context.getString(R.string.upload_date_text, formatDate(context, offsetDateTime)); return context.getString(R.string.upload_date_text, formatDate(offsetDateTime));
} }
public static String localizeViewCount(@NonNull final Context context, final long viewCount) { public static String localizeViewCount(@NonNull final Context context, final long viewCount) {
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
localizeNumber(context, viewCount)); localizeNumber(viewCount));
} }
public static String localizeStreamCount(@NonNull final Context context, public static String localizeStreamCount(@NonNull final Context context,
@ -166,7 +158,7 @@ public final class Localization {
return context.getResources().getString(R.string.more_than_100_videos); return context.getResources().getString(R.string.more_than_100_videos);
default: default:
return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount,
localizeNumber(context, streamCount)); localizeNumber(streamCount));
} }
} }
@ -187,27 +179,33 @@ public final class Localization {
public static String localizeWatchingCount(@NonNull final Context context, public static String localizeWatchingCount(@NonNull final Context context,
final long watchingCount) { final long watchingCount) {
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
localizeNumber(context, watchingCount)); localizeNumber(watchingCount));
} }
public static String shortCount(@NonNull final Context context, final long count) { public static String shortCount(@NonNull final Context context, final long count) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return CompactDecimalFormat.getInstance(getAppLocale(context), return CompactDecimalFormat.getInstance(getAppLocale(),
CompactDecimalFormat.CompactStyle.SHORT).format(count); CompactDecimalFormat.CompactStyle.SHORT).format(count);
} }
final double value = (double) count; final double value = (double) count;
if (count >= 1000000000) { if (count >= 1000000000) {
return localizeNumber(context, round(value / 1000000000)) final double shortenedValue = value / 1000000000;
+ context.getString(R.string.short_billion); final int scale = shortenedValue >= 100 ? 0 : 1;
return context.getString(R.string.short_billion,
localizeNumber(round(shortenedValue, scale)));
} else if (count >= 1000000) { } else if (count >= 1000000) {
return localizeNumber(context, round(value / 1000000)) final double shortenedValue = value / 1000000;
+ context.getString(R.string.short_million); final int scale = shortenedValue >= 100 ? 0 : 1;
return context.getString(R.string.short_million,
localizeNumber(round(shortenedValue, scale)));
} else if (count >= 1000) { } else if (count >= 1000) {
return localizeNumber(context, round(value / 1000)) final double shortenedValue = value / 1000;
+ context.getString(R.string.short_thousand); final int scale = shortenedValue >= 100 ? 0 : 1;
return context.getString(R.string.short_thousand,
localizeNumber(round(shortenedValue, scale)));
} else { } else {
return localizeNumber(context, value); return localizeNumber(value);
} }
} }
@ -372,8 +370,8 @@ public final class Localization {
prettyTime.removeUnit(Decade.class); prettyTime.removeUnit(Decade.class);
} }
public static PrettyTime resolvePrettyTime(@NonNull final Context context) { public static PrettyTime resolvePrettyTime() {
return new PrettyTime(getAppLocale(context)); return new PrettyTime(getAppLocale());
} }
public static String relativeTime(@NonNull final OffsetDateTime offsetDateTime) { public static String relativeTime(@NonNull final OffsetDateTime offsetDateTime) {
@ -391,9 +389,10 @@ public final class Localization {
* {@code parsed != null} and the relevant setting is enabled, {@code textual} will * {@code parsed != null} and the relevant setting is enabled, {@code textual} will
* be appended to the returned string for debugging purposes. * be appended to the returned string for debugging purposes.
*/ */
@Nullable
public static String relativeTimeOrTextual(@Nullable final Context context, public static String relativeTimeOrTextual(@Nullable final Context context,
@Nullable final DateWrapper parsed, @Nullable final DateWrapper parsed,
final String textual) { @Nullable final String textual) {
if (parsed == null) { if (parsed == null) {
return textual; return textual;
} else if (DEBUG && context != null && PreferenceManager } else if (DEBUG && context != null && PreferenceManager
@ -405,14 +404,6 @@ public final class Localization {
} }
} }
public static void assureCorrectAppLanguage(final Context c) {
final Resources res = c.getResources();
final DisplayMetrics dm = res.getDisplayMetrics();
final Configuration conf = res.getConfiguration();
conf.setLocale(getAppLocale(c));
res.updateConfiguration(conf, dm);
}
private static Locale getLocaleFromPrefs(@NonNull final Context context, private static Locale getLocaleFromPrefs(@NonNull final Context context,
@StringRes final int prefKey) { @StringRes final int prefKey) {
final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
@ -426,8 +417,8 @@ public final class Localization {
} }
} }
private static double round(final double value) { private static double round(final double value, final int scale) {
return new BigDecimal(value).setScale(1, RoundingMode.HALF_UP).doubleValue(); return new BigDecimal(value).setScale(scale, RoundingMode.HALF_UP).doubleValue();
} }
private static String getQuantity(@NonNull final Context context, private static String getQuantity(@NonNull final Context context,
@ -447,29 +438,32 @@ public final class Localization {
return context.getResources().getQuantityString(pluralId, safeCount, formattedCount); return context.getResources().getQuantityString(pluralId, safeCount, formattedCount);
} }
// Starting with pull request #12093, NewPipe exclusively uses Android's
// public per-app language APIs to read and set the UI language for NewPipe.
// The following code will migrate any existing custom app language in SharedPreferences to
// use the public per-app language APIs instead.
// For reference, see
// https://android-developers.googleblog.com/2022/11/per-app-language-preferences-part-1.html
public static void migrateAppLanguageSettingIfNecessary(@NonNull final Context context) { public static void migrateAppLanguageSettingIfNecessary(@NonNull final Context context) {
// Starting with pull request #12093, NewPipe on Android 13+ exclusively uses Android's final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
// public per-app language APIs to read and set the UI language for NewPipe. final String appLanguageKey = context.getString(R.string.app_language_key);
// If running on Android 13+, the following code will migrate any existing custom final String appLanguageValue = sp.getString(appLanguageKey, null);
// app language in SharedPreferences to use the public per-app language APIs instead. if (appLanguageValue != null) {
if (Build.VERSION.SDK_INT >= 33) { // The app language key is used on Android versions < 33
final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); // for more info, see ContentSettingsFragment
final String appLanguageKey = context.getString(R.string.app_language_key); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
final String appLanguageValue = sp.getString(appLanguageKey, null);
if (appLanguageValue != null) {
sp.edit().remove(appLanguageKey).apply(); sp.edit().remove(appLanguageKey).apply();
final String appLanguageDefaultValue = }
context.getString(R.string.default_localization_key); final String appLanguageDefaultValue =
if (!appLanguageValue.equals(appLanguageDefaultValue)) { context.getString(R.string.default_localization_key);
try { if (!appLanguageValue.equals(appLanguageDefaultValue)) {
AppCompatDelegate.setApplicationLocales( try {
LocaleListCompat.forLanguageTags(appLanguageValue) AppCompatDelegate.setApplicationLocales(
); LocaleListCompat.forLanguageTags(appLanguageValue));
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
Log.e(TAG, "Failed to migrate previous custom app language " Log.e(TAG, "Failed to migrate previous custom app language "
+ "setting to public per-app language APIs" + "setting to public per-app language APIs"
); );
}
} }
} }
} }

View file

@ -200,7 +200,7 @@ public final class NavigationHelper {
} }
public static void enqueueOnPlayer(final Context context, final PlayQueue queue) { public static void enqueueOnPlayer(final Context context, final PlayQueue queue) {
PlayerType playerType = PlayerHolder.getInstance().getType(); PlayerType playerType = PlayerHolder.INSTANCE.getType();
if (playerType == null) { if (playerType == null) {
Log.e(TAG, "Enqueueing but no player is open; defaulting to background player"); Log.e(TAG, "Enqueueing but no player is open; defaulting to background player");
playerType = PlayerType.AUDIO; playerType = PlayerType.AUDIO;
@ -211,7 +211,7 @@ public final class NavigationHelper {
/* ENQUEUE NEXT */ /* ENQUEUE NEXT */
public static void enqueueNextOnPlayer(final Context context, final PlayQueue queue) { public static void enqueueNextOnPlayer(final Context context, final PlayQueue queue) {
PlayerType playerType = PlayerHolder.getInstance().getType(); PlayerType playerType = PlayerHolder.INSTANCE.getType();
if (playerType == null) { if (playerType == null) {
Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player"); Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player");
playerType = PlayerType.AUDIO; playerType = PlayerType.AUDIO;
@ -421,13 +421,13 @@ public final class NavigationHelper {
final boolean switchingPlayers) { final boolean switchingPlayers) {
final boolean autoPlay; final boolean autoPlay;
@Nullable final PlayerType playerType = PlayerHolder.getInstance().getType(); @Nullable final PlayerType playerType = PlayerHolder.INSTANCE.getType();
if (playerType == null) { if (playerType == null) {
// no player open // no player open
autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); autoPlay = PlayerHelper.isAutoplayAllowedByUser(context);
} else if (switchingPlayers) { } else if (switchingPlayers) {
// switching player to main player // switching player to main player
autoPlay = PlayerHolder.getInstance().isPlaying(); // keep play/pause state autoPlay = PlayerHolder.INSTANCE.isPlaying(); // keep play/pause state
} else if (playerType == PlayerType.MAIN) { } else if (playerType == PlayerType.MAIN) {
// opening new stream while already playing in main player // opening new stream while already playing in main player
autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); autoPlay = PlayerHelper.isAutoplayAllowedByUser(context);

View file

@ -6,12 +6,12 @@ import org.schabi.newpipe.streams.io.StoredFileHelper;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@ -37,9 +37,6 @@ import java.util.zip.ZipOutputStream;
*/ */
public final class ZipHelper { public final class ZipHelper {
private static final int BUFFER_SIZE = 2048;
@FunctionalInterface @FunctionalInterface
public interface InputStreamConsumer { public interface InputStreamConsumer {
void acceptStream(InputStream inputStream) throws IOException; void acceptStream(InputStream inputStream) throws IOException;
@ -55,17 +52,17 @@ public final class ZipHelper {
/** /**
* This function helps to create zip files. Caution this will overwrite the original file. * This function helps to create zip files. Caution, this will overwrite the original file.
* *
* @param outZip the ZipOutputStream where the data should be stored in * @param outZip the ZipOutputStream where the data should be stored in
* @param nameInZip the path of the file inside the zip * @param nameInZip the path of the file inside the zip
* @param fileOnDisk the path of the file on the disk that should be added to zip * @param path the path of the file on the disk that should be added to zip
*/ */
public static void addFileToZip(final ZipOutputStream outZip, public static void addFileToZip(final ZipOutputStream outZip,
final String nameInZip, final String nameInZip,
final String fileOnDisk) throws IOException { final Path path) throws IOException {
try (FileInputStream fi = new FileInputStream(fileOnDisk)) { try (var inputStream = Files.newInputStream(path)) {
addFileToZip(outZip, nameInZip, fi); addFileToZip(outZip, nameInZip, inputStream);
} }
} }
@ -80,13 +77,13 @@ public final class ZipHelper {
final String nameInZip, final String nameInZip,
final OutputStreamConsumer streamConsumer) throws IOException { final OutputStreamConsumer streamConsumer) throws IOException {
final byte[] bytes; final byte[] bytes;
try (ByteArrayOutputStream byteOutput = new ByteArrayOutputStream()) { try (var byteOutput = new ByteArrayOutputStream()) {
streamConsumer.acceptStream(byteOutput); streamConsumer.acceptStream(byteOutput);
bytes = byteOutput.toByteArray(); bytes = byteOutput.toByteArray();
} }
try (ByteArrayInputStream byteInput = new ByteArrayInputStream(bytes)) { try (var byteInput = new ByteArrayInputStream(bytes)) {
ZipHelper.addFileToZip(outZip, nameInZip, byteInput); addFileToZip(outZip, nameInZip, byteInput);
} }
} }
@ -97,49 +94,26 @@ public final class ZipHelper {
* @param nameInZip the path of the file inside the zip * @param nameInZip the path of the file inside the zip
* @param inputStream the content to put inside the file * @param inputStream the content to put inside the file
*/ */
public static void addFileToZip(final ZipOutputStream outZip, private static void addFileToZip(final ZipOutputStream outZip,
final String nameInZip, final String nameInZip,
final InputStream inputStream) throws IOException { final InputStream inputStream) throws IOException {
final byte[] data = new byte[BUFFER_SIZE]; outZip.putNextEntry(new ZipEntry(nameInZip));
try (BufferedInputStream bufferedInputStream = inputStream.transferTo(outZip);
new BufferedInputStream(inputStream, BUFFER_SIZE)) {
final ZipEntry entry = new ZipEntry(nameInZip);
outZip.putNextEntry(entry);
int count;
while ((count = bufferedInputStream.read(data, 0, BUFFER_SIZE)) != -1) {
outZip.write(data, 0, count);
}
}
} }
/** /**
* This will extract data from ZipInputStream. Caution this will overwrite the original file. * This will extract data from ZipInputStream. Caution, this will overwrite the original file.
* *
* @param zipFile the zip file to extract from * @param zipFile the zip file to extract from
* @param nameInZip the path of the file inside the zip * @param nameInZip the path of the file inside the zip
* @param fileOnDisk the path of the file on the disk where the data should be extracted to * @param path the path of the file on the disk where the data should be extracted to
* @return will return true if the file was found within the zip file * @return will return true if the file was found within the zip file
*/ */
public static boolean extractFileFromZip(final StoredFileHelper zipFile, public static boolean extractFileFromZip(final StoredFileHelper zipFile,
final String nameInZip, final String nameInZip,
final String fileOnDisk) throws IOException { final Path path) throws IOException {
return extractFileFromZip(zipFile, nameInZip, input -> { return extractFileFromZip(zipFile, nameInZip, input ->
// delete old file first Files.copy(input, path, StandardCopyOption.REPLACE_EXISTING));
final File oldFile = new File(fileOnDisk);
if (oldFile.exists()) {
if (!oldFile.delete()) {
throw new IOException("Could not delete " + fileOnDisk);
}
}
final byte[] data = new byte[BUFFER_SIZE];
try (FileOutputStream outFile = new FileOutputStream(fileOnDisk)) {
int count;
while ((count = input.read(data)) != -1) {
outFile.write(data, 0, count);
}
}
});
} }
/** /**

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M14,12c0,0.74 -0.4,1.38 -1,1.72V22h-2v-8.28c-0.6,-0.35 -1,-0.98 -1,-1.72c0,-1.1 0.9,-2 2,-2S14,10.9 14,12zM12,6c-3.31,0 -6,2.69 -6,6c0,1.74 0.75,3.31 1.94,4.4l1.42,-1.42C8.53,14.25 8,13.19 8,12c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,1.19 -0.53,2.25 -1.36,2.98l1.42,1.42C17.25,15.31 18,13.74 18,12C18,8.69 15.31,6 12,6zM12,2C6.48,2 2,6.48 2,12c0,2.85 1.2,5.41 3.11,7.24l1.42,-1.42C4.98,16.36 4,14.29 4,12c0,-4.41 3.59,-8 8,-8s8,3.59 8,8c0,2.29 -0.98,4.36 -2.53,5.82l1.42,1.42C20.8,17.41 22,14.85 22,12C22,6.48 17.52,2 12,2z"/>
</vector>

View file

@ -4,7 +4,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<include layout="@layout/main_bg" /> <include
android:id="@+id/blank_page_content"
layout="@layout/main_bg" />
<include <include
android:id="@+id/error_panel" android:id="@+id/error_panel"

View file

@ -2,5 +2,6 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/menu_services_group" /> <group android:id="@+id/menu_services_group" />
<group android:id="@+id/menu_tabs_group" /> <group android:id="@+id/menu_tabs_group" />
<group android:id="@+id/menu_kiosks_group" />
<group android:id="@+id/menu_options_about_group" /> <group android:id="@+id/menu_options_about_group" />
</menu> </menu>

View file

@ -75,13 +75,11 @@
<string name="downloads_title">الملفات المحملة</string> <string name="downloads_title">الملفات المحملة</string>
<string name="invalid_source">لا يوجد مثل هذا الملف/مصدر المحتوى</string> <string name="invalid_source">لا يوجد مثل هذا الملف/مصدر المحتوى</string>
<string name="most_liked">الأكثر إعجابًا</string> <string name="most_liked">الأكثر إعجابًا</string>
<string name="short_billion">بليون</string>
<string name="feed_load_error_account_info">تعذر تحميل موجز \'%s\'.</string> <string name="feed_load_error_account_info">تعذر تحميل موجز \'%s\'.</string>
<string name="question_mark">؟</string> <string name="question_mark">؟</string>
<string name="check_for_updates">التحقق من وجود تحديثات</string> <string name="check_for_updates">التحقق من وجود تحديثات</string>
<string name="peertube_instance_url_title">مثيلات خوادم پيرتيوب</string> <string name="peertube_instance_url_title">مثيلات خوادم پيرتيوب</string>
<string name="more_than_100_videos">+100 فيديو</string> <string name="more_than_100_videos">+100 فيديو</string>
<string name="short_thousand">ألف</string>
<string name="peertube_instance_add_exists">مثيل الخادم موجود بالفعل</string> <string name="peertube_instance_add_exists">مثيل الخادم موجود بالفعل</string>
<string name="clear_queue_confirmation_title">طلب تأكيد قبل مسح قائمة الانتظار</string> <string name="clear_queue_confirmation_title">طلب تأكيد قبل مسح قائمة الانتظار</string>
<string name="metadata_subscribers">المشتركون</string> <string name="metadata_subscribers">المشتركون</string>
@ -227,7 +225,6 @@
<string name="featured">المميزة</string> <string name="featured">المميزة</string>
<string name="show_age_restricted_content_summary">عرض المحتوى الذي يُحتمل أن يكون غير مناسب للأطفال لأن له حدًا عمريًا (مثل 18+)</string> <string name="show_age_restricted_content_summary">عرض المحتوى الذي يُحتمل أن يكون غير مناسب للأطفال لأن له حدًا عمريًا (مثل 18+)</string>
<string name="start_here_on_background">بدأ التشغيل في الخلفية</string> <string name="start_here_on_background">بدأ التشغيل في الخلفية</string>
<string name="localization_changes_requires_app_restart">ستتغير اللغة بمجرد إعادة تشغيل التطبيق</string>
<string name="channel_tab_shorts">القصيرة</string> <string name="channel_tab_shorts">القصيرة</string>
<string name="playlists">قوائم التشغيل</string> <string name="playlists">قوائم التشغيل</string>
<string name="clear">تنظيف</string> <string name="clear">تنظيف</string>
@ -649,7 +646,6 @@
<string name="seek_duration_title">تسريع إلى الأمام/-ترجيع وقت البحث</string> <string name="seek_duration_title">تسريع إلى الأمام/-ترجيع وقت البحث</string>
<string name="permission_denied">تم رفضها من قبل النظام</string> <string name="permission_denied">تم رفضها من قبل النظام</string>
<string name="no_comments">ليس هناك تعليقات</string> <string name="no_comments">ليس هناك تعليقات</string>
<string name="short_million">مليون</string>
<string name="checking_updates_toast">جاري التحقق من وجود تحديثات…</string> <string name="checking_updates_toast">جاري التحقق من وجود تحديثات…</string>
<string name="content">المحتوى</string> <string name="content">المحتوى</string>
<string name="downloads_storage_ask_title">اسأل عن مكان التنزيل</string> <string name="downloads_storage_ask_title">اسأل عن مكان التنزيل</string>

View file

@ -37,7 +37,7 @@
<string name="show_play_with_kodi_summary">اعرض خيار لتشغيل الفيديو عبر مركز وسائط Kodi</string> <string name="show_play_with_kodi_summary">اعرض خيار لتشغيل الفيديو عبر مركز وسائط Kodi</string>
<string name="show_play_with_kodi_title">عرض خيار التشغيل بواسطة كودي</string> <string name="show_play_with_kodi_title">عرض خيار التشغيل بواسطة كودي</string>
<string name="theme_title">السمة</string> <string name="theme_title">السمة</string>
<string name="upload_date_text">تم النشر في %1$s</string> <string name="upload_date_text">منشورة على %1$s</string>
<string name="unsupported_url">رابط غير مدعوم</string> <string name="unsupported_url">رابط غير مدعوم</string>
<string name="use_external_audio_player_title">استخدام مشغل صوت خارجي</string> <string name="use_external_audio_player_title">استخدام مشغل صوت خارجي</string>
<string name="use_external_video_player_title">استخدام مشغل فيديو خارجي</string> <string name="use_external_video_player_title">استخدام مشغل فيديو خارجي</string>
@ -116,9 +116,6 @@
<string name="empty_list_subtitle">لا شيء هنا سوى الصراصير</string> <string name="empty_list_subtitle">لا شيء هنا سوى الصراصير</string>
<string name="audio">الصوت</string> <string name="audio">الصوت</string>
<string name="retry">إعادة المحاولة</string> <string name="retry">إعادة المحاولة</string>
<string name="short_thousand">ألف</string>
<string name="short_million">مليون</string>
<string name="short_billion">بليون</string>
<string name="no_subscribers">ليس هناك مشترِكون</string> <string name="no_subscribers">ليس هناك مشترِكون</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="zero">%s مشارك</item> <item quantity="zero">%s مشارك</item>
@ -427,7 +424,6 @@
<string name="default_kiosk_page_summary">الكشك الافتراضي</string> <string name="default_kiosk_page_summary">الكشك الافتراضي</string>
<string name="no_one_watching">لا توجد مشاهدة</string> <string name="no_one_watching">لا توجد مشاهدة</string>
<string name="no_one_listening">لا أحد يستمع</string> <string name="no_one_listening">لا أحد يستمع</string>
<string name="localization_changes_requires_app_restart">ستتغير اللغة بمجرد إعادة تشغيل التطبيق</string>
<plurals name="watching"> <plurals name="watching">
<item quantity="zero">%s مشاهدة</item> <item quantity="zero">%s مشاهدة</item>
<item quantity="one">%s مشاهدة</item> <item quantity="one">%s مشاهدة</item>
@ -879,4 +875,14 @@
<string name="no">لا</string> <string name="no">لا</string>
<string name="import_settings_vulnerable_format">تستخدم الإعدادات الموجودة في عملية التصدير التي يتم استيرادها تنسيقًا عرضة للاختراق تم إهماله منذ NewPipe 0.27.0. تأكد من أن التصدير الذي يتم استيراده من مصدر موثوق به، ويفضل استخدام عمليات التصدير التي تم الحصول عليها من NewPipe 0.27.0 أو الأحدث في المستقبل فقط. سيتم قريبًا إزالة دعم استيراد الإعدادات بهذا التنسيق الضعيف تمامًا، وبعد ذلك لن تتمكن الإصدارات القديمة من NewPipe من استيراد إعدادات التصدير من الإصدارات الجديدة بعد الآن.</string> <string name="import_settings_vulnerable_format">تستخدم الإعدادات الموجودة في عملية التصدير التي يتم استيرادها تنسيقًا عرضة للاختراق تم إهماله منذ NewPipe 0.27.0. تأكد من أن التصدير الذي يتم استيراده من مصدر موثوق به، ويفضل استخدام عمليات التصدير التي تم الحصول عليها من NewPipe 0.27.0 أو الأحدث في المستقبل فقط. سيتم قريبًا إزالة دعم استيراد الإعدادات بهذا التنسيق الضعيف تمامًا، وبعد ذلك لن تتمكن الإصدارات القديمة من NewPipe من استيراد إعدادات التصدير من الإصدارات الجديدة بعد الآن.</string>
<string name="audio_track_type_secondary">الثانوي</string> <string name="audio_track_type_secondary">الثانوي</string>
<string name="share_playlist_as_youtube_temporary_playlist">المشاركة كقائمة تشغيل مؤقتة على YouTube</string>
<string name="tab_bookmarks_short">قوائم التشغيل</string>
<string name="feed_group_page_summary">صفحة مجموعة القناة</string>
<string name="select_a_feed_group">حدد مجموعة المحتوى</string>
<string name="no_feed_group_created_yet">لم تنشئ مجموعة محتوى</string>
<string name="channel_tab_likes">الإعجابات</string>
<string name="search_with_service_name">البحث %1$s</string>
<string name="search_with_service_name_and_filter">البحث %1$s (%2$s)</string>
<string name="migration_info_6_7_title">تمت إزالة صفحة أفضل 50 من SoundCloud</string>
<string name="migration_info_6_7_message">أوقفت SoundCloud صفحة أفضل 50 الأصلية. تمت إزالة علامة التبويب المقابلة من صفحتك الرئيسية.</string>
</resources> </resources>

View file

@ -297,9 +297,6 @@
<string name="detail_likes_img_view_description">Bəyən</string> <string name="detail_likes_img_view_description">Bəyən</string>
<string name="detail_dislikes_img_view_description">Bəyənmə</string> <string name="detail_dislikes_img_view_description">Bəyənmə</string>
<string name="detail_drag_description">Yenidən sıralamaq üçün sürüklə</string> <string name="detail_drag_description">Yenidən sıralamaq üçün sürüklə</string>
<string name="short_thousand">min</string>
<string name="short_million">Mln</string>
<string name="short_billion">Mlrd</string>
<string name="drawer_header_description">Xidməti dəyiş, hazırda seçilmiş:</string> <string name="drawer_header_description">Xidməti dəyiş, hazırda seçilmiş:</string>
<string name="no_subscribers">Abunəçi yoxdur</string> <string name="no_subscribers">Abunəçi yoxdur</string>
<string name="no_views">Baxış yoxdur</string> <string name="no_views">Baxış yoxdur</string>
@ -342,7 +339,6 @@
<string name="no_valid_zip_file">Etibarlı ZIP faylı yoxdur</string> <string name="no_valid_zip_file">Etibarlı ZIP faylı yoxdur</string>
<string name="could_not_import_all_files">Xəbərdarlıq: Bütün faylları idxal etmək mümkün olmadı.</string> <string name="could_not_import_all_files">Xəbərdarlıq: Bütün faylları idxal etmək mümkün olmadı.</string>
<string name="import_settings">Tənzimləmələri də idxal etmək istəyirsiniz\?</string> <string name="import_settings">Tənzimləmələri də idxal etmək istəyirsiniz\?</string>
<string name="localization_changes_requires_app_restart">Tətbiq yenidən başladıldıqdan sonra dil dəyişəcəkdir</string>
<string name="top_50">Ən yaxşı 50</string> <string name="top_50">Ən yaxşı 50</string>
<string name="new_and_hot">Yeni və populyar</string> <string name="new_and_hot">Yeni və populyar</string>
<string name="local">Yerli</string> <string name="local">Yerli</string>
@ -801,4 +797,9 @@
<string name="image_quality_summary">Məlumat və yaddaş istifadəsini azaltmaq üçün şəkillərin keyfiyyətini və ya şəkillərin əsla yüklənib-yüklənilməməsini seçin. Dəyişikliklər həm yaddaşdaxili, həm də diskdə olan təsvir qalığın təmizləyir — %s</string> <string name="image_quality_summary">Məlumat və yaddaş istifadəsini azaltmaq üçün şəkillərin keyfiyyətini və ya şəkillərin əsla yüklənib-yüklənilməməsini seçin. Dəyişikliklər həm yaddaşdaxili, həm də diskdə olan təsvir qalığın təmizləyir — %s</string>
<string name="share_playlist_with_list">URL siyahısını paylaşın</string> <string name="share_playlist_with_list">URL siyahısını paylaşın</string>
<string name="audio_track_type_secondary">ikinci dərəcəli</string> <string name="audio_track_type_secondary">ikinci dərəcəli</string>
<string name="share_playlist_as_youtube_temporary_playlist">YouTube müvəqqəti pleylisti kimi paylaş</string>
<string name="tab_bookmarks_short">Pleylistlər</string>
<string name="select_a_feed_group">Axın qrupu seçin</string>
<string name="no_feed_group_created_yet">Hələ heç bir axın qrupu yaradılmayıb</string>
<string name="feed_group_page_summary">Kanal qrupu səhifəsi</string>
</resources> </resources>

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View file

@ -43,9 +43,6 @@
<string name="detail_dislikes_img_view_description">Tarrezmes</string> <string name="detail_dislikes_img_view_description">Tarrezmes</string>
<string name="default_video_format_title">Formatu de videu predetermináu</string> <string name="default_video_format_title">Formatu de videu predetermináu</string>
<string name="black_theme_title">Prietu</string> <string name="black_theme_title">Prietu</string>
<string name="short_thousand">mil</string>
<string name="short_million">mill.</string>
<string name="short_billion">mil mill.</string>
<string name="msg_popup_permission">Precísase esti permisu p\'abrir <string name="msg_popup_permission">Precísase esti permisu p\'abrir
\nnel mou ventanu</string> \nnel mou ventanu</string>
<string name="title_activity_recaptcha">Retu de reCAPTCHA</string> <string name="title_activity_recaptcha">Retu de reCAPTCHA</string>
@ -223,7 +220,6 @@
<string name="privacy_policy_title">Política de privacidá de NewPipe</string> <string name="privacy_policy_title">Política de privacidá de NewPipe</string>
<string name="error_file_creation">El ficheru nun pue crease</string> <string name="error_file_creation">El ficheru nun pue crease</string>
<string name="error_http_no_content">El sirvidor nun unvia datos</string> <string name="error_http_no_content">El sirvidor nun unvia datos</string>
<string name="localization_changes_requires_app_restart">La llingua va camudar namás que se reanicie l\'aplicación.</string>
<string name="search">Buscar</string> <string name="search">Buscar</string>
<string name="share_dialog_title">Compartir con</string> <string name="share_dialog_title">Compartir con</string>
<string name="subscribed_button_title">Soscribiéstite</string> <string name="subscribed_button_title">Soscribiéstite</string>

View file

@ -267,9 +267,6 @@
</plurals> </plurals>
<string name="no_subscribers">Obunachilar yo\'q</string> <string name="no_subscribers">Obunachilar yo\'q</string>
<string name="drawer_header_description">Hozirda tanlangan xizmatni yoqish:</string> <string name="drawer_header_description">Hozirda tanlangan xizmatni yoqish:</string>
<string name="short_billion">B</string>
<string name="short_million">M</string>
<string name="short_thousand">k</string>
<string name="retry">Qayta</string> <string name="retry">Qayta</string>
<string name="audio">Audio</string> <string name="audio">Audio</string>
<string name="video">Video</string> <string name="video">Video</string>
@ -551,7 +548,6 @@
<string name="new_and_hot">Yangi va qaynoqlari</string> <string name="new_and_hot">Yangi va qaynoqlari</string>
<string name="top_50">Top 50</string> <string name="top_50">Top 50</string>
<string name="trending">Ommabop</string> <string name="trending">Ommabop</string>
<string name="localization_changes_requires_app_restart">Ilova qayta ishga tushirilgandan so\'ng til o\'zgaradi.</string>
<string name="error_unable_to_load_comments">Fikrlarni yuklab bolmadi</string> <string name="error_unable_to_load_comments">Fikrlarni yuklab bolmadi</string>
<string name="import_settings">Sozlamalarni ham import qilmoqchimisiz\?</string> <string name="import_settings">Sozlamalarni ham import qilmoqchimisiz\?</string>
<string name="override_current_data">Bu sizning joriy sozlamangizni bekor qiladi.</string> <string name="override_current_data">Bu sizning joriy sozlamangizni bekor qiladi.</string>

View file

@ -55,7 +55,7 @@
<string name="popup_remember_size_pos_summary">Памятаць апошнія памер і пазіцыю ўсплывальнага акна</string> <string name="popup_remember_size_pos_summary">Памятаць апошнія памер і пазіцыю ўсплывальнага акна</string>
<string name="use_inexact_seek_title">Хуткі пошук пазіцыі</string> <string name="use_inexact_seek_title">Хуткі пошук пазіцыі</string>
<string name="use_inexact_seek_summary">Недакладны пошук дазваляе плэеру знаходзіць пазіцыі хутчэй са зніжанай дакладнасцю. Пошук цягам 5, 15 ці 25 секунд пры гэтым немажлівы</string> <string name="use_inexact_seek_summary">Недакладны пошук дазваляе плэеру знаходзіць пазіцыі хутчэй са зніжанай дакладнасцю. Пошук цягам 5, 15 ці 25 секунд пры гэтым немажлівы</string>
<string name="thumbnail_cache_wipe_complete_notice">Кэш малюнкаў ачышчаны</string> <string name="thumbnail_cache_wipe_complete_notice">Кэш відарысаў ачышчаны</string>
<string name="metadata_cache_wipe_title">Ачысціць кэш метаданых</string> <string name="metadata_cache_wipe_title">Ачысціць кэш метаданых</string>
<string name="metadata_cache_wipe_summary">Выдаліць усе даныя вэб-старонак у кэшы</string> <string name="metadata_cache_wipe_summary">Выдаліць усе даныя вэб-старонак у кэшы</string>
<string name="metadata_cache_wipe_complete_notice">Кэш метаданых ачышчаны</string> <string name="metadata_cache_wipe_complete_notice">Кэш метаданых ачышчаны</string>
@ -71,8 +71,8 @@
<string name="resume_on_audio_focus_gain_summary">Працягваць прайграванне пасля перапынкаў (напрыклад, тэлефонных званкоў)</string> <string name="resume_on_audio_focus_gain_summary">Працягваць прайграванне пасля перапынкаў (напрыклад, тэлефонных званкоў)</string>
<string name="download_dialog_title">Спампаваць</string> <string name="download_dialog_title">Спампаваць</string>
<string name="show_next_and_similar_title">«Наступнае» і «Прапанаванае» відэа</string> <string name="show_next_and_similar_title">«Наступнае» і «Прапанаванае» відэа</string>
<string name="show_hold_to_append_title">Паказваць падказку «Зацісніце, каб дадаць»</string> <string name="show_hold_to_append_title">Паказваць падказку «Утрымлівайце, каб дадаць у чаргу»</string>
<string name="show_hold_to_append_summary">Паказваць падказку пры націсканні «У акне» або «У фоне» на старонцы звестак аб відэа</string> <string name="show_hold_to_append_summary">Паказваць падказку пры націсканні кнопкі «У акне» або «У фоне» на старонцы відэа</string>
<string name="unsupported_url">URL не падтрымліваецца</string> <string name="unsupported_url">URL не падтрымліваецца</string>
<string name="default_content_country_title">Прадвызначаная краіна кантэнту</string> <string name="default_content_country_title">Прадвызначаная краіна кантэнту</string>
<string name="content_language_title">Прадвызначаная мова кантэнту</string> <string name="content_language_title">Прадвызначаная мова кантэнту</string>
@ -92,7 +92,7 @@
<string name="error_report_title">Справаздача пра памылку</string> <string name="error_report_title">Справаздача пра памылку</string>
<string name="all">Усе</string> <string name="all">Усе</string>
<string name="channels">Каналы</string> <string name="channels">Каналы</string>
<string name="playlists">Плэйлісты</string> <string name="playlists">Плэй-лісты</string>
<string name="tracks">Трэкі</string> <string name="tracks">Трэкі</string>
<string name="users">Карыстальнікі</string> <string name="users">Карыстальнікі</string>
<string name="disabled">Адключана</string> <string name="disabled">Адключана</string>
@ -111,8 +111,8 @@
<string name="switch_to_main">Перайсці ў галоўнае акно</string> <string name="switch_to_main">Перайсці ў галоўнае акно</string>
<string name="import_data_title">Імпартаваць даныя</string> <string name="import_data_title">Імпартаваць даныя</string>
<string name="export_data_title">Экспартаваць даныя</string> <string name="export_data_title">Экспартаваць даныя</string>
<string name="import_data_summary">Перавызначае вашу бягучую гісторыю, падпіскі, плэйлісты і (неабавязкова) налады</string> <string name="import_data_summary">Перавызначае вашу бягучую гісторыю, падпіскі, плэй-лісты і (неабавязкова) налады</string>
<string name="export_data_summary">Экспарт гісторыі, падпісак, плэйлістоў і налад</string> <string name="export_data_summary">Экспарт гісторыі, падпісак, плэй-лістоў і налад</string>
<string name="clear_views_history_title">Ачысціць гісторыю праглядаў</string> <string name="clear_views_history_title">Ачысціць гісторыю праглядаў</string>
<string name="clear_views_history_summary">Выдаліць гісторыю прайграных патокаў і пазіцыі прайгравання</string> <string name="clear_views_history_summary">Выдаліць гісторыю прайграных патокаў і пазіцыі прайгравання</string>
<string name="delete_view_history_alert">Выдаліць усю гісторыю праглядаў\?</string> <string name="delete_view_history_alert">Выдаліць усю гісторыю праглядаў\?</string>
@ -135,9 +135,9 @@
<string name="video_streams_empty">Відэапатокі не знойдзены</string> <string name="video_streams_empty">Відэапатокі не знойдзены</string>
<string name="audio_streams_empty">Аўдыяпатокі не знойдзены</string> <string name="audio_streams_empty">Аўдыяпатокі не знойдзены</string>
<string name="invalid_directory">Такой папкі не існуе</string> <string name="invalid_directory">Такой папкі не існуе</string>
<string name="invalid_source">Крыніца кантэнту або файла не існуе</string> <string name="invalid_source">Такога файла або крыніцы кантэнту не існуе</string>
<string name="invalid_file">Файл не існуе або няма дазволу на яго чытанне ці запіс</string> <string name="invalid_file">Файл не існуе або няма дазволу на яго чытанне ці запіс</string>
<string name="file_name_empty_error">Імя файла не можа быць пустым</string> <string name="file_name_empty_error">Назва файла не можа быць пустой</string>
<string name="error_occurred_detail">Адбылася памылка: %1$s</string> <string name="error_occurred_detail">Адбылася памылка: %1$s</string>
<string name="no_streams_available_download">Няма трансляцый, даступных для спампоўвання</string> <string name="no_streams_available_download">Няма трансляцый, даступных для спампоўвання</string>
<string name="sorry_string">Прабачце, гэта не павінна было адбыцца.</string> <string name="sorry_string">Прабачце, гэта не павінна было адбыцца.</string>
@ -147,21 +147,18 @@
<string name="what_device_headline">Інфармацыя:</string> <string name="what_device_headline">Інфармацыя:</string>
<string name="what_happened_headline">Што адбылося:</string> <string name="what_happened_headline">Што адбылося:</string>
<string name="info_labels">Што:\\nЗапыт:\\nМова кантэнту:\\nКраіна кантэнту:\\nМова праграмы:\\nСэрвіс:\\nЧас GMT:\\nПакет:\\nВерсія:\\nВерсія АС:</string> <string name="info_labels">Што:\\nЗапыт:\\nМова кантэнту:\\nКраіна кантэнту:\\nМова праграмы:\\nСэрвіс:\\nЧас GMT:\\nПакет:\\nВерсія:\\nВерсія АС:</string>
<string name="your_comment">Ваш каментарый (на англійскай):</string> <string name="your_comment">Ваш каментарый (па-англійску):</string>
<string name="error_details_headline">Падрабязнасці:</string> <string name="error_details_headline">Падрабязнасці:</string>
<string name="detail_thumbnail_view_description">Прайграць відэа, працягласць:</string> <string name="detail_thumbnail_view_description">Прайграць відэа, працягласць:</string>
<string name="detail_uploader_thumbnail_view_description">Мініяцюра аватара карыстальніка</string> <string name="detail_uploader_thumbnail_view_description">Мініяцюра аватара карыстальніка</string>
<string name="detail_likes_img_view_description">Спадабалася</string> <string name="detail_likes_img_view_description">Спадабалася</string>
<string name="detail_dislikes_img_view_description">Не спадабалася</string> <string name="detail_dislikes_img_view_description">Не спадабалася</string>
<string name="search_no_results">Няма вынікаў</string> <string name="search_no_results">Няма вынікаў</string>
<string name="empty_list_subtitle">Нічога няма, акрамя цвыркуноў</string> <string name="empty_list_subtitle">Нічога няма, хоць сабак ганяй</string>
<string name="detail_drag_description">Перацягніце, каб змяніць парадак</string> <string name="detail_drag_description">Перацягніце, каб змяніць парадак</string>
<string name="video">Відэа</string> <string name="video">Відэа</string>
<string name="audio">Аўдыя</string> <string name="audio">Аўдыя</string>
<string name="retry">Паспрабаваць зноў</string> <string name="retry">Паспрабаваць зноў</string>
<string name="short_thousand">тыс.</string>
<string name="short_million">млн</string>
<string name="short_billion">млрд</string>
<string name="no_subscribers">Няма падпісчыкаў</string> <string name="no_subscribers">Няма падпісчыкаў</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s падпісчык</item> <item quantity="one">%s падпісчык</item>
@ -191,7 +188,7 @@
<string name="dismiss">Адхіліць</string> <string name="dismiss">Адхіліць</string>
<string name="rename">Перайменаваць</string> <string name="rename">Перайменаваць</string>
<string name="ok">ОК</string> <string name="ok">ОК</string>
<string name="msg_name">Імя файла</string> <string name="msg_name">Назва файла</string>
<string name="msg_threads">Патокі</string> <string name="msg_threads">Патокі</string>
<string name="msg_error">Памылка</string> <string name="msg_error">Памылка</string>
<string name="msg_running">NewPipe спампоўвае</string> <string name="msg_running">NewPipe спампоўвае</string>
@ -204,7 +201,7 @@
<string name="title_activity_recaptcha">Запыт reCAPTCHA</string> <string name="title_activity_recaptcha">Запыт reCAPTCHA</string>
<string name="recaptcha_request_toast">Запытаны ўвод reCAPTCHA</string> <string name="recaptcha_request_toast">Запытаны ўвод reCAPTCHA</string>
<string name="settings_category_downloads_title">Спампоўванне</string> <string name="settings_category_downloads_title">Спампоўванне</string>
<string name="settings_file_charset_title">Дапушчальныя ў назвах файлаў сімвалы</string> <string name="settings_file_charset_title">Сімвалы, дапушчальныя ў назвах файлаў</string>
<string name="settings_file_replacement_character_summary">Недапушчальныя сімвалы замяняюцца на гэты</string> <string name="settings_file_replacement_character_summary">Недапушчальныя сімвалы замяняюцца на гэты</string>
<string name="settings_file_replacement_character_title">Сімвал для замены</string> <string name="settings_file_replacement_character_title">Сімвал для замены</string>
<string name="charset_letters_and_digits">Літары і лічбы</string> <string name="charset_letters_and_digits">Літары і лічбы</string>
@ -253,7 +250,7 @@
<string name="play_queue_remove">Выдаліць</string> <string name="play_queue_remove">Выдаліць</string>
<string name="play_queue_stream_detail">Падрабязнасці</string> <string name="play_queue_stream_detail">Падрабязнасці</string>
<string name="play_queue_audio_settings">Налады аўдыя</string> <string name="play_queue_audio_settings">Налады аўдыя</string>
<string name="hold_to_append">Зацісніце, каб дадаць у чаргу</string> <string name="hold_to_append">Утрымлівайце, каб дадаць у чаргу</string>
<string name="start_here_on_background">Пачаць прайграванне ў фоне</string> <string name="start_here_on_background">Пачаць прайграванне ў фоне</string>
<string name="start_here_on_popup">Пачаць прайграванне ў акне</string> <string name="start_here_on_popup">Пачаць прайграванне ў акне</string>
<string name="drawer_open">Адкрыць бакавую панэль</string> <string name="drawer_open">Адкрыць бакавую панэль</string>
@ -266,17 +263,17 @@
<string name="always_ask_open_action">Заўсёды пытаць</string> <string name="always_ask_open_action">Заўсёды пытаць</string>
<string name="preferred_player_fetcher_notification_title">Атрыманне звестак…</string> <string name="preferred_player_fetcher_notification_title">Атрыманне звестак…</string>
<string name="preferred_player_fetcher_notification_message">Загрузка запытанага кантэнту</string> <string name="preferred_player_fetcher_notification_message">Загрузка запытанага кантэнту</string>
<string name="create_playlist">Стварыць плэйліст</string> <string name="create_playlist">Стварыць плэй-ліст</string>
<string name="rename_playlist">Перайменаваць</string> <string name="rename_playlist">Перайменаваць</string>
<string name="name">Імя</string> <string name="name">Назва</string>
<string name="add_to_playlist">Дадаць у плэйліст</string> <string name="add_to_playlist">Дадаць у плэй-ліст</string>
<string name="set_as_playlist_thumbnail">Зрабіць мініяцюрай плэйліста</string> <string name="set_as_playlist_thumbnail">Зрабіць мініяцюрай плэй-ліста</string>
<string name="bookmark_playlist">Дадаць плэйліст у закладкі</string> <string name="bookmark_playlist">Дадаць плэй-ліст у закладкі</string>
<string name="unbookmark_playlist">Выдаліць закладку</string> <string name="unbookmark_playlist">Выдаліць закладку</string>
<string name="delete_playlist_prompt">Выдаліць плэйліст\?</string> <string name="delete_playlist_prompt">Выдаліць плэй-ліст?</string>
<string name="playlist_creation_success">Плэйліст створаны</string> <string name="playlist_creation_success">Плэй-ліст створаны</string>
<string name="playlist_add_stream_success">Дададзена ў плэйліст</string> <string name="playlist_add_stream_success">Дададзена ў плэй-ліст</string>
<string name="playlist_thumbnail_change_success">Мініяцюра плэйліста зменена.</string> <string name="playlist_thumbnail_change_success">Мініяцюра плэй-ліста зменена.</string>
<string name="caption_none">Без субцітраў</string> <string name="caption_none">Без субцітраў</string>
<string name="resize_fit">Падагнаць</string> <string name="resize_fit">Падагнаць</string>
<string name="resize_fill">Запоўніць</string> <string name="resize_fill">Запоўніць</string>
@ -327,10 +324,10 @@
<string name="app_update_notification_channel_description">Апавяшчэнні пра новыя версіі NewPipe</string> <string name="app_update_notification_channel_description">Апавяшчэнні пра новыя версіі NewPipe</string>
<string name="download_to_sdcard_error_title">Знешняе сховішча недаступна</string> <string name="download_to_sdcard_error_title">Знешняе сховішча недаступна</string>
<string name="download_to_sdcard_error_message">Спампоўванне на знешнюю SD-карту немагчыма. Скінуць размяшчэнне папкі спампоўвання?</string> <string name="download_to_sdcard_error_message">Спампоўванне на знешнюю SD-карту немагчыма. Скінуць размяшчэнне папкі спампоўвання?</string>
<string name="saved_tabs_invalid_json">Памылка чытання захаваных укладак. Выкарыстоўваюцца ўкладкі па змаўчанні</string> <string name="saved_tabs_invalid_json">Не ўдалося прачытаць захаваныя ўкладкі, таму выкарыстоўваюцца прадвызначаныя</string>
<string name="restore_defaults">Аднавіць прадвызначаныя значэнні</string> <string name="restore_defaults">Аднавіць прадвызначаныя значэнні</string>
<string name="restore_defaults_confirmation">Аднавіць прадвызначаныя значэнні?</string> <string name="restore_defaults_confirmation">Аднавіць прадвызначаныя значэнні?</string>
<string name="subscribers_count_not_available">Колькасць падпісчыкаў недаступная</string> <string name="subscribers_count_not_available">Колькасць падпісчыкаў недаступна</string>
<string name="main_page_content_summary">Укладкі, бачныя на галоўнай старонцы</string> <string name="main_page_content_summary">Укладкі, бачныя на галоўнай старонцы</string>
<string name="updates_setting_title">Абнаўленні</string> <string name="updates_setting_title">Абнаўленні</string>
<string name="updates_setting_description">Паказваць апавяшчэнне пры наяўнасці новай версіі</string> <string name="updates_setting_description">Паказваць апавяшчэнне пры наяўнасці новай версіі</string>
@ -347,9 +344,9 @@
<string name="enqueue">Дадаць у чаргу</string> <string name="enqueue">Дадаць у чаргу</string>
<string name="permission_denied">Дзеянне забаронена сістэмай</string> <string name="permission_denied">Дзеянне забаронена сістэмай</string>
<string name="download_failed">Памылка спампоўвання</string> <string name="download_failed">Памылка спампоўвання</string>
<string name="generate_unique_name">Стварыць унікальнае імя</string> <string name="generate_unique_name">Стварыць унікальную назву</string>
<string name="overwrite">Перазапісаць</string> <string name="overwrite">Перазапісаць</string>
<string name="download_already_running">Файл з такім імем ужо спампоўваецца</string> <string name="download_already_running">Файл з такой назвай ўжо спампоўваецца</string>
<string name="show_error">Паказаць тэкст памылкі</string> <string name="show_error">Паказаць тэкст памылкі</string>
<string name="error_path_creation">Немагчыма стварыць папку прызначэння</string> <string name="error_path_creation">Немагчыма стварыць папку прызначэння</string>
<string name="error_file_creation">Немагчыма стварыць файл</string> <string name="error_file_creation">Немагчыма стварыць файл</string>
@ -376,19 +373,19 @@
<string name="enable_playback_resume_title">Працягваць прайграванне</string> <string name="enable_playback_resume_title">Працягваць прайграванне</string>
<string name="enable_playback_resume_summary">Аднаўляць апошнюю пазіцыю</string> <string name="enable_playback_resume_summary">Аднаўляць апошнюю пазіцыю</string>
<string name="enable_playback_state_lists_title">Пазіцыі ў спісах</string> <string name="enable_playback_state_lists_title">Пазіцыі ў спісах</string>
<string name="enable_playback_state_lists_summary">Адлюстроўваць індыкатары пазіцый прагляду ў спісах</string> <string name="enable_playback_state_lists_summary">Паказваць у спісах пазіцыю прайгравання</string>
<string name="settings_category_clear_data_title">Ачыстка даных</string> <string name="settings_category_clear_data_title">Ачыстка даных</string>
<string name="watch_history_states_deleted">Пазіцыі прайгравання выдалены</string> <string name="watch_history_states_deleted">Пазіцыі прайгравання выдалены</string>
<string name="missing_file">Файл перамешчаны ці выдалены</string> <string name="missing_file">Файл перамешчаны або выдалены</string>
<string name="overwrite_unrelated_warning">Файл з такім імем ужо існуе</string> <string name="overwrite_unrelated_warning">Файл з такой назвай ужо існуе</string>
<string name="overwrite_finished_warning">Файл з такім імем ужо існуе</string> <string name="overwrite_finished_warning">Спампаваны файл з такой назвай ужо існуе</string>
<string name="overwrite_failed">немагчыма перазапісаць файл</string> <string name="overwrite_failed">немагчыма перазапісаць файл</string>
<string name="download_already_pending">Файл з такім імем ужо дададзены ў чаргу спампоўвання</string> <string name="download_already_pending">Файл з такой назвай ужо ў чарзе спампоўвання</string>
<string name="error_postprocessing_stopped">Праграма NewPipe была закрыта падчас працы з файлам</string> <string name="error_postprocessing_stopped">Праграма NewPipe была закрыта падчас працы з файлам</string>
<string name="error_insufficient_storage_left">На прыладзе скончылася вольнае месца</string> <string name="error_insufficient_storage_left">На прыладзе скончылася вольнае месца</string>
<string name="error_progress_lost">Прагрэс страчаны, бо файл быў выдалены</string> <string name="error_progress_lost">Прагрэс страчаны, бо файл быў выдалены</string>
<string name="error_timeout">Скончыўся час злучэння</string> <string name="error_timeout">Скончыўся час злучэння</string>
<string name="confirm_prompt">Вы хочаце ачысціць гісторыю спампоўвання ці выдаліць спампаваныя файлы?</string> <string name="confirm_prompt">Ачысціць гісторыю спампоўвання або выдаліць спампаваныя файлы?</string>
<string name="enable_queue_limit">Абмежаваць чаргу спампоўвання</string> <string name="enable_queue_limit">Абмежаваць чаргу спампоўвання</string>
<string name="enable_queue_limit_desc">Толькі адно адначасовае спампоўванне</string> <string name="enable_queue_limit_desc">Толькі адно адначасовае спампоўванне</string>
<string name="start_downloads">Пачаць спампоўванне</string> <string name="start_downloads">Пачаць спампоўванне</string>
@ -397,7 +394,7 @@
<string name="downloads_storage_ask_summary">Пры кожным спампоўванні вам будзе прапанавана выбраць месца захавання. \nУключыце сістэмны сродак выбару папак (SAF), калі хочаце спампоўваць файлы на знешнюю SD-карту</string> <string name="downloads_storage_ask_summary">Пры кожным спампоўванні вам будзе прапанавана выбраць месца захавання. \nУключыце сістэмны сродак выбару папак (SAF), калі хочаце спампоўваць файлы на знешнюю SD-карту</string>
<string name="downloads_storage_use_saf_title">Выкарыстоўваць сістэмны сродак выбару папак (SAF)</string> <string name="downloads_storage_use_saf_title">Выкарыстоўваць сістэмны сродак выбару папак (SAF)</string>
<string name="downloads_storage_use_saf_summary">«Storage Access Framework» дазваляе выконваць спампоўванне на знешнюю SD-карту</string> <string name="downloads_storage_use_saf_summary">«Storage Access Framework» дазваляе выконваць спампоўванне на знешнюю SD-карту</string>
<string name="drawer_header_description">Пераключыць службу, выбраную ў дадзены момант:</string> <string name="drawer_header_description">Пераключэнне сэрвісу, зараз выбраны:</string>
<string name="clear_playback_states_summary">Выдаліць усе пазіцыі прайгравання</string> <string name="clear_playback_states_summary">Выдаліць усе пазіцыі прайгравання</string>
<string name="youtube_restricted_mode_enabled_title">Уключыць «Абмежаваны рэжым» YouTube</string> <string name="youtube_restricted_mode_enabled_title">Уключыць «Абмежаваны рэжым» YouTube</string>
<string name="peertube_instance_add_https_only">Падтрымліваюцца толькі URL-адрасы HTTPS</string> <string name="peertube_instance_add_https_only">Падтрымліваюцца толькі URL-адрасы HTTPS</string>
@ -435,15 +432,15 @@
<string name="most_liked">Найбольш папулярнае</string> <string name="most_liked">Найбольш папулярнае</string>
<string name="local">Лакальнае</string> <string name="local">Лакальнае</string>
<string name="recently_added">Нядаўна дададзенае</string> <string name="recently_added">Нядаўна дададзенае</string>
<string name="no_playlist_bookmarked_yet">Плэйлісты яшчэ не дададзены</string> <string name="no_playlist_bookmarked_yet">Плэй-лісты яшчэ не дададзены</string>
<string name="select_a_playlist">Выберыце плэйліст</string> <string name="select_a_playlist">Выберыце плэй-ліст</string>
<string name="default_kiosk_page_summary">Прадвызначаны кіёск</string> <string name="default_kiosk_page_summary">Прадвызначаны кіёск</string>
<string name="done">Так</string> <string name="done">Так</string>
<string name="subtitle_activity_recaptcha">Па завяршэнні націсніце «Гатова»</string> <string name="subtitle_activity_recaptcha">Па завяршэнні націсніце «Гатова»</string>
<string name="infinite_videos">∞ відэа</string> <string name="infinite_videos">∞ відэа</string>
<string name="more_than_100_videos">100+ відэа</string> <string name="more_than_100_videos">100+ відэа</string>
<string name="error_report_open_issue_button_text">Багрэпарт на GitHub</string> <string name="error_report_open_issue_button_text">Паведаміць на GitHub</string>
<string name="copy_for_github">Скапіруйце адфарматаваны багрэпарт</string> <string name="copy_for_github">Скапіяваць адфарматаваную справаздачу</string>
<string name="permission_display_over_apps">Дайце дазвол на адлюстраванне паверх іншых праграм</string> <string name="permission_display_over_apps">Дайце дазвол на адлюстраванне паверх іншых праграм</string>
<string name="delete_playback_states_alert">Выдаліць усе пазіцыі прайгравання\?</string> <string name="delete_playback_states_alert">Выдаліць усе пазіцыі прайгравання\?</string>
<string name="clear_playback_states_title">Выдаліць пазіцыі прайгравання</string> <string name="clear_playback_states_title">Выдаліць пазіцыі прайгравання</string>
@ -485,7 +482,7 @@
<string name="night_theme_summary">Выберыце любімую начную тэму - %s</string> <string name="night_theme_summary">Выберыце любімую начную тэму - %s</string>
<string name="description_select_enable">Дазвол вылучэння тэксту ў апісанні</string> <string name="description_select_enable">Дазвол вылучэння тэксту ў апісанні</string>
<string name="select_night_theme_toast">Вы можаце выбраць сваю любімую начную тэму ніжэй</string> <string name="select_night_theme_toast">Вы можаце выбраць сваю любімую начную тэму ніжэй</string>
<string name="night_theme_available">Гэта опцыя даступна толькі тады, калі %s будзе выбранай тэмаю</string> <string name="night_theme_available">Параметр даступны, толькі калі выбрана тэма %s</string>
<string name="download_has_started">Спампоўванне пачалося</string> <string name="download_has_started">Спампоўванне пачалося</string>
<string name="notifications_disabled">Апавяшчэнні адключаны</string> <string name="notifications_disabled">Апавяшчэнні адключаны</string>
<string name="tablet_mode_title">Рэжым планшэта</string> <string name="tablet_mode_title">Рэжым планшэта</string>
@ -505,7 +502,7 @@
<string name="night_theme_title">Начная тэма</string> <string name="night_theme_title">Начная тэма</string>
<string name="open_website_license">Адкрыць вэб-сайт</string> <string name="open_website_license">Адкрыць вэб-сайт</string>
<string name="description_select_note">Цяпер можна вылучаць тэкст у апісанні. Звярніце ўвагу, што ў рэжыме вылучэння старонка можа мільгаць, а спасылкі не націскацца.</string> <string name="description_select_note">Цяпер можна вылучаць тэкст у апісанні. Звярніце ўвагу, што ў рэжыме вылучэння старонка можа мільгаць, а спасылкі не націскацца.</string>
<string name="start_main_player_fullscreen_title">Запускаць галоўны прайгравальнік у поўнаэкранным рэжыме</string> <string name="start_main_player_fullscreen_title">Запускаць асноўны прайгравальнік у поўнаэкранным рэжыме</string>
<string name="show_channel_details">Паказаць дэталі канала</string> <string name="show_channel_details">Паказаць дэталі канала</string>
<string name="low_quality_smaller">Нізкая якасць (менш)</string> <string name="low_quality_smaller">Нізкая якасць (менш)</string>
<string name="hash_channel_name">Апавяшчэнне пра відэахэшаванне</string> <string name="hash_channel_name">Апавяшчэнне пра відэахэшаванне</string>
@ -537,17 +534,16 @@
<item quantity="other">%d дзён</item> <item quantity="other">%d дзён</item>
</plurals> </plurals>
<string name="clear_download_history">Ачысціць гісторыю спампоўвання</string> <string name="clear_download_history">Ачысціць гісторыю спампоўвання</string>
<string name="localization_changes_requires_app_restart">Мова зменіцца пасля перазапуску праграмы</string>
<string name="no_one_listening">Ніхто не слухае</string> <string name="no_one_listening">Ніхто не слухае</string>
<string name="on">Уключыць</string> <string name="on">Уключыць</string>
<string name="hash_channel_description">Апавяшчэнні пра ход відэахэшавання</string> <string name="hash_channel_description">Апавяшчэнні пра ход відэахэшавання</string>
<string name="create_error_notification">Стварыць паведамленне пра памылку</string> <string name="create_error_notification">Стварыць паведамленне пра памылку</string>
<string name="feed_group_dialog_select_subscriptions">Выберыце падпіскі</string> <string name="feed_group_dialog_select_subscriptions">Выберыце падпіскі</string>
<string name="import_subscriptions_hint">Імпарт ці экспарт падпісак з 3-кропкавага меню</string> <string name="import_subscriptions_hint">Імпартуйце або экспартуйце падпіскі праз меню з трыма кропкамі ⁝</string>
<string name="description_select_disable">Забарона вылучэння тэксту ў апісанні</string> <string name="description_select_disable">Забарона вылучэння тэксту ў апісанні</string>
<string name="fast_mode">Хуткі рэжым</string> <string name="fast_mode">Хуткі рэжым</string>
<string name="faq_description">Калі ў вас узніклі праблемы з выкарыстаннем праграмы, абавязкова азнаёмцеся з адказамі на частыя пытанні!</string> <string name="faq_description">Калі ў вас узніклі праблемы з выкарыстаннем праграмы, абавязкова азнаёмцеся з адказамі на частыя пытанні!</string>
<string name="disable_media_tunneling_title">Адключыць тунэляванне медыя</string> <string name="disable_media_tunneling_title">Адключыць тунэляванне мультымедыя</string>
<string name="seekbar_preview_thumbnail_title">Мініяцюра з перадпраглядам у паласе перамотвання</string> <string name="seekbar_preview_thumbnail_title">Мініяцюра з перадпраглядам у паласе перамотвання</string>
<string name="high_quality_larger">Высокая якасць (больш)</string> <string name="high_quality_larger">Высокая якасць (больш)</string>
<string name="dont_show">Не паказваць</string> <string name="dont_show">Не паказваць</string>
@ -576,12 +572,12 @@
<string name="error_report_channel_name">Апавяшчэнне аб памылцы</string> <string name="error_report_channel_name">Апавяшчэнне аб памылцы</string>
<string name="error_report_channel_description">Апавяшчэнні для паведамлення аб памылках</string> <string name="error_report_channel_description">Апавяшчэнні для паведамлення аб памылках</string>
<string name="error_report_notification_title">Адбылася памылка NewPipe, націсніце, каб адправіць справаздачу</string> <string name="error_report_notification_title">Адбылася памылка NewPipe, націсніце, каб адправіць справаздачу</string>
<string name="start_main_player_fullscreen_summary">Запускаць відэа ва ўвесь экран, калі адключаны аўтапаварот. Міні-плэер даступны пры выхадзе з поўнаэкраннага рэжыму</string> <string name="start_main_player_fullscreen_summary">Калі аўтапаварот адключаны, відэа адразу запускаецца ў поўнаэкранным рэжыме. Міні-плэер застаецца даступным, трэба толькі выйсці з поўнаэкраннага рэжыму</string>
<string name="peertube_instance_url_help">Шукайце серверы, якія вам даспадобы, на %s</string> <string name="peertube_instance_url_help">Шукайце серверы, якія вам даспадобы, на %s</string>
<string name="show_meta_info_title">Паказваць метаданыя</string> <string name="show_meta_info_title">Паказваць метаданыя</string>
<string name="ignore_hardware_media_buttons_title">Ігнараваць падзеі апаратных медыякнопак</string> <string name="ignore_hardware_media_buttons_title">Ігнараваць падзеі апаратных медыякнопак</string>
<string name="show_age_restricted_content_summary">Паказваць змесціва, магчыма непрыдатнае для дзяцей, таму што яно мае ўзроставыя абмежаванні (напрыклад, 18+)</string> <string name="show_age_restricted_content_summary">Паказваць змесціва, магчыма непрыдатнае для дзяцей, таму што яно мае ўзроставыя абмежаванні (напрыклад, 18+)</string>
<string name="error_report_open_github_notice">Калі ласка, праверце, ці існуе ўжо праблема з абмеркаваннем вашага збою. Пры стварэнні дублікатаў тыкетаў вы забіраеце ў нас час, які мы маглі б патраціць на выпраўленне фактычнай памылкі.</string> <string name="error_report_open_github_notice">Праверце, ці не існуе заяўкі з абмеркаваннем вашай праблемы. Дублікаты марнуюць наш час і праз гэта адцягваецца вырашэнне сапраўдных задач.</string>
<string name="error_report_notification_toast">Адбылася памылка, глядзіце апавяшчэнне</string> <string name="error_report_notification_toast">Адбылася памылка, глядзіце апавяшчэнне</string>
<string name="crash_the_player">Збой плэера</string> <string name="crash_the_player">Збой плэера</string>
<string name="ignore_hardware_media_buttons_summary">Карысна, напрыклад, калі вы карыстаецеся гарнітурай са зламанымі фізічнымі кнопкамі</string> <string name="ignore_hardware_media_buttons_summary">Карысна, напрыклад, калі вы карыстаецеся гарнітурай са зламанымі фізічнымі кнопкамі</string>
@ -593,7 +589,7 @@
<string name="msg_calculating_hash">Разлік хэша</string> <string name="msg_calculating_hash">Разлік хэша</string>
<string name="recaptcha_solve">Вырашана</string> <string name="recaptcha_solve">Вырашана</string>
<string name="playlist_no_uploader">Створана аўтаматычна (запампавальнік не знойдзены)</string> <string name="playlist_no_uploader">Створана аўтаматычна (запампавальнік не знойдзены)</string>
<string name="duplicate_in_playlist">Плэйлісты, якія пазначаны шэрым, ужо ўтрымліваюць гэты элемент.</string> <string name="duplicate_in_playlist">Плэй-лісты, якія пазначаны шэрым, ужо ўтрымліваюць гэты элемент.</string>
<plurals name="new_streams"> <plurals name="new_streams">
<item quantity="one">%s новая трансляцыя</item> <item quantity="one">%s новая трансляцыя</item>
<item quantity="few">%s новыя трансляцыі</item> <item quantity="few">%s новыя трансляцыі</item>
@ -608,15 +604,15 @@
<string name="playlist_add_stream_success_duplicate">Дублікат дададзены %d раз(ы)</string> <string name="playlist_add_stream_success_duplicate">Дублікат дададзены %d раз(ы)</string>
<string name="leak_canary_not_available">LeakCanary недаступны</string> <string name="leak_canary_not_available">LeakCanary недаступны</string>
<string name="show_memory_leaks">Паказаць уцечкі памяці</string> <string name="show_memory_leaks">Паказаць уцечкі памяці</string>
<string name="disable_media_tunneling_summary">Адключыце мультымедыйнае тунэляванне, калі ў вас з\'яўляецца чорны экран або заіканне падчас прайгравання відэа.</string> <string name="disable_media_tunneling_summary">Адключыце тунэляванне мультымедыя, калі відэа прайграецца перарывіста або паказваецца чорны экран.</string>
<string name="msg_failed_to_copy">Не ўдалося скапіяваць у буфер абмену</string> <string name="msg_failed_to_copy">Не ўдалося скапіяваць у буфер абмену</string>
<string name="no_dir_yet">Папка спампоўвання яшчэ не зададзена, выберыце папку спампоўвання цяпер</string> <string name="no_dir_yet">Папка спампоўвання яшчэ не зададзена, выберыце папку спампоўвання цяпер</string>
<string name="faq_title">Частыя пытанні</string> <string name="faq_title">Частыя пытанні</string>
<string name="faq">Перайсці на вэб-сайт</string> <string name="faq">Перайсці на вэб-сайт</string>
<string name="main_page_content_swipe_remove">Правядзіце пальцам па элементах, каб выдаліць іх</string> <string name="main_page_content_swipe_remove">Каб выдаліць элемент, змахніце яго ўбок</string>
<string name="unset_playlist_thumbnail">Прыбраць пастаянную мініяцюру</string> <string name="unset_playlist_thumbnail">Прыбраць пастаянную мініяцюру</string>
<string name="show_image_indicators_title">Паказваць індыкатары выяў</string> <string name="show_image_indicators_title">Паказваць на відарысах указальнікі</string>
<string name="show_image_indicators_summary">Паказваць каляровыя стужкі Пікаса на выявах, якія пазначаюць іх крыніцу: чырвоная для сеткі, сіняя для дыска і зялёная для памяці</string> <string name="show_image_indicators_summary">Паказваць на відарысах каляровыя меткі Picasso, якія абазначаюць яго крыніцу: чырвоная — сетка, сіняя — дыск, зялёная — памяць</string>
<string name="feed_processing_message">Апрацоўка стужкі…</string> <string name="feed_processing_message">Апрацоўка стужкі…</string>
<string name="downloads_storage_ask_summary_no_saf_notice">Пры кожным спампоўванні вам будзе прапанавана выбраць месца захавання</string> <string name="downloads_storage_ask_summary_no_saf_notice">Пры кожным спампоўванні вам будзе прапанавана выбраць месца захавання</string>
<string name="feed_notification_loading">Загрузка канала…</string> <string name="feed_notification_loading">Загрузка канала…</string>
@ -642,10 +638,10 @@
<item quantity="many">%d выбраных</item> <item quantity="many">%d выбраных</item>
<item quantity="other">%d выбраных</item> <item quantity="other">%d выбраных</item>
</plurals> </plurals>
<string name="feed_group_dialog_empty_name">Пустая назва групы</string> <string name="feed_group_dialog_empty_name">Назва групы пустая</string>
<string name="feed_group_dialog_delete_message">Выдаліць гэту групу?</string> <string name="feed_group_dialog_delete_message">Выдаліць групу?</string>
<string name="feed_create_new_group_button_title">Новая</string> <string name="feed_create_new_group_button_title">Новая</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Паказаць толькі разгрупаваныя падпіскі</string> <string name="feed_group_show_only_ungrouped_subscriptions">Паказваць толькі не згрупаваныя падпіскі</string>
<string name="feed_show_upcoming">Запланаваныя</string> <string name="feed_show_upcoming">Запланаваныя</string>
<string name="show_crash_the_player_title">Паказваць «Збой плэера»</string> <string name="show_crash_the_player_title">Паказваць «Збой плэера»</string>
<string name="check_new_streams">Запусціце праверку новых патокаў</string> <string name="check_new_streams">Запусціце праверку новых патокаў</string>
@ -667,13 +663,13 @@
</plurals> </plurals>
<string name="feed_update_threshold_option_always_update">Заўсёды абнаўляць</string> <string name="feed_update_threshold_option_always_update">Заўсёды абнаўляць</string>
<string name="feed_update_threshold_title">Парог абнаўлення стужкі</string> <string name="feed_update_threshold_title">Парог абнаўлення стужкі</string>
<string name="feed_load_error_account_info">Немагчыма загрузіць канал для «%s».</string> <string name="feed_load_error_account_info">Не ўдалося загрузіць канал для «%s».</string>
<string name="settings_category_feed_title">Стужка</string> <string name="settings_category_feed_title">Стужка</string>
<string name="feed_update_threshold_summary">Час пасля апошняга абнаўлення, перш чым падпіска лічыцца састарэлай — %s</string> <string name="feed_update_threshold_summary">Час пасля апошняга абнаўлення, перш чым падпіска лічыцца састарэлай — %s</string>
<string name="feed_load_error">Памылка загрузкі канала</string> <string name="feed_load_error">Памылка загрузкі канала</string>
<string name="feed_load_error_terminated">Уліковы запіс аўтара быў спынены. \nNewPipe не зможа загрузіць гэты канал у будучыні. \nАдпісацца ад канала?</string> <string name="feed_load_error_terminated">Уліковы запіс аўтара быў спынены. \nNewPipe не зможа загрузіць гэты канал у будучыні. \nАдпісацца ад канала?</string>
<string name="feed_load_error_fast_unknown">Рэжым хуткай загрузкі стужкі не дае дадатковай інфармацыі аб гэтым.</string> <string name="feed_load_error_fast_unknown">Рэжым хуткай загрузкі стужкі не дае дадатковай інфармацыі аб гэтым.</string>
<string name="feed_use_dedicated_fetch_method_title">Атрымлівайце са спецыяльнага канала, калі ён даступны</string> <string name="feed_use_dedicated_fetch_method_title">Атрыманне даных са спецыяльнага канала, калі ён ёсць</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Уключыць хуткі рэжым</string> <string name="feed_use_dedicated_fetch_method_enable_button">Уключыць хуткі рэжым</string>
<string name="metadata_category">Катэгорыя</string> <string name="metadata_category">Катэгорыя</string>
<string name="metadata_tags">Тэгі</string> <string name="metadata_tags">Тэгі</string>
@ -686,7 +682,7 @@
<string name="streams_not_yet_supported_removed">Трансляцыі, спампоўванне якіх яшчэ не падтрымліваецца, не паказваюцца</string> <string name="streams_not_yet_supported_removed">Трансляцыі, спампоўванне якіх яшчэ не падтрымліваецца, не паказваюцца</string>
<string name="detail_sub_channel_thumbnail_view_description">Мініяцюра аватара канала</string> <string name="detail_sub_channel_thumbnail_view_description">Мініяцюра аватара канала</string>
<string name="video_detail_by">Аўтар: %s</string> <string name="video_detail_by">Аўтар: %s</string>
<string name="detail_heart_img_view_description">Аўтару відэа спадабалася гэта</string> <string name="detail_heart_img_view_description">Спадабалася аўтару відэа</string>
<string name="channel_created_by">Створана %s</string> <string name="channel_created_by">Створана %s</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Адключыць хуткі рэжым</string> <string name="feed_use_dedicated_fetch_method_disable_button">Адключыць хуткі рэжым</string>
<string name="metadata_privacy_public">Публічная</string> <string name="metadata_privacy_public">Публічная</string>
@ -699,7 +695,7 @@
<string name="content_not_supported">Гэты кантэнт яшчэ не падтрымліваецца NewPipe. <string name="content_not_supported">Гэты кантэнт яшчэ не падтрымліваецца NewPipe.
\n \n
\nСпадзяюся, ён будзе падтрымлівацца ў наступных версіях.</string> \nСпадзяюся, ён будзе падтрымлівацца ў наступных версіях.</string>
<string name="playlist_page_summary">Старонка плэйліста</string> <string name="playlist_page_summary">Старонка плэй-ліста</string>
<string name="show_thumbnail_title">Паказваць мініяцюру</string> <string name="show_thumbnail_title">Паказваць мініяцюру</string>
<string name="show_thumbnail_summary">Выкарыстоўваць мініяцюру як фон для экрана блакіроўкі і апавяшчэнняў</string> <string name="show_thumbnail_summary">Выкарыстоўваць мініяцюру як фон для экрана блакіроўкі і апавяшчэнняў</string>
<string name="no_appropriate_file_manager_message">Для гэтага дзеяння не знойдзены прыдатны файлавы менеджар. \nУсталюйце файлавы менеджар або паспрабуйце адключыць «%s» у наладах спампоўвання</string> <string name="no_appropriate_file_manager_message">Для гэтага дзеяння не знойдзены прыдатны файлавы менеджар. \nУсталюйце файлавы менеджар або паспрабуйце адключыць «%s» у наладах спампоўвання</string>
@ -713,10 +709,10 @@
<string name="metadata_privacy_internal">Унутраная</string> <string name="metadata_privacy_internal">Унутраная</string>
<string name="feed_show_watched">Прагледжаныя цалкам</string> <string name="feed_show_watched">Прагледжаныя цалкам</string>
<string name="paid_content">Гэты кантэнт даступны толькі для аплачаных карыстальнікаў, таму NewPipe не можа яго трансляваць або спампоўваць.</string> <string name="paid_content">Гэты кантэнт даступны толькі для аплачаных карыстальнікаў, таму NewPipe не можа яго трансляваць або спампоўваць.</string>
<string name="feed_use_dedicated_fetch_method_summary">Даступны ў некаторых службах, звычайна нашмат хутчэй, але можа вяртаць абмежаваную колькасць элементаў і часта няпоўную інфармацыю (напрыклад, без працягласці, тыпу элемента, без актыўнага стану)</string> <string name="feed_use_dedicated_fetch_method_summary">Даступна для некаторых сэрвісаў, звычайна значна хутчэй, але можа перадаваць абмежаваную колькасць элементаў і не ўсю інфармацыю (можа адсутнічаць працягласць, тып элемента, паказчык трансляцыі)</string>
<string name="metadata_age_limit">Узроставае абмежаванне</string> <string name="metadata_age_limit">Узроставае абмежаванне</string>
<string name="no_appropriate_file_manager_message_android_10">Для гэтага дзеяння не знойдзены прыдатны файлавы менеджар. \nУсталюйце файлавы менеджар, сумяшчальны з Storage Access Framework</string> <string name="no_appropriate_file_manager_message_android_10">Для гэтага дзеяння не знойдзены прыдатны файлавы менеджар. \nУсталюйце файлавы менеджар, сумяшчальны з Storage Access Framework</string>
<string name="no_app_to_open_intent">Ніякая праграма на вашай прыладзе не можа адкрыць гэта</string> <string name="no_app_to_open_intent">На прыладзе няма праграмы, каб адкрыць гэты файл</string>
<string name="progressive_load_interval_exoplayer_default">Стандартнае значэнне ExoPlayer</string> <string name="progressive_load_interval_exoplayer_default">Стандартнае значэнне ExoPlayer</string>
<string name="feed_show_partially_watched">Прагледжаныя часткова</string> <string name="feed_show_partially_watched">Прагледжаныя часткова</string>
<string name="feed_use_dedicated_fetch_method_help_text">Лічыце, што загрузка каналаў адбываецца занадта павольна? Калі так, паспрабуйце ўключыць хуткую загрузку (можна змяніць у наладах або націснуўшы кнопку ніжэй). \n \nNewPipe прапануе два спосабы загрузкі каналаў: \n• Атрыманне ўсяго канала падпіскі. Павольны, але інфармацыя поўная). \n• Выкарыстанне спецыяльнай канчатковай кропкі абслугоўвання. Хуткі, але звычайна інфармацыя няпоўная). \n \nРозніца паміж імі ў тым, што ў хуткім звычайна адсутнічае частка інфармацыі, напрыклад, працягласць або тып (немагчыма адрозніць трансляцыі ад звычайных відэа), і ён можа вяртаць менш элементаў. \n \nYouTube з\'яўляецца прыкладам сэрвісу, які прапануе гэты хуткі метад праз RSS-канал. \n \nТакім чынам, выбар залежыць ад таго, чаму вы аддаяце перавагу: хуткасці або дакладнасці інфармацыя.</string> <string name="feed_use_dedicated_fetch_method_help_text">Лічыце, што загрузка каналаў адбываецца занадта павольна? Калі так, паспрабуйце ўключыць хуткую загрузку (можна змяніць у наладах або націснуўшы кнопку ніжэй). \n \nNewPipe прапануе два спосабы загрузкі каналаў: \n• Атрыманне ўсяго канала падпіскі. Павольны, але інфармацыя поўная). \n• Выкарыстанне спецыяльнай канчатковай кропкі абслугоўвання. Хуткі, але звычайна інфармацыя няпоўная). \n \nРозніца паміж імі ў тым, што ў хуткім звычайна адсутнічае частка інфармацыі, напрыклад, працягласць або тып (немагчыма адрозніць трансляцыі ад звычайных відэа), і ён можа вяртаць менш элементаў. \n \nYouTube з\'яўляецца прыкладам сэрвісу, які прапануе гэты хуткі метад праз RSS-канал. \n \nТакім чынам, выбар залежыць ад таго, чаму вы аддаяце перавагу: хуткасці або дакладнасці інфармацыя.</string>
@ -748,8 +744,8 @@
<string name="audio_track_present_in_video">У гэтым патоку ўжо павінна быць гукавая дарожка</string> <string name="audio_track_present_in_video">У гэтым патоку ўжо павінна быць гукавая дарожка</string>
<string name="use_exoplayer_decoder_fallback_summary">Уключыце гэту опцыю, калі ў вас ёсць праблемы з ініцыялізацыяй дэкодэра, якая вяртаецца да дэкодэраў з больш нізкім прыярытэтам, калі ініцыялізацыя асноўных дэкодэраў не ўдаецца. Гэта можа прывесці да нізкай прадукцыйнасці прайгравання, чым пры выкарыстанні асноўных дэкодэраў</string> <string name="use_exoplayer_decoder_fallback_summary">Уключыце гэту опцыю, калі ў вас ёсць праблемы з ініцыялізацыяй дэкодэра, якая вяртаецца да дэкодэраў з больш нізкім прыярытэтам, калі ініцыялізацыя асноўных дэкодэраў не ўдаецца. Гэта можа прывесці да нізкай прадукцыйнасці прайгравання, чым пры выкарыстанні асноўных дэкодэраў</string>
<string name="settings_category_exoplayer_summary">Кіраванне некаторымі наладамі ExoPlayer. Каб гэтыя змены ўступілі ў сілу, патрабуецца перазапуск прайгравальніка</string> <string name="settings_category_exoplayer_summary">Кіраванне некаторымі наладамі ExoPlayer. Каб гэтыя змены ўступілі ў сілу, патрабуецца перазапуск прайгравальніка</string>
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Гэты абыходны шлях вызваляе і паўторна стварае відэакодэкі, калі адбываецца змяненне паверхні, замест таго, каб зажаваць паверхню непасрэдна для кодэка. Ужо выкарыстоўваецца ExoPlayer на некаторых прыладах з такой праблемай, гэты параметр ужываецца толькі на прыладах з Android 6 і вышэй\n\nУключэнне параметра можа прадухіліць памылкі прайгравання пры пераключэнні бягучага відэаплэера або пераключэнні ў поўнаэкранны рэжым</string> <string name="always_use_exoplayer_set_output_surface_workaround_summary">Гэты абыходны шлях вызваляе і паўторна стварае відэакодэкі, калі адбываецца змяненне паверхні, замест таго, каб задаваць паверхню непасрэдна для кодэка. Ужо выкарыстоўваецца ExoPlayer на некаторых прыладах з такой праблемай, гэты параметр ужываецца толькі на прыладах з Android 6 і вышэй\n\nУключэнне параметра можа прадухіліць памылкі прайгравання пры пераключэнні бягучага відэаплэера або пераключэнні ў поўнаэкранны рэжым</string>
<string name="image_quality_title">Якасць выяў</string> <string name="image_quality_title">Якасць відарысаў</string>
<string name="channel_tab_videos">Відэа</string> <string name="channel_tab_videos">Відэа</string>
<string name="question_mark">\?</string> <string name="question_mark">\?</string>
<string name="metadata_subscribers">Падпісчыкі</string> <string name="metadata_subscribers">Падпісчыкі</string>
@ -768,28 +764,28 @@
<string name="feed_fetch_channel_tabs">Атрыманне ўкладак канала</string> <string name="feed_fetch_channel_tabs">Атрыманне ўкладак канала</string>
<string name="metadata_avatars">Аватары</string> <string name="metadata_avatars">Аватары</string>
<string name="next_stream">Наступны паток</string> <string name="next_stream">Наступны паток</string>
<string name="disable_media_tunneling_automatic_info">Прадвызначана на вашай прыладзе адключана медыятунэляванне, бо гэтая мадэль прылады яго не падтрымлівае.</string> <string name="disable_media_tunneling_automatic_info">Прадвызначана на вашай прыладзе адключана тунэляванне мультымедыя, бо вядома, што гэта мадэль яго не падтрымлівае.</string>
<string name="metadata_subchannel_avatars">Аватары падканалаў</string> <string name="metadata_subchannel_avatars">Аватары падканалаў</string>
<string name="open_play_queue">Адкрыць чаргу прайгравання</string> <string name="open_play_queue">Адкрыць чаргу прайгравання</string>
<string name="image_quality_none">Не загружаць выявы</string> <string name="image_quality_none">Не загружаць відарысы</string>
<string name="image_quality_high">Высокая якасць</string> <string name="image_quality_high">Высокая якасць</string>
<string name="channel_tab_about">Аб канале</string> <string name="channel_tab_about">Пра канал</string>
<string name="share_playlist">Абагуліць плэйліст</string> <string name="share_playlist">Абагуліць плэй-ліст</string>
<string name="forward">Пераматаць наперад</string> <string name="forward">Пераматаць наперад</string>
<string name="channel_tab_albums">Альбомы</string> <string name="channel_tab_albums">Альбомы</string>
<string name="rewind">Пераматаць назад</string> <string name="rewind">Пераматаць назад</string>
<string name="replay">Паўтарыць</string> <string name="replay">Паўтарыць</string>
<string name="feed_fetch_channel_tabs_summary">Атрыманыя ўкладкі пры абнаўленні стужкі. Гэты параметр не прымяняецца, калі канал абнаўляецца ў хуткім рэжыме.</string> <string name="feed_fetch_channel_tabs_summary">Укладкі, для якіх атрымліваюцца даныя пры абнаўленні стужкі. Гэты параметр не дзейнічае, калі канал абнаўляецца з выкарыстаннем хуткага рэжыму.</string>
<string name="image_quality_medium">Сярэдняя якасць</string> <string name="image_quality_medium">Сярэдняя якасць</string>
<string name="metadata_uploader_avatars">Загрузнік аватараў</string> <string name="metadata_uploader_avatars">Загрузнік аватараў</string>
<string name="metadata_banners">Банеры</string> <string name="metadata_banners">Банеры</string>
<string name="channel_tab_playlists">Плэйлісты</string> <string name="channel_tab_playlists">Плэй-лісты</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<string name="main_tabs_position_summary">Перамясціць панэль укладак уніз</string> <string name="main_tabs_position_summary">Перамясціць панэль укладак уніз</string>
<string name="no_live_streams">Няма жывых трансляцый</string> <string name="no_live_streams">Няма жывых трансляцый</string>
<string name="image_quality_summary">Выберыце якасць выяў і ці трэба спампоўваць выявы ўвогуле, каб паменшыць выкарыстанне даных і памяці. Змены ачышчаюць кэш выяў як у памяці, так і на дыску - %s</string> <string name="image_quality_summary">Выберыце якасць відарысаў ці ўвогуле не загружаць відарысы, каб паменшыць выкарыстанне даных і памяці. Змены ачышчаюць кэш відарысаў у памяці і на дыску (%s)</string>
<string name="play">Прайграць</string> <string name="play">Прайграць</string>
<string name="more_options">Іншыя опцыі</string> <string name="more_options">Іншыя параметры</string>
<string name="metadata_thumbnails">Мініяцюры</string> <string name="metadata_thumbnails">Мініяцюры</string>
<string name="channel_tab_tracks">Трэкі</string> <string name="channel_tab_tracks">Трэкі</string>
<string name="duration">Працягласць</string> <string name="duration">Працягласць</string>
@ -808,7 +804,7 @@
<string name="notification_actions_summary_android13">Каб адрэдагаваць кожнае з дзеянняў у апавяшчэнні, націсніце на яго. Першыя тры дзеянні (прайграванне/паўза, папярэдні і наступны) зададзены сістэмай, іх змяніць немагчыма.</string> <string name="notification_actions_summary_android13">Каб адрэдагаваць кожнае з дзеянняў у апавяшчэнні, націсніце на яго. Першыя тры дзеянні (прайграванне/паўза, папярэдні і наступны) зададзены сістэмай, іх змяніць немагчыма.</string>
<string name="error_insufficient_storage">Недастаткова вольнага месца на прыладзе</string> <string name="error_insufficient_storage">Недастаткова вольнага месца на прыладзе</string>
<string name="yes">Так</string> <string name="yes">Так</string>
<string name="auto_update_check_description">NewPipe можа аўтаматычна правяраць наяўнасць абнаўленняў і паведаміць вам, калі яны будуць даступны. \nУключыць гэту функцыю?</string> <string name="auto_update_check_description">NewPipe можа час ад часу аўтаматычна правяраць наяўнасць новай версіі і апавяшчаць, калі яна будзе даступна. \nУключыць гэту функцыю?</string>
<string name="import_settings_vulnerable_format">Налады ў імпартаваным экспарце выкарыстоўваюць уразлівы фармат, які састарэў з версіі NewPipe 0.27.0. Пераканайцеся, што імпартаваны экспарт атрыманы з надзейнай крыніцы, і ў будучыні пераважней выкарыстоўваць толькі экспарт, атрыманы з NewPipe 0.27.0 ці навей. Падтрымка імпарту налад у гэтым уразлівым фармаце хутка будзе цалкам выдаленая, і тады старыя версіі NewPipe больш не змогуць імпартаваць наладкі з экспарту з новых версій.</string> <string name="import_settings_vulnerable_format">Налады ў імпартаваным экспарце выкарыстоўваюць уразлівы фармат, які састарэў з версіі NewPipe 0.27.0. Пераканайцеся, што імпартаваны экспарт атрыманы з надзейнай крыніцы, і ў будучыні пераважней выкарыстоўваць толькі экспарт, атрыманы з NewPipe 0.27.0 ці навей. Падтрымка імпарту налад у гэтым уразлівым фармаце хутка будзе цалкам выдаленая, і тады старыя версіі NewPipe больш не змогуць імпартаваць наладкі з экспарту з новых версій.</string>
<string name="no">Не</string> <string name="no">Не</string>
<string name="settings_category_backup_restore_title">Рэзервовае капіяванне і аднаўленне</string> <string name="settings_category_backup_restore_title">Рэзервовае капіяванне і аднаўленне</string>
@ -816,4 +812,12 @@
<string name="reset_settings_summary">Скінуць усе налады на іх прадвызначаныя значэнні</string> <string name="reset_settings_summary">Скінуць усе налады на іх прадвызначаныя значэнні</string>
<string name="reset_all_settings">Пры скіданні ўсіх налад будуць адхілены ўсе вашы змены налад і праграма перазапусціцца. \n \nСапраўды хочаце працягнуць?</string> <string name="reset_all_settings">Пры скіданні ўсіх налад будуць адхілены ўсе вашы змены налад і праграма перазапусціцца. \n \nСапраўды хочаце працягнуць?</string>
<string name="audio_track_type_secondary">другасны</string> <string name="audio_track_type_secondary">другасны</string>
<string name="share_playlist_as_youtube_temporary_playlist">Абагуліць як часовы плэйліст YouTube</string>
<string name="tab_bookmarks_short">Плэй-лісты</string>
<string name="select_a_feed_group">Выберыце групу каналаў</string>
<string name="no_feed_group_created_yet">Група каналаў яшчэ не створана</string>
<string name="feed_group_page_summary">Старонка групы каналаў</string>
<string name="search_with_service_name">Пошук %1$s</string>
<string name="search_with_service_name_and_filter">Пошук %1$s (%2$s)</string>
<string name="channel_tab_likes">Спадабалася</string>
</resources> </resources>

View file

@ -45,9 +45,6 @@
</plurals> </plurals>
<string name="infinite_videos">∞ ⵉⴼⵉⴷⵢⵓⵜⵏ</string> <string name="infinite_videos">∞ ⵉⴼⵉⴷⵢⵓⵜⵏ</string>
<string name="more_than_100_videos">100+ ⵉⴼⵉⴷⵢⵓⵜⵏ</string> <string name="more_than_100_videos">100+ ⵉⴼⵉⴷⵢⵓⵜⵏ</string>
<string name="short_billion"></string>
<string name="short_million"></string>
<string name="short_thousand"></string>
<string name="audio">ⴰⵎⵙⵍⴰⵢ</string> <string name="audio">ⴰⵎⵙⵍⴰⵢ</string>
<string name="video">ⴰⴼⵉⴷⵢⵓ</string> <string name="video">ⴰⴼⵉⴷⵢⵓ</string>
<string name="detail_likes_img_view_description">ⵉⵔⵉⵜⵏ</string> <string name="detail_likes_img_view_description">ⵉⵔⵉⵜⵏ</string>

View file

@ -183,9 +183,6 @@
<string name="file_name_empty_error">Името на файла не може да бъде празно</string> <string name="file_name_empty_error">Името на файла не може да бъде празно</string>
<string name="error_occurred_detail">Възникна грешка: %1$s</string> <string name="error_occurred_detail">Възникна грешка: %1$s</string>
<string name="no_streams_available_download">Не са налични източници за изтегляне</string> <string name="no_streams_available_download">Не са налични източници за изтегляне</string>
<string name="short_thousand">хил.</string>
<string name="short_million">млн.</string>
<string name="short_billion">млрд.</string>
<string name="no_subscribers">Няма абонати</string> <string name="no_subscribers">Няма абонати</string>
<string name="create">Създай</string> <string name="create">Създай</string>
<string name="dismiss">Откажи</string> <string name="dismiss">Откажи</string>
@ -229,7 +226,7 @@
<string name="main_page_content">Съдържание на главната страница</string> <string name="main_page_content">Съдържание на главната страница</string>
<string name="blank_page_summary">Празна страница</string> <string name="blank_page_summary">Празна страница</string>
<string name="kiosk_page_summary">Страница-павилион</string> <string name="kiosk_page_summary">Страница-павилион</string>
<string name="channel_page_summary">Страница на определен канал</string> <string name="channel_page_summary">Страница на канал</string>
<string name="select_a_channel">Изберете канал</string> <string name="select_a_channel">Изберете канал</string>
<string name="no_channel_subscribed_yet">За момента нямате абонаменти</string> <string name="no_channel_subscribed_yet">За момента нямате абонаменти</string>
<string name="select_a_kiosk">Изберете павилион</string> <string name="select_a_kiosk">Изберете павилион</string>
@ -432,7 +429,6 @@
<string name="most_liked">Най-харесвани</string> <string name="most_liked">Най-харесвани</string>
<string name="done">Готово</string> <string name="done">Готово</string>
<string name="comments_tab_description">Коментари</string> <string name="comments_tab_description">Коментари</string>
<string name="localization_changes_requires_app_restart">Езикът ще се смени след рестартиране на приложението</string>
<string name="metadata_privacy_unlisted">Скрит</string> <string name="metadata_privacy_unlisted">Скрит</string>
<string name="metadata_privacy_private">Частен</string> <string name="metadata_privacy_private">Частен</string>
<string name="remote_search_suggestions">Предложения за отдалечено търсене</string> <string name="remote_search_suggestions">Предложения за отдалечено търсене</string>
@ -809,4 +805,14 @@
<string name="always_use_exoplayer_set_output_surface_workaround_title">Винаги използвайте заобикаляне на настройката на повърхността на видеоизхода на ExoPlayer</string> <string name="always_use_exoplayer_set_output_surface_workaround_title">Винаги използвайте заобикаляне на настройката на повърхността на видеоизхода на ExoPlayer</string>
<string name="clear_playback_states_title">Изтрий позиции за възпроизвеждане</string> <string name="clear_playback_states_title">Изтрий позиции за възпроизвеждане</string>
<string name="audio_track_type_secondary">вторичен</string> <string name="audio_track_type_secondary">вторичен</string>
<string name="share_playlist_as_youtube_temporary_playlist">Споделяне като временен плейлист в YouTube</string>
<string name="tab_bookmarks_short">Плейлисти</string>
<string name="no_feed_group_created_yet">Все още няма създадена група за емисии</string>
<string name="feed_group_page_summary">Страница на групата канали</string>
<string name="select_a_feed_group">Изберете група емисии</string>
<string name="search_with_service_name">Търсене %1$s</string>
<string name="search_with_service_name_and_filter">Търсене %1$s (%2$s)</string>
<string name="channel_tab_likes">Харесвания</string>
<string name="migration_info_6_7_title">Страница SoundCloud Top 50 е премахната</string>
<string name="migration_info_6_7_message">SoundCloud преустанови оригиналните класации Топ 50. Съответният раздел е премахнат от главната ви страница.</string>
</resources> </resources>

View file

@ -82,9 +82,6 @@
<string name="video">ভিডিও</string> <string name="video">ভিডিও</string>
<string name="audio">অডিও</string> <string name="audio">অডিও</string>
<string name="retry">পুনরায় চেষ্টা করো</string> <string name="retry">পুনরায় চেষ্টা করো</string>
<string name="short_thousand">হা</string>
<string name="short_million">M</string>
<string name="short_billion">বি</string>
<!-- Missions --> <!-- Missions -->
<string name="start">শুরু</string> <string name="start">শুরু</string>
<string name="pause">বিরতি</string> <string name="pause">বিরতি</string>

View file

@ -104,9 +104,6 @@
<string name="no_videos">কোন ভিডিও নেই</string> <string name="no_videos">কোন ভিডিও নেই</string>
<string name="no_views">কোন ভিউ নেই</string> <string name="no_views">কোন ভিউ নেই</string>
<string name="no_subscribers">কোন সাবস্ক্রাইবার নেই</string> <string name="no_subscribers">কোন সাবস্ক্রাইবার নেই</string>
<string name="short_billion">B</string>
<string name="short_million">M</string>
<string name="short_thousand">K</string>
<string name="retry">পুনরায় চেষ্টা করো</string> <string name="retry">পুনরায় চেষ্টা করো</string>
<string name="audio">অডিও</string> <string name="audio">অডিও</string>
<string name="video">ভিডিও</string> <string name="video">ভিডিও</string>
@ -534,7 +531,6 @@
<string name="downloads_storage_ask_summary">প্রত্যেক ডাউনলোড কোথায় রাখা হবে তা জিজ্ঞেস করা হবে। <string name="downloads_storage_ask_summary">প্রত্যেক ডাউনলোড কোথায় রাখা হবে তা জিজ্ঞেস করা হবে।
\nমেমোরি কার্ডে ডাউনলোড করতে সিস্টেম ফোল্ডার পিকার (SAF) এনেবল করুন</string> \nমেমোরি কার্ডে ডাউনলোড করতে সিস্টেম ফোল্ডার পিকার (SAF) এনেবল করুন</string>
<string name="download_already_running">এই নামের একটি ডাউনলোড চলমান</string> <string name="download_already_running">এই নামের একটি ডাউনলোড চলমান</string>
<string name="localization_changes_requires_app_restart">অ্যাপ আবার শুরু হলে ভাষা পাল্টাবে</string>
<string name="disable_media_tunneling_title">মিডিয়া সুরঙ্গকরণ অক্ষম</string> <string name="disable_media_tunneling_title">মিডিয়া সুরঙ্গকরণ অক্ষম</string>
<string name="feed_load_error_fast_unknown">দ্রুত ফিড অবস্থা এ বিষয়ে এর বেশি তথ্য দেয় না।</string> <string name="feed_load_error_fast_unknown">দ্রুত ফিড অবস্থা এ বিষয়ে এর বেশি তথ্য দেয় না।</string>
<string name="no_dir_yet">কোনো ডাউনলোড ফোল্ডার নির্দিষ্ট করা হয়নি, এখনই একটা সহজাত ডাউনলোড ফোল্ডার নির্বাচন করো</string> <string name="no_dir_yet">কোনো ডাউনলোড ফোল্ডার নির্দিষ্ট করা হয়নি, এখনই একটা সহজাত ডাউনলোড ফোল্ডার নির্বাচন করো</string>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="cancel">Nullañ</string>
<string name="ok">Mat eo</string>
<string name="no">Ket</string>
<string name="open_in_browser">Digeriñ e-barzh ar merdeer</string>
<string name="open_with">Digeriñ gant</string>
<string name="share">Rannañ</string>
<string name="download">Pellgargañ</string>
<string name="did_you_mean">Klask a raec\'h \"%1$s\"?</string>
<string name="share_dialog_title">Rannañ gant</string>
<string name="use_external_audio_player_title">Arverañ ul lenner aodio diavaez</string>
<string name="subscribe_button_title">Koumanantiñ</string>
<string name="unsubscribe">Digoumanantiñ</string>
<string name="subscription_change_failed">N\'haller ket kemmañ ar c\'houmanant</string>
<string name="subscription_update_failed">N\'haller ket hizivaat ar c\'houmanant</string>
<string name="show_info">Diskouez an titouroù</string>
<string name="tab_bookmarks">Rolloù-lenn enrollet</string>
<string name="tab_bookmarks_short">Rolloù-lenn</string>
<string name="tab_choose">Dibab un ivinell</string>
<string name="controls_background_title">Drekleur</string>
<string name="controls_popup_title">Diflugell</string>
<string name="controls_add_to_playlist_title">Ouzhpennañ da</string>
<string name="download_path_title">Teuliad pellgargañ ar videoioù</string>
<string name="download_path_audio_summary">Amañ e vez kadavet ar restroù aodio pellgarget</string>
<string name="download_path_summary">Amañ e vez kadavet ar restroù video pellgarget</string>
<string name="notification_action_buffering">O kargañ</string>
<string name="notification_action_nothing">Netra</string>
<string name="default_audio_format_title">Mentrezh aodio dre ziouer</string>
<string name="theme_title">Dodenn</string>
<string name="night_theme_title">Dodenn noz</string>
<string name="light_theme_title">Sklaer</string>
<string name="dark_theme_title">Teñval</string>
<string name="volume">Tregern</string>
<string name="enable_search_history_title">Roll istor enklask</string>
<string name="download_dialog_title">Pellgargañ</string>
<string name="start_main_player_fullscreen_title">Lañsañ al lenner pennañ e mod skramm a-bezh</string>
<string name="autoplay_title">Lenn emgefreek</string>
<string name="default_content_country_title">Bro an endalc\'had dre ziouer</string>
<string name="peertube_instance_url_title">Erioloù PeerTube</string>
<string name="none">Tra ebet</string>
<string name="default_video_format_title">Mentrezh video dre ziouer</string>
<string name="notification_action_shuffle">Lenn mell-divell</string>
<string name="play_audio">Aodio</string>
<string name="play_with_kodi_title">Lenn gant Kodi</string>
<string name="brightness">Lintr</string>
<string name="settings_category_clear_data_title">Skarzhañ ar roadennoù</string>
<string name="search">Klask</string>
<string name="use_external_video_player_title">Arverañ ul lenner video diavaez</string>
<string name="download_path_audio_title">Teuliad pellgargañ ar restroù aodio</string>
<string name="black_theme_title">Du</string>
<string name="show_search_suggestions_title">Kinnigoù enklask</string>
<string name="resume_on_audio_focus_gain_title">Kenderc\'hel al lenn</string>
<string name="unsupported_url">URL anskor</string>
<string name="content_language_title">Yezh an endalc\'had dre ziouer</string>
<string name="controls_download_desc">Pellgargañ restr al lanv</string>
<string name="install">Staliañ</string>
<string name="yes">Ya</string>
<string name="tab_subscriptions">Koumanantoù</string>
<string name="settings">Arventennoù</string>
<string name="search_showing_result_for">Setu an disoc\'hoù evit: %s</string>
<string name="use_external_video_player_summary">Lamet e vez an aodio gant diarunustedoù \'zo</string>
<string name="channel_unsubscribed">Digoumanantet oc\'h bet d\'ar chadenn</string>
<string name="enable_watch_history_title">Sellet ouzh ar roll istor</string>
<string name="subscribed_button_title">Koumanantet</string>
<string name="default_resolution_title">Diarunusted dre ziouer</string>
<string name="peertube_instance_url_summary">Diuzit hoc\'h erioloù PeerTube gwell ganeoc\'h</string>
<string name="peertube_instance_url_help">Kavit an erioloù a blij deoc\'h war %s</string>
<string name="peertube_instance_add_title">Ouzhpennañ un eriol</string>
<string name="settings_category_player_title">Lenner</string>
<string name="settings_category_video_audio_title">Video hag aodio</string>
<string name="settings_category_history_title">Roll istor ha krubuilh</string>
<string name="settings_category_appearance_title">Neuz</string>
<string name="settings_category_debug_title">Diveugañ</string>
<string name="settings_category_updates_title">Hizivadurioù</string>
<string name="settings_category_player_notification_title">Rebuzadur al lenner</string>
<string name="settings_category_backup_restore_title">Assav ha gwarediñ</string>
<string name="duration_live">War-eeun</string>
<string name="downloads_title">Pellgargadurioù</string>
<string name="all">Pep tra</string>
<string name="channels">Chadennoù</string>
<string name="videos_string">Videoioù</string>
<string name="tracks">Loabroù</string>
<string name="users">Arveriaded</string>
<string name="events">Degouezhioù</string>
<string name="songs">Tonioù</string>
<string name="albums">Albomoù</string>
<string name="artists">Arzourien</string>
<string name="disabled">Diweredekaet</string>
<string name="clear">Skarzhañ</string>
<string name="undo">Dizober</string>
<string name="file_deleted">Dilamet eo bet ar restr</string>
<string name="play_all">Lenn pep tra</string>
<string name="always">Atav</string>
<string name="file">Restr</string>
<string name="notifications">Rebuzadurioù</string>
<string name="notification_channel_name">Rebuzadur NewPipe</string>
<string name="notification_channel_description">Rebuzadurioù evit al lenner NewPipe</string>
<string name="app_update_notification_channel_description">Rebuzadurioù evit handelvoù nevez NewPipe</string>
<string name="streams_notification_channel_name">Lanvioù nevez</string>
<string name="just_once">Ur wech nemetken</string>
<string name="best_resolution">Diarunusted wellañ</string>
<string name="general_error">Fazi</string>
<string name="app_update_notification_channel_name">Rebuzadur hizivadur an arload</string>
<string name="content">Endalc\'had</string>
<string name="settings_category_player_behavior_title">Emzalc\'h</string>
<string name="playlists">Rolloù-lenn</string>
<string name="downloads">Pellgargadurioù</string>
<string name="error_snackbar_action">Sevel un danevell</string>
<string name="error_details_headline">Munudoù:</string>
<string name="audio">Aodio</string>
<string name="retry">Klask en-dro</string>
<string name="description_tab_description">Deskrivadur</string>
<string name="search_no_results">Disoc\'h ebet</string>
<string name="empty_list_subtitle">Endalchad ebet</string>
<string name="what_happened_headline">Petra zo c\'hoarvezet:</string>
<string name="detail_thumbnail_view_description">Lenn ar video, pad:</string>
<string name="what_device_headline">Titouroù:</string>
<string name="video">Video</string>
<string name="streams_notification_channel_description"></string>
</resources>

View file

@ -54,6 +54,7 @@
<string name="audio">Àudio</string> <string name="audio">Àudio</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s subscriptor</item> <item quantity="one">%s subscriptor</item>
<item quantity="many">%s subscriptors</item>
<item quantity="other">%s subscriptors</item> <item quantity="other">%s subscriptors</item>
</plurals> </plurals>
<string name="ok">D\'acord</string> <string name="ok">D\'acord</string>
@ -169,11 +170,13 @@
<string name="no_views">Cap reproducció</string> <string name="no_views">Cap reproducció</string>
<plurals name="views"> <plurals name="views">
<item quantity="one">%s reproducció</item> <item quantity="one">%s reproducció</item>
<item quantity="many">%s reproduccions</item>
<item quantity="other">%s reproduccions</item> <item quantity="other">%s reproduccions</item>
</plurals> </plurals>
<string name="no_videos">Cap vídeo</string> <string name="no_videos">Cap vídeo</string>
<plurals name="videos"> <plurals name="videos">
<item quantity="one">%s vídeo</item> <item quantity="one">%s vídeo</item>
<item quantity="many">%s vídeos</item>
<item quantity="other">%s vídeos</item> <item quantity="other">%s vídeos</item>
</plurals> </plurals>
<string name="pause">Pausa</string> <string name="pause">Pausa</string>
@ -228,9 +231,6 @@
<string name="player_recoverable_failure">S\'està recuperant el reproductor després de l\'error</string> <string name="player_recoverable_failure">S\'està recuperant el reproductor després de l\'error</string>
<string name="sorry_string">Bé, és lamentable.</string> <string name="sorry_string">Bé, és lamentable.</string>
<string name="detail_drag_description">Arrossegueu per reordenar la llista</string> <string name="detail_drag_description">Arrossegueu per reordenar la llista</string>
<string name="short_thousand">mil</string>
<string name="short_million">milions</string>
<string name="short_billion">mil milions</string>
<string name="start">Inicia</string> <string name="start">Inicia</string>
<string name="msg_running_detail">Feu un toc aquí per a més detalls</string> <string name="msg_running_detail">Feu un toc aquí per a més detalls</string>
<string name="no_available_dir">Defineix una carpeta de baixades més endavant als paràmetres</string> <string name="no_available_dir">Defineix una carpeta de baixades més endavant als paràmetres</string>
@ -274,15 +274,7 @@
<string name="enable_leak_canary_summary">La supervisió de fugues de memòria pot fer que l\'aplicació deixi de respondre mentre es bolca la memòria</string> <string name="enable_leak_canary_summary">La supervisió de fugues de memòria pot fer que l\'aplicació deixi de respondre mentre es bolca la memòria</string>
<string name="enable_disposed_exceptions_title">Informa d\'errors fora del cicle de vida</string> <string name="enable_disposed_exceptions_title">Informa d\'errors fora del cicle de vida</string>
<string name="enable_disposed_exceptions_summary">Força l\'informe d\'excepcions Rx que no es puguin transmetre que tinguin lloc fora del cicle de vida d\'un fragment o activitat després de disposar-los</string> <string name="enable_disposed_exceptions_summary">Força l\'informe d\'excepcions Rx que no es puguin transmetre que tinguin lloc fora del cicle de vida d\'un fragment o activitat després de disposar-los</string>
<string name="import_youtube_instructions">Importeu les vostres subscripcions de YouTube mitjançant la còpia de contingut de Google Takeout: <string name="import_youtube_instructions">Importeu les vostres subscripcions de YouTube mitjançant la còpia de contingut de Google Takeout: \n \n1. Aneu a : %1$s \n2. Inicieu la sessió si se us demana \n3. Premeu \"Totes les dades incloses\", després \"Dessel·lecciona-ho tot\", llavors sel·leccioneu només \"Subscripcions\" i finalment premeu \"D\'acord\". \n4. Premeu \"Pas següent\" i llavors a \"Crea una exportació\" \n5. Premeu el botó \"Baixa\" un cop hagi aparegut \n6. Premeu a IMPORTA EL FITXER i sel·leccioneu el fitxer .zip descarregat \n7. [En cas que la importació del fitxer .zip hagi fallat] extreieu-ne el fitxer subscripcions.csv (es troba generalment a \"Takeout/YouTube i YouTube Music/subscripcions/subscripcions.csv\"), premeu a IMPORTA EL FITXER i sel·leccioneu el fitxer .csv extret</string>
\n
\n1. Aneu a : %1$s
\n2. Inicieu la sessió si se us demana
\n3. Premeu \"Totes les dades incloses\", després \"Dessel·lecciona-ho tot\", llavors sel·leccioneu només \"Subscripcions\" i finalment premeu \"D\'acord\".
\n4. Premeu \"Pas següent\" i llavors a \"Crea una exportació\"
\n5. Premeu el botó \"Baixa\" un cop hagi aparegut
\n6. Premeu a IMPORTA EL FITXER i sel·leccioneu el fitxer .zip descarregat
\n7. [En cas que la importació del fitxer .zip hagi fallat] extreieu-ne el fitxer subscripcions.csv (es troba generalment a \"Takeout/YouTube i YouTube Music/subscripcions/subscripcions.csv\"), premeu a IMPORTA EL FITXER i sel·leccioneu el fitxer .csv extret.</string>
<string name="import_soundcloud_instructions">Importeu un perfil del SoundCloud mitjançant l\'URL o l\'identificador del vostre perfil: <string name="import_soundcloud_instructions">Importeu un perfil del SoundCloud mitjançant l\'URL o l\'identificador del vostre perfil:
\n \n
\n1. Activeu el «Mode d\'ordinador» en un navegador (el lloc web no està disponible per a dispositius mòbils) \n1. Activeu el «Mode d\'ordinador» en un navegador (el lloc web no està disponible per a dispositius mòbils)
@ -407,7 +399,7 @@
<string name="missing_file">El fitxer s\'ha mogut o suprimit</string> <string name="missing_file">El fitxer s\'ha mogut o suprimit</string>
<string name="enable_queue_limit_desc">Només una baixada alhora</string> <string name="enable_queue_limit_desc">Només una baixada alhora</string>
<string name="downloads_storage_use_saf_title">Fes servir el SAF</string> <string name="downloads_storage_use_saf_title">Fes servir el SAF</string>
<string name="downloads_storage_use_saf_summary">El SAF (Storage Access Framework; estructura d\'accés a l\'emmagatzematge) us permet realitzar baixades a una memòria externa com una targeta SD.</string> <string name="downloads_storage_use_saf_summary">El SAF (Storage Access Framework; estructura d\'accés a l\'emmagatzematge) us permet realitzar baixades a una memòria externa com una targeta SD</string>
<string name="clear_playback_states_title">Esborra les posicions de reproducció</string> <string name="clear_playback_states_title">Esborra les posicions de reproducció</string>
<string name="clear_playback_states_summary">Esborra totes les posicions de reproducció</string> <string name="clear_playback_states_summary">Esborra totes les posicions de reproducció</string>
<string name="delete_playback_states_alert">Voleu suprimir tots els punts de reproducció\?</string> <string name="delete_playback_states_alert">Voleu suprimir tots els punts de reproducció\?</string>
@ -415,14 +407,15 @@
<string name="no_one_watching">Cap visualització</string> <string name="no_one_watching">Cap visualització</string>
<plurals name="watching"> <plurals name="watching">
<item quantity="one">%s visualització</item> <item quantity="one">%s visualització</item>
<item quantity="many">%s visualitzacions</item>
<item quantity="other">%s visualitzacions</item> <item quantity="other">%s visualitzacions</item>
</plurals> </plurals>
<string name="no_one_listening">Cap reproducció</string> <string name="no_one_listening">Cap reproducció</string>
<plurals name="listening"> <plurals name="listening">
<item quantity="one">%s escoltant</item> <item quantity="one">%s escoltant</item>
<item quantity="many">%s escoltants</item>
<item quantity="other">%s escoltants</item> <item quantity="other">%s escoltants</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">Es canviarà l\'idioma en reiniciar l\'aplicació</string>
<string name="default_kiosk_page_summary">Tendències</string> <string name="default_kiosk_page_summary">Tendències</string>
<string name="show_original_time_ago_title">Ensenya el temps passat original sobre els \"items\"</string> <string name="show_original_time_ago_title">Ensenya el temps passat original sobre els \"items\"</string>
<string name="playlist_no_uploader">Auto-generat (no es troba cap uploader)</string> <string name="playlist_no_uploader">Auto-generat (no es troba cap uploader)</string>
@ -477,17 +470,18 @@
<string name="feed_update_threshold_option_always_update">Actualitza sempre</string> <string name="feed_update_threshold_option_always_update">Actualitza sempre</string>
<string name="feed_update_threshold_summary">Temps que ha de passar perquè una subscripció es consideri obsoleta — %s</string> <string name="feed_update_threshold_summary">Temps que ha de passar perquè una subscripció es consideri obsoleta — %s</string>
<string name="feed_update_threshold_title">Llindar d\'actualització del contingut</string> <string name="feed_update_threshold_title">Llindar d\'actualització del contingut</string>
<string name="settings_category_feed_title">Feed</string> <string name="settings_category_feed_title">Flux</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Mostra només les subscripcions sense grup</string> <string name="feed_group_show_only_ungrouped_subscriptions">Mostra només les subscripcions sense grup</string>
<string name="feed_create_new_group_button_title">Nou</string> <string name="feed_create_new_group_button_title">Nou</string>
<string name="feed_group_dialog_delete_message">Esteu segurs de voler suprimir aquest grup\?</string> <string name="feed_group_dialog_delete_message">Esteu segurs de voler suprimir aquest grup\?</string>
<string name="feed_group_dialog_empty_name">Nom de grup buit</string> <string name="feed_group_dialog_empty_name">Nom de grup buit</string>
<plurals name="feed_group_dialog_selection_count"> <plurals name="feed_group_dialog_selection_count">
<item quantity="one">%d de sel·leccionat</item> <item quantity="one">%d de seleccionat</item>
<item quantity="other">%d de sel·leccionats</item> <item quantity="many">%d de seleccionats</item>
<item quantity="other">%d de seleccionats</item>
</plurals> </plurals>
<string name="feed_group_dialog_empty_selection">Cap subscripció sel·leccionada</string> <string name="feed_group_dialog_empty_selection">Cap subscripció seleccionada</string>
<string name="feed_group_dialog_select_subscriptions">Sel·leccioneu les subscripcions</string> <string name="feed_group_dialog_select_subscriptions">Selecciona subscripcions</string>
<string name="feed_processing_message">Processant el contingut…</string> <string name="feed_processing_message">Processant el contingut…</string>
<string name="feed_notification_loading">Carregant el contingut…</string> <string name="feed_notification_loading">Carregant el contingut…</string>
<string name="feed_subscription_not_loaded_count">No carregat: %d</string> <string name="feed_subscription_not_loaded_count">No carregat: %d</string>
@ -495,18 +489,22 @@
<string name="feed_groups_header_title">Grups de canals</string> <string name="feed_groups_header_title">Grups de canals</string>
<plurals name="days"> <plurals name="days">
<item quantity="one">%d dia</item> <item quantity="one">%d dia</item>
<item quantity="many">%d dies</item>
<item quantity="other">%d dies</item> <item quantity="other">%d dies</item>
</plurals> </plurals>
<plurals name="hours"> <plurals name="hours">
<item quantity="one">%d hora</item> <item quantity="one">%d hora</item>
<item quantity="many">%d hores</item>
<item quantity="other">%d hores</item> <item quantity="other">%d hores</item>
</plurals> </plurals>
<plurals name="minutes"> <plurals name="minutes">
<item quantity="one">%d minut</item> <item quantity="one">%d minut</item>
<item quantity="many">%d minuts</item>
<item quantity="other">%d minuts</item> <item quantity="other">%d minuts</item>
</plurals> </plurals>
<plurals name="seconds"> <plurals name="seconds">
<item quantity="one">%d segon</item> <item quantity="one">%d segon</item>
<item quantity="many">%d segons</item>
<item quantity="other">%d segons</item> <item quantity="other">%d segons</item>
</plurals> </plurals>
<string name="new_seek_duration_toast">A causa de les limitacions d\'ExoPlayer, la durada de cerca és de %d segons</string> <string name="new_seek_duration_toast">A causa de les limitacions d\'ExoPlayer, la durada de cerca és de %d segons</string>
@ -643,13 +641,12 @@
<string name="manual_update_description">Comprovar manualment si hi ha noves versions</string> <string name="manual_update_description">Comprovar manualment si hi ha noves versions</string>
<plurals name="download_finished_notification"> <plurals name="download_finished_notification">
<item quantity="one">Baixada finalitzada</item> <item quantity="one">Baixada finalitzada</item>
<item quantity="many">%s baixades finalitzades</item>
<item quantity="other">%s baixades finalitzades</item> <item quantity="other">%s baixades finalitzades</item>
</plurals> </plurals>
<string name="seekbar_preview_thumbnail_title">Vista prèvia de les miniatures de la barra de cerca</string> <string name="seekbar_preview_thumbnail_title">Vista prèvia de les miniatures de la barra de cerca</string>
<string name="no_appropriate_file_manager_message_android_10">No s\'ha trobat cap gestor de fitxers adequat per a aquesta acció. <string name="no_appropriate_file_manager_message_android_10">No s\'ha trobat cap gestor de fitxers adequat per a aquesta acció. \nInstal·leu un gestor de fitxers compatible amb l\'entorn d\'accés d\'emmagatzematge</string>
\nInstal·leu un gestor de fitxers compatible amb l\'entorn d\'accés d\'emmagatzematge.</string> <string name="no_appropriate_file_manager_message">No s\'ha trobat cap gestor de fitxers adequat per a aquesta acció. \nInstal·leu un gestor de fitxers o intenteu desactivar «%s» als paràmetres de baixada</string>
<string name="no_appropriate_file_manager_message">No s\'ha trobat cap gestor de fitxers adequat per a aquesta acció.
\nInstal·leu un gestor de fitxers o intenteu desactivar «%s» als paràmetres de baixada.</string>
<string name="error_report_notification_toast">S\'ha produït un error, consulteu la notificació</string> <string name="error_report_notification_toast">S\'ha produït un error, consulteu la notificació</string>
<string name="enqueued_next">Afegit el següent vídeo a la cua</string> <string name="enqueued_next">Afegit el següent vídeo a la cua</string>
<string name="error_report_notification_title">NewPipe ha trobat un error, toca per informar</string> <string name="error_report_notification_title">NewPipe ha trobat un error, toca per informar</string>
@ -660,6 +657,7 @@
<string name="checking_updates_toast">S\'estan comprovant les actualitzacions…</string> <string name="checking_updates_toast">S\'estan comprovant les actualitzacions…</string>
<plurals name="deleted_downloads_toast"> <plurals name="deleted_downloads_toast">
<item quantity="one">S\'ha suprimit %1$s baixada</item> <item quantity="one">S\'ha suprimit %1$s baixada</item>
<item quantity="many">S\'han suprimit %1$s baixades</item>
<item quantity="other">S\'han suprimit %1$s baixades</item> <item quantity="other">S\'han suprimit %1$s baixades</item>
</plurals> </plurals>
<string name="downloads_storage_use_saf_summary_api_29">A partir de l\'Android 10 només s\'admet el \"Sistema d\'Accés a l\'Emmagatzematge\"</string> <string name="downloads_storage_use_saf_summary_api_29">A partir de l\'Android 10 només s\'admet el \"Sistema d\'Accés a l\'Emmagatzematge\"</string>
@ -689,8 +687,8 @@
<string name="unknown_format">Format desconegut</string> <string name="unknown_format">Format desconegut</string>
<string name="unknown_quality">Cualitat desconeguda</string> <string name="unknown_quality">Cualitat desconeguda</string>
<string name="sort">Ordenar</string> <string name="sort">Ordenar</string>
<string name="settings_category_player_notification_summary">Configura la notificació de reproducció actual.</string> <string name="settings_category_player_notification_summary">Configura la notificació de reproducció actual</string>
<string name="progressive_load_interval_summary">Canvia la mida de l\'interval de càrrega en continguts progressius (actualment %s). Un valor inferior pot accelerar la càrrega inicial del vídeo.</string> <string name="progressive_load_interval_summary">Canvia la mida de l\'interval de càrrega en continguts progressius (actualment %s). Un valor inferior pot accelerar la càrrega inicial del vídeo</string>
<string name="ignore_hardware_media_buttons_title">Ignora els esdeveniments dels botons de reproducció físics</string> <string name="ignore_hardware_media_buttons_title">Ignora els esdeveniments dels botons de reproducció físics</string>
<string name="ignore_hardware_media_buttons_summary">Útil, per exemple, si feu servir uns auriculars amb els botons físicament trencats</string> <string name="ignore_hardware_media_buttons_summary">Útil, per exemple, si feu servir uns auriculars amb els botons físicament trencats</string>
<string name="left_gesture_control_summary">Trieu un gest per la part esquerra de la pantalla</string> <string name="left_gesture_control_summary">Trieu un gest per la part esquerra de la pantalla</string>
@ -733,4 +731,95 @@
<string name="playlist_add_stream_success_duplicate">Duplicat afegit/s %d vegada/es</string> <string name="playlist_add_stream_success_duplicate">Duplicat afegit/s %d vegada/es</string>
<string name="disable_media_tunneling_automatic_info">El túnel multimèdia s\'ha desactivat de manera predeterminada al dispositiu perquè se sap que el vostre model de dispositiu no ho permet.</string> <string name="disable_media_tunneling_automatic_info">El túnel multimèdia s\'ha desactivat de manera predeterminada al dispositiu perquè se sap que el vostre model de dispositiu no ho permet.</string>
<string name="semitone">Semiton</string> <string name="semitone">Semiton</string>
<string name="app_update_unavailable_toast">Estàs fent servir la darrera versió de NewPipe</string>
<string name="error_insufficient_storage">No hi ha prou espai lliure al dispositiu</string>
<string name="tab_bookmarks_short">Llistes de reproducció</string>
<string name="card">Targeta</string>
<string name="remove_duplicates_message">Vols suprimir tots els elements duplicats d\'aquesta llista de reproducció?</string>
<string name="channel_tab_playlists">Llistes de reproducció</string>
<string name="remove_duplicates">Suprimeix els duplicats</string>
<string name="reset_settings_title">Restableix la configuració</string>
<string name="auto_update_check_description">NewPipe pot cercar automàticament actualitzacions i fer-t\'ho saber en estar disponibles.\nVols habilitar-ho?</string>
<string name="remove_duplicates_title">Suprimeixo els duplicats?</string>
<string name="reset_settings_summary">Restableix tots els paràmetres als valors per defecte</string>
<string name="reset_all_settings">Restablir tots els paràmetres descartarà els teus paràmetres preferits i reiniciarà l\'aplicació.\n\nN\'estàs segur?</string>
<string name="app_update_available_notification_text">Clica per descarregar%s</string>
<string name="audio_track_type_dubbed">doblat</string>
<string name="toggle_all">Commuta-ho tot</string>
<string name="feed_show_upcoming">Pròximament</string>
<string name="channel_tab_livestreams">En directe</string>
<string name="play">Reprodueix</string>
<string name="replay">Torna a reproduir</string>
<string name="more_options">Més opcions</string>
<string name="share_playlist_with_list">Comparteix la llista dels URLs</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="metadata_uploader_avatars">Avatars de l\'autor</string>
<string name="metadata_subchannel_avatars">Avatars del sots-canal</string>
<string name="metadata_subscribers">Subscriptors</string>
<string name="audio_track_present_in_video">Ja hi hauria d\'haver una pista d\'àudio en aquest flux</string>
<string name="selected_stream_external_player_not_supported">El contingut escollit no és suportat per cap reproductor extern</string>
<string name="no_video_streams_available_for_external_players">No hi ha cap flux de vídeo disponible per a reproductors externs</string>
<string name="select_audio_track_external_players">Escull la pista d\'àudio per a reproductors externs</string>
<string name="unknown_audio_track">Desconegut</string>
<string name="audio_track_name">%1$s%2$s</string>
<string name="audio_track_type_original">original</string>
<string name="audio_track_type_descriptive">descriptiu</string>
<string name="channel_tab_videos">Vídeos</string>
<string name="show_channel_tabs_summary">Quines pestanyes es mostren a les pàgines del canal</string>
<string name="open_play_queue">Obre la cua de reproducció</string>
<string name="toggle_screen_orientation">Canvia l\'orientació de la pantalla</string>
<string name="previous_stream">Vídeo anterior</string>
<string name="forward">Avança</string>
<string name="image_quality_title">Qualitat de la imatge</string>
<string name="image_quality_none">No carregues les imatges</string>
<string name="image_quality_medium">Qualitat mitjana</string>
<string name="image_quality_low">Qualitat baixa</string>
<string name="share_playlist_as_youtube_temporary_playlist">Comparteix com a llista de reproducció temporal de Youtube</string>
<string name="share_playlist_content_details">%1$s\n%2$s</string>
<string name="show_more">Mostra més</string>
<string name="metadata_avatars">Avatars</string>
<string name="metadata_banners">Bàners</string>
<string name="duration">Durada</string>
<string name="rewind">Rebobina</string>
<string name="share_playlist_with_titles">Comparteix amb els títols</string>
<string name="streams_not_yet_supported_removed">No es mostren els contiguts que no suporten descàrrega</string>
<string name="feed_hide_streams_title">Mostra els vídeos següents</string>
<string name="no_audio_streams_available_for_external_players">No hi ha cap flux d\'àudio disponible per a reproductors externs</string>
<string name="feed_show_hide_streams">Mostra/Amaga els vídeos</string>
<string name="night_theme_available">Aquesta opció només està disponible si%ss\'ha seleccionat per al tema</string>
<string name="next_stream">Vídeo següent</string>
<plurals name="replies">
<item quantity="one">%sresposta</item>
<item quantity="many">%srespostes</item>
<item quantity="other">%srespostes</item>
</plurals>
<string name="feed_fetch_channel_tabs">Recupera les pestanyes del canal</string>
<string name="progressive_load_interval_exoplayer_default">Valor per defecte d\'ExoPlayer</string>
<string name="metadata_thumbnails">Miniatures</string>
<string name="channel_tab_about">Quant a</string>
<string name="always_use_exoplayer_set_output_surface_workaround_title">Usa sempre la sortida de vídeo d\'ExoPlayer com a solució de contingència</string>
<string name="import_settings_vulnerable_format">La configuració exportada que vols importar té un format vulnerable que és obsolet des de NewPipe 0.27.0. Assegura\'t que l\'exportació que vols importar prové d\'una font de confiança i prefereix només les exportacions fetes amb NewPipe 0.27.0 o posterior d\'ara endavant. El suport a la importació de configuracions en aquest format vulnerable aviat serà suprimit completament i aleshores les antigues versions de NewPipe ja no podran importar les exportacions de les configuracions des de les noves versions.</string>
<string name="channel_tab_albums">Àlbums</string>
<string name="show_channel_tabs">Pestanyes del canal</string>
<string name="image_quality_high">Qualitat alta</string>
<string name="image_quality_summary">Tria la qualitat de les imatges i si carregar-les totalment o no per reduir l\'ús de les dades i la memòria. Els canvis suprimiran la memòria cau de les imatges a la memòria i al disc — %s</string>
<string name="feed_fetch_channel_tabs_summary">Pestanyes que es recuperaran en actualitzar el contingut. Aquesta opció no s\'aplica si el canal s\'actualitza en mode ràpid.</string>
<string name="feed_show_partially_watched">Vist parcialment</string>
<string name="feed_show_watched">Vist completament</string>
<string name="settings_category_exoplayer_title">Paràmetres d\'ExoPlayer</string>
<string name="settings_category_exoplayer_summary">Gestiona alguns paràmetres d\'ExoPlayer. Caldrà reinciciar el reproductor per activar-los</string>
<string name="use_exoplayer_decoder_fallback_title">Usa la funció de suport de decodificació d\'ExoPlayer</string>
<string name="use_exoplayer_decoder_fallback_summary">Habilita aquesta opció si tens problemes en iniciar el decodificador. S\'usaran decodificadors alternatius de baixa prioritat si falla el decodificador primari. Això pot provocar una disminució de la qualitat de la reproducció en relació a l\'ús del decodificador primari</string>
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Aquesta alternativa allibera i reinstancia els còdecs de vídeo si hi ha un canvi de màscara en lloc de configurar-la directament al còdec. ExoPlayer ja ho aplica en alguns dispositius amb aquest problema. Aquesta configuració només té efecte en Android 6 i posteriors\n\nHabilitar aquest opció pot prevenir errors de reproducció en canviar el reproductor actual o en passar a pantalla completa</string>
<string name="channel_tab_tracks">Pistes</string>
<string name="channel_tab_shorts">Curts</string>
<string name="toggle_fullscreen">Canvia a pantalla completa</string>
<string name="question_mark">\?</string>
<string name="share_playlist">Comparteix la llista de reproducció</string>
<string name="show_less">Mostra menys</string>
<string name="audio_track_type_secondary">secundària</string>
<string name="channel_tab_channels">Canals</string>
<string name="no_feed_group_created_yet">Encara no s\'ha creat cap grup de continguts</string>
<string name="select_a_feed_group">Tria un grup de continguts</string>
<string name="feed_group_page_summary">Pàgina del grup de canals</string>
</resources> </resources>

View file

@ -25,7 +25,6 @@
<string name="subscribers_count_not_available">ژمارەی بەژداری نادیارە</string> <string name="subscribers_count_not_available">ژمارەی بەژداری نادیارە</string>
<string name="overwrite_failed">ناتوانرێت لەسەر ئەو فایله‌وه‌ جێگیر بکرێت</string> <string name="overwrite_failed">ناتوانرێت لەسەر ئەو فایله‌وه‌ جێگیر بکرێت</string>
<string name="tab_choose">په‌ڕه‌ هەڵبژێرە</string> <string name="tab_choose">په‌ڕه‌ هەڵبژێرە</string>
<string name="short_million">ملیۆن</string>
<string name="more_than_100_videos">+١٠٠ ڤیدیۆیان</string> <string name="more_than_100_videos">+١٠٠ ڤیدیۆیان</string>
<string name="settings_category_player_title">لێده‌ر</string> <string name="settings_category_player_title">لێده‌ر</string>
<string name="import_title">هاوردە</string> <string name="import_title">هاوردە</string>
@ -46,7 +45,6 @@
<string name="override_current_data">ئەمە لەسەر ڕێکخستنەکانی ئێستات جێگیر دەبێت.</string> <string name="override_current_data">ئەمە لەسەر ڕێکخستنەکانی ئێستات جێگیر دەبێت.</string>
<string name="notification_channel_name">پەیامەکانی نیوپایپ</string> <string name="notification_channel_name">پەیامەکانی نیوپایپ</string>
<string name="donation_encouragement">نیوپایپ لەلایەن چەند خۆبەخشێکەوە دروستکراوە کە کاته‌كانی خۆیان پێ بەخشیووە تاکو باشترین خزمەتگوزاریت پێشکەش بکەن. هیچ نەبێت بە کڕینی کوپێک قاوە یارمەتی گەشەپێدەرەکانمان بدە بۆ ئەوەی کاتی زیاتر تەرخان بکەین بۆ بەرەوپێشبردنی نیوپایپ.</string> <string name="donation_encouragement">نیوپایپ لەلایەن چەند خۆبەخشێکەوە دروستکراوە کە کاته‌كانی خۆیان پێ بەخشیووە تاکو باشترین خزمەتگوزاریت پێشکەش بکەن. هیچ نەبێت بە کڕینی کوپێک قاوە یارمەتی گەشەپێدەرەکانمان بدە بۆ ئەوەی کاتی زیاتر تەرخان بکەین بۆ بەرەوپێشبردنی نیوپایپ.</string>
<string name="short_billion">ملیار</string>
<string name="show_search_suggestions_title">گەڕانی پێشنیارکراوەکان</string> <string name="show_search_suggestions_title">گەڕانی پێشنیارکراوەکان</string>
<string name="playback_tempo">خێرا</string> <string name="playback_tempo">خێرا</string>
<string name="file_deleted">فایل سڕایەوە</string> <string name="file_deleted">فایل سڕایەوە</string>
@ -107,7 +105,6 @@
<string name="subscription_update_failed">ناتوانرێت به‌ژداریكردنه‌كه‌ نوێبكرێته‌وه‌</string> <string name="subscription_update_failed">ناتوانرێت به‌ژداریكردنه‌كه‌ نوێبكرێته‌وه‌</string>
<string name="controls_background_title">پشت شاشە</string> <string name="controls_background_title">پشت شاشە</string>
<string name="search_no_results">بێ ئەنجامه‌</string> <string name="search_no_results">بێ ئەنجامه‌</string>
<string name="localization_changes_requires_app_restart">زمان دەگۆڕدرێت لەدوای داگیرساندنەوەی به‌رنامه‌كه‌</string>
<string name="remove_watched">لادانی سەیرکراو</string> <string name="remove_watched">لادانی سەیرکراو</string>
<string name="enable_playback_state_lists_summary">پیشاندانی نیشانەکەری شوێنی کارپێکەر لە خشتەکاندا</string> <string name="enable_playback_state_lists_summary">پیشاندانی نیشانەکەری شوێنی کارپێکەر لە خشتەکاندا</string>
<string name="enable_playback_state_lists_title">شوێنەکان لە خشتەکاندا</string> <string name="enable_playback_state_lists_title">شوێنەکان لە خشتەکاندا</string>
@ -374,7 +371,6 @@
<string name="download_failed">ناتوانرێت داببه‌زێنرێت</string> <string name="download_failed">ناتوانرێت داببه‌زێنرێت</string>
<string name="error_connect_host">ناتوانرێت بە ڕاژەكه‌وە پەیوەست ببیت</string> <string name="error_connect_host">ناتوانرێت بە ڕاژەكه‌وە پەیوەست ببیت</string>
<string name="detail_thumbnail_view_description">لێدانی ڤیدیۆ، مه‌ودا:</string> <string name="detail_thumbnail_view_description">لێدانی ڤیدیۆ، مه‌ودا:</string>
<string name="short_thousand">هەزار</string>
<string name="most_liked">زۆرترین بەدڵ</string> <string name="most_liked">زۆرترین بەدڵ</string>
<string name="delete">سڕینەوە</string> <string name="delete">سڕینەوە</string>
<string name="default_video_format_title">جۆری بنەڕەتی ڤیدیۆ</string> <string name="default_video_format_title">جۆری بنەڕەتی ڤیدیۆ</string>

View file

@ -57,7 +57,7 @@
<string name="msg_name">Jméno souboru</string> <string name="msg_name">Jméno souboru</string>
<string name="msg_threads">Vlákna</string> <string name="msg_threads">Vlákna</string>
<string name="pause">Zastavit</string> <string name="pause">Zastavit</string>
<string name="delete">Smazat</string> <string name="delete">Odstranit</string>
<string name="start">Start</string> <string name="start">Start</string>
<string name="retry">Zkusit znovu</string> <string name="retry">Zkusit znovu</string>
<string name="video">Video</string> <string name="video">Video</string>
@ -83,9 +83,7 @@
<string name="no_available_dir">Určete prosím složku pro stahování později v nastavení</string> <string name="no_available_dir">Určete prosím složku pro stahování později v nastavení</string>
<string name="info_labels">Co:\\nŽádost:\\nJazyk obsahu:\\nZemě obsahu:\\nJazyk aplikace:\\nSlužba:\\nČas GMT:\\nBalíček:\\nVerze:\\nVerze OS:</string> <string name="info_labels">Co:\\nŽádost:\\nJazyk obsahu:\\nZemě obsahu:\\nJazyk aplikace:\\nSlužba:\\nČas GMT:\\nBalíček:\\nVerze:\\nVerze OS:</string>
<string name="all">Vše</string> <string name="all">Vše</string>
<string name="short_thousand">tis.</string>
<string name="open_in_popup_mode">Otevřít ve vyskakovacím okně</string> <string name="open_in_popup_mode">Otevřít ve vyskakovacím okně</string>
<string name="short_million">mil.</string>
<string name="msg_popup_permission">Toto oprávnění je vyžadováno <string name="msg_popup_permission">Toto oprávnění je vyžadováno
\npro otevření ve vyskakovacím okně</string> \npro otevření ve vyskakovacím okně</string>
<string name="use_external_video_player_summary">Odstraňuje zvuk v některých rozlišeních</string> <string name="use_external_video_player_summary">Odstraňuje zvuk v některých rozlišeních</string>
@ -124,7 +122,6 @@
<string name="notification_channel_description">Oznámení pro NewPipe přehrávač</string> <string name="notification_channel_description">Oznámení pro NewPipe přehrávač</string>
<string name="search_no_results">Žádné výsledky</string> <string name="search_no_results">Žádné výsledky</string>
<string name="empty_list_subtitle">Je tu sranda jak v márnici</string> <string name="empty_list_subtitle">Je tu sranda jak v márnici</string>
<string name="short_billion">mld.</string>
<string name="no_subscribers">Žádní odběratelé</string> <string name="no_subscribers">Žádní odběratelé</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s odběratel</item> <item quantity="one">%s odběratel</item>
@ -139,9 +136,9 @@
</plurals> </plurals>
<string name="no_videos">Žádná videa</string> <string name="no_videos">Žádná videa</string>
<plurals name="videos"> <plurals name="videos">
<item quantity="one">%s Video</item> <item quantity="one">%s video</item>
<item quantity="few">%s Videa</item> <item quantity="few">%s videa</item>
<item quantity="other">%s Videí</item> <item quantity="other">%s videí</item>
</plurals> </plurals>
<string name="settings_category_downloads_title">Stahování</string> <string name="settings_category_downloads_title">Stahování</string>
<string name="settings_file_charset_title">Povolené znaky v názvech souborů</string> <string name="settings_file_charset_title">Povolené znaky v názvech souborů</string>
@ -234,8 +231,8 @@
<string name="add_to_playlist">Přidat do playlistu</string> <string name="add_to_playlist">Přidat do playlistu</string>
<string name="set_as_playlist_thumbnail">Nastavit jako náhled playlistu</string> <string name="set_as_playlist_thumbnail">Nastavit jako náhled playlistu</string>
<string name="bookmark_playlist">Přidat playlist do záložek</string> <string name="bookmark_playlist">Přidat playlist do záložek</string>
<string name="unbookmark_playlist">Smazat záložku</string> <string name="unbookmark_playlist">Odstranit záložku</string>
<string name="delete_playlist_prompt">Smazat tento playlist\?</string> <string name="delete_playlist_prompt">Odstranit tento playlist?</string>
<string name="playlist_creation_success">Playlist vytvořen</string> <string name="playlist_creation_success">Playlist vytvořen</string>
<string name="playlist_add_stream_success">V playlistu</string> <string name="playlist_add_stream_success">V playlistu</string>
<string name="playlist_thumbnail_change_success">Náhled playlistu změněn.</string> <string name="playlist_thumbnail_change_success">Náhled playlistu změněn.</string>
@ -411,9 +408,9 @@
<string name="enable_playback_state_lists_summary">Zobrazit pozici přehrávání v seznamech</string> <string name="enable_playback_state_lists_summary">Zobrazit pozici přehrávání v seznamech</string>
<string name="watch_history_states_deleted">Pozice playbacku smazány</string> <string name="watch_history_states_deleted">Pozice playbacku smazány</string>
<string name="error_timeout">Timeout spojení</string> <string name="error_timeout">Timeout spojení</string>
<string name="clear_playback_states_title">Smazat pozice playbacku</string> <string name="clear_playback_states_title">Vymazat pozice přehrávání</string>
<string name="clear_playback_states_summary">Smazat všechny pozice playbacku</string> <string name="clear_playback_states_summary">Vymaže všechny pozice přehrávání</string>
<string name="delete_playback_states_alert">Smazat všechny pozice playbacku\?</string> <string name="delete_playback_states_alert">Vymazat všechny pozice přehrávání?</string>
<string name="drawer_header_description">Přepnout službu, právě vybráno:</string> <string name="drawer_header_description">Přepnout službu, právě vybráno:</string>
<string name="no_one_watching">Nikdo nesleduje</string> <string name="no_one_watching">Nikdo nesleduje</string>
<plurals name="watching"> <plurals name="watching">
@ -427,7 +424,6 @@
<item quantity="few">%s posluchači</item> <item quantity="few">%s posluchači</item>
<item quantity="other">%s posluchačů</item> <item quantity="other">%s posluchačů</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">Ke změně jazyka dojde po restartu aplikace</string>
<string name="default_kiosk_page_summary">Výchozí kiosek</string> <string name="default_kiosk_page_summary">Výchozí kiosek</string>
<string name="seek_duration_title">Délka přetočení vpřed/zpět</string> <string name="seek_duration_title">Délka přetočení vpřed/zpět</string>
<string name="peertube_instance_url_title">Instance PeerTube</string> <string name="peertube_instance_url_title">Instance PeerTube</string>
@ -445,8 +441,8 @@
<string name="recovering">obnovuji</string> <string name="recovering">obnovuji</string>
<string name="error_download_resource_gone">Toto stahování nelze obnovit</string> <string name="error_download_resource_gone">Toto stahování nelze obnovit</string>
<string name="choose_instance_prompt">Vyberte instanci</string> <string name="choose_instance_prompt">Vyberte instanci</string>
<string name="clear_download_history">Smazat historii stahování</string> <string name="clear_download_history">Vymazat historii stahování</string>
<string name="delete_downloaded_files">Smazat stažené soubory</string> <string name="delete_downloaded_files">Odstranit stažené soubory</string>
<string name="permission_display_over_apps">Souhlasit se zobrazením přes jiné aplikace</string> <string name="permission_display_over_apps">Souhlasit se zobrazením přes jiné aplikace</string>
<string name="app_language_title">Jazyk aplikace</string> <string name="app_language_title">Jazyk aplikace</string>
<string name="systems_language">Jazyk systému</string> <string name="systems_language">Jazyk systému</string>
@ -489,7 +485,7 @@
<item quantity="other">%d vybráno</item> <item quantity="other">%d vybráno</item>
</plurals> </plurals>
<string name="feed_group_dialog_empty_name">Prázdné jméno skupiny</string> <string name="feed_group_dialog_empty_name">Prázdné jméno skupiny</string>
<string name="feed_group_dialog_delete_message">Přejete si smazat tuto skupinu\?</string> <string name="feed_group_dialog_delete_message">Přejete si odstranit tuto skupinu?</string>
<string name="feed_create_new_group_button_title">Nová</string> <string name="feed_create_new_group_button_title">Nová</string>
<string name="settings_category_feed_title">Novinky</string> <string name="settings_category_feed_title">Novinky</string>
<string name="feed_update_threshold_title">Limit aktualizace novinek</string> <string name="feed_update_threshold_title">Limit aktualizace novinek</string>
@ -688,7 +684,7 @@
<string name="streams_notifications_interval_title">Frekvence kontroly</string> <string name="streams_notifications_interval_title">Frekvence kontroly</string>
<string name="any_network">Jakákoli síť</string> <string name="any_network">Jakákoli síť</string>
<string name="streams_notifications_network_title">Požadované síťové připojení</string> <string name="streams_notifications_network_title">Požadované síťové připojení</string>
<string name="delete_downloaded_files_confirm">Smazat všechny stažené soubory z disku\?</string> <string name="delete_downloaded_files_confirm">Odstranit všechny stažené soubory z disku?</string>
<string name="you_successfully_subscribed">Objednali jste si nyní tento kanál</string> <string name="you_successfully_subscribed">Objednali jste si nyní tento kanál</string>
<string name="toggle_all">Všechny přepnout</string> <string name="toggle_all">Všechny přepnout</string>
<string name="streams_notification_channel_name">Nové streamy</string> <string name="streams_notification_channel_name">Nové streamy</string>
@ -837,4 +833,14 @@
\nChcete tuto funkci povolit?</string> \nChcete tuto funkci povolit?</string>
<string name="import_settings_vulnerable_format">Nastavení v importovaném exportu používají zranitelný formát. NewPipe používá nový formát od verze 0.27.0. Ujistěte se, že export importujete z důvěryhodného zdroje a v budoucnu upřednostňujte používání exportů získaných z NewPipe 0.27.0 nebo novějších. Podpora importu nastavení v tomto zranitelném formátu bude brzy kompletně odstraněna, kvůli čemuž staré verze NewPipe nebudou moci importovat nastavení z exportů z nových verzí.</string> <string name="import_settings_vulnerable_format">Nastavení v importovaném exportu používají zranitelný formát. NewPipe používá nový formát od verze 0.27.0. Ujistěte se, že export importujete z důvěryhodného zdroje a v budoucnu upřednostňujte používání exportů získaných z NewPipe 0.27.0 nebo novějších. Podpora importu nastavení v tomto zranitelném formátu bude brzy kompletně odstraněna, kvůli čemuž staré verze NewPipe nebudou moci importovat nastavení z exportů z nových verzí.</string>
<string name="audio_track_type_secondary">sekundární</string> <string name="audio_track_type_secondary">sekundární</string>
<string name="share_playlist_as_youtube_temporary_playlist">Sdílet jako dočasný playlist YouTube</string>
<string name="tab_bookmarks_short">Playlisty</string>
<string name="select_a_feed_group">Vybrat skupinu kanálů</string>
<string name="no_feed_group_created_yet">Zatím nebyla vytvořena žádná skupina kanálů</string>
<string name="feed_group_page_summary">Stránka skupiny kanálů</string>
<string name="search_with_service_name">Hledat %1$s</string>
<string name="search_with_service_name_and_filter">Hledat %1$s (%2$s)</string>
<string name="channel_tab_likes">Líbí se</string>
<string name="migration_info_6_7_title">Stránka SoundCloud Top 50 odstraněna</string>
<string name="migration_info_6_7_message">SoundCloud zrušil původní žebříčky Top 50. Příslušná karta byla odstraněna z vaší hlavní stránky.</string>
</resources> </resources>

View file

@ -305,9 +305,6 @@
<string name="stop">Stop</string> <string name="stop">Stop</string>
<string name="events">Hændelser</string> <string name="events">Hændelser</string>
<string name="empty_list_subtitle">Ikke andet end fårekyllinger her</string> <string name="empty_list_subtitle">Ikke andet end fårekyllinger her</string>
<string name="short_thousand">t</string>
<string name="short_million">mio.</string>
<string name="short_billion">mia.</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s abonnent</item> <item quantity="one">%s abonnent</item>
<item quantity="other">%s abonnenter</item> <item quantity="other">%s abonnenter</item>
@ -507,7 +504,6 @@
<string name="main_page_content_swipe_remove">Stryg på elementer for at fjerne dem</string> <string name="main_page_content_swipe_remove">Stryg på elementer for at fjerne dem</string>
<string name="select_a_playlist">Vælg en playliste</string> <string name="select_a_playlist">Vælg en playliste</string>
<string name="no_playlist_bookmarked_yet">Ingen playliste-bogmærker endnu</string> <string name="no_playlist_bookmarked_yet">Ingen playliste-bogmærker endnu</string>
<string name="localization_changes_requires_app_restart">Sproget ændres, når appen genstarter</string>
<string name="title_activity_play_queue">Afspillerkø</string> <string name="title_activity_play_queue">Afspillerkø</string>
<string name="show_channel_details">Vis kanalens detaljer</string> <string name="show_channel_details">Vis kanalens detaljer</string>
<string name="enqueue_stream">Sæt i kø</string> <string name="enqueue_stream">Sæt i kø</string>
@ -823,4 +819,9 @@
<string name="import_settings_vulnerable_format">Indstillingerne i den eksport, der importeres, bruger et sårbart format, der er blevet forældet siden NewPipe 0.27.0. Sørg for, at den eksport, der importeres, er fra en pålidelig kilde, og brug helst kun eksport fra NewPipe 0.27.0 eller nyere i fremtiden. Understøttelse af import af indstillinger i dette sårbare format fjernes snart helt, og så vil gamle versioner af NewPipe ikke længere være i stand til at importere indstillinger fra eksport fra nye versioner.</string> <string name="import_settings_vulnerable_format">Indstillingerne i den eksport, der importeres, bruger et sårbart format, der er blevet forældet siden NewPipe 0.27.0. Sørg for, at den eksport, der importeres, er fra en pålidelig kilde, og brug helst kun eksport fra NewPipe 0.27.0 eller nyere i fremtiden. Understøttelse af import af indstillinger i dette sårbare format fjernes snart helt, og så vil gamle versioner af NewPipe ikke længere være i stand til at importere indstillinger fra eksport fra nye versioner.</string>
<string name="settings_category_backup_restore_title">Sikkerhedskopiering og gendannelse</string> <string name="settings_category_backup_restore_title">Sikkerhedskopiering og gendannelse</string>
<string name="audio_track_type_secondary">sekundær</string> <string name="audio_track_type_secondary">sekundær</string>
<string name="share_playlist_as_youtube_temporary_playlist">Del som midlertidig YouTube-playliste</string>
<string name="tab_bookmarks_short">Playlister</string>
<string name="feed_group_page_summary">Kanalgruppeside</string>
<string name="select_a_feed_group">Vælg en feed-gruppe</string>
<string name="no_feed_group_created_yet">Ingen feed-gruppe oprettet endnu</string>
</resources> </resources>

View file

@ -69,9 +69,6 @@
<string name="error_report_title">Fehlerbericht</string> <string name="error_report_title">Fehlerbericht</string>
<string name="delete">Löschen</string> <string name="delete">Löschen</string>
<string name="checksum">Prüfsumme</string> <string name="checksum">Prüfsumme</string>
<string name="short_thousand">Tsd.</string>
<string name="short_million">Mio.</string>
<string name="short_billion">Mrd.</string>
<string name="msg_name">Dateiname</string> <string name="msg_name">Dateiname</string>
<string name="msg_error">Fehler</string> <string name="msg_error">Fehler</string>
<string name="msg_wait">Bitte warten </string> <string name="msg_wait">Bitte warten </string>
@ -432,7 +429,6 @@
<item quantity="one">%s Zuhörer</item> <item quantity="one">%s Zuhörer</item>
<item quantity="other">%s Zuhörer</item> <item quantity="other">%s Zuhörer</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">Die Sprache ändert sich, sobald die App neu gestartet wird</string>
<string name="peertube_instance_url_title">PeerTube-Instanzen</string> <string name="peertube_instance_url_title">PeerTube-Instanzen</string>
<string name="peertube_instance_url_help">Finde auf %s die Instanzen, die dir gefallen</string> <string name="peertube_instance_url_help">Finde auf %s die Instanzen, die dir gefallen</string>
<string name="peertube_instance_add_title">Instanz hinzufügen</string> <string name="peertube_instance_add_title">Instanz hinzufügen</string>
@ -587,7 +583,7 @@
<string name="no_app_to_open_intent">Keine App auf deinem Gerät kann dies öffnen</string> <string name="no_app_to_open_intent">Keine App auf deinem Gerät kann dies öffnen</string>
<string name="private_content">Dieser Inhalt ist privat, kann also nicht von NewPipe gestreamt oder heruntergeladen werden.</string> <string name="private_content">Dieser Inhalt ist privat, kann also nicht von NewPipe gestreamt oder heruntergeladen werden.</string>
<string name="paid_content">Diese Inhalte sind nur für Benutzer verfügbar, die bezahlt haben, können also nicht von NewPipe gestreamt oder heruntergeladen werden.</string> <string name="paid_content">Diese Inhalte sind nur für Benutzer verfügbar, die bezahlt haben, können also nicht von NewPipe gestreamt oder heruntergeladen werden.</string>
<string name="youtube_music_premium_content">Dieses Video ist nur für YouTube Music Premium-Mitglieder verfügbar und kann daher nicht von NewPipe gestreamt oder heruntergeladen werden.</string> <string name="youtube_music_premium_content">Dieses Video ist nur für YouTube-Music-Premium-Mitglieder verfügbar und kann daher nicht von NewPipe gestreamt oder heruntergeladen werden.</string>
<string name="soundcloud_go_plus_content">Dies ist ein SoundCloud Go+ Track, zumindest in deinem Land, kann er von NewPipe nicht gestreamt oder heruntergeladen werden.</string> <string name="soundcloud_go_plus_content">Dies ist ein SoundCloud Go+ Track, zumindest in deinem Land, kann er von NewPipe nicht gestreamt oder heruntergeladen werden.</string>
<string name="georestricted_content">Dieser Inhalt ist in deinem Land nicht verfügbar.</string> <string name="georestricted_content">Dieser Inhalt ist in deinem Land nicht verfügbar.</string>
<string name="crash_the_app">App abstürzen lassen</string> <string name="crash_the_app">App abstürzen lassen</string>
@ -823,4 +819,14 @@
\nMöchtest du wirklich fortfahren?</string> \nMöchtest du wirklich fortfahren?</string>
<string name="import_settings_vulnerable_format">Die Einstellungen in dem zu importierenden Export verwenden ein angreifbares Format, das seit NewPipe 0.27.0 veraltet ist. Stellen Sie sicher, dass der zu importierende Export aus einer vertrauenswürdigen Quelle stammt, und verwenden Sie in Zukunft nur noch Exporte, die aus NewPipe 0.27.0 oder neuer stammen. Die Unterstützung für den Import von Einstellungen in diesem angreifbaren Format wird bald vollständig entfernt werden, und dann werden alte Versionen von NewPipe nicht mehr in der Lage sein, Einstellungen von Exporten aus neuen Versionen zu importieren.</string> <string name="import_settings_vulnerable_format">Die Einstellungen in dem zu importierenden Export verwenden ein angreifbares Format, das seit NewPipe 0.27.0 veraltet ist. Stellen Sie sicher, dass der zu importierende Export aus einer vertrauenswürdigen Quelle stammt, und verwenden Sie in Zukunft nur noch Exporte, die aus NewPipe 0.27.0 oder neuer stammen. Die Unterstützung für den Import von Einstellungen in diesem angreifbaren Format wird bald vollständig entfernt werden, und dann werden alte Versionen von NewPipe nicht mehr in der Lage sein, Einstellungen von Exporten aus neuen Versionen zu importieren.</string>
<string name="audio_track_type_secondary">Sekundär</string> <string name="audio_track_type_secondary">Sekundär</string>
<string name="share_playlist_as_youtube_temporary_playlist">Als temporäre YouTube-Wiedergabeliste teilen</string>
<string name="tab_bookmarks_short">Wiedergabelisten</string>
<string name="select_a_feed_group">Eine Feed-Gruppe auswählen</string>
<string name="feed_group_page_summary">Kanalgruppen-Seite</string>
<string name="no_feed_group_created_yet">Es wurde noch keine Feed-Gruppe erstellt</string>
<string name="search_with_service_name">Suche %1$s</string>
<string name="search_with_service_name_and_filter">Suche %1$s (%2$s)</string>
<string name="channel_tab_likes">Gefällt mir</string>
<string name="migration_info_6_7_title">SoundCloud-Top-50-Seite entfernt</string>
<string name="migration_info_6_7_message">SoundCloud hat die ursprünglichen Top-50-Charts abgeschafft. Der entsprechende Tab wurde von deiner Hauptseite entfernt.</string>
</resources> </resources>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="upload_date_text">Δημοσιεύθηκε στις %1$s</string> <string name="upload_date_text">Δημοσιεύθηκε στις %1$s</string>
<string name="no_player_found">Δε βρέθηκε πρόγραμμα αναπαραγωγής. Εγκατάσταση του VLC;</string> <string name="no_player_found">Δε βρέθηκε αναπαραγωγός ροής δεδομένων. Εγκατάσταση του VLC;</string>
<string name="install">Εγκατάσταση</string> <string name="install">Εγκατάσταση</string>
<string name="cancel">Άκυρο</string> <string name="cancel">Άκυρο</string>
<string name="open_in_browser">Άνοιγμα σε πρόγραμμα περιήγησης</string> <string name="open_in_browser">Άνοιγμα σε πρόγραμμα περιήγησης</string>
@ -41,7 +41,6 @@
<string name="detail_uploader_thumbnail_view_description">Μικρογραφία εικόνας προφίλ του χρήστη</string> <string name="detail_uploader_thumbnail_view_description">Μικρογραφία εικόνας προφίλ του χρήστη</string>
<string name="detail_likes_img_view_description">Like</string> <string name="detail_likes_img_view_description">Like</string>
<string name="detail_dislikes_img_view_description">Dislike</string> <string name="detail_dislikes_img_view_description">Dislike</string>
<string name="short_billion">δισ/ρια</string>
<string name="open_in_popup_mode">Άνοιγμα σε αναδυόμενο παράθυρο</string> <string name="open_in_popup_mode">Άνοιγμα σε αναδυόμενο παράθυρο</string>
<string name="subscribe_button_title">Εγγραφή</string> <string name="subscribe_button_title">Εγγραφή</string>
<string name="subscribed_button_title">Εγγεγραμμένος</string> <string name="subscribed_button_title">Εγγεγραμμένος</string>
@ -71,7 +70,7 @@
<string name="action_history">Ιστορικό</string> <string name="action_history">Ιστορικό</string>
<string name="show_info">Εμφάνιση πληροφοριών</string> <string name="show_info">Εμφάνιση πληροφοριών</string>
<string name="main_bg_subtitle">Πατήστε το μεγεθυντικό φακό για να ξεκινήσετε.</string> <string name="main_bg_subtitle">Πατήστε το μεγεθυντικό φακό για να ξεκινήσετε.</string>
<string name="no_player_found_toast">Δε βρέθηκε πρόγραμμα αναπαραγωγής ροής δεδομένων (μπορείτε να εγκαταστήσετε το VLC για να κάνετε αναπαραγωγή).</string> <string name="no_player_found_toast">Δε βρέθηκε αναπαραγωγός ροής δεδομένων (μπορείτε να εγκαταστήσετε το VLC για να κάνετε αναπαραγωγή).</string>
<string name="controls_download_desc">Λήψη του αρχείου ροής</string> <string name="controls_download_desc">Λήψη του αρχείου ροής</string>
<string name="use_external_video_player_summary">Αφαιρείται ο ήχος από κάποιες αναλύσεις</string> <string name="use_external_video_player_summary">Αφαιρείται ο ήχος από κάποιες αναλύσεις</string>
<string name="channel_unsubscribed">Το κανάλι διαγράφηκε</string> <string name="channel_unsubscribed">Το κανάλι διαγράφηκε</string>
@ -103,7 +102,7 @@
<string name="resume_on_audio_focus_gain_title">Ανάκτηση αναπαραγωγής</string> <string name="resume_on_audio_focus_gain_title">Ανάκτηση αναπαραγωγής</string>
<string name="resume_on_audio_focus_gain_summary">Συνέχιση της αναπαραγωγής έπειτα από διακοπές (π.χ. κλήσεις)</string> <string name="resume_on_audio_focus_gain_summary">Συνέχιση της αναπαραγωγής έπειτα από διακοπές (π.χ. κλήσεις)</string>
<string name="show_hold_to_append_title">Εμφάνιση επεξήγησης του «Πιέστε παρατεταμένα για προσθήκη στην ουρά»</string> <string name="show_hold_to_append_title">Εμφάνιση επεξήγησης του «Πιέστε παρατεταμένα για προσθήκη στην ουρά»</string>
<string name="show_hold_to_append_summary">Εμφάνιση υπόδειξης όταν πατηθεί το κουμπί παρασκηνίου ή αναδυόμενου παραθύρου στις \"Λεπτομέρειες:\\ στο βίντεο</string> <string name="show_hold_to_append_summary">Εμφάνιση συμβουλής κατά το πάτημα του φόντου ή του αναδυόμενου κουμπιού στο βίντεο «Λεπτομέρειες:»</string>
<string name="default_content_country_title">Προεπιλεγμένη χώρα περιεχομένου</string> <string name="default_content_country_title">Προεπιλεγμένη χώρα περιεχομένου</string>
<string name="settings_category_player_title">Αναπαραγωγός</string> <string name="settings_category_player_title">Αναπαραγωγός</string>
<string name="settings_category_player_behavior_title">Συμπεριφορά</string> <string name="settings_category_player_behavior_title">Συμπεριφορά</string>
@ -169,8 +168,6 @@
<string name="empty_list_subtitle">Δεν υπάρχει τίποτα εδώ</string> <string name="empty_list_subtitle">Δεν υπάρχει τίποτα εδώ</string>
<string name="detail_drag_description">Σύρετε για ταξινόμηση</string> <string name="detail_drag_description">Σύρετε για ταξινόμηση</string>
<string name="retry">Προσπάθεια εκ νέου</string> <string name="retry">Προσπάθεια εκ νέου</string>
<string name="short_thousand">χιλ.</string>
<string name="short_million">εκ/ρια</string>
<string name="no_subscribers">Κανένας συνδρομητής</string> <string name="no_subscribers">Κανένας συνδρομητής</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s συνδρομητής</item> <item quantity="one">%s συνδρομητής</item>
@ -184,7 +181,7 @@
<string name="no_videos">Κανένα βίντεο</string> <string name="no_videos">Κανένα βίντεο</string>
<plurals name="videos"> <plurals name="videos">
<item quantity="one">%s βίντεο</item> <item quantity="one">%s βίντεο</item>
<item quantity="other">%s βίντεο</item> <item quantity="other">%s βίντεο(πολλά)</item>
</plurals> </plurals>
<string name="start">Εκκίνηση</string> <string name="start">Εκκίνηση</string>
<string name="create">Δημιουργία</string> <string name="create">Δημιουργία</string>
@ -422,7 +419,6 @@
<item quantity="one">%s ακροατής</item> <item quantity="one">%s ακροατής</item>
<item quantity="other">%s ακροατές</item> <item quantity="other">%s ακροατές</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">Η γλώσσα θα αλλάξει αφού επανεκκινηθεί η εφαρμογή</string>
<string name="default_kiosk_page_summary">Προεπιλεγμένο περίπτερο</string> <string name="default_kiosk_page_summary">Προεπιλεγμένο περίπτερο</string>
<string name="peertube_instance_add_https_only">Μόνο HTTPS σύνδεσμοι υποστηρίζονται</string> <string name="peertube_instance_add_https_only">Μόνο HTTPS σύνδεσμοι υποστηρίζονται</string>
<string name="local">Τοπικά</string> <string name="local">Τοπικά</string>
@ -472,7 +468,7 @@
<string name="restricted_video">Αυτό το βίντεο έχει περιορισμό ηλικίας. <string name="restricted_video">Αυτό το βίντεο έχει περιορισμό ηλικίας.
\n \n
\nΕνεργοποιήστε το «%1$s» στις ρυθμίσεις εάν θέλετε να το δείτε.</string> \nΕνεργοποιήστε το «%1$s» στις ρυθμίσεις εάν θέλετε να το δείτε.</string>
<string name="youtube_restricted_mode_enabled_title">Ενεργοποίηση \"Περιορισμένη Λειτουργία\\ του YouTube</string> <string name="youtube_restricted_mode_enabled_title">Ενεργοποίηση \"Περιορισμένη Λειτουργία\" του YouTube</string>
<string name="unsupported_url_dialog_message">Δεν ήταν δυνατή η αναγνώριση της διεύθυνσης URL. Άνοιγμα με άλλη εφαρμογή;</string> <string name="unsupported_url_dialog_message">Δεν ήταν δυνατή η αναγνώριση της διεύθυνσης URL. Άνοιγμα με άλλη εφαρμογή;</string>
<string name="auto_queue_toggle">Αυτόματη προσθήκη στην ουρά</string> <string name="auto_queue_toggle">Αυτόματη προσθήκη στην ουρά</string>
<string name="clear_queue_confirmation_description">Η ουρά του ενεργού αναπαραγωγού θα αντικατασταθεί</string> <string name="clear_queue_confirmation_description">Η ουρά του ενεργού αναπαραγωγού θα αντικατασταθεί</string>
@ -654,7 +650,7 @@
<string name="manual_update_description">Χειροκίνητος έλεγχος για νέα έκδοση</string> <string name="manual_update_description">Χειροκίνητος έλεγχος για νέα έκδοση</string>
<string name="check_for_updates">Έλεγχος αναβάθμισης</string> <string name="check_for_updates">Έλεγχος αναβάθμισης</string>
<string name="feed_new_items">Νέα αντικείμενα τροφοδοσίας</string> <string name="feed_new_items">Νέα αντικείμενα τροφοδοσίας</string>
<string name="show_crash_the_player_title">Εμφάνιση «Κατάρρευση αναπαραγωγέα\\</string> <string name="show_crash_the_player_title">Εμφάνιση \"Κατάρρευση αναπαραγωγέα\"</string>
<string name="show_crash_the_player_summary">Εμφανίζει μια επιλογή κατάρρευσης κατά τη χρήση του αναπαραγωγέα</string> <string name="show_crash_the_player_summary">Εμφανίζει μια επιλογή κατάρρευσης κατά τη χρήση του αναπαραγωγέα</string>
<string name="crash_the_player">Κατάρρευση αναπαραγωγέα</string> <string name="crash_the_player">Κατάρρευση αναπαραγωγέα</string>
<string name="error_report_channel_name">Ειδοποίηση αναφοράς σφάλματος</string> <string name="error_report_channel_name">Ειδοποίηση αναφοράς σφάλματος</string>
@ -823,4 +819,14 @@
\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;</string> \nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;</string>
<string name="import_settings_vulnerable_format">Οι ρυθμίσεις στην εξαγωγή που εισάγεται χρησιμοποιούν μια ευάλωτη μορφή που είχε καταργηθεί από το NewPipe 0.27.0. Βεβαιωθείτε ότι η εξαγωγή που εισάγεται προέρχεται από αξιόπιστη πηγή και προτιμήστε να χρησιμοποιείτε μόνο εξαγωγές που λαμβάνονται από το NewPipe 0.27.0 ή νεότερο στο μέλλον. Η υποστήριξη για εισαγωγή ρυθμίσεων σε αυτήν την ευάλωτη μορφή θα καταργηθεί σύντομα εντελώς και, στη συνέχεια, οι παλιές εκδόσεις του NewPipe δεν θα μπορούν πλέον να εισάγουν ρυθμίσεις εξαγωγών από νέες εκδόσεις.</string> <string name="import_settings_vulnerable_format">Οι ρυθμίσεις στην εξαγωγή που εισάγεται χρησιμοποιούν μια ευάλωτη μορφή που είχε καταργηθεί από το NewPipe 0.27.0. Βεβαιωθείτε ότι η εξαγωγή που εισάγεται προέρχεται από αξιόπιστη πηγή και προτιμήστε να χρησιμοποιείτε μόνο εξαγωγές που λαμβάνονται από το NewPipe 0.27.0 ή νεότερο στο μέλλον. Η υποστήριξη για εισαγωγή ρυθμίσεων σε αυτήν την ευάλωτη μορφή θα καταργηθεί σύντομα εντελώς και, στη συνέχεια, οι παλιές εκδόσεις του NewPipe δεν θα μπορούν πλέον να εισάγουν ρυθμίσεις εξαγωγών από νέες εκδόσεις.</string>
<string name="audio_track_type_secondary">δευτερεύων</string> <string name="audio_track_type_secondary">δευτερεύων</string>
<string name="tab_bookmarks_short">Λίστες αναπαραγωγής</string>
<string name="share_playlist_as_youtube_temporary_playlist">Μοιραστείτε ως προσωρινή λίστα αναπαραγωγής στο YouTube</string>
<string name="select_a_feed_group">Επιλογή ομάδας τροφοδοσίας</string>
<string name="no_feed_group_created_yet">Δεν δημιουργήθηκε ομάδα τροφοδοσίας ακόμα</string>
<string name="feed_group_page_summary">Σελίδα καναλιού ομάδας</string>
<string name="search_with_service_name">Αναζήτηση %1$s</string>
<string name="search_with_service_name_and_filter">Αναζήτηση %1$s (%2$s)</string>
<string name="channel_tab_likes">Likes</string>
<string name="migration_info_6_7_title">Η σελίδα των SoundCloud Top 50 αφαιρέθηκε</string>
<string name="migration_info_6_7_message">Το SoundCloud έχει καταργήσει τα αρχικά charts με τα Top 50. Η αντίστοιχη καρτέλα έχει αφαιρεθεί από την κύρια σελίδα σας.</string>
</resources> </resources>

View file

@ -261,9 +261,6 @@
<item quantity="one">%s spekto</item> <item quantity="one">%s spekto</item>
<item quantity="other">%s spektoj</item> <item quantity="other">%s spektoj</item>
</plurals> </plurals>
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="short_billion">Mrd</string>
<string name="title_activity_about">Pri NewPipe</string> <string name="title_activity_about">Pri NewPipe</string>
<string name="title_licenses">Eksteraj permesiloj</string> <string name="title_licenses">Eksteraj permesiloj</string>
<string name="copyright" formatted="true">© %1$s de %2$s sub %3$s</string> <string name="copyright" formatted="true">© %1$s de %2$s sub %3$s</string>
@ -421,7 +418,6 @@
<item quantity="one">%s aŭskultanto</item> <item quantity="one">%s aŭskultanto</item>
<item quantity="other">%s aŭskultantoj</item> <item quantity="other">%s aŭskultantoj</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">La lingvo ŝanĝos kiam la apo restartos</string>
<string name="seek_duration_title">Daŭro de rapidpluiga/revolva serĉo</string> <string name="seek_duration_title">Daŭro de rapidpluiga/revolva serĉo</string>
<string name="peertube_instance_url_title">Instancoj de PeerTube</string> <string name="peertube_instance_url_title">Instancoj de PeerTube</string>
<string name="peertube_instance_url_summary">Elekti viajn preferitajn instancojn de PeerTube</string> <string name="peertube_instance_url_summary">Elekti viajn preferitajn instancojn de PeerTube</string>

View file

@ -47,7 +47,7 @@
<string name="detail_uploader_thumbnail_view_description">Miniatura del avatar del usuario</string> <string name="detail_uploader_thumbnail_view_description">Miniatura del avatar del usuario</string>
<string name="content">Contenido</string> <string name="content">Contenido</string>
<string name="show_age_restricted_content_title">Mostrar contenido con restricción de edad</string> <string name="show_age_restricted_content_title">Mostrar contenido con restricción de edad</string>
<string name="main_bg_subtitle">Toca la lupa para comenzar..</string> <string name="main_bg_subtitle">Toca la lupa para comenzar.</string>
<string name="duration_live">En directo</string> <string name="duration_live">En directo</string>
<string name="downloads">Descargas</string> <string name="downloads">Descargas</string>
<string name="downloads_title">Descargas</string> <string name="downloads_title">Descargas</string>
@ -78,12 +78,9 @@
<string name="msg_copied">Copiado al portapapeles</string> <string name="msg_copied">Copiado al portapapeles</string>
<string name="no_available_dir">Defina una carpeta de descargas más tarde en los ajustes</string> <string name="no_available_dir">Defina una carpeta de descargas más tarde en los ajustes</string>
<string name="app_ui_crash">La interfaz de la aplicación dejó de funcionar</string> <string name="app_ui_crash">La interfaz de la aplicación dejó de funcionar</string>
<string name="info_labels">Qué:\\nSolicitar:\\nIntenga de contenido:\\nPaíse de contenido:\\nIdiomaño de la aplicación:\\nServicio:\\nTiempo de GTT:\\nPaquete:\\nVersión:\\nVersion de SO:</string> <string name="info_labels">Qué:\\nSolicitud:\\nIdioma del contenido:\\nPaís del contenido:\\nIdioma de la aplicación:\\nServicio:\\nMarca de tiempo:\\nPaquete:\\nVersión:\\nVersión del SO:</string>
<string name="black_theme_title">Negro</string> <string name="black_theme_title">Negro</string>
<string name="all">Todo</string> <string name="all">Todo</string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="short_billion">MM</string>
<string name="open_in_popup_mode">Abrir en modo emergente</string> <string name="open_in_popup_mode">Abrir en modo emergente</string>
<string name="msg_popup_permission">Se necesita este permiso <string name="msg_popup_permission">Se necesita este permiso
\npara abrir en modo emergente</string> \npara abrir en modo emergente</string>
@ -91,7 +88,7 @@
<string name="recaptcha_request_toast">Reto reCAPTCHA requerido</string> <string name="recaptcha_request_toast">Reto reCAPTCHA requerido</string>
<string name="popup_playing_toast">Reproduciendo en modo emergente</string> <string name="popup_playing_toast">Reproduciendo en modo emergente</string>
<string name="default_video_format_title">Formato de vídeo predefinido</string> <string name="default_video_format_title">Formato de vídeo predefinido</string>
<string name="disabled">Desactivado</string> <string name="disabled">Deshabilitado</string>
<string name="show_higher_resolutions_title">Mostrar resoluciones más altas</string> <string name="show_higher_resolutions_title">Mostrar resoluciones más altas</string>
<string name="show_higher_resolutions_summary">Solo algunos dispositivos pueden reproducir vídeos en 2K / 4K</string> <string name="show_higher_resolutions_summary">Solo algunos dispositivos pueden reproducir vídeos en 2K / 4K</string>
<string name="default_popup_resolution_title">Resolución predefinida de emergente</string> <string name="default_popup_resolution_title">Resolución predefinida de emergente</string>
@ -130,7 +127,7 @@
<string name="settings_file_replacement_character_title">Carácter de reemplazo</string> <string name="settings_file_replacement_character_title">Carácter de reemplazo</string>
<string name="charset_letters_and_digits">Letras y dígitos</string> <string name="charset_letters_and_digits">Letras y dígitos</string>
<string name="charset_most_special_characters">La mayoría de los caracteres especiales</string> <string name="charset_most_special_characters">La mayoría de los caracteres especiales</string>
<string name="enable_search_history_title">Historial de búsquedas</string> <string name="enable_search_history_title">Historial de búsqueda</string>
<string name="enable_search_history_summary">Almacenar búsquedas localmente</string> <string name="enable_search_history_summary">Almacenar búsquedas localmente</string>
<string name="enable_watch_history_title">Historial de vistas</string> <string name="enable_watch_history_title">Historial de vistas</string>
<string name="enable_watch_history_summary">Almacenar historial de vídeos vistos</string> <string name="enable_watch_history_summary">Almacenar historial de vídeos vistos</string>
@ -184,7 +181,7 @@
<string name="start_here_on_background">Comenzar a reproducir en segundo plano</string> <string name="start_here_on_background">Comenzar a reproducir en segundo plano</string>
<string name="start_here_on_popup">Reproducir en modo emergente</string> <string name="start_here_on_popup">Reproducir en modo emergente</string>
<string name="show_hold_to_append_title">Mostrar la sugerencia \"Mantener presionado para poner a la cola\"</string> <string name="show_hold_to_append_title">Mostrar la sugerencia \"Mantener presionado para poner a la cola\"</string>
<string name="new_and_hot">Nuevo y lo mejor</string> <string name="new_and_hot">Lo nuevo y lo mejor</string>
<string name="hold_to_append">Mantener pulsado para añadir a la cola</string> <string name="hold_to_append">Mantener pulsado para añadir a la cola</string>
<string name="donation_title">Donar</string> <string name="donation_title">Donar</string>
<string name="donation_encouragement">NewPipe es desarrollado por voluntarios que emplean su tiempo libre para brindarle la mejor experiencia. Haz una aportación para ayudarlos a crear un NewPipe mejor mientras disfrutan de una taza de café.</string> <string name="donation_encouragement">NewPipe es desarrollado por voluntarios que emplean su tiempo libre para brindarle la mejor experiencia. Haz una aportación para ayudarlos a crear un NewPipe mejor mientras disfrutan de una taza de café.</string>
@ -277,12 +274,7 @@
\n5. Haga clic en el botón de \"Descargar\" una vez que aparezca \n5. Haga clic en el botón de \"Descargar\" una vez que aparezca
\n6. Haga clic en el botón IMPORTAR ARCHIVO que se muestra abajo y seleccione el archivo zip descargado \n6. Haga clic en el botón IMPORTAR ARCHIVO que se muestra abajo y seleccione el archivo zip descargado
\n7. [En caso de que la importación falle] Extraiga el archivo .csv (generalmente dentro de \"Youtube y Youtube Music/suscripciones/suscripciones.csv\"), haga clic en IMPORTAR ARCHIVO y seleccione el archivo csv extraído anteriormente</string> \n7. [En caso de que la importación falle] Extraiga el archivo .csv (generalmente dentro de \"Youtube y Youtube Music/suscripciones/suscripciones.csv\"), haga clic en IMPORTAR ARCHIVO y seleccione el archivo csv extraído anteriormente</string>
<string name="import_soundcloud_instructions">Importe un perfil de SoundCloud escribiendo la URL o su ID: <string name="import_soundcloud_instructions">Importa un perfil de SoundCloud escribiendo la URL o tu ID: \n \n1. Habilita el «modo escritorio» en un navegador web (el sitio no está disponible para dispositivos móviles) \n2. Ve a esta URL: %1$s \n3. Inicia sesión cuando se te pida \n4. Copia la URL del perfil a la que fuiste redireccionado.</string>
\n
\n1. Active el «modo escritorio» en un navegador web (el sitio no está disponible para dispositivos móviles)
\n2. Vaya a esta URL: %1$s
\n3. Inicie sesión cuando se le pida
\n4. Copie la URL del perfil a la que fue redireccionado.</string>
<string name="import_soundcloud_instructions_hint">tuID, soundcloud.com/tuID</string> <string name="import_soundcloud_instructions_hint">tuID, soundcloud.com/tuID</string>
<string name="import_network_expensive_warning">Esta operación puede causar un uso intensivo de la red. <string name="import_network_expensive_warning">Esta operación puede causar un uso intensivo de la red.
\n \n
@ -381,8 +373,7 @@
<string name="error_timeout">El tiempo de conexión expiro</string> <string name="error_timeout">El tiempo de conexión expiro</string>
<string name="error_download_resource_gone">No se puede recuperar esta descarga</string> <string name="error_download_resource_gone">No se puede recuperar esta descarga</string>
<string name="downloads_storage_ask_title">Preguntar dónde descargar</string> <string name="downloads_storage_ask_title">Preguntar dónde descargar</string>
<string name="downloads_storage_ask_summary">Se le preguntará dónde guardar cada descarga. <string name="downloads_storage_ask_summary">Se te preguntará dónde guardar cada descarga. \nHabilita elegir carpetas del sistema (SAF) si quieres guardar las descargas en una tarjeta SD externa</string>
\nHabilite Elegir carpetas del sistema (SAF) si desea guardar las descargas en una tarjeta SD externa</string>
<string name="downloads_storage_use_saf_title">Usar Elegir carpetas del sistema (SAF)</string> <string name="downloads_storage_use_saf_title">Usar Elegir carpetas del sistema (SAF)</string>
<string name="downloads_storage_use_saf_summary">El \'Sistema de Acceso al Almacenamiento\' permite descargar en una tarjeta SD externa</string> <string name="downloads_storage_use_saf_summary">El \'Sistema de Acceso al Almacenamiento\' permite descargar en una tarjeta SD externa</string>
<string name="unsubscribe">Desuscribirse</string> <string name="unsubscribe">Desuscribirse</string>
@ -405,7 +396,7 @@
<string name="auto">Automático</string> <string name="auto">Automático</string>
<string name="app_update_available_notification_title">¡Actualización de NewPipe disponible!</string> <string name="app_update_available_notification_title">¡Actualización de NewPipe disponible!</string>
<string name="show_comments_title">Mostrar comentarios</string> <string name="show_comments_title">Mostrar comentarios</string>
<string name="show_comments_summary">Desactivar para ocultar comentarios</string> <string name="show_comments_summary">Deshabilitar para ocultar comentarios</string>
<string name="autoplay_title">Reproducción automática</string> <string name="autoplay_title">Reproducción automática</string>
<string name="no_comments">Sin comentarios</string> <string name="no_comments">Sin comentarios</string>
<string name="error_unable_to_load_comments">No se pudieron cargar los comentarios</string> <string name="error_unable_to_load_comments">No se pudieron cargar los comentarios</string>
@ -433,7 +424,6 @@
<item quantity="many">%s oyentes</item> <item quantity="many">%s oyentes</item>
<item quantity="other">%s oyentes</item> <item quantity="other">%s oyentes</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">El idioma cambiará luego de que se reinicie la aplicación</string>
<string name="seek_duration_title">Duración de búsqueda al avanzar y/o retroceder</string> <string name="seek_duration_title">Duración de búsqueda al avanzar y/o retroceder</string>
<string name="peertube_instance_url_title">Instancias de PeerTube</string> <string name="peertube_instance_url_title">Instancias de PeerTube</string>
<string name="peertube_instance_url_summary">Selecciona tus instancias favoritas de PeerTube</string> <string name="peertube_instance_url_summary">Selecciona tus instancias favoritas de PeerTube</string>
@ -494,14 +484,14 @@
<string name="feed_group_dialog_empty_name">Nombre de grupo vacío</string> <string name="feed_group_dialog_empty_name">Nombre de grupo vacío</string>
<string name="feed_group_dialog_delete_message">¿Quieres borrar este grupo?</string> <string name="feed_group_dialog_delete_message">¿Quieres borrar este grupo?</string>
<string name="feed_create_new_group_button_title">Nuevo</string> <string name="feed_create_new_group_button_title">Nuevo</string>
<string name="settings_category_feed_title">Fuente</string> <string name="settings_category_feed_title">Contenido</string>
<string name="feed_update_threshold_title">Velocidad de actualización del contenido</string> <string name="feed_update_threshold_title">Velocidad de actualización del contenido</string>
<string name="feed_update_threshold_summary">Tiempo para que una suscripción se considere desactualizada — %s</string> <string name="feed_update_threshold_summary">Tiempo para que una suscripción se considere desactualizada — %s</string>
<string name="feed_update_threshold_option_always_update">Actualizar siempre</string> <string name="feed_update_threshold_option_always_update">Actualizar siempre</string>
<string name="feed_use_dedicated_fetch_method_title">Extraer desde feed dedicado cuando esté disponible</string> <string name="feed_use_dedicated_fetch_method_title">Extraer desde feed dedicado cuando esté disponible</string>
<string name="feed_use_dedicated_fetch_method_summary">Disponible para algunos servicios, suele ser más rápido pero puede mostrar una cantidad limitada de ítems y a menudo información incompleta (por ejemplo falta de duración, tipo de ítem o estado)</string> <string name="feed_use_dedicated_fetch_method_summary">Disponible para algunos servicios, suele ser más rápido pero puede mostrar una cantidad limitada de ítems y a menudo información incompleta (por ejemplo falta de duración, tipo de ítem o estado)</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Activar modo rápido</string> <string name="feed_use_dedicated_fetch_method_enable_button">Habilitar modo rápido</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Desactivar modo rápido</string> <string name="feed_use_dedicated_fetch_method_disable_button">Deshabilitar modo rápido</string>
<string name="feed_use_dedicated_fetch_method_help_text">¿Piensas que la carga de contenidos es muy lenta\? Entonces intenta habilitar la carga rápida (puedes cambiarlo en los ajustes o pulsando el botón debajo). <string name="feed_use_dedicated_fetch_method_help_text">¿Piensas que la carga de contenidos es muy lenta\? Entonces intenta habilitar la carga rápida (puedes cambiarlo en los ajustes o pulsando el botón debajo).
\n \n
\nNewpipe ofrece dos formas de cargar los contenidos: \nNewpipe ofrece dos formas de cargar los contenidos:
@ -521,9 +511,7 @@
<string name="artists">Artistas</string> <string name="artists">Artistas</string>
<string name="albums">Álbumes</string> <string name="albums">Álbumes</string>
<string name="songs">Canciones</string> <string name="songs">Canciones</string>
<string name="restricted_video">Este vídeo tiene restricción de edad. <string name="restricted_video">Este vídeo tiene restricción de edad. \n \nHabilitar \"%1$s\" en los ajustes si quieres verlo.</string>
\n
\nActivar \"%1$s\" en los ajustes si quieres verlo.</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Sí, y también vídeos vistos parcialmente</string> <string name="remove_watched_popup_yes_and_partially_watched_videos">Sí, y también vídeos vistos parcialmente</string>
<string name="remove_watched_popup_warning">Los vídeos que ya se hayan visto luego de añadidos a la lista de reproducción, serán quitados. <string name="remove_watched_popup_warning">Los vídeos que ya se hayan visto luego de añadidos a la lista de reproducción, serán quitados.
\n¿Estás seguro\? ¡Esta acción no se puede deshacer!</string> \n¿Estás seguro\? ¡Esta acción no se puede deshacer!</string>
@ -534,7 +522,7 @@
<string name="detail_sub_channel_thumbnail_view_description">Miniatura de avatar del canal</string> <string name="detail_sub_channel_thumbnail_view_description">Miniatura de avatar del canal</string>
<string name="show_original_time_ago_summary">Los textos originales de los servicios serán visibles en los ítems de transmisiones</string> <string name="show_original_time_ago_summary">Los textos originales de los servicios serán visibles en los ítems de transmisiones</string>
<string name="show_original_time_ago_title">Mostrar tiempo atrás original en ítems</string> <string name="show_original_time_ago_title">Mostrar tiempo atrás original en ítems</string>
<string name="youtube_restricted_mode_enabled_title">Activar el «Modo restringido» de YouTube</string> <string name="youtube_restricted_mode_enabled_title">Habilitar el «Modo restringido» de YouTube</string>
<string name="playlist_page_summary">Página de lista de reproducción</string> <string name="playlist_page_summary">Página de lista de reproducción</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Mostrar solo suscripciones desagrupadas</string> <string name="feed_group_show_only_ungrouped_subscriptions">Mostrar solo suscripciones desagrupadas</string>
<string name="no_playlist_bookmarked_yet">Aún no hay marcadores para listas de reproducción</string> <string name="no_playlist_bookmarked_yet">Aún no hay marcadores para listas de reproducción</string>
@ -577,8 +565,8 @@
<string name="notification_colorize_summary">Hacer que Android personalice el color de la notificación de acuerdo con el color principal de la miniatura (tenga en cuenta que esto no está disponible en todos los dispositivos)</string> <string name="notification_colorize_summary">Hacer que Android personalice el color de la notificación de acuerdo con el color principal de la miniatura (tenga en cuenta que esto no está disponible en todos los dispositivos)</string>
<string name="show_thumbnail_summary">Usar miniatura como fondo de pantalla de bloqueo y notificaciones</string> <string name="show_thumbnail_summary">Usar miniatura como fondo de pantalla de bloqueo y notificaciones</string>
<string name="show_thumbnail_title">Mostrar vista previa</string> <string name="show_thumbnail_title">Mostrar vista previa</string>
<string name="show_meta_info_summary">Desactivar para ocultar información adicional sobre el creador o contenido de la transmisión</string> <string name="show_meta_info_summary">Deshabilitar para ocultar información adicional sobre el creador o contenido de la transmisión</string>
<string name="show_description_summary">Desactivar para ocultar la descripción del vídeo y la información adicional</string> <string name="show_description_summary">Deshabilitar para ocultar la descripción del vídeo y la información adicional</string>
<string name="no_app_to_open_intent">Ninguna aplicación en su dispositivo puede abrir esto</string> <string name="no_app_to_open_intent">Ninguna aplicación en su dispositivo puede abrir esto</string>
<string name="chapters">Capítulos</string> <string name="chapters">Capítulos</string>
<string name="recent">Reciente</string> <string name="recent">Reciente</string>
@ -614,24 +602,24 @@
<string name="metadata_privacy_unlisted">No listado</string> <string name="metadata_privacy_unlisted">No listado</string>
<string name="metadata_privacy_public">Público</string> <string name="metadata_privacy_public">Público</string>
<string name="metadata_support">Soporte</string> <string name="metadata_support">Soporte</string>
<string name="metadata_language">Lenguaje</string> <string name="metadata_language">Idioma</string>
<string name="metadata_age_limit">Límite de edad</string> <string name="metadata_age_limit">Límite de edad</string>
<string name="metadata_privacy">Privacidad</string> <string name="metadata_privacy">Privacidad</string>
<string name="metadata_licence">Licencia</string> <string name="metadata_licence">Licencia</string>
<string name="metadata_tags">Etiquetas</string> <string name="metadata_tags">Etiquetas</string>
<string name="metadata_category">Categoría</string> <string name="metadata_category">Categoría</string>
<string name="description_select_disable">Inhabilitar la selección de texto de la descripción</string> <string name="description_select_disable">Deshabilitar la selección de texto de la descripción</string>
<string name="description_select_enable">Habilitar la selección de texto de la descripción</string> <string name="description_select_enable">Habilitar la selección de texto de la descripción</string>
<string name="description_select_note">Ahora puede seleccionar el texto dentro de la descripción. Note que la página puede parpadear y los links no serán cliqueables mientras está en el modo de selección.</string> <string name="description_select_note">Ahora puede seleccionar el texto dentro de la descripción. Note que la página puede parpadear y los links no serán cliqueables mientras está en el modo de selección.</string>
<string name="service_provides_reason">%s da esta razón:</string> <string name="service_provides_reason">%s da esta razón:</string>
<string name="feed_load_error_account_info">No fue posible cargar el muro por \'%s\'.</string> <string name="feed_load_error_account_info">No fue posible cargar el feed por \'%s\'.</string>
<string name="account_terminated">Cuenta cancelada</string> <string name="account_terminated">Cuenta cancelada</string>
<string name="feed_load_error_fast_unknown">El modo de muro rápido no arroja más información sobre esto.</string> <string name="feed_load_error_fast_unknown">El modo de muro rápido no arroja más información sobre esto.</string>
<string name="feed_load_error_terminated">La cuenta del autor ha sido cancelada.\nNewPipe no podrá acceder a ella en el futuro.\n¿Quieres desuscribirte de este canal?</string> <string name="feed_load_error_terminated">La cuenta del autor ha sido cancelada.\nNewPipe no podrá acceder a ella en el futuro.\n¿Quieres desuscribirte de este canal?</string>
<string name="feed_load_error">Error al cargar el muro</string> <string name="feed_load_error">Error al cargar el muro</string>
<string name="downloads_storage_use_saf_summary_api_29">Desde Android 10 solo el \'Sistema de Acceso al Almacenamiento\' es soportado</string> <string name="downloads_storage_use_saf_summary_api_29">Desde Android 10 solo el \'Sistema de Acceso al Almacenamiento\' es soportado</string>
<string name="downloads_storage_ask_summary_no_saf_notice">Se le preguntará dónde guardar cada descarga</string> <string name="downloads_storage_ask_summary_no_saf_notice">Se le preguntará dónde guardar cada descarga</string>
<string name="disable_media_tunneling_summary">Desactiva la tunelización de los medios si experimentas una pantalla negra durante la reproducción o si la visualización de la imagen es intermitente.</string> <string name="disable_media_tunneling_summary">Deshabilita la tunelización de medios si experimentas una pantalla negra durante la reproducción o si la visualización de la imagen es intermitente.</string>
<string name="disable_media_tunneling_title">Deshabilitar el túnel de medios</string> <string name="disable_media_tunneling_title">Deshabilitar el túnel de medios</string>
<string name="no_dir_yet">Aún no se ha seleccionado ninguna carpeta de descargas, elija la carpeta de descargas por defecto ahora</string> <string name="no_dir_yet">Aún no se ha seleccionado ninguna carpeta de descargas, elija la carpeta de descargas por defecto ahora</string>
<string name="metadata_host">Anfitrión</string> <string name="metadata_host">Anfitrión</string>
@ -666,7 +654,7 @@
<string name="check_for_updates">Buscar actualizaciones</string> <string name="check_for_updates">Buscar actualizaciones</string>
<string name="manual_update_description">Buscar nuevas versiones manualmente</string> <string name="manual_update_description">Buscar nuevas versiones manualmente</string>
<string name="checking_updates_toast">Buscando actualizaciones…</string> <string name="checking_updates_toast">Buscando actualizaciones…</string>
<string name="feed_new_items">Nuevos elementos en el muro</string> <string name="feed_new_items">Nuevos elementos en el feed</string>
<string name="crash_the_player">Cerrar abruptamente el reproductor</string> <string name="crash_the_player">Cerrar abruptamente el reproductor</string>
<string name="show_crash_the_player_summary">Muestra una opción de cierre abrupto al usar el reproductor</string> <string name="show_crash_the_player_summary">Muestra una opción de cierre abrupto al usar el reproductor</string>
<string name="show_crash_the_player_title">Mostrar \"Cerrar abruptamente el reproductor\"</string> <string name="show_crash_the_player_title">Mostrar \"Cerrar abruptamente el reproductor\"</string>
@ -676,8 +664,7 @@
<string name="error_report_notification_toast">Se produjo un error, vea la notificación</string> <string name="error_report_notification_toast">Se produjo un error, vea la notificación</string>
<string name="create_error_notification">Crear una notificación de error</string> <string name="create_error_notification">Crear una notificación de error</string>
<string name="show_error_snackbar">Mostrar una barra de error</string> <string name="show_error_snackbar">Mostrar una barra de error</string>
<string name="no_appropriate_file_manager_message">No se ha encontrado un gestor de archivos apropiado para esta acción. <string name="no_appropriate_file_manager_message">No se ha encontrado un gestor de archivos apropiado para esta acción. \nPor favor, instala un gestor de archivos o intenta deshabilitarlo \'%s\' en los ajustes de la descarga</string>
\nPor favor, instale un gestor de archivos o intente desactivar \'%s\' en los ajustes de la descarga</string>
<string name="no_appropriate_file_manager_message_android_10">No se encontró ningún administrador de archivos apropiado para esta acción. <string name="no_appropriate_file_manager_message_android_10">No se encontró ningún administrador de archivos apropiado para esta acción.
\n Instale un administrador de archivos compatible con Storage Access Framework</string> \n Instale un administrador de archivos compatible con Storage Access Framework</string>
<string name="detail_pinned_comment_view_description">Comentario fijado</string> <string name="detail_pinned_comment_view_description">Comentario fijado</string>
@ -695,7 +682,7 @@
<string name="enable_streams_notifications_summary">Notificar de nuevos directos desde las suscripciones</string> <string name="enable_streams_notifications_summary">Notificar de nuevos directos desde las suscripciones</string>
<string name="streams_notifications_interval_title">Frecuencia de comprobación</string> <string name="streams_notifications_interval_title">Frecuencia de comprobación</string>
<string name="delete_downloaded_files_confirm">¿Desea borrar del disco todos los archivos descargados\?</string> <string name="delete_downloaded_files_confirm">¿Desea borrar del disco todos los archivos descargados\?</string>
<string name="notifications_disabled">Las notificaciones están desactivadas</string> <string name="notifications_disabled">Las notificaciones están deshabilitadas</string>
<string name="get_notified">Recibir notificaciones</string> <string name="get_notified">Recibir notificaciones</string>
<string name="toggle_all">Conmutar todo</string> <string name="toggle_all">Conmutar todo</string>
<string name="loading_stream_details">Cargando detalles del directo…</string> <string name="loading_stream_details">Cargando detalles del directo…</string>
@ -735,7 +722,7 @@
<string name="remove_duplicates_title">¿Eliminar los duplicados\?</string> <string name="remove_duplicates_title">¿Eliminar los duplicados\?</string>
<string name="remove_duplicates_message">¿Quieres eliminar todas las secuencias duplicadas de esta lista de reproducción?</string> <string name="remove_duplicates_message">¿Quieres eliminar todas las secuencias duplicadas de esta lista de reproducción?</string>
<string name="feed_hide_streams_title">Mostrar las siguientes secuencias</string> <string name="feed_hide_streams_title">Mostrar las siguientes secuencias</string>
<string name="feed_show_hide_streams">Mostrar/Ocultar secuencias</string> <string name="feed_show_hide_streams">Mostrar/ocultar secuencias</string>
<string name="feed_show_upcoming">Próximamente</string> <string name="feed_show_upcoming">Próximamente</string>
<string name="remove_duplicates">Eliminar los duplicados</string> <string name="remove_duplicates">Eliminar los duplicados</string>
<string name="feed_show_watched">Completamente visto</string> <string name="feed_show_watched">Completamente visto</string>
@ -765,7 +752,7 @@
<string name="progressive_load_interval_summary">Cambia el tamaño del intervalo de carga en contenidos progresivos (actualmente %s). Un valor más bajo puede acelerar la carga inicial</string> <string name="progressive_load_interval_summary">Cambia el tamaño del intervalo de carga en contenidos progresivos (actualmente %s). Un valor más bajo puede acelerar la carga inicial</string>
<string name="settings_category_exoplayer_title">Ajustes de ExoPlayer</string> <string name="settings_category_exoplayer_title">Ajustes de ExoPlayer</string>
<string name="settings_category_exoplayer_summary">Gestiona algunos ajustes de ExoPlayer. Estos cambios requieren reiniciar el reproductor para que surtan efecto</string> <string name="settings_category_exoplayer_summary">Gestiona algunos ajustes de ExoPlayer. Estos cambios requieren reiniciar el reproductor para que surtan efecto</string>
<string name="use_exoplayer_decoder_fallback_summary">Habilite esta opción si tiene problemas con la inicialización del decodificador recurriendo a decodificadores de menor prioridad si el decodificador principal no se inicializa. Esto puede dar como resultado un rendimiento de reproducción más bajo que cuando se usan decodificadores primarios</string> <string name="use_exoplayer_decoder_fallback_summary">Habilita esta opción si tiene problemas con la inicialización del decodificador recurriendo a decodificadores de menor prioridad si el decodificador principal no se inicializa. Esto puede dar como resultado un rendimiento de reproducción más bajo que cuando se usan decodificadores primarios</string>
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Esta solución alternativa libera los códecs de video y los vuelve a instanciar cuando cambia la máscara, en lugar de configurar la máscara directamente en el códec. ExoPlayer ya usa esta configuración en algunos dispositivos con este problema y solo afecta a Android 6 y versiones posteriores <string name="always_use_exoplayer_set_output_surface_workaround_summary">Esta solución alternativa libera los códecs de video y los vuelve a instanciar cuando cambia la máscara, en lugar de configurar la máscara directamente en el códec. ExoPlayer ya usa esta configuración en algunos dispositivos con este problema y solo afecta a Android 6 y versiones posteriores
\n \n
\nHabilitar esta opción puede evitar errores de reproducción al cambiar el reproductor de video actual o cambiar al modo de pantalla completa</string> \nHabilitar esta opción puede evitar errores de reproducción al cambiar el reproductor de video actual o cambiar al modo de pantalla completa</string>
@ -776,7 +763,7 @@
<string name="no_live_streams">Sin transmisiones en directo</string> <string name="no_live_streams">Sin transmisiones en directo</string>
<string name="channel_tab_videos">Vídeos</string> <string name="channel_tab_videos">Vídeos</string>
<string name="metadata_subscribers">Suscriptores</string> <string name="metadata_subscribers">Suscriptores</string>
<string name="show_channel_tabs_summary">Qué pestañas se muestran en las páginas de los canales</string> <string name="show_channel_tabs_summary">Qué pestañas se muestran en las páginas del canal</string>
<string name="show_channel_tabs">Pestañas del canal</string> <string name="show_channel_tabs">Pestañas del canal</string>
<string name="channel_tab_shorts">Shorts</string> <string name="channel_tab_shorts">Shorts</string>
<string name="loading_metadata_title">Cargando metadatos…</string> <string name="loading_metadata_title">Cargando metadatos…</string>
@ -829,13 +816,16 @@
<string name="settings_category_backup_restore_title">Respaldar y restaurar</string> <string name="settings_category_backup_restore_title">Respaldar y restaurar</string>
<string name="reset_settings_title">Restablecer ajustes</string> <string name="reset_settings_title">Restablecer ajustes</string>
<string name="reset_settings_summary">Restablecer todos los ajustes a sus valores predeterminados</string> <string name="reset_settings_summary">Restablecer todos los ajustes a sus valores predeterminados</string>
<string name="reset_all_settings">Restablecer todos los ajustes descartará todos sus ajustes preferidos y reiniciará la aplicación. <string name="reset_all_settings">Restablecer todos los ajustes descartará todos sus ajustes preferidos y reiniciará la aplicación. \n \n¿Estás seguro que quieres continuar?</string>
\n
\n¿Estas seguro que deseas continuar?</string>
<string name="yes"></string> <string name="yes"></string>
<string name="no">No</string> <string name="no">No</string>
<string name="auto_update_check_description">NewPipe puede buscar automáticamente nuevas versiones de vez en cuando y notificarle cuando estén disponibles. <string name="auto_update_check_description">NewPipe puede buscar automáticamente nuevas versiones de vez en cuando y notificarle cuando estén disponibles.
\n¿Quieres habilitar esto?</string> \n¿Quieres habilitar esto?</string>
<string name="import_settings_vulnerable_format">La configuración de la exportación que se importa utiliza un formato vulnerable que quedó obsoleto desde NewPipe 0.27.0. Asegúrese de que la exportación que se está importando provenga de una fuente confiable y prefiera usar solo exportaciones obtenidas de NewPipe 0.27.0 o posterior en el futuro. La compatibilidad con la importación de configuraciones en este formato vulnerable pronto se eliminará por completo y, luego, las versiones antiguas de NewPipe ya no podrán importar configuraciones de exportaciones desde las nuevas versiones.</string> <string name="import_settings_vulnerable_format">La configuración de la exportación que se importa utiliza un formato vulnerable que quedó obsoleto desde NewPipe 0.27.0. Asegúrese de que la exportación que se está importando provenga de una fuente confiable y prefiera usar solo exportaciones obtenidas de NewPipe 0.27.0 o posterior en el futuro. La compatibilidad con la importación de configuraciones en este formato vulnerable pronto se eliminará por completo y, luego, las versiones antiguas de NewPipe ya no podrán importar configuraciones de exportaciones desde las nuevas versiones.</string>
<string name="audio_track_type_secondary">secundaria</string> <string name="audio_track_type_secondary">secundaria</string>
<string name="share_playlist_as_youtube_temporary_playlist">Compartir como lista de reproducción temporal de YouTube</string>
<string name="tab_bookmarks_short">Lista de reproducción</string>
<string name="select_a_feed_group">Selecciona un grupo de feed</string>
<string name="no_feed_group_created_yet">Aún no se ha creado ningún grupo de feed</string>
<string name="feed_group_page_summary">Página de grupo de canales</string>
</resources> </resources>

View file

@ -146,7 +146,7 @@
<string name="error_details_headline">Üksikasjad:</string> <string name="error_details_headline">Üksikasjad:</string>
<string name="detail_thumbnail_view_description">Esita video, kestus:</string> <string name="detail_thumbnail_view_description">Esita video, kestus:</string>
<string name="detail_uploader_thumbnail_view_description">Üleslaadiaja avatari pisipilt</string> <string name="detail_uploader_thumbnail_view_description">Üleslaadiaja avatari pisipilt</string>
<string name="detail_likes_img_view_description">Meeldib</string> <string name="detail_likes_img_view_description">Meeldimisi</string>
<string name="detail_dislikes_img_view_description">Ei meeldi</string> <string name="detail_dislikes_img_view_description">Ei meeldi</string>
<string name="search_no_results">Tulemusi pole</string> <string name="search_no_results">Tulemusi pole</string>
<string name="empty_list_subtitle">Siin pole veel midagi</string> <string name="empty_list_subtitle">Siin pole veel midagi</string>
@ -154,9 +154,6 @@
<string name="video">Video</string> <string name="video">Video</string>
<string name="audio">Audio</string> <string name="audio">Audio</string>
<string name="retry">Proovi uuesti</string> <string name="retry">Proovi uuesti</string>
<string name="short_thousand">tuh</string>
<string name="short_million">mln</string>
<string name="short_billion">mld</string>
<string name="no_subscribers">Tellijaid pole</string> <string name="no_subscribers">Tellijaid pole</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s tellija</item> <item quantity="one">%s tellija</item>
@ -206,8 +203,8 @@
<string name="donation_title">Anneta</string> <string name="donation_title">Anneta</string>
<string name="website_title">Veebisait</string> <string name="website_title">Veebisait</string>
<string name="website_encouragement">Täiendava info ja uudiste lugemiseks külasta NewPipe\'i veebisaiti.</string> <string name="website_encouragement">Täiendava info ja uudiste lugemiseks külasta NewPipe\'i veebisaiti.</string>
<string name="privacy_policy_title">NewPipe\'i privaatsuspoliitika</string> <string name="privacy_policy_title">NewPipe\'i andmekaitsepõhimõtted</string>
<string name="read_privacy_policy">Loe privaatsuspoliitikat</string> <string name="read_privacy_policy">Loe andmekaitsepõhimõtteid</string>
<string name="app_license_title">NewPipe\'i litsents</string> <string name="app_license_title">NewPipe\'i litsents</string>
<string name="read_full_license">Loe litsentsi</string> <string name="read_full_license">Loe litsentsi</string>
<string name="title_activity_history">Ajalugu</string> <string name="title_activity_history">Ajalugu</string>
@ -217,7 +214,7 @@
<string name="title_most_played">Enim esitatud</string> <string name="title_most_played">Enim esitatud</string>
<string name="main_page_content">Avalehe sisu</string> <string name="main_page_content">Avalehe sisu</string>
<string name="blank_page_summary">Tühi leht</string> <string name="blank_page_summary">Tühi leht</string>
<string name="kiosk_page_summary">Kioski leht</string> <string name="kiosk_page_summary">Kioskivaade</string>
<string name="channel_page_summary">Kanali leht</string> <string name="channel_page_summary">Kanali leht</string>
<string name="select_a_channel">Vali kanal</string> <string name="select_a_channel">Vali kanal</string>
<string name="no_channel_subscribed_yet">Kanaleid pole veel tellitud</string> <string name="no_channel_subscribed_yet">Kanaleid pole veel tellitud</string>
@ -305,7 +302,7 @@
<string name="contribution_encouragement">Kui sul on ideid kujunduse muutmisest, koodi puhastamisest või suurtest koodi muudatustest - abi on alati teretulnud. Mida rohkem tehtud, seda paremaks läheb!</string> <string name="contribution_encouragement">Kui sul on ideid kujunduse muutmisest, koodi puhastamisest või suurtest koodi muudatustest - abi on alati teretulnud. Mida rohkem tehtud, seda paremaks läheb!</string>
<string name="donation_encouragement">NewPipe\'i arendajad on vabatahtlikud, kes kulutavad oma vaba aega, toomaks sulle parimat kasutuskogemust. On aeg anda tagasi aidates arendajaid ja muuta NewPipe veel paremaks, nautides ise tassi kohvi.</string> <string name="donation_encouragement">NewPipe\'i arendajad on vabatahtlikud, kes kulutavad oma vaba aega, toomaks sulle parimat kasutuskogemust. On aeg anda tagasi aidates arendajaid ja muuta NewPipe veel paremaks, nautides ise tassi kohvi.</string>
<string name="give_back">Anneta</string> <string name="give_back">Anneta</string>
<string name="privacy_policy_encouragement">NewPipe võtab privaatsust väga tõsiselt. Seetõttu ei kogu rakendus ilma nõusolekuta mingeid andmeid. \nNewPipe\'i privaatsuspoliitika selgitab üksikasjalikult, milliseid andmeid saadetakse ja kogutakse veateate saatmisel.</string> <string name="privacy_policy_encouragement">NewPipe võtab privaatsust väga tõsiselt. Seetõttu ei kogu rakendus ilma nõusolekuta mingeid andmeid. \nNewPipe\'i andmekaitsepõhimõtted selgitavad üksikasjalikult, milliseid andmeid saadetakse ja kogutakse veateate saatmisel.</string>
<string name="app_license">NewPipe on vaba ja avatud lähtekoodiga tarkvara. Seada võid kasutada, uurida, jagada ja parandada nii, nagu õigemaks pead. Täpsemalt - seda võid levitada ja/või muuta vastavalt Vaba Tarkvara Sihtasutuse avaldatud GNU Üldise Avaliku Litsentsi v.3 (või sinu valikul hilisema versiooni) tingimustele.</string> <string name="app_license">NewPipe on vaba ja avatud lähtekoodiga tarkvara. Seada võid kasutada, uurida, jagada ja parandada nii, nagu õigemaks pead. Täpsemalt - seda võid levitada ja/või muuta vastavalt Vaba Tarkvara Sihtasutuse avaldatud GNU Üldise Avaliku Litsentsi v.3 (või sinu valikul hilisema versiooni) tingimustele.</string>
<string name="enable_disposed_exceptions_title">Teavita elutsüklist väljas vigadest</string> <string name="enable_disposed_exceptions_title">Teavita elutsüklist väljas vigadest</string>
<string name="import_soundcloud_instructions">Impordi SoundCloudi profiil trükkides URL või oma ID: <string name="import_soundcloud_instructions">Impordi SoundCloudi profiil trükkides URL või oma ID:
@ -320,7 +317,7 @@
<string name="skip_silence_checkbox">Keri helitu koht edasi</string> <string name="skip_silence_checkbox">Keri helitu koht edasi</string>
<string name="playback_step">Samm</string> <string name="playback_step">Samm</string>
<string name="playback_reset">Lähtesta</string> <string name="playback_reset">Lähtesta</string>
<string name="start_accept_privacy_policy">Selleks, et täita Euroopa Üldist Andmekaitse Määrust (GDPR), juhime tähelepanu NewPipe\'i privaatsuspoliitikale. Palun loe seda hoolikalt. \nMeile veateate saatmiseks pead sellega nõustuma.</string> <string name="start_accept_privacy_policy">Selleks, et täita Euroopa Üldist Andmekaitse Määrust (GDPR), juhime tähelepanu NewPipe\'i andmekaitsepõhimõtetele. Palun loe seda hoolikalt. \nMeile veateate saatmiseks pead sellega nõustuma.</string>
<string name="minimize_on_exit_title">Minimeeri, kui kasutad teisi rakendusi</string> <string name="minimize_on_exit_title">Minimeeri, kui kasutad teisi rakendusi</string>
<string name="minimize_on_exit_summary">Tegevus lülitusel peamiselt videopleierilt teisele rakendusele — %s</string> <string name="minimize_on_exit_summary">Tegevus lülitusel peamiselt videopleierilt teisele rakendusele — %s</string>
<string name="minimize_on_exit_none_description">Pole</string> <string name="minimize_on_exit_none_description">Pole</string>
@ -487,7 +484,6 @@
<string name="enqueue_stream">Lisa esitusjärjekorda</string> <string name="enqueue_stream">Lisa esitusjärjekorda</string>
<string name="recently_added">Hiljuti lisatud</string> <string name="recently_added">Hiljuti lisatud</string>
<string name="local">Kohalikud</string> <string name="local">Kohalikud</string>
<string name="localization_changes_requires_app_restart">Keele muutus jõustub rakenduse uuesti käivitamisel</string>
<string name="error_unable_to_load_comments">Kommentaaride laadimine ei õnnestunud</string> <string name="error_unable_to_load_comments">Kommentaaride laadimine ei õnnestunud</string>
<string name="no_playlist_bookmarked_yet">Esitusloendi järjehoidjaid veel pole</string> <string name="no_playlist_bookmarked_yet">Esitusloendi järjehoidjaid veel pole</string>
<string name="select_a_playlist">Vali esitusloend</string> <string name="select_a_playlist">Vali esitusloend</string>
@ -808,4 +804,14 @@
<string name="no">Ei</string> <string name="no">Ei</string>
<string name="import_settings_vulnerable_format">Imporditavad andmed kasutavad turvaprobleemidega vormingut, mida alates versioonist 0.27.0 NewPipe enam kasutada ei suuda. Palun kontrolli, et impordifail on loodud usaldusväärse osapoole poolt ning eelista ekspordifaile, mis on loodud NewPipe\'i versiooniga 0.27.0 või uuemaga. Tugi sellise vana vormingu kasutamisele kaob õige pea ja seejärel NewPipe\'i uuemad ja vanemad versioonid ei saa omavahel andmeid enam vahetada.</string> <string name="import_settings_vulnerable_format">Imporditavad andmed kasutavad turvaprobleemidega vormingut, mida alates versioonist 0.27.0 NewPipe enam kasutada ei suuda. Palun kontrolli, et impordifail on loodud usaldusväärse osapoole poolt ning eelista ekspordifaile, mis on loodud NewPipe\'i versiooniga 0.27.0 või uuemaga. Tugi sellise vana vormingu kasutamisele kaob õige pea ja seejärel NewPipe\'i uuemad ja vanemad versioonid ei saa omavahel andmeid enam vahetada.</string>
<string name="audio_track_type_secondary">täiendav</string> <string name="audio_track_type_secondary">täiendav</string>
<string name="share_playlist_as_youtube_temporary_playlist">Jaga YouTube\'i ajutise esitusloendina</string>
<string name="tab_bookmarks_short">Esitusloendid</string>
<string name="select_a_feed_group">Vali andmevoo grupp</string>
<string name="no_feed_group_created_yet">Ühtegi andmevoo gruppi pole veel loodud</string>
<string name="feed_group_page_summary">Kanalirühmade leht</string>
<string name="search_with_service_name">Otsi: %1$s</string>
<string name="search_with_service_name_and_filter">Otsi: %1$s (%2$s)</string>
<string name="channel_tab_likes">Meeldimisi</string>
<string name="migration_info_6_7_title">SoundCloudi „Top 50“ leht on eemaldatud</string>
<string name="migration_info_6_7_message">SoundCloud on lõpetanud oma algse „Top 50“ edetabeli pidamise. Seega on ka vastav vahekaart meie rakenduse põhivaatest eemaldatud.</string>
</resources> </resources>

View file

@ -84,9 +84,6 @@
<string name="video">Bideoa</string> <string name="video">Bideoa</string>
<string name="audio">Audioa</string> <string name="audio">Audioa</string>
<string name="retry">Saiatu berriro</string> <string name="retry">Saiatu berriro</string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="short_billion">MM</string>
<string name="start">Hasi</string> <string name="start">Hasi</string>
<string name="pause">Pausatu</string> <string name="pause">Pausatu</string>
<string name="delete">Ezabatu</string> <string name="delete">Ezabatu</string>
@ -422,7 +419,6 @@
<string name="clear_playback_states_summary">Erreprodukziorako kokapen guztiak ezabatzen ditu</string> <string name="clear_playback_states_summary">Erreprodukziorako kokapen guztiak ezabatzen ditu</string>
<string name="delete_playback_states_alert">Ezabatu erreprodukziorako kokapen guztiak\?</string> <string name="delete_playback_states_alert">Ezabatu erreprodukziorako kokapen guztiak\?</string>
<string name="drawer_header_description">Aktibatu zerbitzua, orain hautatua:</string> <string name="drawer_header_description">Aktibatu zerbitzua, orain hautatua:</string>
<string name="localization_changes_requires_app_restart">Hizkuntza aldatuko da aplikazioa berrabiarazterakoan</string>
<string name="default_kiosk_page_summary">Kiosko Lehenetsia</string> <string name="default_kiosk_page_summary">Kiosko Lehenetsia</string>
<string name="seek_duration_title">Aurreratze/atzeratze bilaketaren iraupena</string> <string name="seek_duration_title">Aurreratze/atzeratze bilaketaren iraupena</string>
<string name="peertube_instance_url_title">PeerTube instantziak</string> <string name="peertube_instance_url_title">PeerTube instantziak</string>

View file

@ -148,9 +148,6 @@
<string name="error_occurred_detail">خطایی رخ داد: %1$s</string> <string name="error_occurred_detail">خطایی رخ داد: %1$s</string>
<string name="no_streams_available_download">جریانی برای بارگیری در دسترس نیست</string> <string name="no_streams_available_download">جریانی برای بارگیری در دسترس نیست</string>
<string name="search_no_results">بدون نتیجه</string> <string name="search_no_results">بدون نتیجه</string>
<string name="short_thousand">K</string>
<string name="short_million">M</string>
<string name="short_billion">B</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s مشترک</item> <item quantity="one">%s مشترک</item>
<item quantity="other">%s مشترک</item> <item quantity="other">%s مشترک</item>
@ -469,7 +466,6 @@
<string name="most_liked">مورد پسندترین‌ها</string> <string name="most_liked">مورد پسندترین‌ها</string>
<string name="recently_added">اخیرا اضافه شده</string> <string name="recently_added">اخیرا اضافه شده</string>
<string name="local">محلی</string> <string name="local">محلی</string>
<string name="localization_changes_requires_app_restart">با آغاز دوبارهٔ کاره، زبان تغییر خواهد کرد</string>
<string name="default_kiosk_page_summary">کیوسک پیش‌فرض</string> <string name="default_kiosk_page_summary">کیوسک پیش‌فرض</string>
<string name="done">انجام شد</string> <string name="done">انجام شد</string>
<string name="subtitle_activity_recaptcha">وقتی انجام شد، «Done» یا «انجام شد» را بفشارید</string> <string name="subtitle_activity_recaptcha">وقتی انجام شد، «Done» یا «انجام شد» را بفشارید</string>
@ -764,4 +760,59 @@
<string name="question_mark">؟</string> <string name="question_mark">؟</string>
<string name="settings_category_backup_restore_title">پشتیبان‌گیری و بازیابی</string> <string name="settings_category_backup_restore_title">پشتیبان‌گیری و بازیابی</string>
<string name="no_live_streams">بدون جریان زنده</string> <string name="no_live_streams">بدون جریان زنده</string>
<string name="image_quality_title">کیفیت تصویر</string>
<string name="image_quality_medium">کیفیت متوسّط</string>
<string name="image_quality_high">کیفیت زیاد</string>
<string name="prefer_original_audio_title">ترجیح صدای اصلی</string>
<string name="share_playlist">هم‌رسانی سیاههٔ پخش</string>
<string name="image_quality_low">کیفیت کم</string>
<string name="tab_bookmarks_short">سیاهه‌های پخش</string>
<string name="reset_settings_title">بازنشانی تنظیمات</string>
<string name="audio_track_type_secondary">ثانویه</string>
<string name="show_less">نمایش کم‌تر</string>
<string name="main_tabs_position_summary">جابه‌جایی گزینشگر زبانهٔ اصلی به پایین</string>
<string name="disable_media_tunneling_automatic_info">تونل زدن رسانه به صورت پیش‌گزیده روی افزاره‌تان از کار افتاده چرا که از آن پشتیبانی نمی‌کند.</string>
<string name="right_gesture_control_title">کنش ژست راست</string>
<string name="audio_track_present_in_video">قطعه‌ای صوتی باید از پیش در این جریان موجود باشد</string>
<string name="ignore_hardware_media_buttons_summary">برای نمونه اگر از گوشی‌ای با دکمه‌های خراب استفاده می‌کنید مفید است</string>
<string name="reset_all_settings">بازنشانی همهٔ تنظیمات همهٔ تنظیمات ترجیحیتان را دور اندعخته و کاره را دوباره آغاز می‌کند.\n\nمطمئنید که می‌خواهید ادامه دهید؟</string>
<string name="use_exoplayer_decoder_fallback_title">استفاده از ویژگی پشتیبان کدگشای اگزوپلیر</string>
<string name="show_channel_tabs_summary">زبانه‌های نشان داده شده روی صفحه‌های کانال</string>
<string name="toggle_screen_orientation">تغییر جهت صفحه</string>
<string name="image_quality_none">بار نکردن تصویرها</string>
<string name="share_playlist_with_list">هم‌رسانی سیاههٔ نشانی</string>
<string name="share_playlist_as_youtube_temporary_playlist">هم‌رسانی به شکل سیاههٔ پخش موقّتی یوتوب</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="share_playlist_content_details">%1$s\n%2$s</string>
<string name="always_use_exoplayer_set_output_surface_workaround_title">استفادهٔ همیشگی از دور زدن تنظیمات سطح خروجی ویدیوی اگزوپلیر</string>
<string name="main_tabs_position_title">موقعیت زبانه‌های اصلی</string>
<string name="feed_fetch_channel_tabs">واکشی زبانه‌های کانال</string>
<string name="select_audio_track_external_players">گزینش قطعهٔ صوتی برای پخش کننده‌های خارجی</string>
<plurals name="replies">
<item quantity="one">%s پاسخ</item>
<item quantity="other">%s پاسخ</item>
</plurals>
<string name="share_playlist_with_titles">هم‌رسانی با عنوان‌ها</string>
<string name="prefer_descriptive_audio_summary">گزینش قطعهٔ صوتی با شرح برای افزار کم‌بینا در صورت وجود</string>
<string name="prefer_descriptive_audio_title">ترجیح صدای شرح دهنده</string>
<string name="right_gesture_control_summary">گزینش کنش ژست نیمهٔ راست صفحه</string>
<string name="playlist_add_stream_success_duplicate">تعداد %d بار تکرار شده</string>
<string name="metadata_uploader_avatars">چهرک‌های بارکننده</string>
<string name="metadata_subchannel_avatars">چهرک‌های زیرکانال</string>
<string name="left_gesture_control_title">کنش ژست چپ</string>
<string name="image_quality_summary">گزینش کیفیت تصویرها و این که اصلاً بار شوند یا نه، برای کاهش استفادهٔ حافظه و داده. تغییرات انبارهٔ تصویر حافظه و دیسک را پاک می‌کند — %s</string>
<string name="use_exoplayer_decoder_fallback_summary">اگر مشکل شروع رمزگشایی دارید ، این گزینه را فعال کنید ، که اگر رمزگشایی اولیه شکست بخورد ، به رمزگشایی های با اولویت پایین تر باز می گردد. این ممکن است منجر به عملکرد پخش ضعیف نسبت به هنگام استفاده از رمزگشایان اولیه شود</string>
<string name="always_use_exoplayer_set_output_surface_workaround_summary">این روش دور زدن مشکل به جای تنظیم مستقیم سطح روی رمزینه، آن‌ها را هنگام تغییر سطح آزاد کرده و دوباره راه‌اندازی می‌کند. این تنظیم که از پیش روی برخی افزاره‌ها به دست اگزوپلیر استفاده می‌شد فقط روی اندروید ۶ و بالاتر تأثیر دارد\n\nبه کار انداختن این گزینه می‌تواند از خطاهای پخش هنگام تغییر پخش‌کنندهٔ ویدیوی کنونی یا تغییر به حالت تمام‌صفحه جلوگیری کند</string>
<string name="settings_category_exoplayer_summary">مدیریت برخی تنظیمات اگزوپلیر. اعمال این تغییرات نیازمند آغاز دوبارهٔ پخش‌کننده است</string>
<string name="duplicate_in_playlist">سیاهه‌های پخشی که خاکستری شده‌اند این مورد را از پیش دارند.</string>
<string name="notification_actions_summary_android13">ویرایش هر کنش آگاهی زیر با زدن رویش. سه کنش نخست (پخش/مکث، پیشین و بعدی) به دست سامانه تنظیم شده و قابل سفارشی سازی نیستند.</string>
<string name="left_gesture_control_summary">گزینش کنش ژست نیمهٔ چپ صفحه</string>
<string name="prefer_original_audio_summary">گزینش قطعهٔ صوتی اصلی فارغ از زبان</string>
<string name="auto_update_check_description">نیوپایپ می‌تواند گه‌گاه به صورت خودکار نگارش‌های جدید را بررسی کرده و از وجودشان آگاهتان کند.\nمیخواهید به کارش بیندازید؟</string>
<string name="reset_settings_summary">بازنشانی همهٔ تنظیمات به مقدارهای پیش‌گزیده‌شان</string>
<string name="show_more">نمایش بیش‌تر</string>
<string name="import_settings_vulnerable_format">تنظیمات داخل برون‌ریزی‌ از قالبی آسیب‌پذیر استفاده می‌کند که از نگارش ۰٫۲۷٫۰ منسوخ شده. مطمئن شوید برون‌ریزی از منبعی مطمئن آمده و ترجیحاً فقط از برون‌ریزی‌های آمده از نگارش ۰٫۲۷٫۰ به بعد استفاده کنید. پشتیبانی از درون‌ریزی تنظیمات به این قالب آسیب‌پذیر به زودی کاملاً‌برداشته خواهد شد و دیگر نگارش‌خای قدیمی‌تر قادر به درون ریزی تنظیمات از نگارش‌های جدید نخواهند بود.</string>
<string name="open_play_queue">گشودن صف پخش</string>
<string name="remove_duplicates_message">می‌خواهید همهٔ جریان‌های تکراری را در این سیاههٔ پخش بردارید؟</string>
<string name="feed_fetch_channel_tabs_summary">زبانه‌هایی که هنگام به‌روز رسانی خوراک واکشی می‌شوند. این گزینه تأثیری روی کانال‌هایی که با ساتفاده از حالت سریع به‌روز می‌شوند ندارد.</string>
</resources> </resources>

View file

@ -102,9 +102,6 @@
<string name="video">Video</string> <string name="video">Video</string>
<string name="audio">Ääni</string> <string name="audio">Ääni</string>
<string name="retry">Toista uudelleen</string> <string name="retry">Toista uudelleen</string>
<string name="short_thousand">t.</string>
<string name="short_million">milj.</string>
<string name="short_billion">bilj.</string>
<string name="no_subscribers">Ei tilaajia</string> <string name="no_subscribers">Ei tilaajia</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s tilaaja</item> <item quantity="one">%s tilaaja</item>
@ -337,7 +334,6 @@
<string name="recently_added">Hiljattain lisätyt</string> <string name="recently_added">Hiljattain lisätyt</string>
<string name="local">Paikalliset</string> <string name="local">Paikalliset</string>
<string name="most_liked">Pidetyimmät</string> <string name="most_liked">Pidetyimmät</string>
<string name="localization_changes_requires_app_restart">Kieli vaihtuu, kun sovellus uudelleenkäynnistetään</string>
<string name="error_unable_to_load_comments">Kommentteja ei voitu ladata</string> <string name="error_unable_to_load_comments">Kommentteja ei voitu ladata</string>
<string name="main_page_content_summary">Mitkä välilehdet näytetään pääsivulla</string> <string name="main_page_content_summary">Mitkä välilehdet näytetään pääsivulla</string>
<string name="done">Valmis</string> <string name="done">Valmis</string>
@ -807,4 +803,9 @@
<string name="auto_update_check_description">NewPipe voi automaattisesti tarkistaa päivitysten saatavuuden silloin tällöin ja ilmoittaa kun niitä on saatavilla. <string name="auto_update_check_description">NewPipe voi automaattisesti tarkistaa päivitysten saatavuuden silloin tällöin ja ilmoittaa kun niitä on saatavilla.
\nHaluatko ottaa tämän käyttöön?</string> \nHaluatko ottaa tämän käyttöön?</string>
<string name="error_insufficient_storage">Laitteella ei ole riittävästi vapaata tilaa</string> <string name="error_insufficient_storage">Laitteella ei ole riittävästi vapaata tilaa</string>
<string name="share_playlist_as_youtube_temporary_playlist">Jaa tilapäisenä YouTube-soittolistana</string>
<string name="channel_tab_tracks">Raidat</string>
<string name="question_mark">\?</string>
<string name="audio_track_type_secondary">toissijainen</string>
<string name="tab_bookmarks_short">Soittolistat</string>
</resources> </resources>

View file

@ -5,7 +5,7 @@
<string name="did_you_mean">Vouliez-vous dire « %1$s »\?</string> <string name="did_you_mean">Vouliez-vous dire « %1$s »\?</string>
<string name="download">Télécharger</string> <string name="download">Télécharger</string>
<string name="download_path_title">Dossier de téléchargement vidéo</string> <string name="download_path_title">Dossier de téléchargement vidéo</string>
<string name="download_path_dialog_title">Choisissez le dossier de téléchargement des vidéos</string> <string name="download_path_dialog_title">Choisissez le dossier de téléchargement pour les fichiers vidéos</string>
<string name="download_path_summary">Les vidéos téléchargées sont stockées ici</string> <string name="download_path_summary">Les vidéos téléchargées sont stockées ici</string>
<string name="install">Installer</string> <string name="install">Installer</string>
<string name="kore_not_found">Installer lapplication Kore manquante\?</string> <string name="kore_not_found">Installer lapplication Kore manquante\?</string>
@ -87,8 +87,6 @@
<string name="popup_playing_toast">Lecture en mode flottant</string> <string name="popup_playing_toast">Lecture en mode flottant</string>
<string name="disabled">Désactivés</string> <string name="disabled">Désactivés</string>
<string name="info_labels">Quoi:\\nRequest:\\nContent Language:\\nContent Country:\\nApp Language:\\nService:\\nGMT Time:\\nPackage:\\nVersion:\\nOS version:</string> <string name="info_labels">Quoi:\\nRequest:\\nContent Language:\\nContent Country:\\nApp Language:\\nService:\\nGMT Time:\\nPackage:\\nVersion:\\nOS version:</string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="msg_popup_permission">Cette autorisation est nécessaire pour <string name="msg_popup_permission">Cette autorisation est nécessaire pour
\nutiliser le mode flottant</string> \nutiliser le mode flottant</string>
<string name="controls_background_title">Arrière-plan</string> <string name="controls_background_title">Arrière-plan</string>
@ -100,7 +98,6 @@
<string name="popup_remember_size_pos_title">Mémoriser les propriétés de la fenêtre flottante</string> <string name="popup_remember_size_pos_title">Mémoriser les propriétés de la fenêtre flottante</string>
<string name="popup_remember_size_pos_summary">Mémoriser les dernières taille et position de la fenêtre flottante</string> <string name="popup_remember_size_pos_summary">Mémoriser les dernières taille et position de la fenêtre flottante</string>
<string name="clear">Effacer</string> <string name="clear">Effacer</string>
<string name="short_billion">G</string>
<string name="use_external_video_player_summary">Le son peut être absent à certaines définitions</string> <string name="use_external_video_player_summary">Le son peut être absent à certaines définitions</string>
<string name="show_search_suggestions_title">Suggestions de recherche</string> <string name="show_search_suggestions_title">Suggestions de recherche</string>
<string name="show_search_suggestions_summary">Sélectionner les suggestions à afficher lors dune recherche</string> <string name="show_search_suggestions_summary">Sélectionner les suggestions à afficher lors dune recherche</string>
@ -164,7 +161,7 @@
<string name="charset_most_special_characters">Caractères spéciaux</string> <string name="charset_most_special_characters">Caractères spéciaux</string>
<string name="delete_item_search_history">Voulez-vous supprimer cet élément de lhistorique de recherche\?</string> <string name="delete_item_search_history">Voulez-vous supprimer cet élément de lhistorique de recherche\?</string>
<string name="main_page_content">Contenu de la page principale</string> <string name="main_page_content">Contenu de la page principale</string>
<string name="blank_page_summary">Page vide</string> <string name="blank_page_summary">Page blanche</string>
<string name="channel_page_summary">Chaîne</string> <string name="channel_page_summary">Chaîne</string>
<string name="select_a_channel">Sélectionner une chaîne</string> <string name="select_a_channel">Sélectionner une chaîne</string>
<string name="trending">Tendances</string> <string name="trending">Tendances</string>
@ -428,7 +425,6 @@
<item quantity="many">%s auditeurs</item> <item quantity="many">%s auditeurs</item>
<item quantity="other">%s auditeurs</item> <item quantity="other">%s auditeurs</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">La langue changera une fois que lapplication aura redémarré</string>
<string name="seek_duration_title">Durée de lavance et retour rapide</string> <string name="seek_duration_title">Durée de lavance et retour rapide</string>
<string name="peertube_instance_url_title">Instances PeerTube</string> <string name="peertube_instance_url_title">Instances PeerTube</string>
<string name="peertube_instance_url_summary">Veuillez choisir vos instances PeerTube préférées</string> <string name="peertube_instance_url_summary">Veuillez choisir vos instances PeerTube préférées</string>
@ -839,4 +835,14 @@
<string name="error_insufficient_storage">Pas assez d\'espace disponible sur l\'appareil</string> <string name="error_insufficient_storage">Pas assez d\'espace disponible sur l\'appareil</string>
<string name="import_settings_vulnerable_format">Les paramètres de l\'export en cours d\'importation utilisent un format vulnérable qui a été déprécié depuis NewPipe 0.27.0. Assurez-vous que l\'export en cours d\'importation provient d\'une source fiable. Privilégiez les exports obtenues à partir de NewPipe 0.27.0 ou des versions plus récentes à l\'avenir. Le support pour l\'importation des paramètres dans ce format vulnérable sera bientôt complètement supprimé et les anciennes versions de NewPipe ne pourront plus importer les paramètres des exports des nouvelles versions.</string> <string name="import_settings_vulnerable_format">Les paramètres de l\'export en cours d\'importation utilisent un format vulnérable qui a été déprécié depuis NewPipe 0.27.0. Assurez-vous que l\'export en cours d\'importation provient d\'une source fiable. Privilégiez les exports obtenues à partir de NewPipe 0.27.0 ou des versions plus récentes à l\'avenir. Le support pour l\'importation des paramètres dans ce format vulnérable sera bientôt complètement supprimé et les anciennes versions de NewPipe ne pourront plus importer les paramètres des exports des nouvelles versions.</string>
<string name="audio_track_type_secondary">secondaire</string> <string name="audio_track_type_secondary">secondaire</string>
<string name="share_playlist_as_youtube_temporary_playlist">Partager comme liste de lecture YouTube temporaire</string>
<string name="tab_bookmarks_short">Listes de lecture</string>
<string name="select_a_feed_group">Sélectionnez un groupe de flux</string>
<string name="no_feed_group_created_yet">Aucun groupe de flux n\'a encore été créé</string>
<string name="feed_group_page_summary">Page du groupe de chaînes</string>
<string name="search_with_service_name">Rechercher %1$s</string>
<string name="search_with_service_name_and_filter">Rechercher %1$s (%2$s)</string>
<string name="channel_tab_likes">Likes</string>
<string name="migration_info_6_7_title">Page SoundCloud Top 50 supprimée</string>
<string name="migration_info_6_7_message">SoundCloud a abandonné le classement original du Top 50. L\'onglet correspondant a été supprimé de votre page d\'accueil.</string>
</resources> </resources>

View file

@ -158,9 +158,6 @@
<string name="video">Vídeo</string> <string name="video">Vídeo</string>
<string name="audio">Audio</string> <string name="audio">Audio</string>
<string name="retry">Tentar de novo</string> <string name="retry">Tentar de novo</string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="short_billion">B</string>
<string name="no_subscribers">Ningún subscrito</string> <string name="no_subscribers">Ningún subscrito</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s subscrito</item> <item quantity="one">%s subscrito</item>
@ -463,7 +460,6 @@
<string name="conferences">Conferencias</string> <string name="conferences">Conferencias</string>
<string name="most_liked">O que ten mais gústames</string> <string name="most_liked">O que ten mais gústames</string>
<string name="recently_added">Engadiuse recentemente</string> <string name="recently_added">Engadiuse recentemente</string>
<string name="localization_changes_requires_app_restart">O idioma cambiará unha vez que se reinicie o aplicativo</string>
<string name="error_unable_to_load_comments">Non se puideron cargar os comentarios</string> <string name="error_unable_to_load_comments">Non se puideron cargar os comentarios</string>
<string name="no_playlist_bookmarked_yet">Aínda non hai marcadores nesta lista de reprodución</string> <string name="no_playlist_bookmarked_yet">Aínda non hai marcadores nesta lista de reprodución</string>
<string name="select_a_playlist">Seleccionar unha lista de reprodución</string> <string name="select_a_playlist">Seleccionar unha lista de reprodución</string>

View file

@ -105,16 +105,13 @@
<string name="error_details_headline">פרטים:</string> <string name="error_details_headline">פרטים:</string>
<string name="detail_thumbnail_view_description">נגינת סרטון, משך:</string> <string name="detail_thumbnail_view_description">נגינת סרטון, משך:</string>
<string name="detail_uploader_thumbnail_view_description">תמונה ייצוגית של המפרסם</string> <string name="detail_uploader_thumbnail_view_description">תמונה ייצוגית של המפרסם</string>
<string name="detail_likes_img_view_description">אהבו</string> <string name="detail_likes_img_view_description">לייקים</string>
<string name="detail_dislikes_img_view_description">לא אהבו</string> <string name="detail_dislikes_img_view_description">לא אהבו</string>
<string name="search_no_results">אין תוצאות</string> <string name="search_no_results">אין תוצאות</string>
<string name="empty_list_subtitle">אין כאן כלום מלבד צרצרים</string> <string name="empty_list_subtitle">אין כאן כלום מלבד צרצרים</string>
<string name="video">סרטון</string> <string name="video">סרטון</string>
<string name="audio">שמע</string> <string name="audio">שמע</string>
<string name="retry">ניסיון חוזר</string> <string name="retry">ניסיון חוזר</string>
<string name="short_thousand">אלפ.</string>
<string name="short_million">מיל.</string>
<string name="short_billion">מיליארד</string>
<string name="no_subscribers">אין מנויים</string> <string name="no_subscribers">אין מנויים</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">מנוי אחד</item> <item quantity="one">מנוי אחד</item>
@ -179,9 +176,9 @@
<string name="action_history">היסטוריה</string> <string name="action_history">היסטוריה</string>
<string name="delete_item_search_history">למחוק את הפריט הזה מהיסטוריית החיפושים\?</string> <string name="delete_item_search_history">למחוק את הפריט הזה מהיסטוריית החיפושים\?</string>
<string name="main_page_content">תוכן הדף הראשי</string> <string name="main_page_content">תוכן הדף הראשי</string>
<string name="blank_page_summary">דף ריק</string> <string name="blank_page_summary">עמוד ריק</string>
<string name="kiosk_page_summary">דף גישה מזדמנת</string> <string name="kiosk_page_summary">עמוד גישה מזדמנת</string>
<string name="channel_page_summary">דף ערוצים</string> <string name="channel_page_summary">עמוד הערוץ</string>
<string name="select_a_channel">נא לבחור ערוץ</string> <string name="select_a_channel">נא לבחור ערוץ</string>
<string name="no_channel_subscribed_yet">אין עדיין מינויים לערוצים</string> <string name="no_channel_subscribed_yet">אין עדיין מינויים לערוצים</string>
<string name="select_a_kiosk">נא לבחור סוג גישה מזדמנת</string> <string name="select_a_kiosk">נא לבחור סוג גישה מזדמנת</string>
@ -196,7 +193,7 @@
<string name="start_here_on_popup">להתחיל לנגן בנגן צף</string> <string name="start_here_on_popup">להתחיל לנגן בנגן צף</string>
<string name="controls_download_desc">הורדת קובץ הזרמה</string> <string name="controls_download_desc">הורדת קובץ הזרמה</string>
<string name="show_info">הצגת מידע</string> <string name="show_info">הצגת מידע</string>
<string name="tab_bookmarks">רשימות נגינה מסומנות</string> <string name="tab_bookmarks">רשימות השמעה מסומנות</string>
<string name="controls_add_to_playlist_title">הוספה אל</string> <string name="controls_add_to_playlist_title">הוספה אל</string>
<string name="default_content_country_title">מדינת תוכן כברירת מחדל</string> <string name="default_content_country_title">מדינת תוכן כברירת מחדל</string>
<string name="settings_category_debug_title">ניפוי שגיאות</string> <string name="settings_category_debug_title">ניפוי שגיאות</string>
@ -432,7 +429,6 @@
<item quantity="many">%s מאזינים</item> <item quantity="many">%s מאזינים</item>
<item quantity="other">%s מאזינים</item> <item quantity="other">%s מאזינים</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">השפה תוחלף עם הפעלת היישומון מחדש</string>
<string name="default_kiosk_page_summary">קיוסק ברירת מחדל</string> <string name="default_kiosk_page_summary">קיוסק ברירת מחדל</string>
<string name="seek_duration_title">משך קפיצה מהירה קדימה/אחורה</string> <string name="seek_duration_title">משך קפיצה מהירה קדימה/אחורה</string>
<string name="peertube_instance_url_title">מופעים של PeerTube</string> <string name="peertube_instance_url_title">מופעים של PeerTube</string>
@ -850,4 +846,12 @@
<string name="error_insufficient_storage">אין מספיק מקום פנוי במכשיר</string> <string name="error_insufficient_storage">אין מספיק מקום פנוי במכשיר</string>
<string name="import_settings_vulnerable_format">ההגדרות בייצוא המיובא משתמשות בתסדיר פגיע שהוצא משימוש מאז NewPipe 0.27.0. יש לוודא שהייצוא המיובא הוא ממקור מהימן, ועדיף להשתמש רק בייצוא שהושג מ־NewPipe 0.27.0 ומעלה בעתיד. תמיכה בייבוא הגדרות בתסדיר פגיע זה תוסר בקרוב לחלוטין, ואז גרסאות ישנות של NewPipe לא יוכלו לייבא עוד הגדרות של ייצוא מגרסאות חדשות.</string> <string name="import_settings_vulnerable_format">ההגדרות בייצוא המיובא משתמשות בתסדיר פגיע שהוצא משימוש מאז NewPipe 0.27.0. יש לוודא שהייצוא המיובא הוא ממקור מהימן, ועדיף להשתמש רק בייצוא שהושג מ־NewPipe 0.27.0 ומעלה בעתיד. תמיכה בייבוא הגדרות בתסדיר פגיע זה תוסר בקרוב לחלוטין, ואז גרסאות ישנות של NewPipe לא יוכלו לייבא עוד הגדרות של ייצוא מגרסאות חדשות.</string>
<string name="audio_track_type_secondary">משני</string> <string name="audio_track_type_secondary">משני</string>
<string name="tab_bookmarks_short">רשימות נגינה</string>
<string name="share_playlist_as_youtube_temporary_playlist">שיתוף כרשימת נגינה זמנית של YouTube</string>
<string name="select_a_feed_group">הגדרת קבוצת ערוצי עדכונים</string>
<string name="no_feed_group_created_yet">לא נוצרו עדיין קבוצות ערוצי עדכונים</string>
<string name="feed_group_page_summary">עמוד קבוצת ערוצים</string>
<string name="search_with_service_name">חיפוש ב־%1$s</string>
<string name="search_with_service_name_and_filter">חיפוש ב־%1$s (%2$s)</string>
<string name="channel_tab_likes">לייקים</string>
</resources> </resources>

View file

@ -126,9 +126,6 @@
<string name="video">वीडियो</string> <string name="video">वीडियो</string>
<string name="audio">ऑडियो</string> <string name="audio">ऑडियो</string>
<string name="retry">फिर से कोशिश करें</string> <string name="retry">फिर से कोशिश करें</string>
<string name="short_thousand">हज़ार</string>
<string name="short_million">मिलियन</string>
<string name="short_billion">अरब</string>
<string name="no_subscribers">कोई सब्सक्राइबर नहीं</string> <string name="no_subscribers">कोई सब्सक्राइबर नहीं</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s सब्सक्राइबर</item> <item quantity="one">%s सब्सक्राइबर</item>
@ -423,7 +420,6 @@
<item quantity="one">%s श्रोता</item> <item quantity="one">%s श्रोता</item>
<item quantity="other">%s श्रोता</item> <item quantity="other">%s श्रोता</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">ऐप के पुनः आरंभ होने के बाद भाषा बदल जाएगी</string>
<string name="feed_use_dedicated_fetch_method_enable_button">तेज मोड सक्षम करें</string> <string name="feed_use_dedicated_fetch_method_enable_button">तेज मोड सक्षम करें</string>
<string name="feed_use_dedicated_fetch_method_disable_button">तेज मोड अक्षम करें</string> <string name="feed_use_dedicated_fetch_method_disable_button">तेज मोड अक्षम करें</string>
<string name="feed_use_dedicated_fetch_method_help_text">क्या आपको लगता है कि फीड लोडिंग बहुत धीमी है\? यदि ऐसा है, तो तेज़ लोडिंग को सक्षम करने का प्रयास करें (आप इसे सेटिंग्स में या नीचे दिए गए बटन को दबाकर बदल सकते हैं)। <string name="feed_use_dedicated_fetch_method_help_text">क्या आपको लगता है कि फीड लोडिंग बहुत धीमी है\? यदि ऐसा है, तो तेज़ लोडिंग को सक्षम करने का प्रयास करें (आप इसे सेटिंग्स में या नीचे दिए गए बटन को दबाकर बदल सकते हैं)।
@ -823,4 +819,12 @@
<string name="settings_category_backup_restore_title">बैकअप और रिस्टोर</string> <string name="settings_category_backup_restore_title">बैकअप और रिस्टोर</string>
<string name="import_settings_vulnerable_format">आयात किए जा रहे निर्यात में सेटिंग्स एक कमजोर प्रारूप का उपयोग करती हैं जिसे न्यूपाइप 0.27.0 के बाद से हटा दिया गया था। सुनिश्चित करें कि आयात किया जा रहा निर्यात किसी विश्वसनीय स्रोत से है, और भविष्य में केवल न्यूपाइप 0.27.0 या नए से प्राप्त निर्यात का उपयोग करना पसंद करें। इस असुरक्षित प्रारूप में सेटिंग्स आयात करने के लिए समर्थन जल्द ही पूरी तरह से हटा दिया जाएगा, और फिर न्यूपाइप के पुराने संस्करण अब नए संस्करणों से निर्यात की सेटिंग्स आयात नहीं कर पाएंगे।</string> <string name="import_settings_vulnerable_format">आयात किए जा रहे निर्यात में सेटिंग्स एक कमजोर प्रारूप का उपयोग करती हैं जिसे न्यूपाइप 0.27.0 के बाद से हटा दिया गया था। सुनिश्चित करें कि आयात किया जा रहा निर्यात किसी विश्वसनीय स्रोत से है, और भविष्य में केवल न्यूपाइप 0.27.0 या नए से प्राप्त निर्यात का उपयोग करना पसंद करें। इस असुरक्षित प्रारूप में सेटिंग्स आयात करने के लिए समर्थन जल्द ही पूरी तरह से हटा दिया जाएगा, और फिर न्यूपाइप के पुराने संस्करण अब नए संस्करणों से निर्यात की सेटिंग्स आयात नहीं कर पाएंगे।</string>
<string name="audio_track_type_secondary">सेकेंडरी</string> <string name="audio_track_type_secondary">सेकेंडरी</string>
<string name="search_with_service_name">%1$s खोजें</string>
<string name="search_with_service_name_and_filter">%1$s (%2$s) खोजें</string>
<string name="tab_bookmarks_short">प्लेलिस्ट</string>
<string name="select_a_feed_group">कृपया एक फ़ीड समूह चुनें</string>
<string name="no_feed_group_created_yet">अभी तक कोई फ़ीड समूह नहीं बनाया गया है</string>
<string name="feed_group_page_summary">चैनल समूह पेज</string>
<string name="channel_tab_likes">पसंद</string>
<string name="share_playlist_as_youtube_temporary_playlist">यूट्यूब अस्थायी प्लेलिस्ट के रूप में साझा करें</string>
</resources> </resources>

View file

@ -97,9 +97,6 @@
<string name="video">Video</string> <string name="video">Video</string>
<string name="audio">Audio</string> <string name="audio">Audio</string>
<string name="retry">Pokušaj ponovo</string> <string name="retry">Pokušaj ponovo</string>
<string name="short_thousand">tis.</string>
<string name="short_million">mil</string>
<string name="short_billion">mlrd.</string>
<string name="start">Počni</string> <string name="start">Počni</string>
<string name="pause">Pauziraj</string> <string name="pause">Pauziraj</string>
<string name="delete">Izbriši</string> <string name="delete">Izbriši</string>
@ -406,7 +403,6 @@
<string name="delete_playback_states_alert">Izbrisati sve pozicije reprodukcije\?</string> <string name="delete_playback_states_alert">Izbrisati sve pozicije reprodukcije\?</string>
<string name="no_one_watching">Nitko ne gleda</string> <string name="no_one_watching">Nitko ne gleda</string>
<string name="no_one_listening">Nitko ne sluša</string> <string name="no_one_listening">Nitko ne sluša</string>
<string name="localization_changes_requires_app_restart">Jezik će se promijeniti nakon ponovnog pokretanja aplikcije</string>
<string name="default_kiosk_page_summary">Standardni kiosk</string> <string name="default_kiosk_page_summary">Standardni kiosk</string>
<string name="peertube_instance_add_https_only">Podržani su samo HTTP URL-ovi</string> <string name="peertube_instance_add_https_only">Podržani su samo HTTP URL-ovi</string>
<string name="local">Lokalni</string> <string name="local">Lokalni</string>

View file

@ -49,7 +49,7 @@
<string name="error_snackbar_action">Jelentés</string> <string name="error_snackbar_action">Jelentés</string>
<string name="what_device_headline">Információ:</string> <string name="what_device_headline">Információ:</string>
<string name="what_happened_headline">Ez történt:</string> <string name="what_happened_headline">Ez történt:</string>
<string name="your_comment">Az Ön megjegyzése (angolul):</string> <string name="your_comment">Saját hozzászólás (angolul):</string>
<string name="error_details_headline">Részletek:</string> <string name="error_details_headline">Részletek:</string>
<string name="error_snackbar_message">Elnézést, valami balul sült el.</string> <string name="error_snackbar_message">Elnézést, valami balul sült el.</string>
<string name="sorry_string">Elnézést, ennek nem kellett volna megtörténnie.</string> <string name="sorry_string">Elnézést, ennek nem kellett volna megtörténnie.</string>
@ -127,18 +127,18 @@
<string name="app_ui_crash">Az alkalmazás/kezelőfelület összeomlott</string> <string name="app_ui_crash">Az alkalmazás/kezelőfelület összeomlott</string>
<string name="player_stream_failure">Nem sikerült a videó lejátszása</string> <string name="player_stream_failure">Nem sikerült a videó lejátszása</string>
<string name="external_player_unsupported_link_type">A külső lejátszó nem támogatja az ilyen típusú hivatkozásokat</string> <string name="external_player_unsupported_link_type">A külső lejátszó nem támogatja az ilyen típusú hivatkozásokat</string>
<string name="video_streams_empty">Nem található videó adatfolyam</string> <string name="video_streams_empty">Nem található videófolyam</string>
<string name="audio_streams_empty">Nem található hang adatfolyam</string> <string name="audio_streams_empty">Nem található hangfolyam</string>
<string name="info_labels">Mi:\\nKérés:\\nTartalom nyelve:\\nTartalom származási országa:\\nAlkalmazás nyelve:\\nSzolgáltatás:\\nGMT idő:\\nCsomag:\\nVerzió:\\nOperációs rendszer verzió:</string> <string name="info_labels">Mi:\\nKérés:\\nTartalom nyelve:\\nTartalom származási országa:\\nAlkalmazás nyelve:\\nSzolgáltatás:\\nGMT idő:\\nCsomag:\\nVerzió:\\nOperációs rendszer verzió:</string>
<string name="search_no_results">Nincs találat</string> <string name="search_no_results">Nincs találat</string>
<string name="controls_download_desc">Közvetítési fájl letöltése</string> <string name="controls_download_desc">Közvetítési fájl letöltése</string>
<string name="controls_add_to_playlist_title">Hozzáadás ehhez</string> <string name="controls_add_to_playlist_title">Hozzáadás ehhez</string>
<string name="use_inexact_seek_title">Gyorsabb, de pontatlan tekerés használata</string> <string name="use_inexact_seek_title">Gyorsabb, de pontatlan tekerés használata</string>
<string name="use_inexact_seek_summary">A pontatlan tekerés lehetővé teszi, hogy gyorsabban ugorjon a pozíciókra, de kisebb pontossággal. Az 5, 15, vagy 25 másodperces tekerés nem működik ebben a módban</string> <string name="use_inexact_seek_summary">A pontatlan tekerés lehetővé teszi, hogy gyorsabban ugorjon a pozíciókra, de kisebb pontossággal. Az 5, 15, vagy 25 másodperces tekerés nem működik ebben a módban</string>
<string name="thumbnail_cache_wipe_complete_notice">A bélyegkép gyorsítótára törölve</string> <string name="thumbnail_cache_wipe_complete_notice">Bélyegkép gyorsítótára törölve</string>
<string name="metadata_cache_wipe_title">Gyorsítótárazott metaadatok törlése</string> <string name="metadata_cache_wipe_title">Gyorsítótárazott metaadatok törlése</string>
<string name="metadata_cache_wipe_summary">Minden gyorsítótárazott weboldaladat törlése</string> <string name="metadata_cache_wipe_summary">Minden gyorsítótárazott weboldaladat törölve</string>
<string name="metadata_cache_wipe_complete_notice">A metaadatok gyorsítótára törölve lett</string> <string name="metadata_cache_wipe_complete_notice">Metaadatok gyorsítótára törölve</string>
<string name="auto_queue_title">Következő videó automatikus sorba állítása</string> <string name="auto_queue_title">Következő videó automatikus sorba állítása</string>
<string name="enable_search_history_summary">Keresési előzmények helyi tárolása</string> <string name="enable_search_history_summary">Keresési előzmények helyi tárolása</string>
<string name="channels">Csatornák</string> <string name="channels">Csatornák</string>
@ -166,9 +166,6 @@
<string name="no_streams_available_download">Nincs letölthető adatfolyam</string> <string name="no_streams_available_download">Nincs letölthető adatfolyam</string>
<string name="empty_list_subtitle">Nincs itt semmi pár tücskön kívül</string> <string name="empty_list_subtitle">Nincs itt semmi pár tücskön kívül</string>
<string name="detail_drag_description">Húzza az átrendezéshez</string> <string name="detail_drag_description">Húzza az átrendezéshez</string>
<string name="short_thousand">e</string>
<string name="short_million">m</string>
<string name="short_billion">M</string>
<string name="no_subscribers">Nincs feliratkozó</string> <string name="no_subscribers">Nincs feliratkozó</string>
<plurals name="subscribers"> <plurals name="subscribers">
<item quantity="one">%s feliratkozó</item> <item quantity="one">%s feliratkozó</item>
@ -202,13 +199,13 @@
<string name="tab_licenses">Licencek</string> <string name="tab_licenses">Licencek</string>
<string name="app_description">Szabad, egyszerű közvetítésnézés Androidon.</string> <string name="app_description">Szabad, egyszerű közvetítésnézés Androidon.</string>
<string name="contribution_title">Közreműködés</string> <string name="contribution_title">Közreműködés</string>
<string name="contribution_encouragement">Legyen ötleted a fordítással, a dizájnnal, a forráskód tisztításával vagy egy komolyabb átszervezésével kapcsolatban, bármilyen segítséget szívesen fogadunk. Minél több minden készül el, annál jobb lesz!</string> <string name="contribution_encouragement">Akár fordítással, tervezési változtatásokkal, kódtisztítással, vagy valódi nehéz kódváltoztatással kapcsolatos ötletei vannak, bármilyen segítséget szívesen fogadunk. Minél több minden készül el, annál jobb lesz!</string>
<string name="view_on_github">Megtekintés a GitHubon</string> <string name="view_on_github">Megtekintés a GitHubon</string>
<string name="donation_title">Adományozás</string> <string name="donation_title">Adományozás</string>
<string name="donation_encouragement">A NewPipe alkalmazást önkéntesek fejlesztik a szabadidejükben, hogy a lehető legjobb felhasználói élményt nyújtsák. Járuljon hozzá, hogy a fejlesztők még jobbá tegyék alkalmazást, miközben egy csésze kávét szürcsölnek.</string> <string name="donation_encouragement">A NewPipe alkalmazást önkéntesek fejlesztik a szabadidejükben, hogy a lehető legjobb felhasználói élményt nyújtsák. Járuljon hozzá, hogy a fejlesztők még jobbá tegyék alkalmazást, miközben egy csésze kávét szürcsölnek.</string>
<string name="give_back">Hozzájárulás</string> <string name="give_back">Hozzájárulás</string>
<string name="website_title">Honlap</string> <string name="website_title">Weboldal</string>
<string name="website_encouragement">Látogassa meg a NewPipe honlapját további információkért és hírekért.</string> <string name="website_encouragement">Látogasson el a NewPipe weboldalára további információkért és hírekért.</string>
<string name="privacy_policy_title">A NewPipe adatvédelmi irányelvei</string> <string name="privacy_policy_title">A NewPipe adatvédelmi irányelvei</string>
<string name="privacy_policy_encouragement">A NewPipe projekt komolyan veszi az adatvédelmét. Az alkalmazás nem gyűjt semmilyen adatot a beleegyezése nélkül. <string name="privacy_policy_encouragement">A NewPipe projekt komolyan veszi az adatvédelmét. Az alkalmazás nem gyűjt semmilyen adatot a beleegyezése nélkül.
\nA NewPipe adatvédelmi irányelve részletesen elmagyarázza, mely adatok kerülnek elküldésre és tárolásra az alkalmazás összeomlásának jelentésekor.</string> \nA NewPipe adatvédelmi irányelve részletesen elmagyarázza, mely adatok kerülnek elküldésre és tárolásra az alkalmazás összeomlásának jelentésekor.</string>
@ -270,23 +267,10 @@
<string name="export_ongoing">Exportálás…</string> <string name="export_ongoing">Exportálás…</string>
<string name="import_file_title">Fájl importálása</string> <string name="import_file_title">Fájl importálása</string>
<string name="previous_export">Előző exportálás</string> <string name="previous_export">Előző exportálás</string>
<string name="subscriptions_import_unsuccessful">A feliratkozások importálása nem sikerült</string> <string name="subscriptions_import_unsuccessful">A feliratkozások importálása sikertelen</string>
<string name="subscriptions_export_unsuccessful">A feliratkozások exportálása nem sikerült</string> <string name="subscriptions_export_unsuccessful">A feliratkozások exportálása sikertelen</string>
<string name="import_youtube_instructions">YouTube feliratkozások importálása a Google Takeoutból: <string name="import_youtube_instructions">YouTube feliratkozások importálása a Google Takeoutból: \n \n1. Navigáljon erre az oldalra: %1$s \n2. Jelentkezzen be, ha kérik \n3. Kattintson „Az összes adatot tartalmazza” gombra, majd a „Kijelölések megszüntetése” gombra, majd válassza ki a „feliratkozások” lehetőséget és kattintson az „OK” gombra \n4. Kattintson a „Következő lépés”, majd az \"Exportálás indítása” gombra \n5. Kattintson a „Letöltés” gombra, amikor megjelenik, \n6. Kattintson a lenti FÁJL IMPORTÁLÁSA gombra, és válassza ki a letöltött ZIP-fájlt \n7. [Ha a ZIP-fájl importálása nem sikerül] Bontsa ki a .csv fájlt (általában: „YouTube és YouTube Music/feliratkozások/feliratkozások.csv”), majd kattintson lent a FÁJL IMPORTÁLÁSA gombra, és válassza az exportált CSV-fájlt</string>
\n <string name="import_soundcloud_instructions">SoundCloud-profil importálása a webcím vagy az azonosítójának begépelésével: \n \n1. A webböngészőben engedélyezze az „asztali módot” (az oldal nem érhető el mobileszközökön) \n2. Navigáljon a következő webcímre: %1$s \n3. Jelentkezzen be, ha kéri \n4. Másolja ki a profil webcímét, ahova át lett irányítva.</string>
\n1. Navigáljon erre az oldalra: %1$s
\n2. Jelentkezzen be, ha kérik
\n3. Kattintson „Az összes adatot tartalmazza” gombra, majd a „Kijelölések megszüntetése” gombra, majd válassza ki a „feliratkozások” lehetőséget és kattintson az „OK” gombra
\n4. Kattintson a „Következő lépés”, majd az \"Exportálás indítása” gombra
\n5. Kattintson a „Letöltés” gombra, amikor megjelenik,
\n6. Kattintson a lenti FÁJL IMPORTÁLÁSA gombra, és válassza ki a letöltött ZIP-fájlt
\n7. [Ha a ZIP-fájl importálása nem sikerül] Bontsa ki a .csv fájlt (általában: „YouTube és YouTube Music/feliratkozások/feliratkozások.csv\"), majd kattintson lent a FÁJL IMPORTÁLÁSA gombra, és válassza az exportált CSV-fájlt</string>
<string name="import_soundcloud_instructions">SoundCloud-profil importálása a webcím vagy az azonosítójának begépelésével:
\n
\n1. A webböngészőben engedélyezze az „asztali módot” (az oldal nem érhető el mobileszközökön)
\n2. Navigáljon erre a webcímre: %1$s
\n3. Jelentkezzen be, ha kéri
\n4. Másolja ki a profil webcímét, ahova át lett irányítva.</string>
<string name="import_soundcloud_instructions_hint">saját azonosítója, soundcloud.com/azonosító</string> <string name="import_soundcloud_instructions_hint">saját azonosítója, soundcloud.com/azonosító</string>
<string name="import_network_expensive_warning">Ez a művelet adatforgalom-igényes lehet. <string name="import_network_expensive_warning">Ez a művelet adatforgalom-igényes lehet.
\n \n
@ -305,11 +289,11 @@
<string name="minimize_on_exit_background_description">Lejátszás folytatása a háttérben</string> <string name="minimize_on_exit_background_description">Lejátszás folytatása a háttérben</string>
<string name="minimize_on_exit_popup_description">Lejátszás folytatása felugró ablakban</string> <string name="minimize_on_exit_popup_description">Lejátszás folytatása felugró ablakban</string>
<string name="resume_on_audio_focus_gain_title">Lejátszás folytatása</string> <string name="resume_on_audio_focus_gain_title">Lejátszás folytatása</string>
<string name="show_hold_to_append_title">A „Tartsa lenyomva a sorba állításhoz\" tipp megjelenítése</string> <string name="show_hold_to_append_title">A „Tartsa lenyomva a sorba állításhoz tipp megjelenítése</string>
<string name="unsubscribe">Leiratkozás</string> <string name="unsubscribe">Leiratkozás</string>
<string name="tab_choose">Válasszon lapot</string> <string name="tab_choose">Válasszon lapot</string>
<string name="show_comments_title">Megjegyzések megjelenítése</string> <string name="show_comments_title">Hozzászólások megjelenítése</string>
<string name="show_comments_summary">Kapcsolja ki a megjegyzések elrejtéséhez</string> <string name="show_comments_summary">Kapcsolja ki a hozzászólások elrejtéséhez</string>
<string name="default_content_country_title">Tartalom alapértelmezett országa</string> <string name="default_content_country_title">Tartalom alapértelmezett országa</string>
<string name="switch_to_main">Folytatás főnézetben</string> <string name="switch_to_main">Folytatás főnézetben</string>
<string name="dismiss">Eltüntetés</string> <string name="dismiss">Eltüntetés</string>
@ -350,7 +334,7 @@
<string name="error_progress_lost">Az előrehaladás elveszett, mert a fájlt törölték</string> <string name="error_progress_lost">Az előrehaladás elveszett, mert a fájlt törölték</string>
<string name="error_insufficient_storage_left">Nincs hely az eszközön</string> <string name="error_insufficient_storage_left">Nincs hely az eszközön</string>
<string name="error_postprocessing_stopped">A NewPipe leállt a fájl feldolgozása közben</string> <string name="error_postprocessing_stopped">A NewPipe leállt a fájl feldolgozása közben</string>
<string name="error_postprocessing_failed">Utófeldolgozás sikertelen</string> <string name="error_postprocessing_failed">Az utófeldolgozás sikertelen</string>
<string name="error_http_not_found">Nincs talalat</string> <string name="error_http_not_found">Nincs talalat</string>
<string name="error_http_unsupported_range">A kiszolgáló nem fogad többszálú letöltést, próbálkozzon újra ezzel: @string/msg_threads = 1</string> <string name="error_http_unsupported_range">A kiszolgáló nem fogad többszálú letöltést, próbálkozzon újra ezzel: @string/msg_threads = 1</string>
<string name="error_http_no_content">A kiszolgáló nem küld adatokat</string> <string name="error_http_no_content">A kiszolgáló nem küld adatokat</string>
@ -366,7 +350,7 @@
<string name="overwrite_unrelated_warning">Ilyen névű fájl már létezik</string> <string name="overwrite_unrelated_warning">Ilyen névű fájl már létezik</string>
<string name="overwrite">Felülírás</string> <string name="overwrite">Felülírás</string>
<string name="generate_unique_name">Egyedi név előállítása</string> <string name="generate_unique_name">Egyedi név előállítása</string>
<string name="download_failed">Letöltés sikertelen</string> <string name="download_failed">A letöltés sikertelen</string>
<string name="recovering">helyrehozás</string> <string name="recovering">helyrehozás</string>
<string name="post_processing">utófeldolgozás</string> <string name="post_processing">utófeldolgozás</string>
<string name="queued">sorba állítva</string> <string name="queued">sorba állítva</string>
@ -398,10 +382,9 @@
<string name="auto_queue_toggle">Automatikus sorba állítás</string> <string name="auto_queue_toggle">Automatikus sorba állítás</string>
<string name="show_description_summary">Kapcsolja ki, hogy elrejtse a videó leírását és a további információkat</string> <string name="show_description_summary">Kapcsolja ki, hogy elrejtse a videó leírását és a további információkat</string>
<string name="restore_defaults_confirmation">Visszaállítja az alapértelmezéseket\?</string> <string name="restore_defaults_confirmation">Visszaállítja az alapértelmezéseket\?</string>
<string name="restricted_video_no_stream">Ez a videó korhatáros. <string name="restricted_video_no_stream">Ez a videó korhatáros. \nAz új, korhatáros videókkal kapcsolatos YouTube irányelvek miatt a NewPipe nem férhet hozzá a videófolyamokhoz, így nem tudja lejátszani.</string>
\nAz új, korhatáros videókkal kapcsolatos YouTube irányelvek miatt a NewPipe nem férhet hozzá a videóhoz, így nem tudja lejátszani.</string>
<string name="description_tab_description">Leírás</string> <string name="description_tab_description">Leírás</string>
<string name="comments_tab_description">Megjegyzések</string> <string name="comments_tab_description">Hozzászólások</string>
<string name="copy_for_github">Formázott jelentés másolása</string> <string name="copy_for_github">Formázott jelentés másolása</string>
<string name="permission_display_over_apps">Adjon engedélyt a más alkalmazások feletti megjelenéshez</string> <string name="permission_display_over_apps">Adjon engedélyt a más alkalmazások feletti megjelenéshez</string>
<string name="no_playlist_bookmarked_yet">Még nincs könyvjelző lejátszási listához</string> <string name="no_playlist_bookmarked_yet">Még nincs könyvjelző lejátszási listához</string>
@ -421,8 +404,7 @@
<string name="new_seek_duration_toast">Az ExoPlayer korlátai miatt az előre- és visszatekerés időtartama %d másodpercre lett állítva</string> <string name="new_seek_duration_toast">Az ExoPlayer korlátai miatt az előre- és visszatekerés időtartama %d másodpercre lett állítva</string>
<string name="feed_groups_header_title">Csatornacsoportok</string> <string name="feed_groups_header_title">Csatornacsoportok</string>
<string name="systems_language">Rendszer alapértelmezése</string> <string name="systems_language">Rendszer alapértelmezése</string>
<string name="start_accept_privacy_policy">Az Általános adatvédelmi rendeletnek (GDPR) való megfelelés érdekében felhívjuk figyelmét a NewPipe adatvédelmi nyilatkozatára. Olvassa el figyelmesen. <string name="start_accept_privacy_policy">Az Általános adatvédelmi rendeletnek (GDPR) való megfelelés érdekében felhívjuk figyelmét a NewPipe adatvédelmi irányelveire. Olvassa el figyelmesen. \nEl kell fogadnia, ha hibajelentést szeretne küldeni.</string>
\nEl kell fogadnia, ha hibajelentést szeretne küldeni.</string>
<string name="crash_the_app">Alkalmazás összeomlasztása</string> <string name="crash_the_app">Alkalmazás összeomlasztása</string>
<string name="show_memory_leaks">Memóriaszivárgások megjelenítése</string> <string name="show_memory_leaks">Memóriaszivárgások megjelenítése</string>
<string name="enable_leak_canary_summary">A memóriaszivárgás-monitorozás az alkalmazás megállását okozhatja, amíg a dinamikus memória mentése folyik</string> <string name="enable_leak_canary_summary">A memóriaszivárgás-monitorozás az alkalmazás megállását okozhatja, amíg a dinamikus memória mentése folyik</string>
@ -443,8 +425,7 @@
<string name="no_one_watching">Senki sem nézi</string> <string name="no_one_watching">Senki sem nézi</string>
<string name="subscribers_count_not_available">A feliratkozók száma nem érhető el</string> <string name="subscribers_count_not_available">A feliratkozók száma nem érhető el</string>
<string name="local">Helyi</string> <string name="local">Helyi</string>
<string name="localization_changes_requires_app_restart">A nyelv az alkalmazás újraindításakor fog megváltozni</string> <string name="error_unable_to_load_comments">A megjegyzések betöltése sikertelen</string>
<string name="error_unable_to_load_comments">Megjegyzések betöltése sikertelen</string>
<string name="select_a_playlist">Válasszon egy lejátszási listát</string> <string name="select_a_playlist">Válasszon egy lejátszási listát</string>
<string name="autoplay_summary">Lejátszás automatikus indítása — %s</string> <string name="autoplay_summary">Lejátszás automatikus indítása — %s</string>
<string name="playback_speed_control">Lejátszás sebességének beállítása</string> <string name="playback_speed_control">Lejátszás sebességének beállítása</string>
@ -458,7 +439,7 @@
<string name="copyright">© %1$s %2$s, %3$s licenc alatt</string> <string name="copyright">© %1$s %2$s, %3$s licenc alatt</string>
<string name="title_licenses">Harmadik féltől származó licencek</string> <string name="title_licenses">Harmadik féltől származó licencek</string>
<string name="done">Kész</string> <string name="done">Kész</string>
<string name="no_comments">Nincs megjegyzés</string> <string name="no_comments">Nincsenek hozzászólások</string>
<string name="infinite_videos">∞ videó</string> <string name="infinite_videos">∞ videó</string>
<string name="more_than_100_videos">100+ videó</string> <string name="more_than_100_videos">100+ videó</string>
<string name="error_report_open_issue_button_text">Jelentés a GitHubon</string> <string name="error_report_open_issue_button_text">Jelentés a GitHubon</string>
@ -468,7 +449,7 @@
<string name="clear_cookie_title">reCAPTCHA sütik törlése</string> <string name="clear_cookie_title">reCAPTCHA sütik törlése</string>
<string name="artists">Előadók</string> <string name="artists">Előadók</string>
<string name="albums">Albumok</string> <string name="albums">Albumok</string>
<string name="songs">Számok</string> <string name="songs">Dalok</string>
<string name="events">Események</string> <string name="events">Események</string>
<string name="videos_string">Videók</string> <string name="videos_string">Videók</string>
<string name="restricted_video">Ez a videó korhatáros. <string name="restricted_video">Ez a videó korhatáros.
@ -516,7 +497,7 @@
<string name="content_not_supported">Ezt a tartalmat még nem támogatja a NewPipe. <string name="content_not_supported">Ezt a tartalmat még nem támogatja a NewPipe.
\n \n
\nRemélhetőleg egy következő verzióban már támogatott lesz.</string> \nRemélhetőleg egy következő verzióban már támogatott lesz.</string>
<string name="no_app_to_open_intent">Nincs a készülékén olyan alkalmazás, amely meg tudja ezt nyitni</string> <string name="no_app_to_open_intent">Az eszközön nincs olyan alkalmazás, amely meg tudja ezt nyitni</string>
<string name="youtube_music_premium_content">Ez a videó csak YouTube Music Prémium előfizetők számára érhető el, így nem tekinthető meg és nem tölthető le a NewPipe-pal.</string> <string name="youtube_music_premium_content">Ez a videó csak YouTube Music Prémium előfizetők számára érhető el, így nem tekinthető meg és nem tölthető le a NewPipe-pal.</string>
<string name="auto_device_theme_title">Automatikus (rendszertéma)</string> <string name="auto_device_theme_title">Automatikus (rendszertéma)</string>
<string name="paid_content">Ez a tartalom csak előfizetőknek érhető el, nem tekinthető meg és nem tölthető le a NewPipe-pal.</string> <string name="paid_content">Ez a tartalom csak előfizetőknek érhető el, nem tekinthető meg és nem tölthető le a NewPipe-pal.</string>
@ -536,7 +517,7 @@
<string name="youtube_restricted_mode_enabled_summary">A YouTube biztosít egy „Korlátozott módot”, amely elrejti a lehetséges felnőtteknek szóló tartalmat</string> <string name="youtube_restricted_mode_enabled_summary">A YouTube biztosít egy „Korlátozott módot”, amely elrejti a lehetséges felnőtteknek szóló tartalmat</string>
<string name="youtube_restricted_mode_enabled_title">A YouTube „Korlátozott mód” bekapcsolása</string> <string name="youtube_restricted_mode_enabled_title">A YouTube „Korlátozott mód” bekapcsolása</string>
<string name="peertube_instance_add_exists">A példány már létezik</string> <string name="peertube_instance_add_exists">A példány már létezik</string>
<string name="peertube_instance_add_fail">A példány érvényesítése nem sikerült</string> <string name="peertube_instance_add_fail">A példány érvényesítése sikertelen</string>
<string name="peertube_instance_add_help">Adja meg a példány webcímét</string> <string name="peertube_instance_add_help">Adja meg a példány webcímét</string>
<string name="peertube_instance_add_title">Példány hozzáadása</string> <string name="peertube_instance_add_title">Példány hozzáadása</string>
<string name="peertube_instance_url_help">Találjon Önnek tetsző példányokat itt: %s</string> <string name="peertube_instance_url_help">Találjon Önnek tetsző példányokat itt: %s</string>
@ -562,7 +543,7 @@
<string name="start_main_player_fullscreen_title">A fő lejátszó teljes képernyős indítása</string> <string name="start_main_player_fullscreen_title">A fő lejátszó teljes képernyős indítása</string>
<string name="start_main_player_fullscreen_summary">A videókat ne a kis lejátszóban indítsa el, hanem kapcsolja be a teljes képernyős módot, ha az automatikus forgatás zárolva van. Továbbra is elérheti a kis lejátszót, ha kilép a teljes képernyőből</string> <string name="start_main_player_fullscreen_summary">A videókat ne a kis lejátszóban indítsa el, hanem kapcsolja be a teljes képernyős módot, ha az automatikus forgatás zárolva van. Továbbra is elérheti a kis lejátszót, ha kilép a teljes képernyőből</string>
<string name="drawer_header_description">Szolgáltatás be/ki, jelenleg kiválasztott:</string> <string name="drawer_header_description">Szolgáltatás be/ki, jelenleg kiválasztott:</string>
<string name="comments_are_disabled">A megjegyzések ki vannak kapcsolva</string> <string name="comments_are_disabled">A hozzászólások ki vannak kapcsolva</string>
<string name="main_page_content_swipe_remove">Húzza oldalra az elemeket az eltávolításukhoz</string> <string name="main_page_content_swipe_remove">Húzza oldalra az elemeket az eltávolításukhoz</string>
<string name="enqueue_next_stream">A következő sorba állítása</string> <string name="enqueue_next_stream">A következő sorba állítása</string>
<string name="enqueued_next">A következő sorba állítva</string> <string name="enqueued_next">A következő sorba állítva</string>
@ -572,29 +553,17 @@
<string name="disable_media_tunneling_summary">Tiltsa le a médiacsatornázást, ha fekete képernyőt vagy akadozást tapasztal videólejátszáskor.</string> <string name="disable_media_tunneling_summary">Tiltsa le a médiacsatornázást, ha fekete képernyőt vagy akadozást tapasztal videólejátszáskor.</string>
<string name="downloads_storage_ask_summary_no_saf_notice">Minden letöltésnél meg fogja kérdezni, hogy hova mentse el</string> <string name="downloads_storage_ask_summary_no_saf_notice">Minden letöltésnél meg fogja kérdezni, hogy hova mentse el</string>
<string name="choose_instance_prompt">Válasszon egy példányt</string> <string name="choose_instance_prompt">Válasszon egy példányt</string>
<string name="feed_oldest_subscription_update">Lista legutóbbi frissítése: %s</string> <string name="feed_oldest_subscription_update">Hírfolyam utoljára frissítve: %s</string>
<string name="feed_notification_loading">Lista betöltése…</string> <string name="feed_notification_loading">Hírfolyam betöltése…</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Csak a nem csoportosított feliratkozások megjelenítése</string> <string name="feed_group_show_only_ungrouped_subscriptions">Csak a nem csoportosított feliratkozások megjelenítése</string>
<string name="settings_category_feed_title">Lista</string> <string name="settings_category_feed_title">Hírfolyam</string>
<string name="feed_update_threshold_title">Lista frissítési küszöb</string> <string name="feed_update_threshold_title">Hírfolyam frissítési küszöb</string>
<string name="feed_update_threshold_summary">A legutóbbi frissítés óta eltelt idő, ami után a feliratkozás elavultnak számít %s</string> <string name="feed_update_threshold_summary">A legutóbbi frissítés óta eltelt idő, ami után a feliratkozás elavultnak számít %s</string>
<string name="feed_load_error_terminated">A szerző fiókját eltávolították. <string name="feed_load_error_terminated">A szerző fiókját eltávolították. \nA NewPipe nem fogja tudni betölteni ezt a hírfolyamot a jövőben. \nLeiratkozik erről a csatornáról?</string>
\nA NewPipe nem fogja tudni betölteni ezt a listát a jövőben. <string name="feed_load_error_fast_unknown">A gyors hírfolyammód nem szolgáltat több információt.</string>
\nLeiratkozik erről a csatornáról\?</string> <string name="feed_use_dedicated_fetch_method_title">Lekérés egy dedikált hírfolyamból, ha lehetséges</string>
<string name="feed_load_error_fast_unknown">A gyors listamód nem ad ennél több információt.</string>
<string name="feed_use_dedicated_fetch_method_title">Lekérés egy dedikált listából, ha lehetséges</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Gyors mód engedélyezése</string> <string name="feed_use_dedicated_fetch_method_enable_button">Gyors mód engedélyezése</string>
<string name="feed_use_dedicated_fetch_method_help_text">Úgy gondolja, hogy a lista betöltése lassú\? Ha így van, akkor próbálja engedélyezni a gyors betöltést (ezt a beállításokban változtathatja meg, vagy a lenti gomb megnyomásával). <string name="feed_use_dedicated_fetch_method_help_text">Úgy gondolja, hogy a hírfolyam betöltése lassú? Ha így van, akkor próbálja engedélyezni a gyors betöltést (ezt a beállításokban változtathatja meg, vagy a lenti gomb megnyomásával). \n \nA NewPipe két hírfolyam betöltési stratégiát kínál: \n• A teljes feliratkozott csatorna lekérése, amely lassú, de teljes. \n• Egy dedikált szolgáltatási végpont, amely gyors, de általában nem teljes. \n \nA különbség a kettő között az, hogy a gyorsból általában hiányoznak egyes információk, mint az elem hossza vagy a típusa (nem lehet megkülönböztetni az élő videókat a normálaktól), valamint kevesebb elemet adhat vissza. \n \nA YouTube például egy olyan szolgáltatás, amely ezt a gyors módot RSS hírcsatornával kínálja. \n \nÍgy a választása azon múlik, hogy melyiket tartja fontosabbnak: a sebességet vagy a pontos információkat.</string>
\n
\nA NewPipe két listabetöltési stratégiát kínál:
\n• A teljes feliratkozott csatorna lekérése, amely lassú, de teljes.
\n• Egy dedikált szolgáltatási végpont, amely gyors, de általában nem teljes.
\n
\nA különbség a kettő között az, hogy a gyorsból általában hiányoznak egyes információk, mint az elem hossza vagy a típusa (nem lehet megkülönböztetni az élő videókat a normálaktól), valamint kevesebb elemet adhat vissza.
\n
\nA YouTube például egy olyan szolgáltatás, amely ezt a gyors módot RSS hírcsatornával kínálja.
\n
\nÍgy a választása azon múlik, hogy melyiket tartja fontosabbnak: a sebességet vagy a pontos információkat.</string>
<string name="detail_sub_channel_thumbnail_view_description">Csatorna profilképének bélyegképe</string> <string name="detail_sub_channel_thumbnail_view_description">Csatorna profilképének bélyegképe</string>
<string name="recent">Legutóbbi</string> <string name="recent">Legutóbbi</string>
<string name="featured">Kiemelt</string> <string name="featured">Kiemelt</string>
@ -612,25 +581,25 @@
<item quantity="one">Letöltés befejezve</item> <item quantity="one">Letöltés befejezve</item>
<item quantity="other">%s letöltés befejezve</item> <item quantity="other">%s letöltés befejezve</item>
</plurals> </plurals>
<string name="feed_processing_message">Lista feldolgozása…</string> <string name="feed_processing_message">Hírfolyam feldolgozása…</string>
<string name="feed_use_dedicated_fetch_method_summary">Egyes szolgáltatásoknál érhető el, általában sokkal gyorsabb, és korlátozott számú elemet adhat vissza, gyakran hiányos információkkal (például nincs hossz, elemtípus, vagy élő videó állapot)</string> <string name="feed_use_dedicated_fetch_method_summary">Egyes szolgáltatásoknál érhető el, általában sokkal gyorsabb, és korlátozott számú elemet adhat vissza, gyakran hiányos információkkal (például nincs hossz, elemtípus, vagy élő videó állapot)</string>
<string name="account_terminated">Fiók eltávolítva</string> <string name="account_terminated">Fiók eltávolítva</string>
<string name="mark_as_watched">Megjelölés megnézettként</string> <string name="mark_as_watched">Megjelölés megnézettként</string>
<string name="no_dir_yet">Még nincs letöltési mappa beállítva, válassza ki az alapértelmezett letöltési mappát most</string> <string name="no_dir_yet">Még nincs letöltési mappa beállítva, válassza ki az alapértelmezett letöltési mappát most</string>
<string name="seekbar_preview_thumbnail_title">Tekerősáv bélyegkép-előnézete</string> <string name="seekbar_preview_thumbnail_title">Tekerősáv bélyegkép-előnézete</string>
<string name="high_quality_larger">Magas minőségű (nagyobb)</string> <string name="high_quality_larger">Magas minőségű (nagyobb)</string>
<string name="feed_load_error">Hiba a lista betöltésekor</string> <string name="feed_load_error">Hiba a hírfolyam betöltésekor</string>
<string name="metadata_language">Nyelv</string> <string name="metadata_language">Nyelv</string>
<string name="metadata_support">Támogatás</string> <string name="metadata_support">Támogatás</string>
<string name="open_website_license">Weboldal megnyitása</string> <string name="open_website_license">Weboldal megnyitása</string>
<string name="tablet_mode_title">Táblagép mód</string> <string name="tablet_mode_title">Táblagép mód</string>
<string name="downloads_storage_use_saf_summary_api_29">Az Android 10-től kezdve, csak a „Storage Access Framework” támogatott</string> <string name="downloads_storage_use_saf_summary_api_29">Az Android 10-től kezdve, csak a „Storage Access Framework” támogatott</string>
<string name="feed_new_items">Új listaelemek</string> <string name="feed_new_items">Új hírfolyamelemek</string>
<string name="metadata_privacy_private">Privát</string> <string name="metadata_privacy_private">Privát</string>
<string name="metadata_privacy_internal">Belső</string> <string name="metadata_privacy_internal">Belső</string>
<string name="detail_heart_img_view_description">Készítő által szívecskézve</string> <string name="detail_heart_img_view_description">Készítő által szívecskézve</string>
<string name="on">Be</string> <string name="on">Be</string>
<string name="feed_load_error_account_info">A(z) „%s” listája nem tölthető be.</string> <string name="feed_load_error_account_info">A(z) „%s” hírfolyam nem tölthető be.</string>
<string name="soundcloud_go_plus_content">Ez egy SoundCloud Go+ szám, legalábbis az Ön országában, így nem játszható le vagy tölthető le a NewPipe-pal.</string> <string name="soundcloud_go_plus_content">Ez egy SoundCloud Go+ szám, legalábbis az Ön országában, így nem játszható le vagy tölthető le a NewPipe-pal.</string>
<string name="show_meta_info_summary">Kapcsolja ki, hogy elrejtse a metainformációs dobozokat, melyek további információkat tartalmaznak a közvetítés létrehozójáról, annak tartalmáról vagy egy keresési kérésről</string> <string name="show_meta_info_summary">Kapcsolja ki, hogy elrejtse a metainformációs dobozokat, melyek további információkat tartalmaznak a közvetítés létrehozójáról, annak tartalmáról vagy egy keresési kérésről</string>
<string name="error_report_channel_name">Hibajelentési értesítés</string> <string name="error_report_channel_name">Hibajelentési értesítés</string>
@ -668,9 +637,9 @@
<item quantity="one">%1$s letöltés törölve</item> <item quantity="one">%1$s letöltés törölve</item>
<item quantity="other">%1$s letöltés törölve</item> <item quantity="other">%1$s letöltés törölve</item>
</plurals> </plurals>
<string name="detail_pinned_comment_view_description">Rögzített megjegyzés</string> <string name="detail_pinned_comment_view_description">Kitűzött hozzászólás</string>
<string name="leak_canary_not_available">LeakCanary nem elérhető</string> <string name="leak_canary_not_available">LeakCanary nem elérhető</string>
<string name="settings_category_player_notification_title">Lejátszó értesítés</string> <string name="settings_category_player_notification_title">Lejátszási értesítés</string>
<string name="progressive_load_interval_summary">Módosítsa a progresszív tartalmak betöltési intervallumának méretét (jelenleg %s). Az alacsonyabb érték felgyorsíthatja a kezdeti betöltésüket.</string> <string name="progressive_load_interval_summary">Módosítsa a progresszív tartalmak betöltési intervallumának méretét (jelenleg %s). Az alacsonyabb érték felgyorsíthatja a kezdeti betöltésüket.</string>
<string name="settings_category_player_notification_summary">Jelenleg játszott közvetítés értesítésének testreszabása</string> <string name="settings_category_player_notification_summary">Jelenleg játszott közvetítés értesítésének testreszabása</string>
<string name="notifications">Értesítések</string> <string name="notifications">Értesítések</string>
@ -678,14 +647,14 @@
<string name="streams_notification_channel_description">Értesítések új élő közvetítésekről a feliratkozott csatornák esetén</string> <string name="streams_notification_channel_description">Értesítések új élő közvetítésekről a feliratkozott csatornák esetén</string>
<string name="loading_stream_details">Közvetítés részleteinek betöltése.…</string> <string name="loading_stream_details">Közvetítés részleteinek betöltése.…</string>
<string name="check_new_streams">Keressen új élő közvetítést</string> <string name="check_new_streams">Keressen új élő közvetítést</string>
<string name="enable_streams_notifications_title">Új közvetítésértesítések</string> <string name="enable_streams_notifications_title">Új közvetítések értesítései</string>
<string name="enable_streams_notifications_summary">Értesítésen új élő közvetítés esetén a feliratkozott csatornákhoz</string> <string name="enable_streams_notifications_summary">Értesítésen új élő közvetítés esetén a feliratkozott csatornákhoz</string>
<string name="streams_notifications_interval_title">Ellenőrzési gyakoriság</string> <string name="streams_notifications_interval_title">Ellenőrzési gyakoriság</string>
<string name="streams_notifications_network_title">Szükséges hálózati kapcsolat</string> <string name="streams_notifications_network_title">Szükséges hálózati kapcsolat</string>
<string name="any_network">Bármilyen hálózat</string> <string name="any_network">Bármilyen hálózat</string>
<string name="delete_downloaded_files_confirm">Törli az összes letöltött fájlt a lemezről\?</string> <string name="delete_downloaded_files_confirm">Törli az összes letöltött fájlt a lemezről\?</string>
<string name="get_notified">Értesítsen</string> <string name="get_notified">Értesítsen</string>
<string name="notifications_disabled">Értesítéstek kikapcsolva</string> <string name="notifications_disabled">Az értesítések le vannak tiltva</string>
<string name="progressive_load_interval_title">Lejátszás betöltési intervallumának mérete</string> <string name="progressive_load_interval_title">Lejátszás betöltési intervallumának mérete</string>
<string name="percent">Százaléka</string> <string name="percent">Százaléka</string>
<plurals name="new_streams"> <plurals name="new_streams">
@ -693,14 +662,14 @@
<item quantity="other">%s új elő közvetítés</item> <item quantity="other">%s új elő közvetítés</item>
</plurals> </plurals>
<string name="progressive_load_interval_exoplayer_default">ExoPlayer alapértelmezett</string> <string name="progressive_load_interval_exoplayer_default">ExoPlayer alapértelmezett</string>
<string name="you_successfully_subscribed">Feliratkoztál erre a csatornára</string> <string name="you_successfully_subscribed">Feliratkozott erre a csatornára</string>
<string name="enumeration_comma">,</string> <string name="enumeration_comma">,</string>
<string name="streams_not_yet_supported_removed">Azok az élő adások melyek nem támogatottak a letöltő által, rejtve vannak</string> <string name="streams_not_yet_supported_removed">Azok az élő adások melyek nem támogatottak a letöltő által, rejtve vannak</string>
<string name="selected_stream_external_player_not_supported">A választott élő adást nem lehet külső lejátszóval lejátszani</string> <string name="selected_stream_external_player_not_supported">A választott élő adást nem lehet külső lejátszóval lejátszani</string>
<string name="toggle_all">Összes váltása</string> <string name="toggle_all">Összes be/ki</string>
<string name="no_audio_streams_available_for_external_players">Külső lejátszók számára nem érhető el az hang csatorna</string> <string name="no_audio_streams_available_for_external_players">Külső lejátszók számára nem érhető el hangfolyam</string>
<string name="no_video_streams_available_for_external_players">Külső lejátszók számára nem érhető el videó</string> <string name="no_video_streams_available_for_external_players">Külső lejátszók számára nem érhető el videófolyamok</string>
<string name="select_quality_external_players">Válassz minőséget külső lejátszókhoz</string> <string name="select_quality_external_players">Válasszon minőséget a külső lejátszókhoz</string>
<string name="unknown_format">Ismeretlen formátum</string> <string name="unknown_format">Ismeretlen formátum</string>
<string name="unknown_quality">Ismeretlen minőség</string> <string name="unknown_quality">Ismeretlen minőség</string>
<string name="semitone">Félhang</string> <string name="semitone">Félhang</string>
@ -720,11 +689,11 @@
<string name="duplicate_in_playlist">A kiszürkített lejátszólisták már tartalmazzák ezt az elemet.</string> <string name="duplicate_in_playlist">A kiszürkített lejátszólisták már tartalmazzák ezt az elemet.</string>
<string name="unset_playlist_thumbnail">Állandó bélyegkép feloldása</string> <string name="unset_playlist_thumbnail">Állandó bélyegkép feloldása</string>
<string name="remove_duplicates">Ismétlődések eltávolítása</string> <string name="remove_duplicates">Ismétlődések eltávolítása</string>
<string name="feed_show_watched">Végignézve</string> <string name="feed_show_watched">Teljesen megtekintett</string>
<string name="feed_show_partially_watched">Részben megnézve</string> <string name="feed_show_partially_watched">Részben megtekintett</string>
<string name="card">Kártya</string> <string name="card">Kártya</string>
<string name="night_theme_available">Ez a beállítás csak a(z) %s téma esetén érhető el</string> <string name="night_theme_available">Ez a beállítás csak a(z) %s téma esetén érhető el</string>
<string name="ignore_hardware_media_buttons_title">Hardveresmédiagomb-események figyelmen kívül hagyása</string> <string name="ignore_hardware_media_buttons_title">Hardveres médialejátszó gombok eseményeinek figyelmen kívül hagyása</string>
<string name="feed_hide_streams_title">A következő közvetítések megjelenítése</string> <string name="feed_hide_streams_title">A következő közvetítések megjelenítése</string>
<string name="prefer_original_audio_summary">Az eredeti hangsáv választása, a nyelvtől függetlenül</string> <string name="prefer_original_audio_summary">Az eredeti hangsáv választása, a nyelvtől függetlenül</string>
<string name="prefer_descriptive_audio_summary">A látássérülteknek szóló leírást tartalmazó hangsáv választása, ha van ilyen</string> <string name="prefer_descriptive_audio_summary">A látássérülteknek szóló leírást tartalmazó hangsáv választása, ha van ilyen</string>
@ -741,9 +710,7 @@
<string name="use_exoplayer_decoder_fallback_title">Az ExoPlayer dekódoló tartalék funkciójának használata</string> <string name="use_exoplayer_decoder_fallback_title">Az ExoPlayer dekódoló tartalék funkciójának használata</string>
<string name="use_exoplayer_decoder_fallback_summary">Engedélyezze ezt a beállítást, ha dekóder előkészítési problémái vannak, ami alacsonyabb prioritású dekóderekre váltást okoz, ha az elsődleges dekóderek előkészítése sikertelen. Ez rosszabb lejátszási teljesítményt eredményezhet, mint az elsődleges dekóderek használata.</string> <string name="use_exoplayer_decoder_fallback_summary">Engedélyezze ezt a beállítást, ha dekóder előkészítési problémái vannak, ami alacsonyabb prioritású dekóderekre váltást okoz, ha az elsődleges dekóderek előkészítése sikertelen. Ez rosszabb lejátszási teljesítményt eredményezhet, mint az elsődleges dekóderek használata.</string>
<string name="always_use_exoplayer_set_output_surface_workaround_title">Kerülőmegoldás: mindig az ExoPlayer videokimeneti felületének használata</string> <string name="always_use_exoplayer_set_output_surface_workaround_title">Kerülőmegoldás: mindig az ExoPlayer videokimeneti felületének használata</string>
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Ez a kerülőmegoldás elengedi és újból előkészíti a videokodekeket, ha felületváltozás történik, ahelyett, hogy közvetlenül a kodeknél állítaná be a felületet. Ez már alapból használatban van egyes, az ezzel a problémával érintett eszközöknél, a beállításnak Android 6 vagy újabb esetén van hatása. <string name="always_use_exoplayer_set_output_surface_workaround_summary">Ez a kerülőmegoldás elengedi és újból előkészíti a videokodekeket, ha felületváltozás történik, ahelyett, hogy közvetlenül a kodeknél állítaná be a felületet. Ez már alapból használatban van egyes, az ezzel a problémával érintett eszközöknél, a beállításnak Android 6 vagy újabb esetén van hatása\n\nA beállítás bekapcsolása megakadályozhatja a lejátszási hibákat abban az esetben, ha átváltja a jelenlegi videolejátszót, vagy teljes képernyőre vált</string>
\n
\nA beállítás bekapcsolása megakadályozhatja a lejátszási hibákat, ha átváltja a jelenlegi videolejátszót, vagy teljes képernyőre vált.</string>
<string name="audio_track_name">%1$s %2$s</string> <string name="audio_track_name">%1$s %2$s</string>
<string name="audio_track_type_dubbed">szinkronizált</string> <string name="audio_track_type_dubbed">szinkronizált</string>
<string name="audio_track_type_descriptive">leíró</string> <string name="audio_track_type_descriptive">leíró</string>
@ -757,18 +724,18 @@
<string name="remove_duplicates_message">Eltávolítja az összes ismétlődő közvetítést ebből a lejátszólistáról\?</string> <string name="remove_duplicates_message">Eltávolítja az összes ismétlődő közvetítést ebből a lejátszólistáról\?</string>
<string name="audio_track_type_original">eredeti</string> <string name="audio_track_type_original">eredeti</string>
<string name="main_tabs_position_title">Kezdőlap pozíciója</string> <string name="main_tabs_position_title">Kezdőlap pozíciója</string>
<string name="disable_media_tunneling_automatic_info">A médiacsatornázás alapértelmezés szerint le van tiltva az Ön készülékén, mivel az Ön készülékmodellje nem támogatja azt.</string> <string name="disable_media_tunneling_automatic_info">A médiacsatornázás alapértelmezés szerint le van tiltva a saját eszközén, mivel a saját eszközmodellje nem támogatja azt.</string>
<string name="main_tabs_position_summary">Kezdőlapválasztó alulra helyezése</string> <string name="main_tabs_position_summary">Kezdőlapválasztó alulra helyezése</string>
<string name="no_live_streams">Nincs élő közvetítés</string> <string name="no_live_streams">Nincs élő közvetítés</string>
<string name="no_streams">Nincs adatfolyam</string> <string name="no_streams">Nincs adatfolyam</string>
<string name="notification_actions_summary_android13">Az alábbi értesítési műveletek szerkesztéséhez koppintson rá. Az első három műveletet (lejátszás/szünet, előző és következő) a rendszer állítja be, és nem szabhatók testre.</string> <string name="notification_actions_summary_android13">Az alábbi értesítési műveletek szerkesztéséhez koppintson rá. Az első három műveletet (lejátszás/szünet, előző és következő) a rendszer állítja be, és nem szabhatók testre.</string>
<string name="feed_fetch_channel_tabs">Csatornalapok lekérése</string> <string name="feed_fetch_channel_tabs">Csatornalapok lekérése</string>
<string name="feed_fetch_channel_tabs_summary">A hírcsatorna frissítésekor lekérendő lapok. Ennek az opciónak nincs hatása, ha egy csatorna frissítése gyors módban történik.</string> <string name="feed_fetch_channel_tabs_summary">A hírfolyam frissítésekor lekérendő lapok. Ennek a beállításnak nincs hatása, ha egy csatorna frissítése gyors módban történik.</string>
<string name="metadata_thumbnails">Miniatűrök</string> <string name="metadata_thumbnails">Miniatűrök</string>
<string name="metadata_uploader_avatars">Feltöltő avatarjai</string> <string name="metadata_uploader_avatars">Feltöltő profilképei</string>
<string name="metadata_subchannel_avatars">Alcsatorna avatarok</string> <string name="metadata_subchannel_avatars">Alcsatorna profilképei</string>
<string name="metadata_avatars">Avatarok</string> <string name="metadata_avatars">Profilképek</string>
<string name="metadata_banners">Bannerek</string> <string name="metadata_banners">Borítóképek</string>
<string name="metadata_subscribers">Feliratkozók</string> <string name="metadata_subscribers">Feliratkozók</string>
<string name="channel_tab_channels">Csatornák</string> <string name="channel_tab_channels">Csatornák</string>
<string name="channel_tab_playlists">Lejátszási listák</string> <string name="channel_tab_playlists">Lejátszási listák</string>
@ -777,13 +744,13 @@
<string name="show_channel_tabs">Csatorna fülek</string> <string name="show_channel_tabs">Csatorna fülek</string>
<string name="show_channel_tabs_summary">Milyen lapok jelennek meg a csatornaoldalakon</string> <string name="show_channel_tabs_summary">Milyen lapok jelennek meg a csatornaoldalakon</string>
<string name="open_play_queue">Lejátszási sor megnyitása</string> <string name="open_play_queue">Lejátszási sor megnyitása</string>
<string name="toggle_screen_orientation">Képernyő tájolásának váltása</string> <string name="toggle_screen_orientation">Képernyő tájolás be/ki</string>
<string name="toggle_fullscreen">Teljes képernyőre váltás</string> <string name="toggle_fullscreen">Teljes képernyő be/ki</string>
<string name="next_stream">Következő közvetítés</string> <string name="next_stream">Következő közvetítés</string>
<string name="previous_stream">Előző közvetítés</string> <string name="previous_stream">Előző közvetítés</string>
<string name="play">Lejátszás</string> <string name="play">Lejátszás</string>
<string name="replay">Visszajátszás</string> <string name="replay">Visszajátszás</string>
<string name="more_options">További opciók</string> <string name="more_options">További lehetőségek</string>
<string name="duration">Időtartam</string> <string name="duration">Időtartam</string>
<string name="rewind">Visszatekerés</string> <string name="rewind">Visszatekerés</string>
<string name="forward">Előre</string> <string name="forward">Előre</string>
@ -808,7 +775,7 @@
<string name="share_playlist_with_list">Webcímlista megosztása</string> <string name="share_playlist_with_list">Webcímlista megosztása</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<string name="channel_tab_videos">Videók</string> <string name="channel_tab_videos">Videók</string>
<string name="channel_tab_tracks">Dalok</string> <string name="channel_tab_tracks">Zeneszámok</string>
<string name="channel_tab_shorts">Rövidek</string> <string name="channel_tab_shorts">Rövidek</string>
<string name="channel_tab_livestreams">Élő</string> <string name="channel_tab_livestreams">Élő</string>
<string name="error_insufficient_storage">Nincs elég szabad hely az eszközön</string> <string name="error_insufficient_storage">Nincs elég szabad hely az eszközön</string>
@ -822,6 +789,16 @@
<string name="reset_all_settings">Az összes beállítás visszaállítása elveti az összes preferált beállítást, és újraindítja az alkalmazást. <string name="reset_all_settings">Az összes beállítás visszaállítása elveti az összes preferált beállítást, és újraindítja az alkalmazást.
\n \n
\nBiztosan folytatja?</string> \nBiztosan folytatja?</string>
<string name="import_settings_vulnerable_format">Az importálandó exportban lévő beállítások sérülékeny formátumot használnak, amely a NewPipe 0.27.0-s verziója óta elavult. Győződjön meg arról, hogy megbízható forrásból importálja, és a jövőben csak a NewPipe 0.27.0-s vagy újabb verziójából származó exportokat használjon. A beállítások ebből a sérülékeny forrásból történő importálása hamarosan végleg el lesz távolítva, és a NewPipe régi verziói nem fogják tudni importálni az újabb verziókból származó exportokat.</string> <string name="import_settings_vulnerable_format">Az importálandó exportban lévő beállítások sérülékeny formátumot használnak, amely a NewPipe 0.27.0-ás verziója óta elavult. Győződjön meg arról, hogy megbízható forrásból importálja, és a jövőben csak a NewPipe 0.27.0-ás vagy újabb verziójából származó exportokat használjon. A beállítások ebből a sérülékeny forrásból történő importálása hamarosan végleg el lesz távolítva, és a NewPipe régi verziói nem fogják tudni importálni az újabb verziókból származó exportokat.</string>
<string name="audio_track_type_secondary">másodlagos</string> <string name="audio_track_type_secondary">másodlagos</string>
<string name="share_playlist_as_youtube_temporary_playlist">Megosztás YouTube ideiglenes lejátszási listaként</string>
<string name="tab_bookmarks_short">Lejátszási listák</string>
<string name="select_a_feed_group">Válasszon ki egy hírfolyamcsoportot</string>
<string name="no_feed_group_created_yet">Még nincs létrehozott hírfolyamcsoport</string>
<string name="feed_group_page_summary">Csatornacsoport-oldal</string>
<string name="search_with_service_name">Keresés %1$s</string>
<string name="search_with_service_name_and_filter">Keresés %1$s (%2$s)</string>
<string name="channel_tab_likes">Kedvelések</string>
<string name="migration_info_6_7_title">SoundCloud Top 50 oldal eltávolítva</string>
<string name="migration_info_6_7_message">A SoundCloud megszüntette az eredeti Top 50-es listákat. A megfelelő lap el lett távolítva a főoldalról.</string>
</resources> </resources>

View file

@ -82,9 +82,6 @@
<string name="recaptcha_request_toast">Meminta kode reCAPTCHA</string> <string name="recaptcha_request_toast">Meminta kode reCAPTCHA</string>
<string name="black_theme_title">Hitam</string> <string name="black_theme_title">Hitam</string>
<string name="all">Semua</string> <string name="all">Semua</string>
<string name="short_thousand">r</string>
<string name="short_million">J</string>
<string name="short_billion">T</string>
<string name="open_in_popup_mode">Buka pada mode sembulan</string> <string name="open_in_popup_mode">Buka pada mode sembulan</string>
<string name="msg_popup_permission">Izin ini dibutuhkan untuk <string name="msg_popup_permission">Izin ini dibutuhkan untuk
\nmembuka di mode sembul</string> \nmembuka di mode sembul</string>
@ -193,8 +190,8 @@
<string name="title_last_played">Terakhir Diputar</string> <string name="title_last_played">Terakhir Diputar</string>
<string name="title_most_played">Sering Diputar</string> <string name="title_most_played">Sering Diputar</string>
<string name="main_page_content">Konten halaman utama</string> <string name="main_page_content">Konten halaman utama</string>
<string name="blank_page_summary">Halaman Kosong</string> <string name="blank_page_summary">Halaman kosong</string>
<string name="kiosk_page_summary">Halaman Kedai</string> <string name="kiosk_page_summary">Halaman kiosk</string>
<string name="channel_page_summary">Halaman saluran</string> <string name="channel_page_summary">Halaman saluran</string>
<string name="select_a_channel">Pilih saluran</string> <string name="select_a_channel">Pilih saluran</string>
<string name="no_channel_subscribed_yet">Belum ada saluran langganan</string> <string name="no_channel_subscribed_yet">Belum ada saluran langganan</string>
@ -417,7 +414,6 @@
<plurals name="listening"> <plurals name="listening">
<item quantity="other">%s pendengar</item> <item quantity="other">%s pendengar</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">Bahasa yang diubah akan diterapkan setelah aplikasi dimulai ulang</string>
<string name="peertube_instance_url_title">Situs PeerTube</string> <string name="peertube_instance_url_title">Situs PeerTube</string>
<string name="peertube_instance_url_summary">Pilih situs PeerTube favorit Anda</string> <string name="peertube_instance_url_summary">Pilih situs PeerTube favorit Anda</string>
<string name="peertube_instance_url_help">Temukan situs yang Anda suka di %s</string> <string name="peertube_instance_url_help">Temukan situs yang Anda suka di %s</string>
@ -809,4 +805,14 @@
<string name="settings_category_backup_restore_title">Cadangkan dan pulihkan</string> <string name="settings_category_backup_restore_title">Cadangkan dan pulihkan</string>
<string name="import_settings_vulnerable_format">Pengaturan dalam ekspor yang diimpor menggunakan format rentan yang tidak digunakan lagi sejak NewPipe 0.27.0. Pastikan ekspor yang diimpor berasal dari sumber tepercaya, dan lebih memilih hanya menggunakan ekspor yang diperoleh dari NewPipe 0.27.0 atau yang lebih baru di masa mendatang. Dukungan untuk mengimpor pengaturan dalam format rentan ini akan segera dihapus sepenuhnya, dan NewPipe versi lama tidak akan dapat lagi mengimpor pengaturan ekspor dari versi baru.</string> <string name="import_settings_vulnerable_format">Pengaturan dalam ekspor yang diimpor menggunakan format rentan yang tidak digunakan lagi sejak NewPipe 0.27.0. Pastikan ekspor yang diimpor berasal dari sumber tepercaya, dan lebih memilih hanya menggunakan ekspor yang diperoleh dari NewPipe 0.27.0 atau yang lebih baru di masa mendatang. Dukungan untuk mengimpor pengaturan dalam format rentan ini akan segera dihapus sepenuhnya, dan NewPipe versi lama tidak akan dapat lagi mengimpor pengaturan ekspor dari versi baru.</string>
<string name="audio_track_type_secondary">sekunder</string> <string name="audio_track_type_secondary">sekunder</string>
<string name="share_playlist_as_youtube_temporary_playlist">Daftar putar</string>
<string name="tab_bookmarks_short">Daftar putar</string>
<string name="feed_group_page_summary">Halaman grup saluran</string>
<string name="no_feed_group_created_yet">Belum ada grup umpan yang dibuat</string>
<string name="select_a_feed_group">Pilih grup umpan</string>
<string name="search_with_service_name">Cari %1$s</string>
<string name="search_with_service_name_and_filter">Cari %1$s (%2$s)</string>
<string name="channel_tab_likes">Suka</string>
<string name="migration_info_6_7_title">Halaman Top 50 SoundCloud dihapus</string>
<string name="migration_info_6_7_message">SoundCloud telah menghentikan dukungan tangga lagu Top 50. Tab terkait telah dihapus dari halaman utama Anda.</string>
</resources> </resources>

View file

@ -97,7 +97,6 @@
<string name="play_queue_audio_settings">Hljóðstillingar</string> <string name="play_queue_audio_settings">Hljóðstillingar</string>
<string name="start_here_on_background">Spila í bakgrunni</string> <string name="start_here_on_background">Spila í bakgrunni</string>
<string name="preferred_open_action_settings_summary">Þegar hlekkur er opnaður — %s</string> <string name="preferred_open_action_settings_summary">Þegar hlekkur er opnaður — %s</string>
<string name="short_thousand">þús.</string>
<string name="detail_dislikes_img_view_description">Líkar ekki við</string> <string name="detail_dislikes_img_view_description">Líkar ekki við</string>
<string name="retry">Reyna aftur</string> <string name="retry">Reyna aftur</string>
<string name="description_tab_description">Lýsing</string> <string name="description_tab_description">Lýsing</string>
@ -217,7 +216,6 @@
<string name="your_comment">Athugasemd þín (á ensku):</string> <string name="your_comment">Athugasemd þín (á ensku):</string>
<string name="search_no_results">Engar niðurstöður</string> <string name="search_no_results">Engar niðurstöður</string>
<string name="video">Myndskeið</string> <string name="video">Myndskeið</string>
<string name="short_billion">ma.</string>
<string name="no_views">Engin áhorf</string> <string name="no_views">Engin áhorf</string>
<plurals name="views"> <plurals name="views">
<item quantity="one">%s áhorf</item> <item quantity="one">%s áhorf</item>
@ -268,7 +266,6 @@
<string name="title_last_played">Nýlega spilað</string> <string name="title_last_played">Nýlega spilað</string>
<string name="title_most_played">Mest spilað</string> <string name="title_most_played">Mest spilað</string>
<string name="main_page_content">Aðalsíða</string> <string name="main_page_content">Aðalsíða</string>
<string name="localization_changes_requires_app_restart">Tungumálið breytist þegar forritið er endurræst</string>
<string name="export_complete_toast">Flutt út</string> <string name="export_complete_toast">Flutt út</string>
<string name="import_complete_toast">Flutt inn</string> <string name="import_complete_toast">Flutt inn</string>
<string name="local">Staðbundið</string> <string name="local">Staðbundið</string>
@ -353,7 +350,6 @@
<string name="metadata_category">Flokkur</string> <string name="metadata_category">Flokkur</string>
<string name="metadata_tags">Merki</string> <string name="metadata_tags">Merki</string>
<string name="donation_encouragement">NewPipe er þróað af sjálfboðaliðum sem eyða frítíma sínum í að færa þér bestu notendaupplifunina. Gefðu til baka til að hjálpa forriturum að gera NewPipe enn betri á meðan þeir njóta kaffibolla.</string> <string name="donation_encouragement">NewPipe er þróað af sjálfboðaliðum sem eyða frítíma sínum í að færa þér bestu notendaupplifunina. Gefðu til baka til að hjálpa forriturum að gera NewPipe enn betri á meðan þeir njóta kaffibolla.</string>
<string name="short_million">millj.</string>
<string name="show_description_summary">Slökktu á til að fela lýsingu og viðbótarupplýsingar myndskeiðs</string> <string name="show_description_summary">Slökktu á til að fela lýsingu og viðbótarupplýsingar myndskeiðs</string>
<string name="error_occurred_detail">Villa kom upp: %1$s</string> <string name="error_occurred_detail">Villa kom upp: %1$s</string>
<string name="title_activity_recaptcha">Þraut reCAPTCHA</string> <string name="title_activity_recaptcha">Þraut reCAPTCHA</string>
@ -802,4 +798,14 @@
<string name="show_error_snackbar">Sýna villustiku</string> <string name="show_error_snackbar">Sýna villustiku</string>
<string name="image_quality_summary">Veldu gæði mynda og hvort eigi að hlaða myndum inn yfirhöfuð, til að minnka notun gagna og minnis. Breytingar munu hreinsa bæði vinnsluminni og diskminni - %s</string> <string name="image_quality_summary">Veldu gæði mynda og hvort eigi að hlaða myndum inn yfirhöfuð, til að minnka notun gagna og minnis. Breytingar munu hreinsa bæði vinnsluminni og diskminni - %s</string>
<string name="audio_track_type_secondary">auka</string> <string name="audio_track_type_secondary">auka</string>
<string name="share_playlist_as_youtube_temporary_playlist">Deila sem YouTube-bráðabirgðaspilunarlista</string>
<string name="tab_bookmarks_short">Spilunarlistar</string>
<string name="search_with_service_name">Leita í %1$s</string>
<string name="search_with_service_name_and_filter">Leita í %1$s (%2$s)</string>
<string name="select_a_feed_group">Veldu hóp streyma</string>
<string name="no_feed_group_created_yet">Enginn hópur streyma útbúinn ennþá</string>
<string name="feed_group_page_summary">Síða rásahóps</string>
<string name="channel_tab_likes">Líkar við</string>
<string name="migration_info_6_7_title">Topp 50 síða SoundCloud fjarlægð</string>
<string name="migration_info_6_7_message">SoundCloud er hætt með Topp 50 vinsældalistann. Viðkomandi flipi hefur verið fjarlægður af aðalsíðunni þinni.</string>
</resources> </resources>

View file

@ -82,9 +82,6 @@
<string name="title_activity_recaptcha">Risoluzione reCAPTCHA</string> <string name="title_activity_recaptcha">Risoluzione reCAPTCHA</string>
<string name="black_theme_title">Nero</string> <string name="black_theme_title">Nero</string>
<string name="all">Tutto</string> <string name="all">Tutto</string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="short_billion">Mrd</string>
<string name="recaptcha_request_toast">È richiesta la risoluzione del reCAPTCHA</string> <string name="recaptcha_request_toast">È richiesta la risoluzione del reCAPTCHA</string>
<string name="open_in_popup_mode">Apri in modalità popup</string> <string name="open_in_popup_mode">Apri in modalità popup</string>
<string name="popup_playing_toast">Riproduzione in modalità popup</string> <string name="popup_playing_toast">Riproduzione in modalità popup</string>
@ -427,7 +424,6 @@
<item quantity="many">%s ascoltatori</item> <item quantity="many">%s ascoltatori</item>
<item quantity="other">%s ascoltatori</item> <item quantity="other">%s ascoltatori</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">La lingua verrà cambiata al riavvio dell\'applicazione</string>
<string name="default_kiosk_page_summary">Contenuti in evidenza predefiniti</string> <string name="default_kiosk_page_summary">Contenuti in evidenza predefiniti</string>
<string name="seek_duration_title">Durata avanzamento e riavvolgimento rapidi</string> <string name="seek_duration_title">Durata avanzamento e riavvolgimento rapidi</string>
<string name="peertube_instance_url_title">Istanze PeerTube</string> <string name="peertube_instance_url_title">Istanze PeerTube</string>
@ -837,4 +833,14 @@
\nVuoi attivarlo?</string> \nVuoi attivarlo?</string>
<string name="import_settings_vulnerable_format">Le impostazioni nell\'export che viene importato usano un formato vulnerabile che è stato deprecato dalla versione 0.27.0 di NewPipe. Assicuratevi che l\'export importato venga da una fonte fidata, sarebbe preferibile usare solo exports ottenuti da NewPipe 0.27.0 o superiori, nel futuro. Il supporto all\'importazione di Impostazioni in questo formato vulnerabile sarà presto rimosso completamente, da quel momento le versioni di NewPipe più vecchie non saranno più in grado di importare impostazioni tramite export di versioni più recenti.</string> <string name="import_settings_vulnerable_format">Le impostazioni nell\'export che viene importato usano un formato vulnerabile che è stato deprecato dalla versione 0.27.0 di NewPipe. Assicuratevi che l\'export importato venga da una fonte fidata, sarebbe preferibile usare solo exports ottenuti da NewPipe 0.27.0 o superiori, nel futuro. Il supporto all\'importazione di Impostazioni in questo formato vulnerabile sarà presto rimosso completamente, da quel momento le versioni di NewPipe più vecchie non saranno più in grado di importare impostazioni tramite export di versioni più recenti.</string>
<string name="audio_track_type_secondary">secondaria</string> <string name="audio_track_type_secondary">secondaria</string>
<string name="share_playlist_as_youtube_temporary_playlist">Condividi come playlist YouTube temporanea</string>
<string name="tab_bookmarks_short">Playlist</string>
<string name="select_a_feed_group">Seleziona un gruppo di feed</string>
<string name="no_feed_group_created_yet">Ancora nessun gruppo di feed creato</string>
<string name="feed_group_page_summary">Pagina gruppo canali</string>
<string name="search_with_service_name_and_filter">Cerca %1$s (%2$s)</string>
<string name="search_with_service_name">Cerca su %1$s</string>
<string name="channel_tab_likes">Mi piace</string>
<string name="migration_info_6_7_title">Pagina Top 50 di SoundCloud rimossa</string>
<string name="migration_info_6_7_message">SoundCloud ha dismesso i grafici Top 50 originali. La scheda relativa è stata rimossa dalla pagina principale.</string>
</resources> </resources>

View file

@ -83,9 +83,6 @@
<string name="recaptcha_request_toast">reCAPTCHA を要求しました</string> <string name="recaptcha_request_toast">reCAPTCHA を要求しました</string>
<string name="black_theme_title">ブラック</string> <string name="black_theme_title">ブラック</string>
<string name="all">すべて</string> <string name="all">すべて</string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="short_billion">B</string>
<string name="open_in_popup_mode">ポップアップモードで開く</string> <string name="open_in_popup_mode">ポップアップモードで開く</string>
<string name="msg_popup_permission">ポップアップモードで開くには <string name="msg_popup_permission">ポップアップモードで開くには
\n権限の許可が必要です</string> \n権限の許可が必要です</string>
@ -418,7 +415,6 @@
<plurals name="listening"> <plurals name="listening">
<item quantity="other">%s 人が聴取中</item> <item quantity="other">%s 人が聴取中</item>
</plurals> </plurals>
<string name="localization_changes_requires_app_restart">アプリを再起動すると、言語が変更されます</string>
<string name="seek_duration_title">高速早送り/巻き戻し間隔</string> <string name="seek_duration_title">高速早送り/巻き戻し間隔</string>
<string name="peertube_instance_url_title">PeerTube インスタンス</string> <string name="peertube_instance_url_title">PeerTube インスタンス</string>
<string name="peertube_instance_url_summary">PeerTube インスタンスを選択する</string> <string name="peertube_instance_url_summary">PeerTube インスタンスを選択する</string>
@ -808,4 +804,6 @@
\n \n
\n続行しますか</string> \n続行しますか</string>
<string name="import_settings_vulnerable_format">インポートされているエクスポートの設定は、NewPipe 0.27.0以降は非推奨であった脆弱な形式を使用します。 インポートされているエクスポートは信頼できる情報源からであり、将来的にはNewPipe 0.27.0かこれより新しいバージョンから得られるエクスポートのみを優先して使用します。 この脆弱な形式で設定をインポートするための対応はすぐに完全に削除され、新しいバージョンからエクスポートの設定をインポートすることは出来ません。</string> <string name="import_settings_vulnerable_format">インポートされているエクスポートの設定は、NewPipe 0.27.0以降は非推奨であった脆弱な形式を使用します。 インポートされているエクスポートは信頼できる情報源からであり、将来的にはNewPipe 0.27.0かこれより新しいバージョンから得られるエクスポートのみを優先して使用します。 この脆弱な形式で設定をインポートするための対応はすぐに完全に削除され、新しいバージョンからエクスポートの設定をインポートすることは出来ません。</string>
<string name="share_playlist_as_youtube_temporary_playlist">YouTubeの一時的なプレイリストとして共有</string>
<string name="audio_track_type_secondary">二次的</string>
</resources> </resources>

Some files were not shown because too many files have changed in this diff Show more