Merge branch 'sponsorblock' into sb-translation-id

This commit is contained in:
poly 2022-07-09 11:00:14 -06:00 committed by GitHub
commit b4907ee932
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
251 changed files with 7107 additions and 2582 deletions

View file

@ -16,8 +16,8 @@ android {
resValue "string", "app_name", "NewPipe SponsorBlock" resValue "string", "app_name", "NewPipe SponsorBlock"
minSdk 19 minSdk 19
targetSdk 29 targetSdk 29
versionCode 986 versionCode 987
versionName "0.23.0" versionName "0.23.1"
multiDexEnabled true multiDexEnabled true
@ -107,7 +107,7 @@ ext {
icepickVersion = '3.2.0' icepickVersion = '3.2.0'
exoPlayerVersion = '2.17.1' exoPlayerVersion = '2.17.1'
googleAutoServiceVersion = '1.0.1' googleAutoServiceVersion = '1.0.1'
groupieVersion = '2.10.0' groupieVersion = '2.10.1'
markwonVersion = '4.6.2' markwonVersion = '4.6.2'
leakCanaryVersion = '2.5' leakCanaryVersion = '2.5'
@ -190,7 +190,7 @@ dependencies {
// name and the commit hash with the commit hash of the (pushed) commit you want to test // name and the commit hash with the commit hash of the (pushed) commit you want to test
// This works thanks to JitPack: https://jitpack.io/ // This works thanks to JitPack: https://jitpack.io/
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:ac1c22d81c65b7b0c5427f4e1989f5256d617f32' implementation 'com.github.TeamNewPipe:NewPipeExtractor:5219a705bab539cf8c6624d0cec216e76e85f0b1'
/** Checkstyle **/ /** Checkstyle **/
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
@ -221,10 +221,9 @@ dependencies {
// https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01 // https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.webkit:webkit:1.4.0' implementation 'androidx.webkit:webkit:1.4.0'
implementation 'com.google.android.material:material:1.5.0'
implementation "androidx.work:work-runtime:${androidxWorkVersion}"
implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}"
implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" implementation "androidx.work:work-rxjava3:${androidxWorkVersion}"
implementation 'com.google.android.material:material:1.5.0'
/** Third-party libraries **/ /** Third-party libraries **/
// Instance state boilerplate elimination // Instance state boilerplate elimination
@ -262,7 +261,7 @@ dependencies {
implementation "com.nononsenseapps:filepicker:4.2.1" implementation "com.nononsenseapps:filepicker:4.2.1"
// Crash reporting // Crash reporting
implementation "ch.acra:acra-core:5.8.4" implementation "ch.acra:acra-core:5.9.3"
// Properly restarting // Properly restarting
implementation 'com.jakewharton:process-phoenix:2.1.2' implementation 'com.jakewharton:process-phoenix:2.1.2'

View file

@ -91,7 +91,12 @@ class StreamItemAdapterTest {
context, context,
StreamItemAdapter.StreamSizeWrapper( StreamItemAdapter.StreamSizeWrapper(
(0 until 5).map { (0 until 5).map {
SubtitlesStream(MediaFormat.SRT, "pt-BR", "https://example.com", false) SubtitlesStream.Builder()
.setContent("https://example.com", true)
.setMediaFormat(MediaFormat.SRT)
.setLanguageCode("pt-BR")
.setAutoGenerated(false)
.build()
}, },
context context
), ),
@ -108,7 +113,14 @@ class StreamItemAdapterTest {
val adapter = StreamItemAdapter<AudioStream, Stream>( val adapter = StreamItemAdapter<AudioStream, Stream>(
context, context,
StreamItemAdapter.StreamSizeWrapper( StreamItemAdapter.StreamSizeWrapper(
(0 until 5).map { AudioStream("https://example.com/$it", MediaFormat.OPUS, 192) }, (0 until 5).map {
AudioStream.Builder()
.setId(Stream.ID_UNKNOWN)
.setContent("https://example.com/$it", true)
.setMediaFormat(MediaFormat.OPUS)
.setAverageBitrate(192)
.build()
},
context context
), ),
null null
@ -126,7 +138,13 @@ class StreamItemAdapterTest {
private fun getVideoStreams(vararg videoOnly: Boolean) = private fun getVideoStreams(vararg videoOnly: Boolean) =
StreamItemAdapter.StreamSizeWrapper( StreamItemAdapter.StreamSizeWrapper(
videoOnly.map { videoOnly.map {
VideoStream("https://example.com", MediaFormat.MPEG_4, "720p", it) VideoStream.Builder()
.setId(Stream.ID_UNKNOWN)
.setContent("https://example.com", true)
.setMediaFormat(MediaFormat.MPEG_4)
.setResolution("720p")
.setIsVideoOnly(it)
.build()
}, },
context context
) )
@ -138,8 +156,16 @@ class StreamItemAdapterTest {
private fun getAudioStreams(vararg shouldBeValid: Boolean) = private fun getAudioStreams(vararg shouldBeValid: Boolean) =
getSecondaryStreamsFromList( getSecondaryStreamsFromList(
shouldBeValid.map { shouldBeValid.map {
if (it) AudioStream("https://example.com", MediaFormat.OPUS, 192) if (it) {
else null AudioStream.Builder()
.setId(Stream.ID_UNKNOWN)
.setContent("https://example.com", true)
.setMediaFormat(MediaFormat.OPUS)
.setAverageBitrate(192)
.build()
} else {
null
}
} }
) )

View file

@ -205,7 +205,7 @@ public class App extends MultiDexApplication {
return; return;
} }
final CoreConfigurationBuilder acraConfig = new CoreConfigurationBuilder(this) final CoreConfigurationBuilder acraConfig = new CoreConfigurationBuilder()
.withBuildConfigClass(BuildConfig.class); .withBuildConfigClass(BuildConfig.class);
ACRA.init(this, acraConfig); ACRA.init(this, acraConfig);
} }

View file

@ -43,7 +43,7 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
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; rv:78.0) Gecko/20100101 Firefox/78.0"; = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.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,6 +20,7 @@ import org.schabi.newpipe.extractor.downloader.Response
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.util.ReleaseVersionUtil.coerceUpdateCheckExpiry import org.schabi.newpipe.util.ReleaseVersionUtil.coerceUpdateCheckExpiry
import org.schabi.newpipe.util.ReleaseVersionUtil.isLastUpdateCheckExpired import org.schabi.newpipe.util.ReleaseVersionUtil.isLastUpdateCheckExpired
import org.schabi.newpipe.util.ReleaseVersionUtil.isReleaseApk
import org.schabi.newpipe.util.Version import org.schabi.newpipe.util.Version
import java.io.IOException import java.io.IOException
@ -69,6 +70,11 @@ class NewVersionWorker(
@Throws(IOException::class, ReCaptchaException::class) @Throws(IOException::class, ReCaptchaException::class)
private fun checkNewVersion() { private fun checkNewVersion() {
// Check if the current apk is a github one or not.
if (!isReleaseApk()) {
return
}
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext) val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
// Check if the last request has happened a certain time ago // Check if the last request has happened a certain time ago
// to reduce the number of API requests. // to reduce the number of API requests.

View file

@ -24,12 +24,12 @@ import android.widget.Toast;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat; import androidx.core.app.ServiceCompat;
import androidx.core.widget.TextViewCompat;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@ -58,7 +58,6 @@ import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException; import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.MainPlayer;
@ -71,7 +70,7 @@ import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
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.ListHelper; 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;
@ -127,8 +126,10 @@ public class RouterActivity extends AppCompatActivity {
} }
} }
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);
} }
@Override @Override
@ -257,80 +258,122 @@ public class RouterActivity extends AppCompatActivity {
protected void onSuccess() { protected void onSuccess() {
final SharedPreferences preferences = PreferenceManager final SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(this); .getDefaultSharedPreferences(this);
final String selectedChoiceKey = preferences
.getString(getString(R.string.preferred_open_action_key),
getString(R.string.preferred_open_action_default));
final String showInfoKey = getString(R.string.show_info_key); final ChoiceAvailabilityChecker choiceChecker = new ChoiceAvailabilityChecker(
final String videoPlayerKey = getString(R.string.video_player_key); getChoicesForService(currentService, currentLinkType),
final String backgroundPlayerKey = getString(R.string.background_player_key); preferences.getString(getString(R.string.preferred_open_action_key),
final String popupPlayerKey = getString(R.string.popup_player_key); getString(R.string.preferred_open_action_default)));
final String downloadKey = getString(R.string.download_key);
final String alwaysAskKey = getString(R.string.always_ask_open_action_key);
if (selectedChoiceKey.equals(alwaysAskKey)) { // Check for non-player related choices
final List<AdapterChoiceItem> choices if (choiceChecker.isAvailableAndSelected(
= getChoicesForService(currentService, currentLinkType); R.string.show_info_key,
R.string.download_key,
switch (choices.size()) { R.string.add_to_playlist_key)) {
case 1: handleChoice(choiceChecker.getSelectedChoiceKey());
handleChoice(choices.get(0).key); return;
break;
case 0:
handleChoice(showInfoKey);
break;
default:
showDialog(choices);
break;
} }
} else if (selectedChoiceKey.equals(showInfoKey)) { // Check if the choice is player related
handleChoice(showInfoKey); if (choiceChecker.isAvailableAndSelected(
} else if (selectedChoiceKey.equals(downloadKey)) { R.string.video_player_key,
handleChoice(downloadKey); R.string.background_player_key,
} else { R.string.popup_player_key)) {
final String selectedChoice = choiceChecker.getSelectedChoiceKey();
final boolean isExtVideoEnabled = preferences.getBoolean( final boolean isExtVideoEnabled = preferences.getBoolean(
getString(R.string.use_external_video_player_key), false); getString(R.string.use_external_video_player_key), false);
final boolean isExtAudioEnabled = preferences.getBoolean( final boolean isExtAudioEnabled = preferences.getBoolean(
getString(R.string.use_external_audio_player_key), false); getString(R.string.use_external_audio_player_key), false);
final boolean isVideoPlayerSelected = selectedChoiceKey.equals(videoPlayerKey) final boolean isVideoPlayerSelected =
|| selectedChoiceKey.equals(popupPlayerKey); selectedChoice.equals(getString(R.string.video_player_key))
final boolean isAudioPlayerSelected = selectedChoiceKey.equals(backgroundPlayerKey); || selectedChoice.equals(getString(R.string.popup_player_key));
final boolean isAudioPlayerSelected =
selectedChoice.equals(getString(R.string.background_player_key));
if (currentLinkType != LinkType.STREAM) { if (currentLinkType != LinkType.STREAM
if (isExtAudioEnabled && isAudioPlayerSelected && ((isExtAudioEnabled && isAudioPlayerSelected)
|| isExtVideoEnabled && isVideoPlayerSelected) { || (isExtVideoEnabled && isVideoPlayerSelected))
) {
Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.makeText(this, R.string.external_player_unsupported_link_type,
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
handleChoice(showInfoKey); handleChoice(getString(R.string.show_info_key));
return; return;
} }
}
final List<StreamingService.ServiceInfo.MediaCapability> capabilities final List<StreamingService.ServiceInfo.MediaCapability> capabilities =
= currentService.getServiceInfo().getMediaCapabilities(); currentService.getServiceInfo().getMediaCapabilities();
boolean serviceSupportsChoice = false; // Check if the service supports the choice
if (isVideoPlayerSelected) { if ((isVideoPlayerSelected && capabilities.contains(VIDEO))
serviceSupportsChoice = capabilities.contains(VIDEO); || (isAudioPlayerSelected && capabilities.contains(AUDIO))) {
} else if (selectedChoiceKey.equals(backgroundPlayerKey)) { handleChoice(selectedChoice);
serviceSupportsChoice = capabilities.contains(AUDIO);
}
if (serviceSupportsChoice) {
handleChoice(selectedChoiceKey);
} else { } else {
handleChoice(showInfoKey); handleChoice(getString(R.string.show_info_key));
} }
return;
}
// Default / Ask always
final List<AdapterChoiceItem> availableChoices = choiceChecker.getAvailableChoices();
switch (availableChoices.size()) {
case 1:
handleChoice(availableChoices.get(0).key);
break;
case 0:
handleChoice(getString(R.string.show_info_key));
break;
default:
showDialog(availableChoices);
break;
}
}
/**
* This is a helper class for checking if the choices are available and/or selected.
*/
class ChoiceAvailabilityChecker {
private final List<AdapterChoiceItem> availableChoices;
private final String selectedChoiceKey;
ChoiceAvailabilityChecker(
@NonNull final List<AdapterChoiceItem> availableChoices,
@NonNull final String selectedChoiceKey) {
this.availableChoices = availableChoices;
this.selectedChoiceKey = selectedChoiceKey;
}
public List<AdapterChoiceItem> getAvailableChoices() {
return availableChoices;
}
public String getSelectedChoiceKey() {
return selectedChoiceKey;
}
public boolean isAvailableAndSelected(@StringRes final int... wantedKeys) {
return Arrays.stream(wantedKeys).anyMatch(this::isAvailableAndSelected);
}
public boolean isAvailableAndSelected(@StringRes final int wantedKey) {
final String wanted = getString(wantedKey);
// Check if the wanted option is selected
if (!selectedChoiceKey.equals(wanted)) {
return false;
}
// Check if it's available
return availableChoices.stream().anyMatch(item -> wanted.equals(item.key));
} }
} }
private void showDialog(final List<AdapterChoiceItem> choices) { private void showDialog(final List<AdapterChoiceItem> choices) {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final Context themeWrapperContext = getThemeWrapperContext();
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext); final Context themeWrapperContext = getThemeWrapperContext();
final RadioGroup radioGroup = SingleChoiceDialogViewBinding.inflate(getLayoutInflater()) final LayoutInflater layoutInflater = LayoutInflater.from(themeWrapperContext);
.list;
final SingleChoiceDialogViewBinding binding =
SingleChoiceDialogViewBinding.inflate(layoutInflater);
final RadioGroup radioGroup = binding.list;
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> { final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
final int indexOfChild = radioGroup.indexOfChild( final int indexOfChild = radioGroup.indexOfChild(
@ -349,21 +392,19 @@ public class RouterActivity extends AppCompatActivity {
alertDialogChoice = new AlertDialog.Builder(themeWrapperContext) alertDialogChoice = new AlertDialog.Builder(themeWrapperContext)
.setTitle(R.string.preferred_open_action_share_menu_title) .setTitle(R.string.preferred_open_action_share_menu_title)
.setView(radioGroup) .setView(binding.getRoot())
.setCancelable(true) .setCancelable(true)
.setNegativeButton(R.string.just_once, dialogButtonsClickListener) .setNegativeButton(R.string.just_once, dialogButtonsClickListener)
.setPositiveButton(R.string.always, dialogButtonsClickListener) .setPositiveButton(R.string.always, dialogButtonsClickListener)
.setOnDismissListener((dialog) -> { .setOnDismissListener(dialog -> {
if (!selectionIsDownload && !selectionIsAddToPlaylist) { if (!selectionIsDownload && !selectionIsAddToPlaylist) {
finish(); finish();
} }
}) })
.create(); .create();
//noinspection CodeBlock2Expr alertDialogChoice.setOnShowListener(dialog -> setDialogButtonsState(
alertDialogChoice.setOnShowListener(dialog -> { alertDialogChoice, radioGroup.getCheckedRadioButtonId() != -1));
setDialogButtonsState(alertDialogChoice, radioGroup.getCheckedRadioButtonId() != -1);
});
radioGroup.setOnCheckedChangeListener((group, checkedId) -> radioGroup.setOnCheckedChangeListener((group, checkedId) ->
setDialogButtonsState(alertDialogChoice, true)); setDialogButtonsState(alertDialogChoice, true));
@ -383,9 +424,10 @@ public class RouterActivity extends AppCompatActivity {
int id = 12345; int id = 12345;
for (final AdapterChoiceItem item : choices) { for (final AdapterChoiceItem item : choices) {
final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot(); final RadioButton radioButton = ListRadioIconItemBinding.inflate(layoutInflater)
.getRoot();
radioButton.setText(item.description); radioButton.setText(item.description);
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(radioButton, radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
AppCompatResources.getDrawable(themeWrapperContext, item.icon), AppCompatResources.getDrawable(themeWrapperContext, item.icon),
null, null, null); null, null, null);
radioButton.setChecked(false); radioButton.setChecked(false);
@ -425,12 +467,47 @@ public class RouterActivity extends AppCompatActivity {
private List<AdapterChoiceItem> getChoicesForService(final StreamingService service, private List<AdapterChoiceItem> getChoicesForService(final StreamingService service,
final LinkType linkType) { final LinkType linkType) {
final Context context = getThemeWrapperContext(); final AdapterChoiceItem showInfo = new AdapterChoiceItem(
getString(R.string.show_info_key), getString(R.string.show_info),
R.drawable.ic_info_outline);
final AdapterChoiceItem videoPlayer = new AdapterChoiceItem(
getString(R.string.video_player_key), getString(R.string.video_player),
R.drawable.ic_play_arrow);
final AdapterChoiceItem backgroundPlayer = new AdapterChoiceItem(
getString(R.string.background_player_key), getString(R.string.background_player),
R.drawable.ic_headset);
final AdapterChoiceItem popupPlayer = new AdapterChoiceItem(
getString(R.string.popup_player_key), getString(R.string.popup_player),
R.drawable.ic_picture_in_picture);
final List<AdapterChoiceItem> returnList = new ArrayList<>(); final List<AdapterChoiceItem> returnedItems = new ArrayList<>();
final List<StreamingService.ServiceInfo.MediaCapability> capabilities returnedItems.add(showInfo); // Always present
= service.getServiceInfo().getMediaCapabilities();
final List<StreamingService.ServiceInfo.MediaCapability> capabilities =
service.getServiceInfo().getMediaCapabilities();
if (linkType == LinkType.STREAM) {
if (capabilities.contains(VIDEO)) {
returnedItems.add(videoPlayer);
returnedItems.add(popupPlayer);
}
if (capabilities.contains(AUDIO)) {
returnedItems.add(backgroundPlayer);
}
// download is redundant for linkType CHANNEL AND PLAYLIST (till playlist downloading is
// not supported )
returnedItems.add(new AdapterChoiceItem(getString(R.string.download_key),
getString(R.string.download),
R.drawable.ic_file_download));
// Add to playlist is not necessary for CHANNEL and PLAYLIST linkType since those can
// not be added to a playlist
returnedItems.add(new AdapterChoiceItem(getString(R.string.add_to_playlist_key),
getString(R.string.add_to_playlist),
R.drawable.ic_add));
} else {
// LinkType.NONE is never present because it's filtered out before
// channels and playlist can be played as they contain a list of videos
final SharedPreferences preferences = PreferenceManager final SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(this); .getDefaultSharedPreferences(this);
final boolean isExtVideoEnabled = preferences.getBoolean( final boolean isExtVideoEnabled = preferences.getBoolean(
@ -438,74 +515,16 @@ public class RouterActivity extends AppCompatActivity {
final boolean isExtAudioEnabled = preferences.getBoolean( final boolean isExtAudioEnabled = preferences.getBoolean(
getString(R.string.use_external_audio_player_key), false); getString(R.string.use_external_audio_player_key), false);
final AdapterChoiceItem videoPlayer = new AdapterChoiceItem(
getString(R.string.video_player_key), getString(R.string.video_player),
R.drawable.ic_play_arrow);
final AdapterChoiceItem showInfo = new AdapterChoiceItem(
getString(R.string.show_info_key), getString(R.string.show_info),
R.drawable.ic_info_outline);
final AdapterChoiceItem popupPlayer = new AdapterChoiceItem(
getString(R.string.popup_player_key), getString(R.string.popup_player),
R.drawable.ic_picture_in_picture);
final AdapterChoiceItem backgroundPlayer = new AdapterChoiceItem(
getString(R.string.background_player_key), getString(R.string.background_player),
R.drawable.ic_headset);
final AdapterChoiceItem addToPlaylist = new AdapterChoiceItem(
getString(R.string.add_to_playlist_key), getString(R.string.add_to_playlist),
R.drawable.ic_add);
if (linkType == LinkType.STREAM) {
if (isExtVideoEnabled) {
// show both "show info" and "video player", they are two different activities
returnList.add(showInfo);
returnList.add(videoPlayer);
} else {
final MainPlayer.PlayerType playerType = PlayerHolder.getInstance().getType();
if (capabilities.contains(VIDEO)
&& PlayerHelper.isAutoplayAllowedByUser(context)
&& playerType == null || playerType == MainPlayer.PlayerType.VIDEO) {
// show only "video player" since the details activity will be opened and the
// video will be auto played there. Since "show info" would do the exact same
// thing, use that as a key to let VideoDetailFragment load the stream instead
// of using FetcherService (see comment in handleChoice())
returnList.add(new AdapterChoiceItem(
showInfo.key, videoPlayer.description, videoPlayer.icon));
} else {
// show only "show info" if video player is not applicable, auto play is
// disabled or a video is playing in a player different than the main one
returnList.add(showInfo);
}
}
if (capabilities.contains(VIDEO)) {
returnList.add(popupPlayer);
}
if (capabilities.contains(AUDIO)) {
returnList.add(backgroundPlayer);
}
// download is redundant for linkType CHANNEL AND PLAYLIST (till playlist downloading is
// not supported )
returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
getString(R.string.download),
R.drawable.ic_file_download));
// Add to playlist is not necessary for CHANNEL and PLAYLIST linkType since those can
// not be added to a playlist
returnList.add(addToPlaylist);
} else {
returnList.add(showInfo);
if (capabilities.contains(VIDEO) && !isExtVideoEnabled) { if (capabilities.contains(VIDEO) && !isExtVideoEnabled) {
returnList.add(videoPlayer); returnedItems.add(videoPlayer);
returnList.add(popupPlayer); returnedItems.add(popupPlayer);
} }
if (capabilities.contains(AUDIO) && !isExtAudioEnabled) { if (capabilities.contains(AUDIO) && !isExtAudioEnabled) {
returnList.add(backgroundPlayer); returnedItems.add(backgroundPlayer);
} }
} }
return returnList; return returnedItems;
} }
private Context getThemeWrapperContext() { private Context getThemeWrapperContext() {
@ -567,7 +586,8 @@ public class RouterActivity extends AppCompatActivity {
// stop and bypass FetcherService if InfoScreen was selected since // stop and bypass FetcherService if InfoScreen was selected since
// StreamDetailFragment can fetch data itself // StreamDetailFragment can fetch data itself
if (selectedChoiceKey.equals(getString(R.string.show_info_key))) { if (selectedChoiceKey.equals(getString(R.string.show_info_key))
|| canHandleChoiceLikeShowInfo(selectedChoiceKey)) {
disposables.add(Observable disposables.add(Observable
.fromCallable(() -> NavigationHelper.getIntentByLink(this, currentUrl)) .fromCallable(() -> NavigationHelper.getIntentByLink(this, currentUrl))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@ -590,6 +610,30 @@ public class RouterActivity extends AppCompatActivity {
finish(); finish();
} }
private boolean canHandleChoiceLikeShowInfo(final String selectedChoiceKey) {
if (!selectedChoiceKey.equals(getString(R.string.video_player_key))) {
return false;
}
// "video player" can be handled like "show info" (because VideoDetailFragment can load
// the stream instead of FetcherService) when...
// ...Autoplay is enabled
if (!PlayerHelper.isAutoplayAllowedByUser(getThemeWrapperContext())) {
return false;
}
final boolean isExtVideoEnabled = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(getString(R.string.use_external_video_player_key), false);
// ...it's not done via an external player
if (isExtVideoEnabled) {
return false;
}
// ...the player is not running or in normal Video-mode/type
final MainPlayer.PlayerType playerType = PlayerHolder.getInstance().getType();
return playerType == null || playerType == MainPlayer.PlayerType.VIDEO;
}
private void openAddToPlaylistDialog() { private void openAddToPlaylistDialog() {
// Getting the stream info usually takes a moment // Getting the stream info usually takes a moment
// Notifying the user here to ensure that no confusion arises // Notifying the user here to ensure that no confusion arises
@ -631,22 +675,13 @@ public class RouterActivity extends AppCompatActivity {
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> { .subscribe(result -> {
final List<VideoStream> sortedVideoStreams = ListHelper final DownloadDialog downloadDialog = new DownloadDialog(this, result);
.getSortedStreamVideosList(this, result.getVideoStreams(), downloadDialog.setOnDismissListener(dialog -> finish());
result.getVideoOnlyStreams(), false, false);
final int selectedVideoStreamIndex = ListHelper
.getDefaultResolutionIndex(this, sortedVideoStreams);
final FragmentManager fm = getSupportFragmentManager(); final FragmentManager fm = getSupportFragmentManager();
final DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
downloadDialog.setVideoStreams(sortedVideoStreams);
downloadDialog.setAudioStreams(result.getAudioStreams());
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.setOnDismissListener(dialog -> finish());
downloadDialog.show(fm, "downloadDialog"); downloadDialog.show(fm, "downloadDialog");
fm.executePendingTransactions(); fm.executePendingTransactions();
}, throwable -> }, throwable -> showUnsupportedUrlDialog(currentUrl)));
showUnsupportedUrlDialog(currentUrl)));
} }
@Override @Override
@ -672,8 +707,8 @@ public class RouterActivity extends AppCompatActivity {
final int icon; final int icon;
AdapterChoiceItem(final String key, final String description, final int icon) { AdapterChoiceItem(final String key, final String description, final int icon) {
this.description = description;
this.key = key; this.key = key;
this.description = description;
this.icon = icon; this.icon = icon;
} }
} }

View file

@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
import androidx.room.ColumnInfo; import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.ForeignKey; import androidx.room.ForeignKey;
import androidx.room.Ignore;
import androidx.room.Index; import androidx.room.Index;
import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.database.stream.model.StreamEntity;
@ -42,18 +41,19 @@ public class StreamHistoryEntity {
@ColumnInfo(name = STREAM_REPEAT_COUNT) @ColumnInfo(name = STREAM_REPEAT_COUNT)
private long repeatCount; private long repeatCount;
public StreamHistoryEntity(final long streamUid, @NonNull final OffsetDateTime accessDate, /**
* @param streamUid the stream id this history item will refer to
* @param accessDate the last time the stream was accessed
* @param repeatCount the total number of views this stream received
*/
public StreamHistoryEntity(final long streamUid,
@NonNull final OffsetDateTime accessDate,
final long repeatCount) { final long repeatCount) {
this.streamUid = streamUid; this.streamUid = streamUid;
this.accessDate = accessDate; this.accessDate = accessDate;
this.repeatCount = repeatCount; this.repeatCount = repeatCount;
} }
@Ignore
public StreamHistoryEntity(final long streamUid, @NonNull final OffsetDateTime accessDate) {
this(streamUid, accessDate, 1);
}
public long getStreamUid() { public long getStreamUid() {
return streamUid; return streamUid;
} }

View file

@ -12,8 +12,7 @@ import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM import org.schabi.newpipe.util.StreamTypeUtil
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
import java.time.OffsetDateTime import java.time.OffsetDateTime
@Dao @Dao
@ -91,8 +90,7 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
?: throw IllegalStateException("Stream cannot be null just after insertion.") ?: throw IllegalStateException("Stream cannot be null just after insertion.")
newerStream.uid = existentMinimalStream.uid newerStream.uid = existentMinimalStream.uid
val isNewerStreamLive = newerStream.streamType == AUDIO_LIVE_STREAM || newerStream.streamType == LIVE_STREAM if (!StreamTypeUtil.isLiveStream(newerStream.streamType)) {
if (!isNewerStreamLive) {
// Use the existent upload date if the newer stream does not have a better precision // Use the existent upload date if the newer stream does not have a better precision
// (i.e. is an approximation). This is done to prevent unnecessary changes. // (i.e. is an approximation). This is done to prevent unnecessary changes.

View file

@ -69,9 +69,9 @@ import org.schabi.newpipe.util.VideoSegment;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import icepick.Icepick; import icepick.Icepick;
import icepick.State; import icepick.State;
@ -83,6 +83,8 @@ import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder; import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder;
import us.shandian.giga.service.MissionState; import us.shandian.giga.service.MissionState;
import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP;
import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class DownloadDialog extends DialogFragment public class DownloadDialog extends DialogFragment
@ -93,17 +95,17 @@ public class DownloadDialog extends DialogFragment
@State @State
StreamInfo currentInfo; StreamInfo currentInfo;
@State @State
StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty(); StreamSizeWrapper<AudioStream> wrappedAudioStreams;
@State @State
StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty(); StreamSizeWrapper<VideoStream> wrappedVideoStreams;
@State @State
StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams = StreamSizeWrapper.empty(); StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams;
@State @State
int selectedVideoIndex = 0; int selectedVideoIndex; // set in the constructor
@State @State
int selectedAudioIndex = 0; int selectedAudioIndex = 0; // default to the first item
@State @State
int selectedSubtitleIndex = 0; int selectedSubtitleIndex = 0; // default to the first item
@Nullable @Nullable
private OnDismissListener onDismissListener = null; private OnDismissListener onDismissListener = null;
@ -146,81 +148,47 @@ public class DownloadDialog extends DialogFragment
// Instance creation // Instance creation
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static DownloadDialog newInstance(final StreamInfo info) { /**
final DownloadDialog dialog = new DownloadDialog(); * Create a new download dialog with the video, audio and subtitle streams from the provided
dialog.setInfo(info); * stream info. Video streams and video-only streams will be put into a single list menu,
return dialog; * sorted according to their resolution and the default video resolution will be selected.
} *
* @param context the context to use just to obtain preferences and strings (will not be stored)
public static DownloadDialog newInstance(final Context context, final StreamInfo info) { * @param info the info from which to obtain downloadable streams and other info (e.g. title)
final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper */
.getSortedStreamVideosList(context, info.getVideoStreams(), public DownloadDialog(final Context context, @NonNull final StreamInfo info) {
info.getVideoOnlyStreams(), false, false));
final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList);
final DownloadDialog instance = newInstance(info);
instance.setVideoStreams(streamsList);
instance.setSelectedVideoStream(selectedStreamIndex);
instance.setAudioStreams(info.getAudioStreams());
instance.setSubtitleStreams(info.getSubtitles());
return instance;
}
/*//////////////////////////////////////////////////////////////////////////
// Setters
//////////////////////////////////////////////////////////////////////////*/
private void setInfo(final StreamInfo info) {
this.currentInfo = info; this.currentInfo = info;
}
public void setAudioStreams(final List<AudioStream> audioStreams) { // TODO: Adapt this code when the downloader support other types of stream deliveries
setAudioStreams(new StreamSizeWrapper<>(audioStreams, getContext())); final List<VideoStream> videoStreams = ListHelper.getSortedStreamVideosList(
} context,
getStreamsOfSpecifiedDelivery(info.getVideoStreams(), PROGRESSIVE_HTTP),
getStreamsOfSpecifiedDelivery(info.getVideoOnlyStreams(), PROGRESSIVE_HTTP),
false,
false
);
public void setAudioStreams(final StreamSizeWrapper<AudioStream> was) { this.wrappedVideoStreams = new StreamSizeWrapper<>(videoStreams, context);
this.wrappedAudioStreams = was; this.wrappedAudioStreams = new StreamSizeWrapper<>(
} getStreamsOfSpecifiedDelivery(info.getAudioStreams(), PROGRESSIVE_HTTP), context);
this.wrappedSubtitleStreams = new StreamSizeWrapper<>(
getStreamsOfSpecifiedDelivery(info.getSubtitles(), PROGRESSIVE_HTTP), context);
public void setVideoStreams(final List<VideoStream> videoStreams) { this.selectedVideoIndex = ListHelper.getDefaultResolutionIndex(context, videoStreams);
setVideoStreams(new StreamSizeWrapper<>(videoStreams, getContext()));
}
public void setVideoStreams(final StreamSizeWrapper<VideoStream> wvs) {
this.wrappedVideoStreams = wvs;
}
public void setSubtitleStreams(final List<SubtitlesStream> subtitleStreams) {
setSubtitleStreams(new StreamSizeWrapper<>(subtitleStreams, getContext()));
}
public void setSubtitleStreams(
final StreamSizeWrapper<SubtitlesStream> wss) {
this.wrappedSubtitleStreams = wss;
}
public void setSelectedVideoStream(final int svi) {
this.selectedVideoIndex = svi;
}
public void setSelectedAudioStream(final int sai) {
this.selectedAudioIndex = sai;
}
public void setSelectedSubtitleStream(final int ssi) {
this.selectedSubtitleIndex = ssi;
} }
public void setVideoSegments(final VideoSegment[] seg) { public void setVideoSegments(final VideoSegment[] seg) {
this.segments = seg; this.segments = seg;
} }
/**
* @param onDismissListener the listener to call in {@link #onDismiss(DialogInterface)}
*/
public void setOnDismissListener(@Nullable final OnDismissListener onDismissListener) { public void setOnDismissListener(@Nullable final OnDismissListener onDismissListener) {
this.onDismissListener = onDismissListener; this.onDismissListener = onDismissListener;
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Android lifecycle // Android lifecycle
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -256,11 +224,16 @@ public class DownloadDialog extends DialogFragment
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i)); .getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
if (audioStream != null) { if (audioStream != null) {
secondaryStreams secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams,
.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream)); audioStream));
} else if (DEBUG) { } else if (DEBUG) {
final MediaFormat mediaFormat = videoStreams.get(i).getFormat();
if (mediaFormat != null) {
Log.w(TAG, "No audio stream candidates for video format " Log.w(TAG, "No audio stream candidates for video format "
+ videoStreams.get(i).getFormat().name()); + mediaFormat.name());
} else {
Log.w(TAG, "No audio stream candidates for unknown video format");
}
} }
} }
@ -295,7 +268,8 @@ public class DownloadDialog extends DialogFragment
} }
@Override @Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) { final Bundle savedInstanceState) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onCreateView() called with: " Log.d(TAG, "onCreateView() called with: "
@ -306,14 +280,15 @@ public class DownloadDialog extends DialogFragment
} }
@Override @Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view,
@Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
dialogBinding = DownloadDialogBinding.bind(view); dialogBinding = DownloadDialogBinding.bind(view);
dialogBinding.fileName.setText(FilenameUtils.createFilename(getContext(), dialogBinding.fileName.setText(FilenameUtils.createFilename(getContext(),
currentInfo.getName())); currentInfo.getName()));
selectedAudioIndex = ListHelper selectedAudioIndex = ListHelper
.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams()); .getDefaultAudioFormat(getContext(), wrappedAudioStreams.getStreamsList());
selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll()); selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());
@ -331,7 +306,8 @@ public class DownloadDialog extends DialogFragment
dialogBinding.threads.setProgress(threads - 1); dialogBinding.threads.setProgress(threads - 1);
dialogBinding.threads.setOnSeekBarChangeListener(new SimpleOnSeekBarChangeListener() { dialogBinding.threads.setOnSeekBarChangeListener(new SimpleOnSeekBarChangeListener() {
@Override @Override
public void onProgressChanged(@NonNull final SeekBar seekbar, final int progress, public void onProgressChanged(@NonNull final SeekBar seekbar,
final int progress,
final boolean fromUser) { final boolean fromUser) {
final int newProgress = progress + 1; final int newProgress = progress + 1;
prefs.edit().putInt(getString(R.string.default_download_threads), newProgress) prefs.edit().putInt(getString(R.string.default_download_threads), newProgress)
@ -476,7 +452,7 @@ public class DownloadDialog extends DialogFragment
result, getString(R.string.download_path_video_key), DownloadManager.TAG_VIDEO); result, getString(R.string.download_path_video_key), DownloadManager.TAG_VIDEO);
} }
private void requestDownloadSaveAsResult(final ActivityResult result) { private void requestDownloadSaveAsResult(@NonNull final ActivityResult result) {
if (result.getResultCode() != Activity.RESULT_OK) { if (result.getResultCode() != Activity.RESULT_OK) {
return; return;
} }
@ -493,8 +469,8 @@ public class DownloadDialog extends DialogFragment
return; return;
} }
final DocumentFile docFile final DocumentFile docFile = DocumentFile.fromSingleUri(context,
= DocumentFile.fromSingleUri(context, result.getData().getData()); result.getData().getData());
if (docFile == null) { if (docFile == null) {
showFailedDialog(R.string.general_error); showFailedDialog(R.string.general_error);
return; return;
@ -505,7 +481,7 @@ public class DownloadDialog extends DialogFragment
docFile.getType()); docFile.getType());
} }
private void requestDownloadPickFolderResult(final ActivityResult result, private void requestDownloadPickFolderResult(@NonNull final ActivityResult result,
final String key, final String key,
final String tag) { final String tag) {
if (result.getResultCode() != Activity.RESULT_OK) { if (result.getResultCode() != Activity.RESULT_OK) {
@ -525,12 +501,11 @@ public class DownloadDialog extends DialogFragment
StoredDirectoryHelper.PERMISSION_FLAGS); StoredDirectoryHelper.PERMISSION_FLAGS);
} }
PreferenceManager.getDefaultSharedPreferences(context).edit() PreferenceManager.getDefaultSharedPreferences(context).edit().putString(key,
.putString(key, uri.toString()).apply(); uri.toString()).apply();
try { try {
final StoredDirectoryHelper mainStorage final StoredDirectoryHelper mainStorage = new StoredDirectoryHelper(context, uri, tag);
= new StoredDirectoryHelper(context, uri, tag);
checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp),
filenameTmp, mimeTmp); filenameTmp, mimeTmp);
} catch (final IOException e) { } catch (final IOException e) {
@ -568,8 +543,10 @@ public class DownloadDialog extends DialogFragment
} }
@Override @Override
public void onItemSelected(final AdapterView<?> parent, final View view, public void onItemSelected(final AdapterView<?> parent,
final int position, final long id) { final View view,
final int position,
final long id) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onItemSelected() called with: " Log.d(TAG, "onItemSelected() called with: "
+ "parent = [" + parent + "], view = [" + view + "], " + "parent = [" + parent + "], view = [" + view + "], "
@ -604,8 +581,10 @@ public class DownloadDialog extends DialogFragment
final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0; final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0;
final boolean isSubtitleStreamsAvailable = subtitleStreamsAdapter.getCount() > 0; final boolean isSubtitleStreamsAvailable = subtitleStreamsAdapter.getCount() > 0;
dialogBinding.audioButton.setVisibility(isAudioStreamsAvailable ? View.VISIBLE : View.GONE); dialogBinding.audioButton.setVisibility(isAudioStreamsAvailable ? View.VISIBLE
dialogBinding.videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE); : View.GONE);
dialogBinding.videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE
: View.GONE);
dialogBinding.subtitleButton.setVisibility(isSubtitleStreamsAvailable dialogBinding.subtitleButton.setVisibility(isSubtitleStreamsAvailable
? View.VISIBLE : View.GONE); ? View.VISIBLE : View.GONE);
@ -647,7 +626,7 @@ public class DownloadDialog extends DialogFragment
dialogBinding.subtitleButton.setEnabled(enabled); dialogBinding.subtitleButton.setEnabled(enabled);
} }
private int getSubtitleIndexBy(final List<SubtitlesStream> streams) { private int getSubtitleIndexBy(@NonNull final List<SubtitlesStream> streams) {
final Localization preferredLocalization = NewPipe.getPreferredLocalization(); final Localization preferredLocalization = NewPipe.getPreferredLocalization();
int candidate = 0; int candidate = 0;
@ -673,8 +652,10 @@ public class DownloadDialog extends DialogFragment
return candidate; return candidate;
} }
@NonNull
private String getNameEditText() { private String getNameEditText() {
final String str = dialogBinding.fileName.getText().toString().trim(); final String str = Objects.requireNonNull(dialogBinding.fileName.getText()).toString()
.trim();
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str); return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
} }
@ -690,12 +671,8 @@ public class DownloadDialog extends DialogFragment
} }
private void launchDirectoryPicker(final ActivityResultLauncher<Intent> launcher) { private void launchDirectoryPicker(final ActivityResultLauncher<Intent> launcher) {
NoFileManagerSafeGuard.launchSafe( NoFileManagerSafeGuard.launchSafe(launcher, StoredDirectoryHelper.getPicker(context), TAG,
launcher, context);
StoredDirectoryHelper.getPicker(context),
TAG,
context
);
} }
private void prepareSelectedDownload() { private void prepareSelectedDownload() {
@ -716,7 +693,7 @@ public class DownloadDialog extends DialogFragment
if (format == MediaFormat.WEBMA_OPUS) { if (format == MediaFormat.WEBMA_OPUS) {
mimeTmp = "audio/ogg"; mimeTmp = "audio/ogg";
filenameTmp += "opus"; filenameTmp += "opus";
} else { } else if (format != null) {
mimeTmp = format.mimeType; mimeTmp = format.mimeType;
filenameTmp += format.suffix; filenameTmp += format.suffix;
} }
@ -725,22 +702,30 @@ public class DownloadDialog extends DialogFragment
selectedMediaType = getString(R.string.last_download_type_video_key); selectedMediaType = getString(R.string.last_download_type_video_key);
mainStorage = mainStorageVideo; mainStorage = mainStorageVideo;
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat(); format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
if (format != null) {
mimeTmp = format.mimeType; mimeTmp = format.mimeType;
filenameTmp += format.suffix; filenameTmp += format.suffix;
}
break; break;
case R.id.subtitle_button: case R.id.subtitle_button:
selectedMediaType = getString(R.string.last_download_type_subtitle_key); selectedMediaType = getString(R.string.last_download_type_subtitle_key);
mainStorage = mainStorageVideo; // subtitle & video files go together mainStorage = mainStorageVideo; // subtitle & video files go together
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat(); format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
if (format != null) {
mimeTmp = format.mimeType; mimeTmp = format.mimeType;
filenameTmp += (format == MediaFormat.TTML ? MediaFormat.SRT : format).suffix; }
if (format == MediaFormat.TTML) {
filenameTmp += MediaFormat.SRT.suffix;
} else if (format != null) {
filenameTmp += format.suffix;
}
break; break;
default: default:
throw new RuntimeException("No stream selected"); throw new RuntimeException("No stream selected");
} }
if (!askForSavePath if (!askForSavePath && (mainStorage == null
&& (mainStorage == null
|| mainStorage.isDirect() == NewPipeSettings.useStorageAccessFramework(context) || mainStorage.isDirect() == NewPipeSettings.useStorageAccessFramework(context)
|| mainStorage.isInvalidSafStorage())) { || mainStorage.isInvalidSafStorage())) {
// Pick new download folder if one of: // Pick new download folder if one of:
@ -774,18 +759,16 @@ public class DownloadDialog extends DialogFragment
initialPath = Uri.parse(initialSavePath.getAbsolutePath()); initialPath = Uri.parse(initialSavePath.getAbsolutePath());
} }
NoFileManagerSafeGuard.launchSafe( NoFileManagerSafeGuard.launchSafe(requestDownloadSaveAsLauncher,
requestDownloadSaveAsLauncher, StoredFileHelper.getNewPicker(context, filenameTmp, mimeTmp, initialPath), TAG,
StoredFileHelper.getNewPicker(context, filenameTmp, mimeTmp, initialPath), context);
TAG,
context
);
return; return;
} }
// check for existing file with the same name // check for existing file with the same name
checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp, mimeTmp); checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp,
mimeTmp);
// remember the last media type downloaded by the user // remember the last media type downloaded by the user
prefs.edit().putString(getString(R.string.last_used_download_type), selectedMediaType) prefs.edit().putString(getString(R.string.last_used_download_type), selectedMediaType)
@ -793,7 +776,8 @@ public class DownloadDialog extends DialogFragment
} }
private void checkSelectedDownload(final StoredDirectoryHelper mainStorage, private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
final Uri targetFile, final String filename, final Uri targetFile,
final String filename,
final String mime) { final String mime) {
StoredFileHelper storage; StoredFileHelper storage;
@ -954,7 +938,7 @@ public class DownloadDialog extends DialogFragment
storage.truncate(); storage.truncate();
} }
} catch (final IOException e) { } catch (final IOException e) {
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e); Log.e(TAG, "Failed to truncate the file: " + storage.getUri().toString(), e);
showFailedDialog(R.string.overwrite_failed); showFailedDialog(R.string.overwrite_failed);
return; return;
} }
@ -999,8 +983,8 @@ public class DownloadDialog extends DialogFragment
} }
psArgs = null; psArgs = null;
final long videoSize = wrappedVideoStreams final long videoSize = wrappedVideoStreams.getSizeInBytes(
.getSizeInBytes((VideoStream) selectedStream); (VideoStream) selectedStream);
// set nearLength, only, if both sizes are fetched or known. This probably // set nearLength, only, if both sizes are fetched or known. This probably
// does not work on slow networks but is later updated in the downloader // does not work on slow networks but is later updated in the downloader
@ -1016,7 +1000,7 @@ public class DownloadDialog extends DialogFragment
if (selectedStream.getFormat() == MediaFormat.TTML) { if (selectedStream.getFormat() == MediaFormat.TTML) {
psName = Postprocessing.ALGORITHM_TTML_CONVERTER; psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
psArgs = new String[]{ psArgs = new String[] {
selectedStream.getFormat().getSuffix(), selectedStream.getFormat().getSuffix(),
"false" // ignore empty frames "false" // ignore empty frames
}; };
@ -1027,17 +1011,22 @@ public class DownloadDialog extends DialogFragment
} }
if (secondaryStream == null) { if (secondaryStream == null) {
urls = new String[]{ urls = new String[] {
selectedStream.getUrl() selectedStream.getContent()
}; };
recoveryInfo = new MissionRecoveryInfo[]{ recoveryInfo = new MissionRecoveryInfo[] {
new MissionRecoveryInfo(selectedStream) new MissionRecoveryInfo(selectedStream)
}; };
} else { } else {
urls = new String[]{ if (secondaryStream.getDeliveryMethod() != PROGRESSIVE_HTTP) {
selectedStream.getUrl(), secondaryStream.getUrl() throw new IllegalArgumentException("Unsupported stream delivery format"
+ secondaryStream.getDeliveryMethod());
}
urls = new String[] {
selectedStream.getContent(), secondaryStream.getContent()
}; };
recoveryInfo = new MissionRecoveryInfo[]{new MissionRecoveryInfo(selectedStream), recoveryInfo = new MissionRecoveryInfo[] {new MissionRecoveryInfo(selectedStream),
new MissionRecoveryInfo(secondaryStream)}; new MissionRecoveryInfo(secondaryStream)};
} }

View file

@ -7,13 +7,13 @@ import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.Info import org.schabi.newpipe.extractor.Info
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException
import org.schabi.newpipe.extractor.exceptions.ExtractionException import org.schabi.newpipe.extractor.exceptions.ExtractionException
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException
import org.schabi.newpipe.ktx.isNetworkRelated import org.schabi.newpipe.ktx.isNetworkRelated
import org.schabi.newpipe.util.ServiceHelper
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
@ -65,7 +65,7 @@ class ErrorInfo(
constructor(throwable: Throwable, userAction: UserAction, request: String) : constructor(throwable: Throwable, userAction: UserAction, request: String) :
this(throwable, userAction, SERVICE_NONE, request) this(throwable, userAction, SERVICE_NONE, request)
constructor(throwable: Throwable, userAction: UserAction, request: String, serviceId: Int) : constructor(throwable: Throwable, userAction: UserAction, request: String, serviceId: Int) :
this(throwable, userAction, NewPipe.getNameOfService(serviceId), request) this(throwable, userAction, ServiceHelper.getNameOfServiceById(serviceId), request)
constructor(throwable: Throwable, userAction: UserAction, request: String, info: Info?) : constructor(throwable: Throwable, userAction: UserAction, request: String, info: Info?) :
this(throwable, userAction, getInfoServiceName(info), request) this(throwable, userAction, getInfoServiceName(info), request)
@ -73,7 +73,7 @@ class ErrorInfo(
constructor(throwable: List<Throwable>, userAction: UserAction, request: String) : constructor(throwable: List<Throwable>, userAction: UserAction, request: String) :
this(throwable, userAction, SERVICE_NONE, request) this(throwable, userAction, SERVICE_NONE, request)
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, serviceId: Int) : constructor(throwable: List<Throwable>, userAction: UserAction, request: String, serviceId: Int) :
this(throwable, userAction, NewPipe.getNameOfService(serviceId), request) this(throwable, userAction, ServiceHelper.getNameOfServiceById(serviceId), request)
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, info: Info?) : constructor(throwable: List<Throwable>, userAction: UserAction, request: String, info: Info?) :
this(throwable, userAction, getInfoServiceName(info), request) this(throwable, userAction, getInfoServiceName(info), request)
@ -95,7 +95,7 @@ class ErrorInfo(
Array(throwable.size) { i -> getStackTrace(throwable[i]) } Array(throwable.size) { i -> getStackTrace(throwable[i]) }
private fun getInfoServiceName(info: Info?) = private fun getInfoServiceName(info: Info?) =
if (info == null) SERVICE_NONE else NewPipe.getNameOfService(info.serviceId) if (info == null) SERVICE_NONE else ServiceHelper.getNameOfServiceById(info.serviceId)
@StringRes @StringRes
private fun getMessageStringId( private fun getMessageStringId(

View file

@ -15,7 +15,6 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import org.schabi.newpipe.MainActivity import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
@ -106,7 +105,7 @@ class ErrorPanelHelper(
if (!isNullOrEmpty((errorInfo.throwable as AccountTerminatedException).message)) { if (!isNullOrEmpty((errorInfo.throwable as AccountTerminatedException).message)) {
errorServiceInfoTextView.text = context.resources.getString( errorServiceInfoTextView.text = context.resources.getString(
R.string.service_provides_reason, R.string.service_provides_reason,
NewPipe.getNameOfService(ServiceHelper.getSelectedServiceId(context)) ServiceHelper.getSelectedService(context)?.serviceInfo?.name ?: "<unknown>"
) )
errorServiceInfoTextView.isVisible = true errorServiceInfoTextView.isVisible = true

View file

@ -31,6 +31,7 @@ import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator; import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.Toast;
import androidx.annotation.AttrRes; import androidx.annotation.AttrRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -97,6 +98,7 @@ 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.PicassoHelper; import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.util.SponsorBlockUtils; import org.schabi.newpipe.util.SponsorBlockUtils;
import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
@ -125,6 +127,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientat
import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired; import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired;
import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET; import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET;
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams;
public final class VideoDetailFragment public final class VideoDetailFragment
extends BaseStateFragment<StreamInfo> extends BaseStateFragment<StreamInfo>
@ -192,8 +195,6 @@ public final class VideoDetailFragment
@Nullable @Nullable
private Disposable videoSegmentsSubscriber = null; private Disposable videoSegmentsSubscriber = null;
private List<VideoStream> sortedVideoStreams;
private int selectedVideoStreamIndex = -1;
private BottomSheetBehavior<FrameLayout> bottomSheetBehavior; private BottomSheetBehavior<FrameLayout> bottomSheetBehavior;
private BroadcastReceiver broadcastReceiver; private BroadcastReceiver broadcastReceiver;
@ -672,8 +673,7 @@ public final class VideoDetailFragment
binding.detailControlsCrashThePlayer.setOnClickListener( binding.detailControlsCrashThePlayer.setOnClickListener(
v -> VideoDetailPlayerCrasher.onCrashThePlayer( v -> VideoDetailPlayerCrasher.onCrashThePlayer(
this.getContext(), this.getContext(),
this.player, this.player)
getLayoutInflater())
); );
} }
@ -1102,9 +1102,6 @@ public final class VideoDetailFragment
} }
private void openBackgroundPlayer(final boolean append) { private void openBackgroundPlayer(final boolean append) {
final AudioStream audioStream = currentInfo.getAudioStreams()
.get(ListHelper.getDefaultAudioFormat(activity, currentInfo.getAudioStreams()));
final boolean useExternalAudioPlayer = PreferenceManager final boolean useExternalAudioPlayer = PreferenceManager
.getDefaultSharedPreferences(activity) .getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false); .getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
@ -1119,7 +1116,17 @@ public final class VideoDetailFragment
if (!useExternalAudioPlayer) { if (!useExternalAudioPlayer) {
openNormalBackgroundPlayer(append); openNormalBackgroundPlayer(append);
} else { } else {
startOnExternalPlayer(activity, currentInfo, audioStream); final List<AudioStream> audioStreams = getUrlAndNonTorrentStreams(
currentInfo.getAudioStreams());
final int index = ListHelper.getDefaultAudioFormat(activity, audioStreams);
if (index == -1) {
Toast.makeText(activity, R.string.no_audio_streams_available_for_external_players,
Toast.LENGTH_SHORT).show();
return;
}
startOnExternalPlayer(activity, currentInfo, audioStreams.get(index));
} }
} }
@ -1635,14 +1642,6 @@ public final class VideoDetailFragment
binding.detailToggleSecondaryControlsView.setVisibility(View.VISIBLE); binding.detailToggleSecondaryControlsView.setVisibility(View.VISIBLE);
binding.detailSecondaryControlPanel.setVisibility(View.GONE); binding.detailSecondaryControlPanel.setVisibility(View.GONE);
sortedVideoStreams = ListHelper.getSortedStreamVideosList(
activity,
info.getVideoStreams(),
info.getVideoOnlyStreams(),
false,
false);
selectedVideoStreamIndex = ListHelper
.getDefaultResolutionIndex(activity, sortedVideoStreams);
updateProgressInfo(info); updateProgressInfo(info);
initThumbnailViews(info); initThumbnailViews(info);
showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView, showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
@ -1668,8 +1667,8 @@ public final class VideoDetailFragment
} }
} }
binding.detailControlsDownload.setVisibility(info.getStreamType() == StreamType.LIVE_STREAM binding.detailControlsDownload.setVisibility(
|| info.getStreamType() == StreamType.AUDIO_LIVE_STREAM ? View.GONE : View.VISIBLE); StreamTypeUtil.isLiveStream(info.getStreamType()) ? View.GONE : View.VISIBLE);
binding.detailControlsBackground.setVisibility(info.getAudioStreams().isEmpty() binding.detailControlsBackground.setVisibility(info.getAudioStreams().isEmpty()
? View.GONE : View.VISIBLE); ? View.GONE : View.VISIBLE);
@ -1727,18 +1726,12 @@ public final class VideoDetailFragment
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(videoSegments -> { .subscribe(videoSegments -> {
try { try {
final DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo); final DownloadDialog downloadDialog = new DownloadDialog(activity, currentInfo);
downloadDialog.setVideoStreams(sortedVideoStreams);
downloadDialog.setAudioStreams(currentInfo.getAudioStreams());
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.setSubtitleStreams(currentInfo.getSubtitles());
downloadDialog.setVideoSegments(videoSegments); downloadDialog.setVideoSegments(videoSegments);
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog"); downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (final Exception e) { } catch (final Exception e) {
ErrorUtil.showSnackbar(activity, ErrorUtil.showSnackbar(activity, new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG,
new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog", currentInfo));
"Showing download dialog",
currentInfo));
} }
}); });
} }
@ -1765,8 +1758,7 @@ public final class VideoDetailFragment
binding.detailPositionView.setVisibility(View.GONE); binding.detailPositionView.setVisibility(View.GONE);
// TODO: Remove this check when separation of concerns is done. // TODO: Remove this check when separation of concerns is done.
// (live streams weren't getting updated because they are mixed) // (live streams weren't getting updated because they are mixed)
if (!info.getStreamType().equals(StreamType.LIVE_STREAM) if (!StreamTypeUtil.isLiveStream(info.getStreamType())) {
&& !info.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
return; return;
} }
} else { } else {
@ -2194,25 +2186,52 @@ public final class VideoDetailFragment
} }
private void showExternalPlaybackDialog() { private void showExternalPlaybackDialog() {
if (sortedVideoStreams == null) { if (currentInfo == null) {
return; return;
} }
final CharSequence[] resolutions = new CharSequence[sortedVideoStreams.size()];
for (int i = 0; i < sortedVideoStreams.size(); i++) { final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
resolutions[i] = sortedVideoStreams.get(i).getResolution(); builder.setTitle(R.string.select_quality_external_players);
} builder.setNeutralButton(R.string.open_in_browser, (dialog, i) ->
final AlertDialog.Builder builder = new AlertDialog.Builder(activity) ShareUtils.openUrlInBrowser(requireActivity(), url));
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.open_in_browser, (dialog, i) -> final List<VideoStream> videoStreamsForExternalPlayers =
ShareUtils.openUrlInBrowser(requireActivity(), url) ListHelper.getSortedStreamVideosList(
activity,
getUrlAndNonTorrentStreams(currentInfo.getVideoStreams()),
getUrlAndNonTorrentStreams(currentInfo.getVideoOnlyStreams()),
false,
false
); );
// Maybe there are no video streams available, show just `open in browser` button
if (resolutions.length > 0) { if (videoStreamsForExternalPlayers.isEmpty()) {
builder.setSingleChoiceItems(resolutions, selectedVideoStreamIndex, (dialog, i) -> { builder.setMessage(R.string.no_video_streams_available_for_external_players);
dialog.dismiss(); builder.setPositiveButton(R.string.ok, null);
startOnExternalPlayer(activity, currentInfo, sortedVideoStreams.get(i));
} else {
final int selectedVideoStreamIndexForExternalPlayers =
ListHelper.getDefaultResolutionIndex(activity, videoStreamsForExternalPlayers);
final CharSequence[] resolutions =
new CharSequence[videoStreamsForExternalPlayers.size()];
for (int i = 0; i < videoStreamsForExternalPlayers.size(); i++) {
resolutions[i] = videoStreamsForExternalPlayers.get(i).getResolution();
} }
);
builder.setSingleChoiceItems(resolutions, selectedVideoStreamIndexForExternalPlayers,
null);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.ok, (dialog, i) -> {
final int index = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
// We don't have to manage the index validity because if there is no stream
// available for external players, this code will be not executed and if there is
// no stream which matches the default resolution, 0 is returned by
// ListHelper.getDefaultResolutionIndex.
// The index cannot be outside the bounds of the list as its always between 0 and
// the list size - 1, .
startOnExternalPlayer(activity, currentInfo,
videoStreamsForExternalPlayers.get(index));
});
} }
builder.show(); builder.show();
} }

View file

@ -1,5 +1,9 @@
package org.schabi.newpipe.fragments.detail; package org.schabi.newpipe.fragments.detail;
import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW;
import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_DECODING_FAILED;
import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_UNSPECIFIED;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
@ -29,10 +33,6 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW;
import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_DECODING_FAILED;
import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_UNSPECIFIED;
/** /**
* Outsourced logic for crashing the player in the {@link VideoDetailFragment}. * Outsourced logic for crashing the player in the {@link VideoDetailFragment}.
*/ */
@ -97,8 +97,7 @@ public final class VideoDetailPlayerCrasher {
public static void onCrashThePlayer( public static void onCrashThePlayer(
@NonNull final Context context, @NonNull final Context context,
@Nullable final Player player, @Nullable final Player player
@NonNull final LayoutInflater layoutInflater
) { ) {
if (player == null) { if (player == null) {
Log.d(TAG, "Player is not available"); Log.d(TAG, "Player is not available");
@ -109,16 +108,15 @@ public final class VideoDetailPlayerCrasher {
} }
// -- Build the dialog/UI -- // -- Build the dialog/UI --
final Context themeWrapperContext = getThemeWrapperContext(context); final Context themeWrapperContext = getThemeWrapperContext(context);
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext); final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
final RadioGroup radioGroup = SingleChoiceDialogViewBinding.inflate(layoutInflater)
.list;
final AlertDialog alertDialog = new AlertDialog.Builder(getThemeWrapperContext(context)) final SingleChoiceDialogViewBinding binding =
SingleChoiceDialogViewBinding.inflate(inflater);
final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapperContext)
.setTitle("Choose an exception") .setTitle("Choose an exception")
.setView(radioGroup) .setView(binding.getRoot())
.setCancelable(true) .setCancelable(true)
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.create(); .create();
@ -136,11 +134,9 @@ public final class VideoDetailPlayerCrasher {
); );
radioButton.setOnClickListener(v -> { radioButton.setOnClickListener(v -> {
tryCrashPlayerWith(player, entry.getValue().get()); tryCrashPlayerWith(player, entry.getValue().get());
if (alertDialog != null) {
alertDialog.cancel(); alertDialog.cancel();
}
}); });
radioGroup.addView(radioButton); binding.list.addView(radioButton);
} }
alertDialog.show(); alertDialog.show();

View file

@ -77,6 +77,8 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
private final CompositeDisposable disposables = new CompositeDisposable(); private final CompositeDisposable disposables = new CompositeDisposable();
private Disposable subscribeButtonMonitor; private Disposable subscribeButtonMonitor;
private boolean channelContentNotSupported = false;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Views // Views
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -130,6 +132,7 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState); super.onViewCreated(rootView, savedInstanceState);
channelBinding = FragmentChannelBinding.bind(rootView); channelBinding = FragmentChannelBinding.bind(rootView);
showContentNotSupportedIfNeeded();
} }
@Override @Override
@ -524,9 +527,12 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
playlistControlBinding.getRoot().setVisibility(View.GONE); playlistControlBinding.getRoot().setVisibility(View.GONE);
} }
channelContentNotSupported = false;
for (final Throwable throwable : result.getErrors()) { for (final Throwable throwable : result.getErrors()) {
if (throwable instanceof ContentNotSupportedException) { if (throwable instanceof ContentNotSupportedException) {
showContentNotSupported(); channelContentNotSupported = true;
showContentNotSupportedIfNeeded();
break;
} }
} }
@ -558,7 +564,13 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
}); });
} }
private void showContentNotSupported() { private void showContentNotSupportedIfNeeded() {
// channelBinding might not be initialized when handleResult() is called
// (e.g. after rotating the screen, #6696)
if (!channelContentNotSupported || channelBinding == null) {
return;
}
channelBinding.errorContentNotSupported.setVisibility(View.VISIBLE); channelBinding.errorContentNotSupported.setVisibility(View.VISIBLE);
channelBinding.channelKaomoji.setText("(︶︹︺)"); channelBinding.channelKaomoji.setText("(︶︹︺)");
channelBinding.channelKaomoji.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f); channelBinding.channelKaomoji.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f);

View file

@ -4,7 +4,6 @@ import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@ -18,7 +17,6 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import com.google.android.material.shape.CornerFamily; import com.google.android.material.shape.CornerFamily;
import com.google.android.material.shape.ShapeAppearanceModel; import com.google.android.material.shape.ShapeAppearanceModel;
@ -28,6 +26,7 @@ import org.reactivestreams.Subscription;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.PlaylistControlBinding; import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.databinding.PlaylistHeaderBinding; import org.schabi.newpipe.databinding.PlaylistHeaderBinding;
import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorInfo;
@ -41,6 +40,8 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.player.MainPlayer.PlayerType; import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
@ -49,13 +50,13 @@ import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
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;
@ -237,6 +238,17 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
case R.id.menu_item_bookmark: case R.id.menu_item_bookmark:
onBookmarkClicked(); onBookmarkClicked();
break; break;
case R.id.menu_item_append_playlist:
disposables.add(PlaylistDialog.createCorrespondingDialog(
getContext(),
getPlayQueue()
.getStreams()
.stream()
.map(StreamEntity::new)
.collect(Collectors.toList()),
dialog -> dialog.show(getFM(), TAG)
));
break;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -293,10 +305,8 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
.setAllCorners(CornerFamily.ROUNDED, 0f) .setAllCorners(CornerFamily.ROUNDED, 0f)
.build(); // this turns the image back into a square .build(); // this turns the image back into a square
headerBinding.uploaderAvatarView.setShapeAppearanceModel(model); headerBinding.uploaderAvatarView.setShapeAppearanceModel(model);
headerBinding.uploaderAvatarView.setStrokeColor( headerBinding.uploaderAvatarView.setStrokeColor(AppCompatResources
ColorStateList.valueOf(ContextCompat.getColor( .getColorStateList(requireContext(), R.color.transparent_background_color));
requireContext(), R.color.transparent_background_color))
);
headerBinding.uploaderAvatarView.setImageDrawable( headerBinding.uploaderAvatarView.setImageDrawable(
AppCompatResources.getDrawable(requireContext(), AppCompatResources.getDrawable(requireContext(),
R.drawable.ic_radio) R.drawable.ic_radio)

View file

@ -24,6 +24,7 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.KoreUtils;
import java.util.ArrayList; import java.util.ArrayList;
@ -269,8 +270,7 @@ public final class InfoItemDialog {
*/ */
public Builder addStartHereEntries() { public Builder addStartHereEntries() {
addEntry(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND); addEntry(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND);
if (infoItem.getStreamType() != StreamType.AUDIO_STREAM if (!StreamTypeUtil.isAudio(infoItem.getStreamType())) {
&& infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) {
addEntry(StreamDialogDefaultEntry.START_HERE_ON_POPUP); addEntry(StreamDialogDefaultEntry.START_HERE_ON_POPUP);
} }
return this; return this;
@ -285,9 +285,7 @@ public final class InfoItemDialog {
final boolean isWatchHistoryEnabled = PreferenceManager final boolean isWatchHistoryEnabled = PreferenceManager
.getDefaultSharedPreferences(context) .getDefaultSharedPreferences(context)
.getBoolean(context.getString(R.string.enable_watch_history_key), false); .getBoolean(context.getString(R.string.enable_watch_history_key), false);
if (isWatchHistoryEnabled if (isWatchHistoryEnabled && !StreamTypeUtil.isLiveStream(infoItem.getStreamType())) {
&& infoItem.getStreamType() != StreamType.LIVE_STREAM
&& infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) {
addEntry(StreamDialogDefaultEntry.MARK_AS_WATCHED); addEntry(StreamDialogDefaultEntry.MARK_AS_WATCHED);
} }
return this; return this;

View file

@ -11,12 +11,12 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.views.AnimatedProgressBar; import org.schabi.newpipe.views.AnimatedProgressBar;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -70,8 +70,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
} else { } else {
itemProgressView.setVisibility(View.GONE); itemProgressView.setVisibility(View.GONE);
} }
} else if (item.getStreamType() == StreamType.LIVE_STREAM } else if (StreamTypeUtil.isLiveStream(item.getStreamType())) {
|| item.getStreamType() == StreamType.AUDIO_LIVE_STREAM) {
itemDurationView.setText(R.string.duration_live); itemDurationView.setText(R.string.duration_live);
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
R.color.live_duration_background_color)); R.color.live_duration_background_color));
@ -96,9 +95,10 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
case VIDEO_STREAM: case VIDEO_STREAM:
case LIVE_STREAM: case LIVE_STREAM:
case AUDIO_LIVE_STREAM: case AUDIO_LIVE_STREAM:
case POST_LIVE_STREAM:
case POST_LIVE_AUDIO_STREAM:
enableLongClick(item); enableLongClick(item);
break; break;
case FILE:
case NONE: case NONE:
default: default:
disableLongClick(); disableLongClick();
@ -114,7 +114,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
final StreamStateEntity state final StreamStateEntity state
= historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
if (state != null && item.getDuration() > 0 if (state != null && item.getDuration() > 0
&& item.getStreamType() != StreamType.LIVE_STREAM) { && !StreamTypeUtil.isLiveStream(item.getStreamType())) {
itemProgressView.setMax((int) item.getDuration()); itemProgressView.setMax((int) item.getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) { if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS

View file

@ -300,14 +300,7 @@ private fun View.animateLightSlideAndAlpha(enterOrExit: Boolean, duration: Long,
} }
} }
fun View.slideUp( @JvmOverloads
duration: Long,
delay: Long,
@FloatRange(from = 0.0, to = 1.0) translationPercent: Float
) {
slideUp(duration, delay, translationPercent, null)
}
fun View.slideUp( fun View.slideUp(
duration: Long, duration: Long,
delay: Long = 0L, delay: Long = 0L,

View file

@ -25,7 +25,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
@ -37,7 +36,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import androidx.annotation.AttrRes
import androidx.annotation.Nullable import androidx.annotation.Nullable
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
@ -80,6 +78,7 @@ import org.schabi.newpipe.util.DeviceUtils
import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams
import org.schabi.newpipe.util.ThemeHelper.resolveDrawable
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.util.function.Consumer import java.util.function.Consumer
@ -579,19 +578,6 @@ class FeedFragment : BaseStateFragment<FeedState>() {
lastNewItemsCount = highlightCount lastNewItemsCount = highlightCount
} }
private fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? {
return androidx.core.content.ContextCompat.getDrawable(
context,
android.util.TypedValue().apply {
context.theme.resolveAttribute(
attrResId,
this,
true
)
}.resourceId
)
}
private fun showNewItemsLoaded() { private fun showNewItemsLoaded() {
tryGetNewItemsLoadedButton()?.clearAnimation() tryGetNewItemsLoadedButton()?.clearAnimation()
tryGetNewItemsLoadedButton() tryGetNewItemsLoadedButton()

View file

@ -14,6 +14,8 @@ import org.schabi.newpipe.databinding.ListStreamItemBinding
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_STREAM import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.POST_LIVE_AUDIO_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.POST_LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.VIDEO_STREAM import org.schabi.newpipe.extractor.stream.StreamType.VIDEO_STREAM
import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.PicassoHelper import org.schabi.newpipe.util.PicassoHelper
@ -109,7 +111,7 @@ data class StreamItem(
} }
override fun isLongClickable() = when (stream.streamType) { override fun isLongClickable() = when (stream.streamType) {
AUDIO_STREAM, VIDEO_STREAM, LIVE_STREAM, AUDIO_LIVE_STREAM -> true AUDIO_STREAM, VIDEO_STREAM, LIVE_STREAM, AUDIO_LIVE_STREAM, POST_LIVE_STREAM, POST_LIVE_AUDIO_STREAM -> true
else -> false else -> false
} }

View file

@ -128,13 +128,11 @@ public class HistoryRecordManager {
// Add a history entry // Add a history entry
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId); final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
if (latestEntry != null) { if (latestEntry == null) {
streamHistoryTable.delete(latestEntry); // never actually viewed: add history entry but with 0 views
latestEntry.setAccessDate(currentTime); return streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime, 0));
latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1);
return streamHistoryTable.insert(latestEntry);
} else { } else {
return streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime)); return 0L;
} }
})).subscribeOn(Schedulers.io()); })).subscribeOn(Schedulers.io());
} }
@ -155,7 +153,8 @@ public class HistoryRecordManager {
latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1); latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1);
return streamHistoryTable.insert(latestEntry); return streamHistoryTable.insert(latestEntry);
} else { } else {
return streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime)); // just viewed for the first time: set 1 view
return streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime, 1));
} }
})).subscribeOn(Schedulers.io()); })).subscribeOn(Schedulers.io());
} }

View file

@ -11,12 +11,12 @@ import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.views.AnimatedProgressBar; import org.schabi.newpipe.views.AnimatedProgressBar;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -59,7 +59,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
itemVideoTitleView.setText(item.getStreamEntity().getTitle()); itemVideoTitleView.setText(item.getStreamEntity().getTitle());
itemAdditionalDetailsView.setText(Localization itemAdditionalDetailsView.setText(Localization
.concatenateStrings(item.getStreamEntity().getUploader(), .concatenateStrings(item.getStreamEntity().getUploader(),
NewPipe.getNameOfService(item.getStreamEntity().getServiceId()))); ServiceHelper.getNameOfServiceById(item.getStreamEntity().getServiceId())));
if (item.getStreamEntity().getDuration() > 0) { if (item.getStreamEntity().getDuration() > 0) {
itemDurationView.setText(Localization itemDurationView.setText(Localization

View file

@ -11,12 +11,12 @@ import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.views.AnimatedProgressBar; import org.schabi.newpipe.views.AnimatedProgressBar;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -70,11 +70,12 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
private String getStreamInfoDetailLine(final StreamStatisticsEntry entry, private String getStreamInfoDetailLine(final StreamStatisticsEntry entry,
final DateTimeFormatter dateTimeFormatter) { final DateTimeFormatter dateTimeFormatter) {
final String watchCount = Localization return Localization.concatenateStrings(
.shortViewCount(itemBuilder.getContext(), entry.getWatchCount()); // watchCount
final String uploadDate = dateTimeFormatter.format(entry.getLatestAccessDate()); Localization.shortViewCount(itemBuilder.getContext(), entry.getWatchCount()),
final String serviceName = NewPipe.getNameOfService(entry.getStreamEntity().getServiceId()); dateTimeFormatter.format(entry.getLatestAccessDate()),
return Localization.concatenateStrings(watchCount, uploadDate, serviceName); // serviceName
ServiceHelper.getNameOfServiceById(entry.getStreamEntity().getServiceId()));
} }
@Override @Override

View file

@ -5,11 +5,11 @@ import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ServiceHelper;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -39,9 +39,9 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
// Here is where the uploader name is set in the bookmarked playlists library // Here is where the uploader name is set in the bookmarked playlists library
if (!TextUtils.isEmpty(item.getUploader())) { if (!TextUtils.isEmpty(item.getUploader())) {
itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(),
NewPipe.getNameOfService(item.getServiceId()))); ServiceHelper.getNameOfServiceById(item.getServiceId())));
} else { } else {
itemUploaderView.setText(NewPipe.getNameOfService(item.getServiceId())); itemUploaderView.setText(ServiceHelper.getNameOfServiceById(item.getServiceId()));
} }
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);

View file

@ -419,9 +419,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
final PlaylistStreamEntry playlistItem = playlistIter.next(); final PlaylistStreamEntry playlistItem = playlistIter.next();
final int indexInHistory = Collections.binarySearch(historyStreamIds, final int indexInHistory = Collections.binarySearch(historyStreamIds,
playlistItem.getStreamId()); playlistItem.getStreamId());
final StreamStateEntity streamStateEntity = streamStatesIter.next();
final long duration = playlistItem.toStreamInfoItem().getDuration();
final boolean hasState = streamStatesIter.next() != null; if (indexInHistory < 0 || (streamStateEntity != null
if (indexInHistory < 0 || hasState) { && !streamStateEntity.isFinished(duration))) {
notWatchedItems.add(playlistItem); notWatchedItems.add(playlistItem);
} else if (!thumbnailVideoRemoved } else if (!thumbnailVideoRemoved
&& playlistManager.getPlaylistThumbnail(playlistId) && playlistManager.getPlaylistThumbnail(playlistId)

View file

@ -1,24 +1,24 @@
package org.schabi.newpipe.local.subscription package org.schabi.newpipe.local.subscription
import android.app.Activity import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem
import android.view.SubMenu
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.xwray.groupie.Group import com.xwray.groupie.Group
import com.xwray.groupie.GroupAdapter import com.xwray.groupie.GroupAdapter
@ -34,6 +34,7 @@ import org.schabi.newpipe.databinding.FeedItemCarouselBinding
import org.schabi.newpipe.databinding.FragmentSubscriptionBinding import org.schabi.newpipe.databinding.FragmentSubscriptionBinding
import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.error.UserAction import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.extractor.ServiceList
import org.schabi.newpipe.extractor.channel.ChannelInfoItem import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.fragments.BaseStateFragment import org.schabi.newpipe.fragments.BaseStateFragment
import org.schabi.newpipe.ktx.animate import org.schabi.newpipe.ktx.animate
@ -45,13 +46,10 @@ import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
import org.schabi.newpipe.local.subscription.item.FeedGroupAddItem import org.schabi.newpipe.local.subscription.item.FeedGroupAddItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem
import org.schabi.newpipe.local.subscription.item.FeedImportExportItem
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.IMPORT_COMPLETE_ACTION
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
@ -59,6 +57,7 @@ import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard
import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.streams.io.StoredFileHelper
import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
import org.schabi.newpipe.util.external_communication.ShareUtils import org.schabi.newpipe.util.external_communication.ShareUtils
@ -74,12 +73,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private lateinit var subscriptionManager: SubscriptionManager private lateinit var subscriptionManager: SubscriptionManager
private val disposables: CompositeDisposable = CompositeDisposable() private val disposables: CompositeDisposable = CompositeDisposable()
private var subscriptionBroadcastReceiver: BroadcastReceiver? = null
private val groupAdapter = GroupAdapter<GroupieViewHolder<FeedItemCarouselBinding>>() private val groupAdapter = GroupAdapter<GroupieViewHolder<FeedItemCarouselBinding>>()
private val feedGroupsSection = Section() private val feedGroupsSection = Section()
private var feedGroupsCarousel: FeedGroupCarouselItem? = null private var feedGroupsCarousel: FeedGroupCarouselItem? = null
private lateinit var importExportItem: FeedImportExportItem
private lateinit var feedGroupsSortMenuItem: HeaderWithMenuItem private lateinit var feedGroupsSortMenuItem: HeaderWithMenuItem
private val subscriptionsSection = Section() private val subscriptionsSection = Section()
@ -91,12 +87,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
@State @State
@JvmField @JvmField
var itemsListState: Parcelable? = null var itemsListState: Parcelable? = null
@State @State
@JvmField @JvmField
var feedGroupsListState: Parcelable? = null var feedGroupsListState: Parcelable? = null
@State
@JvmField
var importExportItemExpandedState: Boolean? = null
init { init {
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -120,20 +114,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
return inflater.inflate(R.layout.fragment_subscription, container, false) return inflater.inflate(R.layout.fragment_subscription, container, false)
} }
override fun onResume() {
super.onResume()
setupBroadcastReceiver()
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
itemsListState = binding.itemsList.layoutManager?.onSaveInstanceState() itemsListState = binding.itemsList.layoutManager?.onSaveInstanceState()
feedGroupsListState = feedGroupsCarousel?.onSaveInstanceState() feedGroupsListState = feedGroupsCarousel?.onSaveInstanceState()
importExportItemExpandedState = importExportItem.isExpanded
if (subscriptionBroadcastReceiver != null && activity != null) {
LocalBroadcastManager.getInstance(activity).unregisterReceiver(subscriptionBroadcastReceiver!!)
}
} }
override fun onDestroy() { override fun onDestroy() {
@ -150,28 +134,61 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
activity.supportActionBar?.setDisplayShowTitleEnabled(true) activity.supportActionBar?.setDisplayShowTitleEnabled(true)
activity.supportActionBar?.setTitle(R.string.tab_subscriptions) activity.supportActionBar?.setTitle(R.string.tab_subscriptions)
buildImportExportMenu(menu)
} }
private fun setupBroadcastReceiver() { private fun buildImportExportMenu(menu: Menu) {
if (activity == null) return // -- Import --
val importSubMenu = menu.addSubMenu(R.string.import_from)
if (subscriptionBroadcastReceiver != null) { addMenuItemToSubmenu(importSubMenu, R.string.previous_export) { onImportPreviousSelected() }
LocalBroadcastManager.getInstance(activity).unregisterReceiver(subscriptionBroadcastReceiver!!) .setIcon(R.drawable.ic_backup)
for (service in ServiceList.all()) {
val subscriptionExtractor = service.subscriptionExtractor ?: continue
val supportedSources = subscriptionExtractor.supportedSources
if (supportedSources.isEmpty()) continue
addMenuItemToSubmenu(importSubMenu, service.serviceInfo.name) {
onImportFromServiceSelected(service.serviceId)
}
.setIcon(ServiceHelper.getIcon(service.serviceId))
} }
val filters = IntentFilter() // -- Export --
filters.addAction(EXPORT_COMPLETE_ACTION) val exportSubMenu = menu.addSubMenu(R.string.export_to)
filters.addAction(IMPORT_COMPLETE_ACTION)
subscriptionBroadcastReceiver = object : BroadcastReceiver() { addMenuItemToSubmenu(exportSubMenu, R.string.file) { onExportSelected() }
override fun onReceive(context: Context, intent: Intent) { .setIcon(R.drawable.ic_save)
_binding?.itemsList?.post {
importExportItem.isExpanded = false
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
}
}
} }
LocalBroadcastManager.getInstance(activity).registerReceiver(subscriptionBroadcastReceiver!!, filters) private fun addMenuItemToSubmenu(
subMenu: SubMenu,
@StringRes title: Int,
onClick: Runnable
): MenuItem {
return setClickListenerToMenuItem(subMenu.add(title), onClick)
}
private fun addMenuItemToSubmenu(
subMenu: SubMenu,
title: String,
onClick: Runnable
): MenuItem {
return setClickListenerToMenuItem(subMenu.add(title), onClick)
}
private fun setClickListenerToMenuItem(
menuItem: MenuItem,
onClick: Runnable
): MenuItem {
menuItem.setOnMenuItemClickListener { _ ->
onClick.run()
true
}
return menuItem
} }
private fun onImportFromServiceSelected(serviceId: Int) { private fun onImportFromServiceSelected(serviceId: Int) {
@ -263,13 +280,14 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
subscriptionsSection.setPlaceholder(EmptyPlaceholderItem()) subscriptionsSection.setPlaceholder(EmptyPlaceholderItem())
subscriptionsSection.setHideWhenEmpty(true) subscriptionsSection.setHideWhenEmpty(true)
importExportItem = FeedImportExportItem( groupAdapter.add(
{ onImportPreviousSelected() }, Section(
{ onImportFromServiceSelected(it) }, HeaderWithMenuItem(
{ onExportSelected() }, getString(R.string.tab_subscriptions)
importExportItemExpandedState ?: false ),
listOf(subscriptionsSection)
)
) )
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
} }
override fun initViews(rootView: View, savedInstanceState: Bundle?) { override fun initViews(rootView: View, savedInstanceState: Bundle?) {
@ -371,13 +389,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
subscriptionsSection.update(result.subscriptions) subscriptionsSection.update(result.subscriptions)
subscriptionsSection.setHideWhenEmpty(false) subscriptionsSection.setHideWhenEmpty(false)
if (result.subscriptions.isEmpty() && importExportItemExpandedState == null) {
binding.itemsList.post {
importExportItem.isExpanded = true
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
}
}
if (itemsListState != null) { if (itemsListState != null) {
binding.itemsList.layoutManager?.onRestoreInstanceState(itemsListState) binding.itemsList.layoutManager?.onRestoreInstanceState(itemsListState)
itemsListState = null itemsListState = null

View file

@ -1,5 +1,11 @@
package org.schabi.newpipe.local.subscription; package org.schabi.newpipe.local.subscription;
import static org.schabi.newpipe.extractor.subscription.SubscriptionExtractor.ContentSource.CHANNEL_URL;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.CHANNEL_URL_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.INPUT_STREAM_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@ -40,12 +46,6 @@ import java.util.List;
import icepick.State; import icepick.State;
import static org.schabi.newpipe.extractor.subscription.SubscriptionExtractor.ContentSource.CHANNEL_URL;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.CHANNEL_URL_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.INPUT_STREAM_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE;
public class SubscriptionsImportFragment extends BaseFragment { public class SubscriptionsImportFragment extends BaseFragment {
@State @State
int currentServiceId = Constants.NO_SERVICE_ID; int currentServiceId = Constants.NO_SERVICE_ID;
@ -89,7 +89,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) { if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
ErrorUtil.showSnackbar(activity, ErrorUtil.showSnackbar(activity,
new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT, new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT,
NewPipe.getNameOfService(currentServiceId), ServiceHelper.getNameOfServiceById(currentServiceId),
"Service does not support importing subscriptions", "Service does not support importing subscriptions",
R.string.general_error)); R.string.general_error));
activity.finish(); activity.finish();

View file

@ -1,7 +1,6 @@
package org.schabi.newpipe.local.subscription.dialog package org.schabi.newpipe.local.subscription.dialog
import android.app.Dialog import android.app.Dialog
import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
@ -9,7 +8,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isGone import androidx.core.view.isGone
@ -127,7 +126,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
// KitKat doesn't apply container's theme to <include> content // KitKat doesn't apply container's theme to <include> content
val contrastColor = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.contrastColor)) val contrastColor = AppCompatResources.getColorStateList(requireContext(), R.color.contrastColor)
searchLayoutBinding.toolbarSearchEditText.setTextColor(contrastColor) searchLayoutBinding.toolbarSearchEditText.setTextColor(contrastColor)
searchLayoutBinding.toolbarSearchEditText.setHintTextColor(contrastColor.withAlpha(128)) searchLayoutBinding.toolbarSearchEditText.setHintTextColor(contrastColor.withAlpha(128))
ImageViewCompat.setImageTintList(searchLayoutBinding.toolbarSearchClearIcon, contrastColor) ImageViewCompat.setImageTintList(searchLayoutBinding.toolbarSearchClearIcon, contrastColor)

View file

@ -1,122 +0,0 @@
package org.schabi.newpipe.local.subscription.item
import android.graphics.Color
import android.graphics.PorterDuff
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
import com.xwray.groupie.viewbinding.BindableItem
import com.xwray.groupie.viewbinding.GroupieViewHolder
import org.schabi.newpipe.R
import org.schabi.newpipe.databinding.FeedImportExportGroupBinding
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.exceptions.ExtractionException
import org.schabi.newpipe.ktx.animateRotation
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.ThemeHelper
import org.schabi.newpipe.views.CollapsibleView
class FeedImportExportItem(
val onImportPreviousSelected: () -> Unit,
val onImportFromServiceSelected: (Int) -> Unit,
val onExportSelected: () -> Unit,
var isExpanded: Boolean = false
) : BindableItem<FeedImportExportGroupBinding>() {
companion object {
const val REFRESH_EXPANDED_STATUS = 123
}
override fun bind(viewBinding: FeedImportExportGroupBinding, position: Int, payloads: MutableList<Any>) {
if (payloads.contains(REFRESH_EXPANDED_STATUS)) {
viewBinding.importExportOptions.apply { if (isExpanded) expand() else collapse() }
return
}
super.bind(viewBinding, position, payloads)
}
override fun getLayout(): Int = R.layout.feed_import_export_group
override fun bind(viewBinding: FeedImportExportGroupBinding, position: Int) {
if (viewBinding.importFromOptions.childCount == 0) setupImportFromItems(viewBinding.importFromOptions)
if (viewBinding.exportToOptions.childCount == 0) setupExportToItems(viewBinding.exportToOptions)
expandIconListener?.let { viewBinding.importExportOptions.removeListener(it) }
expandIconListener = CollapsibleView.StateListener { newState ->
viewBinding.importExportExpandIcon.animateRotation(
250, if (newState == CollapsibleView.COLLAPSED) 0 else 180
)
}
viewBinding.importExportOptions.currentState = if (isExpanded) CollapsibleView.EXPANDED else CollapsibleView.COLLAPSED
viewBinding.importExportExpandIcon.rotation = if (isExpanded) 180F else 0F
viewBinding.importExportOptions.ready()
viewBinding.importExportOptions.addListener(expandIconListener)
viewBinding.importExport.setOnClickListener {
viewBinding.importExportOptions.switchState()
isExpanded = viewBinding.importExportOptions.currentState == CollapsibleView.EXPANDED
}
}
override fun unbind(viewHolder: GroupieViewHolder<FeedImportExportGroupBinding>) {
super.unbind(viewHolder)
expandIconListener?.let { viewHolder.binding.importExportOptions.removeListener(it) }
expandIconListener = null
}
override fun initializeViewBinding(view: View) = FeedImportExportGroupBinding.bind(view)
private var expandIconListener: CollapsibleView.StateListener? = null
private fun addItemView(title: String, @DrawableRes icon: Int, container: ViewGroup): View {
val itemRoot = View.inflate(container.context, R.layout.subscription_import_export_item, null)
val titleView = itemRoot.findViewById<TextView>(android.R.id.text1)
val iconView = itemRoot.findViewById<ImageView>(android.R.id.icon1)
titleView.text = title
iconView.setImageResource(icon)
container.addView(itemRoot)
return itemRoot
}
private fun setupImportFromItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView(
listHolder.context.getString(R.string.previous_export),
R.drawable.ic_backup, listHolder
)
previousBackupItem.setOnClickListener { onImportPreviousSelected() }
val iconColor = if (ThemeHelper.isLightThemeSelected(listHolder.context)) Color.BLACK else Color.WHITE
val services = listHolder.context.resources.getStringArray(R.array.service_list)
for (serviceName in services) {
try {
val service = NewPipe.getService(serviceName)
val subscriptionExtractor = service.subscriptionExtractor ?: continue
val supportedSources = subscriptionExtractor.supportedSources
if (supportedSources.isEmpty()) continue
val itemView = addItemView(serviceName, ServiceHelper.getIcon(service.serviceId), listHolder)
val iconView = itemView.findViewById<ImageView>(android.R.id.icon1)
iconView.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
itemView.setOnClickListener { onImportFromServiceSelected(service.serviceId) }
} catch (e: ExtractionException) {
throw RuntimeException("Services array contains an entry that it's not a valid service name ($serviceName)", e)
}
}
}
private fun setupExportToItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView(
listHolder.context.getString(R.string.file),
R.drawable.ic_save, listHolder
)
previousBackupItem.setOnClickListener { onExportSelected() }
}
}

View file

@ -102,7 +102,10 @@ public final class PlayQueueActivity extends AppCompatActivity
getMenuInflater().inflate(R.menu.menu_play_queue, m); getMenuInflater().inflate(R.menu.menu_play_queue, m);
getMenuInflater().inflate(R.menu.menu_play_queue_bg, m); getMenuInflater().inflate(R.menu.menu_play_queue_bg, m);
onMaybeMuteChanged(); onMaybeMuteChanged();
// to avoid null reference
if (player != null) {
onPlaybackParameterChanged(player.getPlaybackParameters()); onPlaybackParameterChanged(player.getPlaybackParameters());
}
return true; return true;
} }

View file

@ -88,6 +88,7 @@ import android.provider.Settings;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.Gravity; import android.view.Gravity;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -119,7 +120,6 @@ import androidx.appcompat.widget.PopupMenu;
import androidx.collection.ArraySet; import androidx.collection.ArraySet;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets; import androidx.core.graphics.Insets;
import androidx.core.view.GestureDetectorCompat;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@ -152,7 +152,6 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target; import com.squareup.picasso.Target;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.database.stream.model.StreamEntity;
@ -396,7 +395,7 @@ public final class Player implements
private static final float MAX_GESTURE_LENGTH = 0.75f; private static final float MAX_GESTURE_LENGTH = 0.75f;
private int maxGestureLength; // scaled private int maxGestureLength; // scaled
private GestureDetectorCompat gestureDetector; private GestureDetector gestureDetector;
private PlayerGestureListener playerGestureListener; private PlayerGestureListener playerGestureListener;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -444,7 +443,7 @@ public final class Player implements
setupBroadcastReceiver(); setupBroadcastReceiver();
trackSelector = new DefaultTrackSelector(context, PlayerHelper.getQualitySelector()); trackSelector = new DefaultTrackSelector(context, PlayerHelper.getQualitySelector());
final PlayerDataSource dataSource = new PlayerDataSource(context, DownloaderImpl.USER_AGENT, final PlayerDataSource dataSource = new PlayerDataSource(context,
new DefaultBandwidthMeter.Builder(context).build()); new DefaultBandwidthMeter.Builder(context).build());
loadController = new LoadController(); loadController = new LoadController();
renderFactory = new DefaultRenderersFactory(context); renderFactory = new DefaultRenderersFactory(context);
@ -570,7 +569,7 @@ public final class Player implements
binding.playbackLiveSync.setOnClickListener(this); binding.playbackLiveSync.setOnClickListener(this);
playerGestureListener = new PlayerGestureListener(this, service); playerGestureListener = new PlayerGestureListener(this, service);
gestureDetector = new GestureDetectorCompat(context, playerGestureListener); gestureDetector = new GestureDetector(context, playerGestureListener);
binding.getRoot().setOnTouchListener(playerGestureListener); binding.getRoot().setOnTouchListener(playerGestureListener);
binding.queueButton.setOnClickListener(v -> onQueueClicked()); binding.queueButton.setOnClickListener(v -> onQueueClicked());
@ -1774,25 +1773,13 @@ public final class Player implements
if (exoPlayerIsNull()) { if (exoPlayerIsNull()) {
return; return;
} }
// Use duration of currentItem for non-live streams,
// because HLS streams are fragmented
// and thus the whole duration is not available to the player
// TODO: revert #6307 when introducing proper HLS support
final int duration;
if (currentItem != null
&& !StreamTypeUtil.isLiveStream(currentItem.getStreamType())
) {
// convert seconds to milliseconds
duration = (int) (currentItem.getDuration() * 1000);
} else {
duration = (int) simpleExoPlayer.getDuration();
}
final int currentProgress = Math.max((int) simpleExoPlayer.getCurrentPosition(), 0); final int currentProgress = Math.max((int) simpleExoPlayer.getCurrentPosition(), 0);
onUpdateProgress( onUpdateProgress(
currentProgress, currentProgress,
duration, (int) simpleExoPlayer.getDuration(),
simpleExoPlayer.getBufferedPercentage() simpleExoPlayer.getBufferedPercentage());
);
if (sponsorBlockMode == SponsorBlockMode.ENABLED && isPrepared) { if (sponsorBlockMode == SponsorBlockMode.ENABLED && isPrepared) {
final VideoSegment segment = getSkippableSegment(currentProgress); final VideoSegment segment = getSkippableSegment(currentProgress);
@ -2603,22 +2590,31 @@ public final class Player implements
Listener.super.onEvents(player, events); Listener.super.onEvents(player, events);
MediaItemTag.from(player.getCurrentMediaItem()).ifPresent(tag -> { MediaItemTag.from(player.getCurrentMediaItem()).ifPresent(tag -> {
if (tag == currentMetadata) { if (tag == currentMetadata) {
return; return; // we still have the same metadata, no need to do anything
} }
final StreamInfo previousInfo = Optional.ofNullable(currentMetadata)
.flatMap(MediaItemTag::getMaybeStreamInfo).orElse(null);
currentMetadata = tag; currentMetadata = tag;
if (!tag.getErrors().isEmpty()) {
if (!currentMetadata.getErrors().isEmpty()) {
// new errors might have been added even if previousInfo == tag.getMaybeStreamInfo()
final ErrorInfo errorInfo = new ErrorInfo( final ErrorInfo errorInfo = new ErrorInfo(
tag.getErrors().get(0), currentMetadata.getErrors(),
UserAction.PLAY_STREAM, UserAction.PLAY_STREAM,
"Loading failed for [" + tag.getTitle() + "]: " + tag.getStreamUrl(), "Loading failed for [" + currentMetadata.getTitle()
tag.getServiceId()); + "]: " + currentMetadata.getStreamUrl(),
currentMetadata.getServiceId());
ErrorUtil.createNotification(context, errorInfo); ErrorUtil.createNotification(context, errorInfo);
} }
tag.getMaybeStreamInfo().ifPresent(info -> {
currentMetadata.getMaybeStreamInfo().ifPresent(info -> {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "ExoPlayer - onEvents() update stream info: " + info.getName()); Log.d(TAG, "ExoPlayer - onEvents() update stream info: " + info.getName());
} }
if (previousInfo == null || !previousInfo.getUrl().equals(info.getUrl())) {
// only update with the new stream info if it has actually changed
updateMetadataWith(info); updateMetadataWith(info);
}
}); });
}); });
} }
@ -3516,6 +3512,7 @@ public final class Player implements
switch (info.getStreamType()) { switch (info.getStreamType()) {
case AUDIO_STREAM: case AUDIO_STREAM:
case POST_LIVE_AUDIO_STREAM:
binding.surfaceView.setVisibility(View.GONE); binding.surfaceView.setVisibility(View.GONE);
binding.endScreen.setVisibility(View.VISIBLE); binding.endScreen.setVisibility(View.VISIBLE);
binding.playbackEndTime.setVisibility(View.VISIBLE); binding.playbackEndTime.setVisibility(View.VISIBLE);
@ -3534,6 +3531,7 @@ public final class Player implements
break; break;
case VIDEO_STREAM: case VIDEO_STREAM:
case POST_LIVE_STREAM:
if (currentMetadata == null if (currentMetadata == null
|| !currentMetadata.getMaybeQuality().isPresent() || !currentMetadata.getMaybeQuality().isPresent()
|| (info.getVideoStreams().isEmpty() || (info.getVideoStreams().isEmpty()
@ -3601,10 +3599,10 @@ public final class Player implements
for (int i = 0; i < availableStreams.size(); i++) { for (int i = 0; i < availableStreams.size(); i++) {
final VideoStream videoStream = availableStreams.get(i); final VideoStream videoStream = availableStreams.get(i);
qualityPopupMenu.getMenu().add(POPUP_MENU_ID_QUALITY, i, Menu.NONE, MediaFormat qualityPopupMenu.getMenu().add(POPUP_MENU_ID_QUALITY, i, Menu.NONE, MediaFormat
.getNameById(videoStream.getFormatId()) + " " + videoStream.resolution); .getNameById(videoStream.getFormatId()) + " " + videoStream.getResolution());
} }
if (getSelectedVideoStream() != null) { if (getSelectedVideoStream() != null) {
binding.qualityTextView.setText(getSelectedVideoStream().resolution); binding.qualityTextView.setText(getSelectedVideoStream().getResolution());
} }
qualityPopupMenu.setOnMenuItemClickListener(this); qualityPopupMenu.setOnMenuItemClickListener(this);
qualityPopupMenu.setOnDismissListener(this); qualityPopupMenu.setOnDismissListener(this);
@ -3722,7 +3720,7 @@ public final class Player implements
} }
saveStreamProgressState(); //TODO added, check if good saveStreamProgressState(); //TODO added, check if good
final String newResolution = availableStreams.get(menuItemIndex).resolution; final String newResolution = availableStreams.get(menuItemIndex).getResolution();
setRecovery(); setRecovery();
setPlaybackQuality(newResolution); setPlaybackQuality(newResolution);
reloadPlayQueueManager(); reloadPlayQueueManager();
@ -3750,7 +3748,7 @@ public final class Player implements
} }
isSomePopupMenuVisible = false; //TODO check if this works isSomePopupMenuVisible = false; //TODO check if this works
if (getSelectedVideoStream() != null) { if (getSelectedVideoStream() != null) {
binding.qualityTextView.setText(getSelectedVideoStream().resolution); binding.qualityTextView.setText(getSelectedVideoStream().getResolution());
} }
if (isPlaying()) { if (isPlaying()) {
hideControls(DEFAULT_CONTROLS_DURATION, 0); hideControls(DEFAULT_CONTROLS_DURATION, 0);
@ -4401,9 +4399,7 @@ public final class Player implements
if (playQueueManagerReloadingNeeded(sourceType, info, getVideoRendererIndex())) { if (playQueueManagerReloadingNeeded(sourceType, info, getVideoRendererIndex())) {
reloadPlayQueueManager(); reloadPlayQueueManager();
} else { } else {
final StreamType streamType = info.getStreamType(); if (StreamTypeUtil.isAudio(info.getStreamType())) {
if (streamType == StreamType.AUDIO_STREAM
|| streamType == StreamType.AUDIO_LIVE_STREAM) {
// Nothing to do more than setting the recovery position // Nothing to do more than setting the recovery position
setRecovery(); setRecovery();
return; return;
@ -4438,13 +4434,15 @@ public final class Player implements
* the content is not an audio content, but also if none of the following cases is met: * the content is not an audio content, but also if none of the following cases is met:
* *
* <ul> * <ul>
* <li>the content is an {@link StreamType#AUDIO_STREAM audio stream} or an * <li>the content is an {@link StreamType#AUDIO_STREAM audio stream}, an
* {@link StreamType#AUDIO_LIVE_STREAM audio live stream};</li> * {@link StreamType#AUDIO_LIVE_STREAM audio live stream}, or a
* {@link StreamType#POST_LIVE_AUDIO_STREAM ended audio live stream};</li>
* <li>the content is a {@link StreamType#LIVE_STREAM live stream} and the source type is a * <li>the content is a {@link StreamType#LIVE_STREAM live stream} and the source type is a
* {@link SourceType#LIVE_STREAM live source};</li> * {@link SourceType#LIVE_STREAM live source};</li>
* <li>the content's source is {@link SourceType#VIDEO_WITH_SEPARATED_AUDIO a video stream * <li>the content's source is {@link SourceType#VIDEO_WITH_SEPARATED_AUDIO a video stream
* with a separated audio source} or has no audio-only streams available <b>and</b> is a * with a separated audio source} or has no audio-only streams available <b>and</b> is a
* {@link StreamType#LIVE_STREAM live stream} or a * {@link StreamType#VIDEO_STREAM video stream}, an
* {@link StreamType#POST_LIVE_STREAM ended live stream}, or a
* {@link StreamType#LIVE_STREAM live stream}. * {@link StreamType#LIVE_STREAM live stream}.
* </li> * </li>
* </ul> * </ul>
@ -4460,17 +4458,16 @@ public final class Player implements
@NonNull final StreamInfo streamInfo, @NonNull final StreamInfo streamInfo,
final int videoRendererIndex) { final int videoRendererIndex) {
final StreamType streamType = streamInfo.getStreamType(); final StreamType streamType = streamInfo.getStreamType();
final boolean isStreamTypeAudio = StreamTypeUtil.isAudio(streamType);
if (videoRendererIndex == RENDERER_UNAVAILABLE && streamType != StreamType.AUDIO_STREAM if (videoRendererIndex == RENDERER_UNAVAILABLE && !isStreamTypeAudio) {
&& streamType != StreamType.AUDIO_LIVE_STREAM) {
return true; return true;
} }
// The content is an audio stream, an audio live stream, or a live stream with a live // The content is an audio stream, an audio live stream, or a live stream with a live
// source: it's not needed to reload the play queue manager because the stream source will // source: it's not needed to reload the play queue manager because the stream source will
// be the same // be the same
if ((streamType == StreamType.AUDIO_STREAM || streamType == StreamType.AUDIO_LIVE_STREAM) if (isStreamTypeAudio || (streamType == StreamType.LIVE_STREAM
|| (streamType == StreamType.LIVE_STREAM
&& sourceType == SourceType.LIVE_STREAM)) { && sourceType == SourceType.LIVE_STREAM)) {
return false; return false;
} }
@ -4484,8 +4481,8 @@ public final class Player implements
|| (sourceType == SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY || (sourceType == SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY
&& isNullOrEmpty(streamInfo.getAudioStreams()))) { && isNullOrEmpty(streamInfo.getAudioStreams()))) {
// It's not needed to reload the play queue manager only if the content's stream type // It's not needed to reload the play queue manager only if the content's stream type
// is a video stream or a live stream // is a video stream, a live stream or an ended live stream
return streamType != StreamType.VIDEO_STREAM && streamType != StreamType.LIVE_STREAM; return !StreamTypeUtil.isVideo(streamType);
} }
// Other cases: the play queue manager reload is needed // Other cases: the play queue manager reload is needed
@ -4581,7 +4578,7 @@ public final class Player implements
return audioReactor; return audioReactor;
} }
public GestureDetectorCompat getGestureDetector() { public GestureDetector getGestureDetector() {
return gestureDetector; return gestureDetector;
} }

View file

@ -0,0 +1,136 @@
package org.schabi.newpipe.player.datasource;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import androidx.annotation.NonNull;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory;
import com.google.android.exoplayer2.upstream.ByteArrayDataSource;
import com.google.android.exoplayer2.upstream.DataSource;
import java.nio.charset.StandardCharsets;
/**
* A {@link HlsDataSourceFactory} which allows playback of non-URI media HLS playlists for
* {@link com.google.android.exoplayer2.source.hls.HlsMediaSource HlsMediaSource}s.
*
* <p>
* If media requests are relative, the URI from which the manifest comes from (either the
* manifest URI (preferred) or the master URI (if applicable)) must be returned, otherwise the
* content will be not playable, as it will be an invalid URL, or it may be treat as something
* unexpected, for instance as a file for
* {@link com.google.android.exoplayer2.upstream.DefaultDataSource DefaultDataSource}s.
* </p>
*
* <p>
* See {@link #createDataSource(int)} for changes and implementation details.
* </p>
*/
public final class NonUriHlsDataSourceFactory implements HlsDataSourceFactory {
/**
* Builder class of {@link NonUriHlsDataSourceFactory} instances.
*/
public static final class Builder {
private DataSource.Factory dataSourceFactory;
private String playlistString;
/**
* Set the {@link DataSource.Factory} which will be used to create non manifest contents
* {@link DataSource}s.
*
* @param dataSourceFactoryForNonManifestContents the {@link DataSource.Factory} which will
* be used to create non manifest contents
* {@link DataSource}s, which cannot be null
*/
public void setDataSourceFactory(
@NonNull final DataSource.Factory dataSourceFactoryForNonManifestContents) {
this.dataSourceFactory = dataSourceFactoryForNonManifestContents;
}
/**
* Set the HLS playlist which will be used for manifests requests.
*
* @param hlsPlaylistString the string which correspond to the response of the HLS
* manifest, which cannot be null or empty
*/
public void setPlaylistString(@NonNull final String hlsPlaylistString) {
this.playlistString = hlsPlaylistString;
}
/**
* Create a new {@link NonUriHlsDataSourceFactory} with the given data source factory and
* the given HLS playlist.
*
* @return a {@link NonUriHlsDataSourceFactory}
* @throws IllegalArgumentException if the data source factory is null or if the HLS
* playlist string set is null or empty
*/
@NonNull
public NonUriHlsDataSourceFactory build() {
if (dataSourceFactory == null) {
throw new IllegalArgumentException(
"No DataSource.Factory valid instance has been specified.");
}
if (isNullOrEmpty(playlistString)) {
throw new IllegalArgumentException("No HLS valid playlist has been specified.");
}
return new NonUriHlsDataSourceFactory(dataSourceFactory,
playlistString.getBytes(StandardCharsets.UTF_8));
}
}
private final DataSource.Factory dataSourceFactory;
private final byte[] playlistStringByteArray;
/**
* Create a {@link NonUriHlsDataSourceFactory} instance.
*
* @param dataSourceFactory the {@link DataSource.Factory} which will be used to build
* non manifests {@link DataSource}s, which must not be null
* @param playlistStringByteArray a byte array of the HLS playlist, which must not be null
*/
private NonUriHlsDataSourceFactory(@NonNull final DataSource.Factory dataSourceFactory,
@NonNull final byte[] playlistStringByteArray) {
this.dataSourceFactory = dataSourceFactory;
this.playlistStringByteArray = playlistStringByteArray;
}
/**
* Create a {@link DataSource} for the given data type.
*
* <p>
* Contrary to {@link com.google.android.exoplayer2.source.hls.DefaultHlsDataSourceFactory
* ExoPlayer's default implementation}, this implementation is not always using the
* {@link DataSource.Factory} passed to the
* {@link com.google.android.exoplayer2.source.hls.HlsMediaSource.Factory
* HlsMediaSource.Factory} constructor, only when it's not
* {@link C#DATA_TYPE_MANIFEST the manifest type}.
* </p>
*
* <p>
* This change allow playback of non-URI HLS contents, when the manifest is not a master
* manifest/playlist (otherwise, endless loops should be encountered because the
* {@link DataSource}s created for media playlists should use the master playlist response
* instead).
* </p>
*
* @param dataType the data type for which the {@link DataSource} will be used, which is one of
* {@link C} {@code .DATA_TYPE_*} constants
* @return a {@link DataSource} for the given data type
*/
@NonNull
@Override
public DataSource createDataSource(final int dataType) {
// The manifest is already downloaded and provided with playlistStringByteArray, so we
// don't need to download it again and we can use a ByteArrayDataSource instead
if (dataType == C.DATA_TYPE_MANIFEST) {
return new ByteArrayDataSource(playlistStringByteArray);
}
return dataSourceFactory.createDataSource();
}
}

File diff suppressed because it is too large Load diff

View file

@ -126,6 +126,14 @@ public class PlayerGestureListener
} }
private void onScrollMainVolume(final float distanceX, final float distanceY) { private void onScrollMainVolume(final float distanceX, final float distanceY) {
// If we just started sliding, change the progress bar to match the system volume
if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
final float volumePercent = player
.getAudioReactor().getVolume() / (float) maxVolume;
player.getVolumeProgressBar().setProgress(
(int) (volumePercent * player.getMaxGestureLength()));
}
player.getVolumeProgressBar().incrementProgressBy((int) distanceY); player.getVolumeProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) player final float currentProgressPercent = (float) player
.getVolumeProgressBar().getProgress() / player.getMaxGestureLength(); .getVolumeProgressBar().getProgress() / player.getMaxGestureLength();

View file

@ -1,96 +1,46 @@
package org.schabi.newpipe.player.helper; package org.schabi.newpipe.player.helper;
import android.content.Context; import android.content.Context;
import android.util.Log;
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider; import androidx.annotation.NonNull;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.FileDataSource; import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.upstream.cache.CacheDataSink; import com.google.android.exoplayer2.upstream.cache.CacheDataSink;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import java.io.File; final class CacheFactory implements DataSource.Factory {
private static final int CACHE_FLAGS = CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR;
import androidx.annotation.NonNull; private final Context context;
private final TransferListener transferListener;
private final DataSource.Factory upstreamDataSourceFactory;
private final SimpleCache cache;
/* package-private */ class CacheFactory implements DataSource.Factory { CacheFactory(final Context context,
private static final String TAG = "CacheFactory"; final TransferListener transferListener,
final SimpleCache cache,
private static final String CACHE_FOLDER_NAME = "exoplayer"; final DataSource.Factory upstreamDataSourceFactory) {
private static final int CACHE_FLAGS = CacheDataSource.FLAG_BLOCK_ON_CACHE this.context = context;
| CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR; this.transferListener = transferListener;
this.cache = cache;
private final DataSource.Factory dataSourceFactory; this.upstreamDataSourceFactory = upstreamDataSourceFactory;
private final File cacheDir;
private final long maxFileSize;
// Creating cache on every instance may cause problems with multiple players when
// sources are not ExtractorMediaSource
// see: https://stackoverflow.com/questions/28700391/using-cache-in-exoplayer
// todo: make this a singleton?
private static SimpleCache cache;
CacheFactory(@NonNull final Context context,
@NonNull final String userAgent,
@NonNull final TransferListener transferListener) {
this(context, userAgent, transferListener, PlayerHelper.getPreferredCacheSize(),
PlayerHelper.getPreferredFileSize());
}
private CacheFactory(@NonNull final Context context,
@NonNull final String userAgent,
@NonNull final TransferListener transferListener,
final long maxCacheSize,
final long maxFileSize) {
this.maxFileSize = maxFileSize;
dataSourceFactory = new DefaultDataSource
.Factory(context, new DefaultHttpDataSource.Factory().setUserAgent(userAgent))
.setTransferListener(transferListener);
cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
if (!cacheDir.exists()) {
//noinspection ResultOfMethodCallIgnored
cacheDir.mkdir();
}
if (cache == null) {
final LeastRecentlyUsedCacheEvictor evictor
= new LeastRecentlyUsedCacheEvictor(maxCacheSize);
cache = new SimpleCache(cacheDir, evictor, new StandaloneDatabaseProvider(context));
}
} }
@NonNull @NonNull
@Override @Override
public DataSource createDataSource() { public DataSource createDataSource() {
Log.d(TAG, "initExoPlayerCache: cacheDir = " + cacheDir.getAbsolutePath()); final DefaultDataSource dataSource = new DefaultDataSource.Factory(context,
upstreamDataSourceFactory)
.setTransferListener(transferListener)
.createDataSource();
final DataSource dataSource = dataSourceFactory.createDataSource();
final FileDataSource fileSource = new FileDataSource(); final FileDataSource fileSource = new FileDataSource();
final CacheDataSink dataSink = new CacheDataSink(cache, maxFileSize); final CacheDataSink dataSink
= new CacheDataSink(cache, PlayerHelper.getPreferredFileSize());
return new CacheDataSource(cache, dataSource, fileSource, dataSink, CACHE_FLAGS, null); return new CacheDataSource(cache, dataSource, fileSource, dataSink, CACHE_FLAGS, null);
} }
public void tryDeleteCacheFiles() {
if (!cacheDir.exists() || !cacheDir.isDirectory()) {
return;
}
try {
for (final File file : cacheDir.listFiles()) {
final String filePath = file.getAbsolutePath();
final boolean deleteSuccessful = file.delete();
Log.d(TAG, "tryDeleteCacheFiles: " + filePath + " deleted = " + deleteSuccessful);
}
} catch (final Exception e) {
Log.e(TAG, "Failed to delete file.", e);
}
}
} }

View file

@ -13,6 +13,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media.session.MediaButtonReceiver; import androidx.media.session.MediaButtonReceiver;
import com.google.android.exoplayer2.ForwardingPlayer;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
@ -55,7 +56,17 @@ public class MediaSessionManager {
sessionConnector = new MediaSessionConnector(mediaSession); sessionConnector = new MediaSessionConnector(mediaSession);
sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback)); sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback));
sessionConnector.setPlayer(player); sessionConnector.setPlayer(new ForwardingPlayer(player) {
@Override
public void play() {
callback.play();
}
@Override
public void pause() {
callback.pause();
}
});
} }
@Nullable @Nullable

View file

@ -1,7 +1,13 @@
package org.schabi.newpipe.player.helper; package org.schabi.newpipe.player.helper;
import android.content.Context; import static org.schabi.newpipe.MainActivity.DEBUG;
import android.content.Context;
import android.util.Log;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider;
import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.SingleSampleMediaSource; import com.google.android.exoplayer2.source.SingleSampleMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource;
@ -13,12 +19,21 @@ import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import androidx.annotation.NonNull; import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeOtfDashManifestCreator;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubePostLiveStreamDvrDashManifestCreator;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeProgressiveDashManifestCreator;
import org.schabi.newpipe.player.datasource.NonUriHlsDataSourceFactory;
import org.schabi.newpipe.player.datasource.YoutubeHttpDataSource;
import java.io.File;
public class PlayerDataSource { public class PlayerDataSource {
public static final String TAG = PlayerDataSource.class.getSimpleName();
public static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000; public static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000;
@ -29,79 +44,174 @@ public class PlayerDataSource {
* early. * early.
*/ */
private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 15; private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 15;
private static final int MANIFEST_MINIMUM_RETRY = 5;
private static final int EXTRACTOR_MINIMUM_RETRY = Integer.MAX_VALUE;
private final int continueLoadingCheckIntervalBytes; /**
private final DataSource.Factory cacheDataSourceFactory; * The maximum number of generated manifests per cache, in
* {@link YoutubeProgressiveDashManifestCreator}, {@link YoutubeOtfDashManifestCreator} and
* {@link YoutubePostLiveStreamDvrDashManifestCreator}.
*/
private static final int MAX_MANIFEST_CACHE_SIZE = 500;
/**
* The folder name in which the ExoPlayer cache will be written.
*/
private static final String CACHE_FOLDER_NAME = "exoplayer";
/**
* The {@link SimpleCache} instance which will be used to build
* {@link com.google.android.exoplayer2.upstream.cache.CacheDataSource}s instances (with
* {@link CacheFactory}).
*/
private static SimpleCache cache;
private final int progressiveLoadIntervalBytes;
// Generic Data Source Factories (without or with cache)
private final DataSource.Factory cachelessDataSourceFactory; private final DataSource.Factory cachelessDataSourceFactory;
private final CacheFactory cacheDataSourceFactory;
public PlayerDataSource(@NonNull final Context context, // YouTube-specific Data Source Factories (with cache)
@NonNull final String userAgent, // They use YoutubeHttpDataSource.Factory, with different parameters each
@NonNull final TransferListener transferListener) { private final CacheFactory ytHlsCacheDataSourceFactory;
continueLoadingCheckIntervalBytes = PlayerHelper.getProgressiveLoadIntervalBytes(context); private final CacheFactory ytDashCacheDataSourceFactory;
cacheDataSourceFactory = new CacheFactory(context, userAgent, transferListener); private final CacheFactory ytProgressiveDashCacheDataSourceFactory;
cachelessDataSourceFactory = new DefaultDataSource
.Factory(context, new DefaultHttpDataSource.Factory().setUserAgent(userAgent))
public PlayerDataSource(final Context context,
final TransferListener transferListener) {
progressiveLoadIntervalBytes = PlayerHelper.getProgressiveLoadIntervalBytes(context);
// make sure the static cache was created: needed by CacheFactories below
instantiateCacheIfNeeded(context);
// generic data source factories use DefaultHttpDataSource.Factory
cachelessDataSourceFactory = new DefaultDataSource.Factory(context,
new DefaultHttpDataSource.Factory().setUserAgent(DownloaderImpl.USER_AGENT))
.setTransferListener(transferListener); .setTransferListener(transferListener);
cacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
new DefaultHttpDataSource.Factory().setUserAgent(DownloaderImpl.USER_AGENT));
// YouTube-specific data source factories use getYoutubeHttpDataSourceFactory()
ytHlsCacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
getYoutubeHttpDataSourceFactory(false, false));
ytDashCacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
getYoutubeHttpDataSourceFactory(true, true));
ytProgressiveDashCacheDataSourceFactory = new CacheFactory(context, transferListener, cache,
getYoutubeHttpDataSourceFactory(false, true));
// set the maximum size to manifest creators
YoutubeProgressiveDashManifestCreator.getCache().setMaximumSize(MAX_MANIFEST_CACHE_SIZE);
YoutubeOtfDashManifestCreator.getCache().setMaximumSize(MAX_MANIFEST_CACHE_SIZE);
YoutubePostLiveStreamDvrDashManifestCreator.getCache().setMaximumSize(
MAX_MANIFEST_CACHE_SIZE);
} }
//region Live media source factories
public SsMediaSource.Factory getLiveSsMediaSourceFactory() { public SsMediaSource.Factory getLiveSsMediaSourceFactory() {
return new SsMediaSource.Factory( return getSSMediaSourceFactory().setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
new DefaultSsChunkSource.Factory(cachelessDataSourceFactory),
cachelessDataSourceFactory
)
.setLoadErrorHandlingPolicy(
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY))
.setLivePresentationDelayMs(LIVE_STREAM_EDGE_GAP_MILLIS);
} }
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() { public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
return new HlsMediaSource.Factory(cachelessDataSourceFactory) return new HlsMediaSource.Factory(cachelessDataSourceFactory)
.setAllowChunklessPreparation(true) .setAllowChunklessPreparation(true)
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(
MANIFEST_MINIMUM_RETRY))
.setPlaylistTrackerFactory((dataSourceFactory, loadErrorHandlingPolicy, .setPlaylistTrackerFactory((dataSourceFactory, loadErrorHandlingPolicy,
playlistParserFactory) -> playlistParserFactory) ->
new DefaultHlsPlaylistTracker(dataSourceFactory, loadErrorHandlingPolicy, new DefaultHlsPlaylistTracker(dataSourceFactory, loadErrorHandlingPolicy,
playlistParserFactory, PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT) playlistParserFactory,
); PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT));
} }
public DashMediaSource.Factory getLiveDashMediaSourceFactory() { public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
return new DashMediaSource.Factory( return new DashMediaSource.Factory(
getDefaultDashChunkSourceFactory(cachelessDataSourceFactory), getDefaultDashChunkSourceFactory(cachelessDataSourceFactory),
cachelessDataSourceFactory cachelessDataSourceFactory);
) }
.setLoadErrorHandlingPolicy( //endregion
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
//region Generic media source factories
public HlsMediaSource.Factory getHlsMediaSourceFactory(
@Nullable final NonUriHlsDataSourceFactory.Builder hlsDataSourceFactoryBuilder) {
if (hlsDataSourceFactoryBuilder != null) {
hlsDataSourceFactoryBuilder.setDataSourceFactory(cacheDataSourceFactory);
return new HlsMediaSource.Factory(hlsDataSourceFactoryBuilder.build());
} }
private DefaultDashChunkSource.Factory getDefaultDashChunkSourceFactory(
final DataSource.Factory dataSourceFactory
) {
return new DefaultDashChunkSource.Factory(dataSourceFactory);
}
public HlsMediaSource.Factory getHlsMediaSourceFactory() {
return new HlsMediaSource.Factory(cacheDataSourceFactory); return new HlsMediaSource.Factory(cacheDataSourceFactory);
} }
public DashMediaSource.Factory getDashMediaSourceFactory() { public DashMediaSource.Factory getDashMediaSourceFactory() {
return new DashMediaSource.Factory( return new DashMediaSource.Factory(
getDefaultDashChunkSourceFactory(cacheDataSourceFactory), getDefaultDashChunkSourceFactory(cacheDataSourceFactory),
cacheDataSourceFactory cacheDataSourceFactory);
);
} }
public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory() { public ProgressiveMediaSource.Factory getProgressiveMediaSourceFactory() {
return new ProgressiveMediaSource.Factory(cacheDataSourceFactory) return new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.setContinueLoadingCheckIntervalBytes(continueLoadingCheckIntervalBytes) .setContinueLoadingCheckIntervalBytes(progressiveLoadIntervalBytes);
.setLoadErrorHandlingPolicy(
new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
} }
public SingleSampleMediaSource.Factory getSampleMediaSourceFactory() { public SsMediaSource.Factory getSSMediaSourceFactory() {
return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(cachelessDataSourceFactory),
cachelessDataSourceFactory);
}
public SingleSampleMediaSource.Factory getSingleSampleMediaSourceFactory() {
return new SingleSampleMediaSource.Factory(cacheDataSourceFactory); return new SingleSampleMediaSource.Factory(cacheDataSourceFactory);
} }
//endregion
//region YouTube media source factories
public HlsMediaSource.Factory getYoutubeHlsMediaSourceFactory() {
return new HlsMediaSource.Factory(ytHlsCacheDataSourceFactory);
}
public DashMediaSource.Factory getYoutubeDashMediaSourceFactory() {
return new DashMediaSource.Factory(
getDefaultDashChunkSourceFactory(ytDashCacheDataSourceFactory),
ytDashCacheDataSourceFactory);
}
public ProgressiveMediaSource.Factory getYoutubeProgressiveMediaSourceFactory() {
return new ProgressiveMediaSource.Factory(ytProgressiveDashCacheDataSourceFactory)
.setContinueLoadingCheckIntervalBytes(progressiveLoadIntervalBytes);
}
//endregion
//region Static methods
private static DefaultDashChunkSource.Factory getDefaultDashChunkSourceFactory(
final DataSource.Factory dataSourceFactory) {
return new DefaultDashChunkSource.Factory(dataSourceFactory);
}
private static YoutubeHttpDataSource.Factory getYoutubeHttpDataSourceFactory(
final boolean rangeParameterEnabled,
final boolean rnParameterEnabled) {
return new YoutubeHttpDataSource.Factory()
.setRangeParameterEnabled(rangeParameterEnabled)
.setRnParameterEnabled(rnParameterEnabled);
}
private static void instantiateCacheIfNeeded(final Context context) {
if (cache == null) {
final File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
if (DEBUG) {
Log.d(TAG, "instantiateCacheIfNeeded: cacheDir = " + cacheDir.getAbsolutePath());
}
if (!cacheDir.exists() && !cacheDir.mkdir()) {
Log.w(TAG, "instantiateCacheIfNeeded: could not create cache dir");
}
final LeastRecentlyUsedCacheEvictor evictor
= new LeastRecentlyUsedCacheEvictor(PlayerHelper.getPreferredCacheSize());
cache = new SimpleCache(cacheDir, evictor, new StandaloneDatabaseProvider(context));
}
}
//endregion
} }

View file

@ -45,11 +45,9 @@ 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.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
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;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.extractor.utils.Utils; import org.schabi.newpipe.extractor.utils.Utils;
import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.Player;
@ -110,12 +108,14 @@ public final class PlayerHelper {
int MINIMIZE_ON_EXIT_MODE_POPUP = 2; int MINIMIZE_ON_EXIT_MODE_POPUP = 2;
} }
private PlayerHelper() { } private PlayerHelper() {
}
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Exposed helpers // Exposed helpers
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@NonNull
public static String getTimeString(final int milliSeconds) { public static String getTimeString(final int milliSeconds) {
final int seconds = (milliSeconds % 60000) / 1000; final int seconds = (milliSeconds % 60000) / 1000;
final int minutes = (milliSeconds % 3600000) / 60000; final int minutes = (milliSeconds % 3600000) / 60000;
@ -131,15 +131,18 @@ public final class PlayerHelper {
).toString(); ).toString();
} }
@NonNull
public static String formatSpeed(final double speed) { public static String formatSpeed(final double speed) {
return SPEED_FORMATTER.format(speed); return SPEED_FORMATTER.format(speed);
} }
@NonNull
public static String formatPitch(final double pitch) { public static String formatPitch(final double pitch) {
return PITCH_FORMATTER.format(pitch); return PITCH_FORMATTER.format(pitch);
} }
public static String subtitleMimeTypesOf(final MediaFormat format) { @NonNull
public static String subtitleMimeTypesOf(@NonNull final MediaFormat format) {
switch (format) { switch (format) {
case VTT: case VTT:
return MimeTypes.TEXT_VTT; return MimeTypes.TEXT_VTT;
@ -190,18 +193,6 @@ public final class PlayerHelper {
} }
} }
@NonNull
public static String cacheKeyOf(@NonNull final StreamInfo info,
@NonNull final VideoStream video) {
return info.getUrl() + video.getResolution() + video.getFormat().getName();
}
@NonNull
public static String cacheKeyOf(@NonNull final StreamInfo info,
@NonNull final AudioStream audio) {
return info.getUrl() + audio.getAverageBitrate() + audio.getFormat().getName();
}
/** /**
* Given a {@link StreamInfo} and the existing queue items, * Given a {@link StreamInfo} and the existing queue items,
* provide the {@link SinglePlayQueue} consisting of the next video for auto queueing. * provide the {@link SinglePlayQueue} consisting of the next video for auto queueing.
@ -233,7 +224,7 @@ public final class PlayerHelper {
return null; return null;
} }
if (relatedItems.get(0) != null && relatedItems.get(0) instanceof StreamInfoItem if (relatedItems.get(0) instanceof StreamInfoItem
&& !urls.contains(relatedItems.get(0).getUrl())) { && !urls.contains(relatedItems.get(0).getUrl())) {
return getAutoQueuedSinglePlayQueue((StreamInfoItem) relatedItems.get(0)); return getAutoQueuedSinglePlayQueue((StreamInfoItem) relatedItems.get(0));
} }
@ -335,6 +326,7 @@ public final class PlayerHelper {
return 2 * 1024 * 1024L; // ExoPlayer CacheDataSink.MIN_RECOMMENDED_FRAGMENT_SIZE return 2 * 1024 * 1024L; // ExoPlayer CacheDataSink.MIN_RECOMMENDED_FRAGMENT_SIZE
} }
@NonNull
public static ExoTrackSelection.Factory getQualitySelector() { public static ExoTrackSelection.Factory getQualitySelector() {
return new AdaptiveTrackSelection.Factory( return new AdaptiveTrackSelection.Factory(
1000, 1000,
@ -480,7 +472,8 @@ public final class PlayerHelper {
return REPEAT_MODE_ONE; return REPEAT_MODE_ONE;
case REPEAT_MODE_ONE: case REPEAT_MODE_ONE:
return REPEAT_MODE_ALL; return REPEAT_MODE_ALL;
case REPEAT_MODE_ALL: default: case REPEAT_MODE_ALL:
default:
return REPEAT_MODE_OFF; return REPEAT_MODE_OFF;
} }
} }

View file

@ -0,0 +1,38 @@
package org.schabi.newpipe.player.helper;
/**
* Converts between percent and 12-tone equal temperament semitones.
* <br/>
* @see
* <a href="https://en.wikipedia.org/wiki/Equal_temperament#Twelve-tone_equal_temperament">
* Wikipedia: Equal temperament#Twelve-tone equal temperament
* </a>
*/
public final class PlayerSemitoneHelper {
public static final int SEMITONE_COUNT = 12;
private PlayerSemitoneHelper() {
// No impl
}
public static String formatPitchSemitones(final double percent) {
return formatPitchSemitones(percentToSemitones(percent));
}
public static String formatPitchSemitones(final int semitones) {
return semitones > 0 ? "+" + semitones : "" + semitones;
}
public static double semitonesToPercent(final int semitones) {
return Math.pow(2, ensureSemitonesInRange(semitones) / (double) SEMITONE_COUNT);
}
public static int percentToSemitones(final double percent) {
return ensureSemitonesInRange(
(int) Math.round(SEMITONE_COUNT * Math.log(percent) / Math.log(2)));
}
private static int ensureSemitonesInRange(final int semitones) {
return Math.max(-SEMITONE_COUNT, Math.min(SEMITONE_COUNT, semitones));
}
}

View file

@ -32,7 +32,7 @@ class QualityClickListener(
val videoStream = player.selectedVideoStream val videoStream = player.selectedVideoStream
if (videoStream != null) { if (videoStream != null) {
player.binding.qualityTextView.text = player.binding.qualityTextView.text =
MediaFormat.getNameById(videoStream.formatId) + " " + videoStream.resolution MediaFormat.getNameById(videoStream.formatId) + " " + videoStream.getResolution()
} }
player.saveWasPlaying() player.saveWasPlaying()

View file

@ -5,9 +5,9 @@ import android.text.TextUtils;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ServiceHelper;
public class PlayQueueItemBuilder { public class PlayQueueItemBuilder {
private static final String TAG = PlayQueueItemBuilder.class.toString(); private static final String TAG = PlayQueueItemBuilder.class.toString();
@ -25,7 +25,7 @@ public class PlayQueueItemBuilder {
holder.itemVideoTitleView.setText(item.getTitle()); holder.itemVideoTitleView.setText(item.getTitle());
} }
holder.itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getUploader(), holder.itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getUploader(),
NewPipe.getNameOfService(item.getServiceId()))); ServiceHelper.getNameOfServiceById(item.getServiceId())));
if (item.getDuration() > 0) { if (item.getDuration() > 0) {
holder.itemDurationView.setText(Localization.getDurationString(item.getDuration())); holder.itemDurationView.setText(Localization.getDurationString(item.getDuration()));

View file

@ -1,22 +1,27 @@
package org.schabi.newpipe.player.resolver; package org.schabi.newpipe.player.resolver;
import static org.schabi.newpipe.util.ListHelper.getNonTorrentStreams;
import android.content.Context; import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerDataSource;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.mediaitem.MediaItemTag; import org.schabi.newpipe.player.mediaitem.MediaItemTag;
import org.schabi.newpipe.player.mediaitem.StreamInfoTag; import org.schabi.newpipe.player.mediaitem.StreamInfoTag;
import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ListHelper;
import java.util.List;
public class AudioPlaybackResolver implements PlaybackResolver { public class AudioPlaybackResolver implements PlaybackResolver {
private static final String TAG = AudioPlaybackResolver.class.getSimpleName();
@NonNull @NonNull
private final Context context; private final Context context;
@NonNull @NonNull
@ -31,19 +36,27 @@ public class AudioPlaybackResolver implements PlaybackResolver {
@Override @Override
@Nullable @Nullable
public MediaSource resolve(@NonNull final StreamInfo info) { public MediaSource resolve(@NonNull final StreamInfo info) {
final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info); final MediaSource liveSource = PlaybackResolver.maybeBuildLiveMediaSource(dataSource, info);
if (liveSource != null) { if (liveSource != null) {
return liveSource; return liveSource;
} }
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams()); final List<AudioStream> audioStreams = getNonTorrentStreams(info.getAudioStreams());
final int index = ListHelper.getDefaultAudioFormat(context, audioStreams);
if (index < 0 || index >= info.getAudioStreams().size()) { if (index < 0 || index >= info.getAudioStreams().size()) {
return null; return null;
} }
final AudioStream audio = info.getAudioStreams().get(index); final AudioStream audio = info.getAudioStreams().get(index);
final MediaItemTag tag = StreamInfoTag.of(info); final MediaItemTag tag = StreamInfoTag.of(info);
return buildMediaSource(dataSource, audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio),
MediaFormat.getSuffixById(audio.getFormatId()), tag); try {
return PlaybackResolver.buildMediaSource(
dataSource, audio, info, PlaybackResolver.cacheKeyOf(info, audio), tag);
} catch (final ResolverException e) {
Log.e(TAG, "Unable to create audio source", e);
return null;
}
} }
} }

View file

@ -1,50 +1,193 @@
package org.schabi.newpipe.player.resolver; package org.schabi.newpipe.player.resolver;
import static org.schabi.newpipe.extractor.stream.AudioStream.UNKNOWN_BITRATE;
import static org.schabi.newpipe.extractor.stream.VideoStream.RESOLUTION_UNKNOWN;
import static org.schabi.newpipe.player.helper.PlayerDataSource.LIVE_STREAM_EDGE_GAP_MILLIS;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.util.Log;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.services.youtube.ItagItem;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.CreationException;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeOtfDashManifestCreator;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubePostLiveStreamDvrDashManifestCreator;
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeProgressiveDashManifestCreator;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.datasource.NonUriHlsDataSourceFactory;
import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerDataSource;
import org.schabi.newpipe.player.mediaitem.MediaItemTag; import org.schabi.newpipe.player.mediaitem.MediaItemTag;
import org.schabi.newpipe.player.mediaitem.StreamInfoTag; import org.schabi.newpipe.player.mediaitem.StreamInfoTag;
import org.schabi.newpipe.util.StreamTypeUtil; import org.schabi.newpipe.util.StreamTypeUtil;
import androidx.annotation.NonNull; import java.io.ByteArrayInputStream;
import androidx.annotation.Nullable; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.schabi.newpipe.player.helper.PlayerDataSource.LIVE_STREAM_EDGE_GAP_MILLIS; import java.util.Objects;
/**
* This interface is just a shorthand for {@link Resolver} with {@link StreamInfo} as source and
* {@link MediaSource} as product. It contains many static methods that can be used by classes
* implementing this interface, and nothing else.
*/
public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> { public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
String TAG = PlaybackResolver.class.getSimpleName();
//region Cache key generation
private static StringBuilder commonCacheKeyOf(final StreamInfo info,
final Stream stream,
final boolean resolutionOrBitrateUnknown) {
// stream info service id
final StringBuilder cacheKey = new StringBuilder(info.getServiceId());
// stream info id
cacheKey.append(" ");
cacheKey.append(info.getId());
// stream id (even if unknown)
cacheKey.append(" ");
cacheKey.append(stream.getId());
// mediaFormat (if not null)
final MediaFormat mediaFormat = stream.getFormat();
if (mediaFormat != null) {
cacheKey.append(" ");
cacheKey.append(mediaFormat.getName());
}
// content (only if other information is missing)
// If the media format and the resolution/bitrate are both missing, then we don't have
// enough information to distinguish this stream from other streams.
// So, only in that case, we use the content (i.e. url or manifest) to differentiate
// between streams.
// Note that if the content were used even when other information is present, then two
// streams with the same stats but with different contents (e.g. because the url was
// refreshed) will be considered different (i.e. with a different cacheKey), making the
// cache useless.
if (resolutionOrBitrateUnknown && mediaFormat == null) {
cacheKey.append(" ");
cacheKey.append(Objects.hash(stream.getContent(), stream.getManifestUrl()));
}
return cacheKey;
}
/**
* Builds the cache key of a {@link VideoStream video stream}.
*
* <p>
* A cache key is unique to the features of the provided video stream, and when possible
* independent of <i>transient</i> parameters (such as the URL of the stream).
* This ensures that there are no conflicts, but also that the cache is used as much as
* possible: the same cache should be used for two streams which have the same features but
* e.g. a different URL, since the URL might have been reloaded in the meantime, but the stream
* actually referenced by the URL is still the same.
* </p>
*
* @param info the {@link StreamInfo stream info}, to distinguish between streams with
* the same features but coming from different stream infos
* @param videoStream the {@link VideoStream video stream} for which the cache key should be
* created
* @return a key to be used to store the cache of the provided {@link VideoStream video stream}
*/
static String cacheKeyOf(final StreamInfo info, final VideoStream videoStream) {
final boolean resolutionUnknown = videoStream.getResolution().equals(RESOLUTION_UNKNOWN);
final StringBuilder cacheKey = commonCacheKeyOf(info, videoStream, resolutionUnknown);
// resolution (if known)
if (!resolutionUnknown) {
cacheKey.append(" ");
cacheKey.append(videoStream.getResolution());
}
// isVideoOnly
cacheKey.append(" ");
cacheKey.append(videoStream.isVideoOnly());
return cacheKey.toString();
}
/**
* Builds the cache key of an audio stream.
*
* <p>
* A cache key is unique to the features of the provided {@link AudioStream audio stream}, and
* when possible independent of <i>transient</i> parameters (such as the URL of the stream).
* This ensures that there are no conflicts, but also that the cache is used as much as
* possible: the same cache should be used for two streams which have the same features but
* e.g. a different URL, since the URL might have been reloaded in the meantime, but the stream
* actually referenced by the URL is still the same.
* </p>
*
* @param info the {@link StreamInfo stream info}, to distinguish between streams with
* the same features but coming from different stream infos
* @param audioStream the {@link AudioStream audio stream} for which the cache key should be
* created
* @return a key to be used to store the cache of the provided {@link AudioStream audio stream}
*/
static String cacheKeyOf(final StreamInfo info, final AudioStream audioStream) {
final boolean averageBitrateUnknown = audioStream.getAverageBitrate() == UNKNOWN_BITRATE;
final StringBuilder cacheKey = commonCacheKeyOf(info, audioStream, averageBitrateUnknown);
// averageBitrate (if known)
if (!averageBitrateUnknown) {
cacheKey.append(" ");
cacheKey.append(audioStream.getAverageBitrate());
}
return cacheKey.toString();
}
//endregion
//region Live media sources
@Nullable @Nullable
default MediaSource maybeBuildLiveMediaSource(@NonNull final PlayerDataSource dataSource, static MediaSource maybeBuildLiveMediaSource(final PlayerDataSource dataSource,
@NonNull final StreamInfo info) { final StreamInfo info) {
final StreamType streamType = info.getStreamType(); if (!StreamTypeUtil.isLiveStream(info.getStreamType())) {
if (!StreamTypeUtil.isLiveStream(streamType)) {
return null; return null;
} }
try {
final StreamInfoTag tag = StreamInfoTag.of(info); final StreamInfoTag tag = StreamInfoTag.of(info);
if (!info.getHlsUrl().isEmpty()) { if (!info.getHlsUrl().isEmpty()) {
return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.TYPE_HLS, tag); return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.TYPE_HLS, tag);
} else if (!info.getDashMpdUrl().isEmpty()) { } else if (!info.getDashMpdUrl().isEmpty()) {
return buildLiveMediaSource(dataSource, info.getDashMpdUrl(), C.TYPE_DASH, tag); return buildLiveMediaSource(dataSource, info.getDashMpdUrl(), C.TYPE_DASH, tag);
} }
} catch (final Exception e) {
Log.w(TAG, "Error when generating live media source, falling back to standard sources",
e);
}
return null; return null;
} }
@NonNull static MediaSource buildLiveMediaSource(final PlayerDataSource dataSource,
default MediaSource buildLiveMediaSource(@NonNull final PlayerDataSource dataSource, final String sourceUrl,
@NonNull final String sourceUrl,
@C.ContentType final int type, @C.ContentType final int type,
@NonNull final MediaItemTag metadata) { final MediaItemTag metadata) throws ResolverException {
final MediaSource.Factory factory; final MediaSource.Factory factory;
switch (type) { switch (type) {
case C.TYPE_SS: case C.TYPE_SS:
@ -56,8 +199,10 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
case C.TYPE_HLS: case C.TYPE_HLS:
factory = dataSource.getLiveHlsMediaSourceFactory(); factory = dataSource.getLiveHlsMediaSourceFactory();
break; break;
case C.TYPE_OTHER:
case C.TYPE_RTSP:
default: default:
throw new IllegalStateException("Unsupported type: " + type); throw new ResolverException("Unsupported type: " + type);
} }
return factory.createMediaSource( return factory.createMediaSource(
@ -67,46 +212,317 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
.setLiveConfiguration( .setLiveConfiguration(
new MediaItem.LiveConfiguration.Builder() new MediaItem.LiveConfiguration.Builder()
.setTargetOffsetMs(LIVE_STREAM_EDGE_GAP_MILLIS) .setTargetOffsetMs(LIVE_STREAM_EDGE_GAP_MILLIS)
.build() .build())
) .build());
.build() }
); //endregion
//region Generic media sources
static MediaSource buildMediaSource(final PlayerDataSource dataSource,
final Stream stream,
final StreamInfo streamInfo,
final String cacheKey,
final MediaItemTag metadata) throws ResolverException {
if (streamInfo.getService() == ServiceList.YouTube) {
return createYoutubeMediaSource(stream, streamInfo, dataSource, cacheKey, metadata);
} }
@NonNull final DeliveryMethod deliveryMethod = stream.getDeliveryMethod();
default MediaSource buildMediaSource(@NonNull final PlayerDataSource dataSource, switch (deliveryMethod) {
@NonNull final String sourceUrl, case PROGRESSIVE_HTTP:
@NonNull final String cacheKey, return buildProgressiveMediaSource(dataSource, stream, cacheKey, metadata);
@NonNull final String overrideExtension, case DASH:
@NonNull final MediaItemTag metadata) { return buildDashMediaSource(dataSource, stream, cacheKey, metadata);
final Uri uri = Uri.parse(sourceUrl); case HLS:
@C.ContentType final int type = TextUtils.isEmpty(overrideExtension) return buildHlsMediaSource(dataSource, stream, cacheKey, metadata);
? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension); case SS:
return buildSSMediaSource(dataSource, stream, cacheKey, metadata);
final MediaSource.Factory factory; // Torrent streams are not supported by ExoPlayer
switch (type) {
case C.TYPE_SS:
factory = dataSource.getLiveSsMediaSourceFactory();
break;
case C.TYPE_DASH:
factory = dataSource.getDashMediaSourceFactory();
break;
case C.TYPE_HLS:
factory = dataSource.getHlsMediaSourceFactory();
break;
case C.TYPE_OTHER:
factory = dataSource.getExtractorMediaSourceFactory();
break;
default: default:
throw new IllegalStateException("Unsupported type: " + type); throw new ResolverException("Unsupported delivery type: " + deliveryMethod);
}
} }
return factory.createMediaSource( private static ProgressiveMediaSource buildProgressiveMediaSource(
final PlayerDataSource dataSource,
final Stream stream,
final String cacheKey,
final MediaItemTag metadata) throws ResolverException {
if (!stream.isUrl()) {
throw new ResolverException("Non URI progressive contents are not supported");
}
throwResolverExceptionIfUrlNullOrEmpty(stream.getContent());
return dataSource.getProgressiveMediaSourceFactory().createMediaSource(
new MediaItem.Builder() new MediaItem.Builder()
.setTag(metadata) .setTag(metadata)
.setUri(uri) .setUri(Uri.parse(stream.getContent()))
.setCustomCacheKey(cacheKey) .setCustomCacheKey(cacheKey)
.build() .build());
);
} }
private static DashMediaSource buildDashMediaSource(final PlayerDataSource dataSource,
final Stream stream,
final String cacheKey,
final MediaItemTag metadata)
throws ResolverException {
if (stream.isUrl()) {
throwResolverExceptionIfUrlNullOrEmpty(stream.getContent());
return dataSource.getDashMediaSourceFactory().createMediaSource(
new MediaItem.Builder()
.setTag(metadata)
.setUri(Uri.parse(stream.getContent()))
.setCustomCacheKey(cacheKey)
.build());
}
try {
return dataSource.getDashMediaSourceFactory().createMediaSource(
createDashManifest(stream.getContent(), stream),
new MediaItem.Builder()
.setTag(metadata)
.setUri(manifestUrlToUri(stream.getManifestUrl()))
.setCustomCacheKey(cacheKey)
.build());
} catch (final IOException e) {
throw new ResolverException(
"Could not create a DASH media source/manifest from the manifest text", e);
}
}
private static DashManifest createDashManifest(final String manifestContent,
final Stream stream) throws IOException {
return new DashManifestParser().parse(manifestUrlToUri(stream.getManifestUrl()),
new ByteArrayInputStream(manifestContent.getBytes(StandardCharsets.UTF_8)));
}
private static HlsMediaSource buildHlsMediaSource(final PlayerDataSource dataSource,
final Stream stream,
final String cacheKey,
final MediaItemTag metadata)
throws ResolverException {
if (stream.isUrl()) {
throwResolverExceptionIfUrlNullOrEmpty(stream.getContent());
return dataSource.getHlsMediaSourceFactory(null).createMediaSource(
new MediaItem.Builder()
.setTag(metadata)
.setUri(Uri.parse(stream.getContent()))
.setCustomCacheKey(cacheKey)
.build());
}
final NonUriHlsDataSourceFactory.Builder hlsDataSourceFactoryBuilder =
new NonUriHlsDataSourceFactory.Builder();
hlsDataSourceFactoryBuilder.setPlaylistString(stream.getContent());
return dataSource.getHlsMediaSourceFactory(hlsDataSourceFactoryBuilder)
.createMediaSource(new MediaItem.Builder()
.setTag(metadata)
.setUri(manifestUrlToUri(stream.getManifestUrl()))
.setCustomCacheKey(cacheKey)
.build());
}
private static SsMediaSource buildSSMediaSource(final PlayerDataSource dataSource,
final Stream stream,
final String cacheKey,
final MediaItemTag metadata)
throws ResolverException {
if (stream.isUrl()) {
throwResolverExceptionIfUrlNullOrEmpty(stream.getContent());
return dataSource.getSSMediaSourceFactory().createMediaSource(
new MediaItem.Builder()
.setTag(metadata)
.setUri(Uri.parse(stream.getContent()))
.setCustomCacheKey(cacheKey)
.build());
}
final Uri manifestUri = manifestUrlToUri(stream.getManifestUrl());
final SsManifest smoothStreamingManifest;
try {
final ByteArrayInputStream smoothStreamingManifestInput = new ByteArrayInputStream(
stream.getContent().getBytes(StandardCharsets.UTF_8));
smoothStreamingManifest = new SsManifestParser().parse(manifestUri,
smoothStreamingManifestInput);
} catch (final IOException e) {
throw new ResolverException("Error when parsing manual SS manifest", e);
}
return dataSource.getSSMediaSourceFactory().createMediaSource(
smoothStreamingManifest,
new MediaItem.Builder()
.setTag(metadata)
.setUri(manifestUri)
.setCustomCacheKey(cacheKey)
.build());
}
//endregion
//region YouTube media sources
private static MediaSource createYoutubeMediaSource(final Stream stream,
final StreamInfo streamInfo,
final PlayerDataSource dataSource,
final String cacheKey,
final MediaItemTag metadata)
throws ResolverException {
if (!(stream instanceof AudioStream || stream instanceof VideoStream)) {
throw new ResolverException("Generation of YouTube DASH manifest for "
+ stream.getClass().getSimpleName() + " is not supported");
}
final StreamType streamType = streamInfo.getStreamType();
if (streamType == StreamType.VIDEO_STREAM) {
return createYoutubeMediaSourceOfVideoStreamType(dataSource, stream, streamInfo,
cacheKey, metadata);
} else if (streamType == StreamType.POST_LIVE_STREAM) {
// If the content is not an URL, uses the DASH delivery method and if the stream type
// of the stream is a post live stream, it means that the content is an ended
// livestream so we need to generate the manifest corresponding to the content
// (which is the last segment of the stream)
try {
final ItagItem itagItem = Objects.requireNonNull(stream.getItagItem());
final String manifestString = YoutubePostLiveStreamDvrDashManifestCreator
.fromPostLiveStreamDvrStreamingUrl(stream.getContent(),
itagItem,
itagItem.getTargetDurationSec(),
streamInfo.getDuration());
return buildYoutubeManualDashMediaSource(dataSource,
createDashManifest(manifestString, stream), stream, cacheKey,
metadata);
} catch (final CreationException | IOException | NullPointerException e) {
throw new ResolverException(
"Error when generating the DASH manifest of YouTube ended live stream", e);
}
} else {
throw new ResolverException(
"DASH manifest generation of YouTube livestreams is not supported");
}
}
private static MediaSource createYoutubeMediaSourceOfVideoStreamType(
final PlayerDataSource dataSource,
final Stream stream,
final StreamInfo streamInfo,
final String cacheKey,
final MediaItemTag metadata) throws ResolverException {
final DeliveryMethod deliveryMethod = stream.getDeliveryMethod();
switch (deliveryMethod) {
case PROGRESSIVE_HTTP:
if ((stream instanceof VideoStream && ((VideoStream) stream).isVideoOnly())
|| stream instanceof AudioStream) {
try {
final String manifestString = YoutubeProgressiveDashManifestCreator
.fromProgressiveStreamingUrl(stream.getContent(),
Objects.requireNonNull(stream.getItagItem()),
streamInfo.getDuration());
return buildYoutubeManualDashMediaSource(dataSource,
createDashManifest(manifestString, stream), stream, cacheKey,
metadata);
} catch (final CreationException | IOException | NullPointerException e) {
Log.w(TAG, "Error when generating or parsing DASH manifest of "
+ "YouTube progressive stream, falling back to a "
+ "ProgressiveMediaSource.", e);
return buildYoutubeProgressiveMediaSource(dataSource, stream, cacheKey,
metadata);
}
} else {
// Legacy progressive streams, subtitles are handled by
// VideoPlaybackResolver
return buildYoutubeProgressiveMediaSource(dataSource, stream, cacheKey,
metadata);
}
case DASH:
// If the content is not a URL, uses the DASH delivery method and if the stream
// type of the stream is a video stream, it means the content is an OTF stream
// so we need to generate the manifest corresponding to the content (which is
// the base URL of the OTF stream).
try {
final String manifestString = YoutubeOtfDashManifestCreator
.fromOtfStreamingUrl(stream.getContent(),
Objects.requireNonNull(stream.getItagItem()),
streamInfo.getDuration());
return buildYoutubeManualDashMediaSource(dataSource,
createDashManifest(manifestString, stream), stream, cacheKey,
metadata);
} catch (final CreationException | IOException | NullPointerException e) {
Log.e(TAG,
"Error when generating the DASH manifest of YouTube OTF stream", e);
throw new ResolverException(
"Error when generating the DASH manifest of YouTube OTF stream", e);
}
case HLS:
return dataSource.getYoutubeHlsMediaSourceFactory().createMediaSource(
new MediaItem.Builder()
.setTag(metadata)
.setUri(Uri.parse(stream.getContent()))
.setCustomCacheKey(cacheKey)
.build());
default:
throw new ResolverException("Unsupported delivery method for YouTube contents: "
+ deliveryMethod);
}
}
private static DashMediaSource buildYoutubeManualDashMediaSource(
final PlayerDataSource dataSource,
final DashManifest dashManifest,
final Stream stream,
final String cacheKey,
final MediaItemTag metadata) {
return dataSource.getYoutubeDashMediaSourceFactory().createMediaSource(dashManifest,
new MediaItem.Builder()
.setTag(metadata)
.setUri(Uri.parse(stream.getContent()))
.setCustomCacheKey(cacheKey)
.build());
}
private static ProgressiveMediaSource buildYoutubeProgressiveMediaSource(
final PlayerDataSource dataSource,
final Stream stream,
final String cacheKey,
final MediaItemTag metadata) {
return dataSource.getYoutubeProgressiveMediaSourceFactory()
.createMediaSource(new MediaItem.Builder()
.setTag(metadata)
.setUri(Uri.parse(stream.getContent()))
.setCustomCacheKey(cacheKey)
.build());
}
//endregion
//region Utils
private static Uri manifestUrlToUri(final String manifestUrl) {
return Uri.parse(Objects.requireNonNullElse(manifestUrl, ""));
}
private static void throwResolverExceptionIfUrlNullOrEmpty(@Nullable final String url)
throws ResolverException {
if (url == null) {
throw new ResolverException("Null stream URL");
} else if (url.isEmpty()) {
throw new ResolverException("Empty stream URL");
}
}
//endregion
//region Resolver exception
final class ResolverException extends Exception {
public ResolverException(final String message) {
super(message);
}
public ResolverException(final String message, final Throwable cause) {
super(message, cause);
}
}
//endregion
} }

View file

@ -2,6 +2,7 @@ package org.schabi.newpipe.player.resolver;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -27,8 +28,12 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import static com.google.android.exoplayer2.C.TIME_UNSET; import static com.google.android.exoplayer2.C.TIME_UNSET;
import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams;
import static org.schabi.newpipe.util.ListHelper.getNonTorrentStreams;
public class VideoPlaybackResolver implements PlaybackResolver { public class VideoPlaybackResolver implements PlaybackResolver {
private static final String TAG = VideoPlaybackResolver.class.getSimpleName();
@NonNull @NonNull
private final Context context; private final Context context;
@NonNull @NonNull
@ -57,7 +62,7 @@ public class VideoPlaybackResolver implements PlaybackResolver {
@Override @Override
@Nullable @Nullable
public MediaSource resolve(@NonNull final StreamInfo info) { public MediaSource resolve(@NonNull final StreamInfo info) {
final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info); final MediaSource liveSource = PlaybackResolver.maybeBuildLiveMediaSource(dataSource, info);
if (liveSource != null) { if (liveSource != null) {
streamSourceType = SourceType.LIVE_STREAM; streamSourceType = SourceType.LIVE_STREAM;
return liveSource; return liveSource;
@ -66,40 +71,51 @@ public class VideoPlaybackResolver implements PlaybackResolver {
final List<MediaSource> mediaSources = new ArrayList<>(); final List<MediaSource> mediaSources = new ArrayList<>();
// Create video stream source // Create video stream source
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context, final List<VideoStream> videoStreamsList = ListHelper.getSortedStreamVideosList(context,
info.getVideoStreams(), info.getVideoOnlyStreams(), false, true); getNonTorrentStreams(info.getVideoStreams()),
getNonTorrentStreams(info.getVideoOnlyStreams()), false, true);
final int index; final int index;
if (videos.isEmpty()) { if (videoStreamsList.isEmpty()) {
index = -1; index = -1;
} else if (playbackQuality == null) { } else if (playbackQuality == null) {
index = qualityResolver.getDefaultResolutionIndex(videos); index = qualityResolver.getDefaultResolutionIndex(videoStreamsList);
} else { } else {
index = qualityResolver.getOverrideResolutionIndex(videos, getPlaybackQuality()); index = qualityResolver.getOverrideResolutionIndex(videoStreamsList,
getPlaybackQuality());
} }
final MediaItemTag tag = StreamInfoTag.of(info, videos, index); final MediaItemTag tag = StreamInfoTag.of(info, videoStreamsList, index);
@Nullable final VideoStream video = tag.getMaybeQuality() @Nullable final VideoStream video = tag.getMaybeQuality()
.map(MediaItemTag.Quality::getSelectedVideoStream) .map(MediaItemTag.Quality::getSelectedVideoStream)
.orElse(null); .orElse(null);
if (video != null) { if (video != null) {
final MediaSource streamSource = buildMediaSource(dataSource, video.getUrl(), try {
PlayerHelper.cacheKeyOf(info, video), final MediaSource streamSource = PlaybackResolver.buildMediaSource(
MediaFormat.getSuffixById(video.getFormatId()), tag); dataSource, video, info, PlaybackResolver.cacheKeyOf(info, video), tag);
mediaSources.add(streamSource); mediaSources.add(streamSource);
} catch (final ResolverException e) {
Log.e(TAG, "Unable to create video source", e);
return null;
}
} }
// Create optional audio stream source // Create optional audio stream source
final List<AudioStream> audioStreams = info.getAudioStreams(); final List<AudioStream> audioStreams = getNonTorrentStreams(info.getAudioStreams());
final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get( final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get(
ListHelper.getDefaultAudioFormat(context, audioStreams)); ListHelper.getDefaultAudioFormat(context, audioStreams));
// Use the audio stream if there is no video stream, or // Use the audio stream if there is no video stream, or
// Merge with audio stream in case if video does not contain audio // merge with audio stream in case if video does not contain audio
if (audio != null && (video == null || video.isVideoOnly)) { if (audio != null && (video == null || video.isVideoOnly())) {
final MediaSource audioSource = buildMediaSource(dataSource, audio.getUrl(), try {
PlayerHelper.cacheKeyOf(info, audio), final MediaSource audioSource = PlaybackResolver.buildMediaSource(
MediaFormat.getSuffixById(audio.getFormatId()), tag); dataSource, audio, info, PlaybackResolver.cacheKeyOf(info, audio), tag);
mediaSources.add(audioSource); mediaSources.add(audioSource);
streamSourceType = SourceType.VIDEO_WITH_SEPARATED_AUDIO; streamSourceType = SourceType.VIDEO_WITH_SEPARATED_AUDIO;
} catch (final ResolverException e) {
Log.e(TAG, "Unable to create audio source", e);
return null;
}
} else { } else {
streamSourceType = SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY; streamSourceType = SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY;
} }
@ -108,36 +124,39 @@ public class VideoPlaybackResolver implements PlaybackResolver {
if (mediaSources.isEmpty()) { if (mediaSources.isEmpty()) {
return null; return null;
} }
// Below are auxiliary media sources // Below are auxiliary media sources
// Create subtitle sources // Create subtitle sources
if (info.getSubtitles() != null) { final List<SubtitlesStream> subtitlesStreams = info.getSubtitles();
for (final SubtitlesStream subtitle : info.getSubtitles()) { if (subtitlesStreams != null) {
final String mimeType = PlayerHelper.subtitleMimeTypesOf(subtitle.getFormat()); // Torrent and non URL subtitles are not supported by ExoPlayer
if (mimeType == null) { final List<SubtitlesStream> nonTorrentAndUrlStreams = getUrlAndNonTorrentStreams(
continue; subtitlesStreams);
} for (final SubtitlesStream subtitle : nonTorrentAndUrlStreams) {
final @C.RoleFlags int textRoleFlag = subtitle.isAutoGenerated() final MediaFormat mediaFormat = subtitle.getFormat();
if (mediaFormat != null) {
@C.RoleFlags final int textRoleFlag = subtitle.isAutoGenerated()
? C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND ? C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND
: C.ROLE_FLAG_CAPTION; : C.ROLE_FLAG_CAPTION;
final MediaItem.SubtitleConfiguration textMediaItem = final MediaItem.SubtitleConfiguration textMediaItem =
new MediaItem.SubtitleConfiguration.Builder(Uri.parse(subtitle.getUrl())) new MediaItem.SubtitleConfiguration.Builder(
.setMimeType(mimeType) Uri.parse(subtitle.getContent()))
.setMimeType(mediaFormat.getMimeType())
.setRoleFlags(textRoleFlag) .setRoleFlags(textRoleFlag)
.setLanguage(PlayerHelper.captionLanguageOf(context, subtitle)) .setLanguage(PlayerHelper.captionLanguageOf(context, subtitle))
.build(); .build();
final MediaSource textSource = dataSource final MediaSource textSource = dataSource.getSingleSampleMediaSourceFactory()
.getSampleMediaSourceFactory()
.createMediaSource(textMediaItem, TIME_UNSET); .createMediaSource(textMediaItem, TIME_UNSET);
mediaSources.add(textSource); mediaSources.add(textSource);
} }
} }
}
if (mediaSources.size() == 1) { if (mediaSources.size() == 1) {
return mediaSources.get(0); return mediaSources.get(0);
} else { } else {
return new MergingMediaSource(mediaSources.toArray( return new MergingMediaSource(true, mediaSources.toArray(new MediaSource[0]));
new MediaSource[0]));
} }
} }

View file

@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ReleaseVersionUtil;
public class MainSettingsFragment extends BasePreferenceFragment { public class MainSettingsFragment extends BasePreferenceFragment {
public static final boolean DEBUG = MainActivity.DEBUG; public static final boolean DEBUG = MainActivity.DEBUG;
@ -21,6 +22,14 @@ public class MainSettingsFragment extends BasePreferenceFragment {
setHasOptionsMenu(true); // Otherwise onCreateOptionsMenu is not called setHasOptionsMenu(true); // Otherwise onCreateOptionsMenu is not called
// Check if the app is updatable
if (!ReleaseVersionUtil.isReleaseApk()) {
getPreferenceScreen().removePreference(
findPreference(getString(R.string.update_pref_screen_key)));
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();
}
// Hide debug preferences in RELEASE build variant // Hide debug preferences in RELEASE build variant
if (!DEBUG) { if (!DEBUG) {
getPreferenceScreen().removePreference( getPreferenceScreen().removePreference(

View file

@ -207,7 +207,7 @@ public class PeertubeInstanceListFragment extends Fragment {
new AlertDialog.Builder(c) new AlertDialog.Builder(c)
.setTitle(R.string.peertube_instance_add_title) .setTitle(R.string.peertube_instance_add_title)
.setIcon(R.drawable.place_holder_peertube) .setIcon(R.drawable.ic_placeholder_peertube)
.setView(dialogBinding.getRoot()) .setView(dialogBinding.getRoot())
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, (dialog1, which) -> { .setPositiveButton(R.string.ok, (dialog1, which) -> {
@ -411,7 +411,7 @@ public class PeertubeInstanceListFragment extends Fragment {
lastChecked = instanceRB; lastChecked = instanceRB;
} }
}); });
instanceIconView.setImageResource(R.drawable.place_holder_peertube); instanceIconView.setImageResource(R.drawable.ic_placeholder_peertube);
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")

View file

@ -10,7 +10,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton; import android.widget.RadioButton;
import android.widget.RadioGroup; import android.widget.RadioGroup;
import android.widget.TextView; import android.widget.TextView;
@ -21,11 +20,12 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.widget.TextViewCompat;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.NotificationConstants; import org.schabi.newpipe.player.NotificationConstants;
import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.DeviceUtils;
@ -190,13 +190,12 @@ public class NotificationActionsPreference extends Preference {
void openActionChooserDialog() { void openActionChooserDialog() {
final LayoutInflater inflater = LayoutInflater.from(getContext()); final LayoutInflater inflater = LayoutInflater.from(getContext());
final LinearLayout rootLayout = (LinearLayout) inflater.inflate( final SingleChoiceDialogViewBinding binding =
R.layout.single_choice_dialog_view, null, false); SingleChoiceDialogViewBinding.inflate(inflater);
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
final AlertDialog alertDialog = new AlertDialog.Builder(getContext()) final AlertDialog alertDialog = new AlertDialog.Builder(getContext())
.setTitle(SLOT_TITLES[i]) .setTitle(SLOT_TITLES[i])
.setView(radioGroup) .setView(binding.getRoot())
.setCancelable(true) .setCancelable(true)
.create(); .create();
@ -208,8 +207,8 @@ public class NotificationActionsPreference extends Preference {
for (int id = 0; id < NotificationConstants.SLOT_ALLOWED_ACTIONS[i].length; ++id) { for (int id = 0; id < NotificationConstants.SLOT_ALLOWED_ACTIONS[i].length; ++id) {
final int action = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][id]; final int action = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][id];
final RadioButton radioButton final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater)
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null); .getRoot();
// if present set action icon with correct color // if present set action icon with correct color
if (NotificationConstants.ACTION_ICONS[action] != 0) { if (NotificationConstants.ACTION_ICONS[action] != 0) {
@ -220,8 +219,8 @@ public class NotificationActionsPreference extends Preference {
android.R.attr.textColorPrimary); android.R.attr.textColorPrimary);
drawable = DrawableCompat.wrap(drawable).mutate(); drawable = DrawableCompat.wrap(drawable).mutate();
DrawableCompat.setTint(drawable, color); DrawableCompat.setTint(drawable, color);
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(radioButton, radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(null,
null, null, drawable, null); null, drawable, null);
} }
} }
@ -231,7 +230,7 @@ public class NotificationActionsPreference extends Preference {
radioButton.setLayoutParams(new RadioGroup.LayoutParams( radioButton.setLayoutParams(new RadioGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
radioButton.setOnClickListener(radioButtonsClickListener); radioButton.setOnClickListener(radioButtonsClickListener);
radioGroup.addView(radioButton); binding.list.addView(radioButton);
} }
alertDialog.show(); alertDialog.show();

View file

@ -1,5 +1,8 @@
package org.schabi.newpipe.settings.tabs; package org.schabi.newpipe.settings.tabs;
import static org.schabi.newpipe.settings.tabs.Tab.typeFrom;
import static org.schabi.newpipe.util.ServiceHelper.getNameOfServiceById;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
@ -28,7 +31,6 @@ 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.extractor.NewPipe;
import org.schabi.newpipe.settings.SelectChannelFragment; import org.schabi.newpipe.settings.SelectChannelFragment;
import org.schabi.newpipe.settings.SelectKioskFragment; import org.schabi.newpipe.settings.SelectKioskFragment;
import org.schabi.newpipe.settings.SelectPlaylistFragment; import org.schabi.newpipe.settings.SelectPlaylistFragment;
@ -39,8 +41,6 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static org.schabi.newpipe.settings.tabs.Tab.typeFrom;
public class ChooseTabsFragment extends Fragment { public class ChooseTabsFragment extends Fragment {
private TabsManager tabsManager; private TabsManager tabsManager;
@ -374,36 +374,31 @@ public class ChooseTabsFragment extends Fragment {
return; return;
} }
final String tabName; tabNameView.setText(getTabName(type, tab));
tabIconView.setImageResource(tab.getTabIconRes(requireContext()));
}
private String getTabName(@NonNull final Tab.Type type, @NonNull final Tab tab) {
switch (type) { switch (type) {
case BLANK: case BLANK:
tabName = getString(R.string.blank_page_summary); return getString(R.string.blank_page_summary);
break;
case DEFAULT_KIOSK: case DEFAULT_KIOSK:
tabName = getString(R.string.default_kiosk_page_summary); return getString(R.string.default_kiosk_page_summary);
break;
case KIOSK: case KIOSK:
tabName = NewPipe.getNameOfService(((Tab.KioskTab) tab) return getNameOfServiceById(((Tab.KioskTab) tab).getKioskServiceId())
.getKioskServiceId()) + "/" + tab.getTabName(requireContext()); + "/" + tab.getTabName(requireContext());
break;
case CHANNEL: case CHANNEL:
tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab) return getNameOfServiceById(((Tab.ChannelTab) tab).getChannelServiceId())
.getChannelServiceId()) + "/" + tab.getTabName(requireContext()); + "/" + tab.getTabName(requireContext());
break;
case PLAYLIST: case PLAYLIST:
final int serviceId = ((Tab.PlaylistTab) tab).getPlaylistServiceId(); final int serviceId = ((Tab.PlaylistTab) tab).getPlaylistServiceId();
final String serviceName = serviceId == -1 final String serviceName = serviceId == -1
? getString(R.string.local) ? getString(R.string.local)
: NewPipe.getNameOfService(serviceId); : getNameOfServiceById(serviceId);
tabName = serviceName + "/" + tab.getTabName(requireContext()); return serviceName + "/" + tab.getTabName(requireContext());
break;
default: default:
tabName = tab.getTabName(requireContext()); return tab.getTabName(requireContext());
break;
} }
tabNameView.setText(tabName);
tabIconView.setImageResource(tab.getTabIconRes(requireContext()));
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")

View file

@ -24,9 +24,21 @@ public final class KeyboardUtil {
if (editText.requestFocus()) { if (editText.requestFocus()) {
final InputMethodManager imm = ContextCompat.getSystemService(activity, final InputMethodManager imm = ContextCompat.getSystemService(activity,
InputMethodManager.class); InputMethodManager.class);
if (!imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED)) {
/*
* Sometimes the keyboard can't be shown because Android's ImeFocusController is in
* a incorrect state e.g. when animations are disabled or the unfocus event of the
* previous view arrives in the wrong moment (see #7647 for details).
* The invalid state can be fixed by to re-focusing the editText.
*/
editText.clearFocus();
editText.requestFocus();
// Try again
imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED); imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED);
} }
} }
}
public static void hideKeyboard(final Activity activity, final EditText editText) { public static void hideKeyboard(final Activity activity, final EditText editText) {
if (activity == null || editText == null) { if (activity == null || editText == null) {

View file

@ -13,6 +13,8 @@ import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
import java.util.ArrayList; import java.util.ArrayList;
@ -24,6 +26,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public final class ListHelper { public final class ListHelper {
@ -37,10 +40,9 @@ public final class ListHelper {
// Audio format in order of efficiency. 0=most efficient, n=least efficient // Audio format in order of efficiency. 0=most efficient, n=least efficient
private static final List<MediaFormat> AUDIO_FORMAT_EFFICIENCY_RANKING = private static final List<MediaFormat> AUDIO_FORMAT_EFFICIENCY_RANKING =
Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3);
// Use a HashSet for better performance
private static final Set<String> HIGH_RESOLUTION_LIST private static final Set<String> HIGH_RESOLUTION_LIST = new HashSet<>(
// Uses a HashSet for better performance Arrays.asList("1440p", "2160p"));
= new HashSet<>(Arrays.asList("1440p", "2160p", "1440p60", "2160p60"));
private ListHelper() { } private ListHelper() { }
@ -110,6 +112,51 @@ public final class ListHelper {
} }
} }
/**
* Return a {@link Stream} list which uses the given delivery method from a {@link Stream}
* list.
*
* @param streamList the original {@link Stream stream} list
* @param deliveryMethod the {@link DeliveryMethod delivery method}
* @param <S> the item type's class that extends {@link Stream}
* @return a {@link Stream stream} list which uses the given delivery method
*/
@NonNull
public static <S extends Stream> List<S> getStreamsOfSpecifiedDelivery(
final List<S> streamList,
final DeliveryMethod deliveryMethod) {
return getFilteredStreamList(streamList,
stream -> stream.getDeliveryMethod() == deliveryMethod);
}
/**
* Return a {@link Stream} list which only contains URL streams and non-torrent streams.
*
* @param streamList the original stream list
* @param <S> the item type's class that extends {@link Stream}
* @return a stream list which only contains URL streams and non-torrent streams
*/
@NonNull
public static <S extends Stream> List<S> getUrlAndNonTorrentStreams(
final List<S> streamList) {
return getFilteredStreamList(streamList,
stream -> stream.isUrl() && stream.getDeliveryMethod() != DeliveryMethod.TORRENT);
}
/**
* Return a {@link Stream} list which only contains non-torrent streams.
*
* @param streamList the original stream list
* @param <S> the item type's class that extends {@link Stream}
* @return a stream list which only contains non-torrent streams
*/
@NonNull
public static <S extends Stream> List<S> getNonTorrentStreams(
final List<S> streamList) {
return getFilteredStreamList(streamList,
stream -> stream.getDeliveryMethod() != DeliveryMethod.TORRENT);
}
/** /**
* Join the two lists of video streams (video_only and normal videos), * Join the two lists of video streams (video_only and normal videos),
* and sort them according with default format chosen by the user. * and sort them according with default format chosen by the user.
@ -145,6 +192,26 @@ public final class ListHelper {
// Utils // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
/**
* Get a filtered stream list, by using Java 8 Stream's API and the given predicate.
*
* @param streamList the stream list to filter
* @param streamListPredicate the predicate which will be used to filter streams
* @param <S> the item type's class that extends {@link Stream}
* @return a new stream list filtered using the given predicate
*/
private static <S extends Stream> List<S> getFilteredStreamList(
final List<S> streamList,
final Predicate<S> streamListPredicate) {
if (streamList == null) {
return Collections.emptyList();
}
return streamList.stream()
.filter(streamListPredicate)
.collect(Collectors.toList());
}
private static String computeDefaultResolution(final Context context, final int key, private static String computeDefaultResolution(final Context context, final int key,
final int value) { final int value) {
final SharedPreferences preferences final SharedPreferences preferences
@ -177,7 +244,7 @@ public final class ListHelper {
static int getDefaultResolutionIndex(final String defaultResolution, static int getDefaultResolutionIndex(final String defaultResolution,
final String bestResolutionKey, final String bestResolutionKey,
final MediaFormat defaultFormat, final MediaFormat defaultFormat,
final List<VideoStream> videoStreams) { @Nullable final List<VideoStream> videoStreams) {
if (videoStreams == null || videoStreams.isEmpty()) { if (videoStreams == null || videoStreams.isEmpty()) {
return -1; return -1;
} }
@ -233,7 +300,9 @@ public final class ListHelper {
.flatMap(List::stream) .flatMap(List::stream)
// Filter out higher resolutions (or not if high resolutions should always be shown) // Filter out higher resolutions (or not if high resolutions should always be shown)
.filter(stream -> showHigherResolutions .filter(stream -> showHigherResolutions
|| !HIGH_RESOLUTION_LIST.contains(stream.getResolution())) || !HIGH_RESOLUTION_LIST.contains(stream.getResolution()
// Replace any frame rate with nothing
.replaceAll("p\\d+$", "p")))
.collect(Collectors.toList()); .collect(Collectors.toList());
final HashMap<String, VideoStream> hashMap = new HashMap<>(); final HashMap<String, VideoStream> hashMap = new HashMap<>();
@ -366,8 +435,9 @@ public final class ListHelper {
* @param videoStreams the available video streams * @param videoStreams the available video streams
* @return the index of the preferred video stream * @return the index of the preferred video stream
*/ */
static int getVideoStreamIndex(final String targetResolution, final MediaFormat targetFormat, static int getVideoStreamIndex(@NonNull final String targetResolution,
final List<VideoStream> videoStreams) { final MediaFormat targetFormat,
@NonNull final List<VideoStream> videoStreams) {
int fullMatchIndex = -1; int fullMatchIndex = -1;
int fullMatchNoRefreshIndex = -1; int fullMatchNoRefreshIndex = -1;
int resMatchOnlyIndex = -1; int resMatchOnlyIndex = -1;
@ -428,7 +498,7 @@ public final class ListHelper {
* @param videoStreams the list of video streams to check * @param videoStreams the list of video streams to check
* @return the index of the preferred video stream * @return the index of the preferred video stream
*/ */
private static int getDefaultResolutionWithDefaultFormat(final Context context, private static int getDefaultResolutionWithDefaultFormat(@NonNull final Context context,
final String defaultResolution, final String defaultResolution,
final List<VideoStream> videoStreams) { final List<VideoStream> videoStreams) {
final MediaFormat defaultFormat = getDefaultFormat(context, final MediaFormat defaultFormat = getDefaultFormat(context,
@ -437,7 +507,7 @@ public final class ListHelper {
context.getString(R.string.best_resolution_key), defaultFormat, videoStreams); context.getString(R.string.best_resolution_key), defaultFormat, videoStreams);
} }
private static MediaFormat getDefaultFormat(final Context context, private static MediaFormat getDefaultFormat(@NonNull final Context context,
@StringRes final int defaultFormatKey, @StringRes final int defaultFormatKey,
@StringRes final int defaultFormatValueKey) { @StringRes final int defaultFormatValueKey) {
final SharedPreferences preferences final SharedPreferences preferences
@ -457,8 +527,8 @@ public final class ListHelper {
return defaultMediaFormat; return defaultMediaFormat;
} }
private static MediaFormat getMediaFormatFromKey(final Context context, private static MediaFormat getMediaFormatFromKey(@NonNull final Context context,
final String formatKey) { @NonNull final String formatKey) {
MediaFormat format = null; MediaFormat format = null;
if (formatKey.equals(context.getString(R.string.video_webm_key))) { if (formatKey.equals(context.getString(R.string.video_webm_key))) {
format = MediaFormat.WEBM; format = MediaFormat.WEBM;
@ -496,12 +566,20 @@ public final class ListHelper {
- formatRanking.indexOf(streamB.getFormat()); - formatRanking.indexOf(streamB.getFormat());
} }
private static int compareVideoStreamResolution(final String r1, final String r2) { private static int compareVideoStreamResolution(@NonNull final String r1,
@NonNull final String r2) {
try {
final int res1 = Integer.parseInt(r1.replaceAll("0p\\d+$", "1") final int res1 = Integer.parseInt(r1.replaceAll("0p\\d+$", "1")
.replaceAll("[^\\d.]", "")); .replaceAll("[^\\d.]", ""));
final int res2 = Integer.parseInt(r2.replaceAll("0p\\d+$", "1") final int res2 = Integer.parseInt(r2.replaceAll("0p\\d+$", "1")
.replaceAll("[^\\d.]", "")); .replaceAll("[^\\d.]", ""));
return res1 - res2; return res1 - res2;
} catch (final NumberFormatException e) {
// Consider the first one greater because we don't know if the two streams are
// different or not (a NumberFormatException was thrown so we don't know the resolution
// of one stream or of all streams)
return 1;
}
} }
// Compares the quality of two video streams. // Compares the quality of two video streams.
@ -536,7 +614,7 @@ public final class ListHelper {
* @param context App context * @param context App context
* @return maximum resolution allowed or null if there is no maximum * @return maximum resolution allowed or null if there is no maximum
*/ */
private static String getResolutionLimit(final Context context) { private static String getResolutionLimit(@NonNull final Context context) {
String resolutionLimit = null; String resolutionLimit = null;
if (isMeteredNetwork(context)) { if (isMeteredNetwork(context)) {
final SharedPreferences preferences final SharedPreferences preferences
@ -555,7 +633,7 @@ public final class ListHelper {
* @param context App context * @param context App context
* @return {@code true} if connected to a metered network * @return {@code true} if connected to a metered network
*/ */
public static boolean isMeteredNetwork(final Context context) { public static boolean isMeteredNetwork(@NonNull final Context context) {
final ConnectivityManager manager final ConnectivityManager manager
= ContextCompat.getSystemService(context, ConnectivityManager.class); = ContextCompat.getSystemService(context, ConnectivityManager.class);
if (manager == null || manager.getActiveNetworkInfo() == null) { if (manager == null || manager.getActiveNetworkInfo() == null) {

View file

@ -33,6 +33,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
import org.schabi.newpipe.extractor.stream.Stream; import org.schabi.newpipe.extractor.stream.Stream;
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;
@ -60,7 +61,9 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.ArrayList; import java.util.List;
import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams;
public final class NavigationHelper { public final class NavigationHelper {
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag"; public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
@ -217,30 +220,47 @@ public final class NavigationHelper {
public static void playOnExternalAudioPlayer(@NonNull final Context context, public static void playOnExternalAudioPlayer(@NonNull final Context context,
@NonNull final StreamInfo info) { @NonNull final StreamInfo info) {
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams()); final List<AudioStream> audioStreams = info.getAudioStreams();
if (audioStreams == null || audioStreams.isEmpty()) {
if (index == -1) {
Toast.makeText(context, R.string.audio_streams_empty, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.audio_streams_empty, Toast.LENGTH_SHORT).show();
return; return;
} }
final AudioStream audioStream = info.getAudioStreams().get(index); final List<AudioStream> audioStreamsForExternalPlayers =
getUrlAndNonTorrentStreams(audioStreams);
if (audioStreamsForExternalPlayers.isEmpty()) {
Toast.makeText(context, R.string.no_audio_streams_available_for_external_players,
Toast.LENGTH_SHORT).show();
return;
}
final int index = ListHelper.getDefaultAudioFormat(context, audioStreamsForExternalPlayers);
final AudioStream audioStream = audioStreamsForExternalPlayers.get(index);
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream); playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream);
} }
public static void playOnExternalVideoPlayer(@NonNull final Context context, public static void playOnExternalVideoPlayer(final Context context,
@NonNull final StreamInfo info) { @NonNull final StreamInfo info) {
final ArrayList<VideoStream> videoStreamsList = new ArrayList<>( final List<VideoStream> videoStreams = info.getVideoStreams();
ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false, if (videoStreams == null || videoStreams.isEmpty()) {
false));
final int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList);
if (index == -1) {
Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show();
return; return;
} }
final VideoStream videoStream = videoStreamsList.get(index); final List<VideoStream> videoStreamsForExternalPlayers =
ListHelper.getSortedStreamVideosList(context,
getUrlAndNonTorrentStreams(videoStreams), null, false, false);
if (videoStreamsForExternalPlayers.isEmpty()) {
Toast.makeText(context, R.string.no_video_streams_available_for_external_players,
Toast.LENGTH_SHORT).show();
return;
}
final int index = ListHelper.getDefaultResolutionIndex(context,
videoStreamsForExternalPlayers);
final VideoStream videoStream = videoStreamsForExternalPlayers.get(index);
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream); playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream);
} }
@ -248,9 +268,48 @@ public final class NavigationHelper {
@Nullable final String name, @Nullable final String name,
@Nullable final String artist, @Nullable final String artist,
@NonNull final Stream stream) { @NonNull final Stream stream) {
final DeliveryMethod deliveryMethod = stream.getDeliveryMethod();
final String mimeType;
if (!stream.isUrl() || deliveryMethod == DeliveryMethod.TORRENT) {
Toast.makeText(context, R.string.selected_stream_external_player_not_supported,
Toast.LENGTH_SHORT).show();
return;
}
switch (deliveryMethod) {
case PROGRESSIVE_HTTP:
if (stream.getFormat() == null) {
if (stream instanceof AudioStream) {
mimeType = "audio/*";
} else if (stream instanceof VideoStream) {
mimeType = "video/*";
} else {
// This should never be reached, because subtitles are not opened in
// external players
return;
}
} else {
mimeType = stream.getFormat().getMimeType();
}
break;
case HLS:
mimeType = "application/x-mpegURL";
break;
case DASH:
mimeType = "application/dash+xml";
break;
case SS:
mimeType = "application/vnd.ms-sstr+xml";
break;
default:
// Torrent streams are not exposed to external players
mimeType = "";
}
final Intent intent = new Intent(); final Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(stream.getUrl()), stream.getFormat().getMimeType()); intent.setDataAndType(Uri.parse(stream.getContent()), mimeType);
intent.putExtra(Intent.EXTRA_TITLE, name); intent.putExtra(Intent.EXTRA_TITLE, name);
intent.putExtra("title", name); intent.putExtra("title", name);
intent.putExtra("artist", artist); intent.putExtra("artist", artist);

View file

@ -7,6 +7,8 @@ import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import androidx.annotation.Nullable;
import com.squareup.picasso.Cache; import com.squareup.picasso.Cache;
import com.squareup.picasso.LruCache; import com.squareup.picasso.LruCache;
import com.squareup.picasso.OkHttp3Downloader; import com.squareup.picasso.OkHttp3Downloader;
@ -158,6 +160,11 @@ public final class PicassoHelper {
}); });
} }
@Nullable
public static Bitmap getImageFromCacheIfPresent(final String imageUrl) {
// URLs in the internal cache finish with \n so we need to add \n to image URLs
return picassoCache.get(imageUrl + "\n");
}
public static void loadNotificationIcon(final String url, public static void loadNotificationIcon(final String url,
final Consumer<Bitmap> bitmapConsumer) { final Consumer<Bitmap> bitmapConsumer) {

View file

@ -22,7 +22,7 @@ import java.time.format.DateTimeFormatter
object ReleaseVersionUtil { object ReleaseVersionUtil {
// Public key of the certificate that is used in NewPipe release versions // Public key of the certificate that is used in NewPipe release versions
private const val RELEASE_CERT_PUBLIC_KEY_SHA1 = private const val RELEASE_CERT_PUBLIC_KEY_SHA1 =
"B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15" "7F:46:0D:D0:6A:2D:A0:6B:57:B5:2C:ED:73:06:B7:87:43:90:66:A9"
@JvmStatic @JvmStatic
fun isReleaseApk(): Boolean { fun isReleaseApk(): Boolean {

View file

@ -1,6 +1,7 @@
package org.schabi.newpipe.util; package org.schabi.newpipe.util;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
@ -14,7 +15,8 @@ public class SecondaryStreamHelper<T extends Stream> {
private final int position; private final int position;
private final StreamSizeWrapper<T> streams; private final StreamSizeWrapper<T> streams;
public SecondaryStreamHelper(final StreamSizeWrapper<T> streams, final T selectedStream) { public SecondaryStreamHelper(@NonNull final StreamSizeWrapper<T> streams,
final T selectedStream) {
this.streams = streams; this.streams = streams;
this.position = streams.getStreamsList().indexOf(selectedStream); this.position = streams.getStreamsList().indexOf(selectedStream);
if (this.position < 0) { if (this.position < 0) {
@ -29,9 +31,15 @@ public class SecondaryStreamHelper<T extends Stream> {
* @param videoStream desired video ONLY stream * @param videoStream desired video ONLY stream
* @return selected audio stream or null if a candidate was not found * @return selected audio stream or null if a candidate was not found
*/ */
@Nullable
public static AudioStream getAudioStreamFor(@NonNull final List<AudioStream> audioStreams, public static AudioStream getAudioStreamFor(@NonNull final List<AudioStream> audioStreams,
@NonNull final VideoStream videoStream) { @NonNull final VideoStream videoStream) {
switch (videoStream.getFormat()) { final MediaFormat mediaFormat = videoStream.getFormat();
if (mediaFormat == null) {
return null;
}
switch (mediaFormat) {
case WEBM: case WEBM:
case MPEG_4:// ¿is mpeg-4 DASH? case MPEG_4:// ¿is mpeg-4 DASH?
break; break;
@ -39,7 +47,7 @@ public class SecondaryStreamHelper<T extends Stream> {
return null; return null;
} }
final boolean m4v = videoStream.getFormat() == MediaFormat.MPEG_4; final boolean m4v = (mediaFormat == MediaFormat.MPEG_4);
for (final AudioStream audio : audioStreams) { for (final AudioStream audio : audioStreams) {
if (audio.getFormat() == (m4v ? MediaFormat.M4A : MediaFormat.WEBMA)) { if (audio.getFormat() == (m4v ? MediaFormat.M4A : MediaFormat.WEBMA)) {

View file

@ -1,9 +1,13 @@
package org.schabi.newpipe.util; package org.schabi.newpipe.util;
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@ -18,10 +22,9 @@ import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
public final class ServiceHelper { public final class ServiceHelper {
private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube; private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube;
@ -31,17 +34,17 @@ public final class ServiceHelper {
public static int getIcon(final int serviceId) { public static int getIcon(final int serviceId) {
switch (serviceId) { switch (serviceId) {
case 0: case 0:
return R.drawable.place_holder_youtube; return R.drawable.ic_smart_display;
case 1: case 1:
return R.drawable.place_holder_cloud; return R.drawable.ic_cloud;
case 2: case 2:
return R.drawable.place_holder_gadse; return R.drawable.ic_placeholder_media_ccc;
case 3: case 3:
return R.drawable.place_holder_peertube; return R.drawable.ic_placeholder_peertube;
case 4: case 4:
return R.drawable.place_holder_bandcamp; return R.drawable.ic_placeholder_bandcamp;
default: default:
return R.drawable.place_holder_circle; return R.drawable.ic_circle;
} }
} }
@ -113,18 +116,32 @@ public final class ServiceHelper {
} }
public static int getSelectedServiceId(final Context context) { public static int getSelectedServiceId(final Context context) {
return Optional.ofNullable(getSelectedService(context))
.orElse(DEFAULT_FALLBACK_SERVICE)
.getServiceId();
}
@Nullable
public static StreamingService getSelectedService(final Context context) {
final String serviceName = PreferenceManager.getDefaultSharedPreferences(context) final String serviceName = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.current_service_key), .getString(context.getString(R.string.current_service_key),
context.getString(R.string.default_service_value)); context.getString(R.string.default_service_value));
int serviceId;
try { try {
serviceId = NewPipe.getService(serviceName).getServiceId(); return NewPipe.getService(serviceName);
} catch (final ExtractionException e) { } catch (final ExtractionException e) {
serviceId = DEFAULT_FALLBACK_SERVICE.getServiceId(); return null;
}
} }
return serviceId; @NonNull
public static String getNameOfServiceById(final int serviceId) {
return ServiceList.all().stream()
.filter(s -> s.getServiceId() == serviceId)
.findFirst()
.map(StreamingService::getServiceInfo)
.map(StreamingService.ServiceInfo::getName)
.orElse("<unknown>");
} }
public static void setSelectedServiceId(final Context context, final int serviceId) { public static void setSelectedServiceId(final Context context, final int serviceId) {
@ -138,16 +155,6 @@ public final class ServiceHelper {
setSelectedServicePreferences(context, serviceName); setSelectedServicePreferences(context, serviceName);
} }
public static void setSelectedServiceId(final Context context, final String serviceName) {
final int serviceId = NewPipe.getIdOfService(serviceName);
if (serviceId == -1) {
setSelectedServicePreferences(context,
DEFAULT_FALLBACK_SERVICE.getServiceInfo().getName());
} else {
setSelectedServicePreferences(context, serviceName);
}
}
private static void setSelectedServicePreferences(final Context context, private static void setSelectedServicePreferences(final Context context,
final String serviceName) { final String serviceName) {
PreferenceManager.getDefaultSharedPreferences(context).edit(). PreferenceManager.getDefaultSharedPreferences(context).edit().

View file

@ -1,7 +1,5 @@
package org.schabi.newpipe.util; package org.schabi.newpipe.util;
import static org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM;
import static org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import android.content.Context; import android.content.Context;
@ -49,8 +47,8 @@ public final class SparseItemUtil {
public static void fetchItemInfoIfSparse(@NonNull final Context context, public static void fetchItemInfoIfSparse(@NonNull final Context context,
@NonNull final StreamInfoItem item, @NonNull final StreamInfoItem item,
@NonNull final Consumer<SinglePlayQueue> callback) { @NonNull final Consumer<SinglePlayQueue> callback) {
if (((item.getStreamType() == LIVE_STREAM || item.getStreamType() == AUDIO_LIVE_STREAM) if ((StreamTypeUtil.isLiveStream(item.getStreamType()) || item.getDuration() >= 0)
|| item.getDuration() >= 0) && !isNullOrEmpty(item.getUploaderUrl())) { && !isNullOrEmpty(item.getUploaderUrl())) {
// if the duration is >= 0 (provided that the item is not a livestream) and there is an // if the duration is >= 0 (provided that the item is not a livestream) and there is an
// uploader url, probably all info is already there, so there is no need to fetch it // uploader url, probably all info is already there, so there is no need to fetch it
callback.accept(new SinglePlayQueue(item)); callback.accept(new SinglePlayQueue(item));

View file

@ -10,6 +10,8 @@ import android.widget.ImageView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
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.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
@ -87,7 +89,8 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
} }
@Override @Override
public View getDropDownView(final int position, final View convertView, public View getDropDownView(final int position,
final View convertView,
final ViewGroup parent) { final ViewGroup parent) {
return getCustomView(position, convertView, parent, true); return getCustomView(position, convertView, parent, true);
} }
@ -98,7 +101,10 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
convertView, parent, false); convertView, parent, false);
} }
private View getCustomView(final int position, final View view, final ViewGroup parent, @NonNull
private View getCustomView(final int position,
final View view,
final ViewGroup parent,
final boolean isDropdownItem) { final boolean isDropdownItem) {
View convertView = view; View convertView = view;
if (convertView == null) { if (convertView == null) {
@ -112,6 +118,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
final TextView sizeView = convertView.findViewById(R.id.stream_size); final TextView sizeView = convertView.findViewById(R.id.stream_size);
final T stream = getItem(position); final T stream = getItem(position);
final MediaFormat mediaFormat = stream.getFormat();
int woSoundIconVisibility = View.GONE; int woSoundIconVisibility = View.GONE;
String qualityString; String qualityString;
@ -135,24 +142,32 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
} }
} else if (stream instanceof AudioStream) { } else if (stream instanceof AudioStream) {
final AudioStream audioStream = ((AudioStream) stream); final AudioStream audioStream = ((AudioStream) stream);
qualityString = audioStream.getAverageBitrate() > 0 if (audioStream.getAverageBitrate() > 0) {
? audioStream.getAverageBitrate() + "kbps" qualityString = audioStream.getAverageBitrate() + "kbps";
: audioStream.getFormat().getName(); } else if (mediaFormat != null) {
qualityString = mediaFormat.getName();
} else {
qualityString = context.getString(R.string.unknown_quality);
}
} else if (stream instanceof SubtitlesStream) { } else if (stream instanceof SubtitlesStream) {
qualityString = ((SubtitlesStream) stream).getDisplayLanguageName(); qualityString = ((SubtitlesStream) stream).getDisplayLanguageName();
if (((SubtitlesStream) stream).isAutoGenerated()) { if (((SubtitlesStream) stream).isAutoGenerated()) {
qualityString += " (" + context.getString(R.string.caption_auto_generated) + ")"; qualityString += " (" + context.getString(R.string.caption_auto_generated) + ")";
} }
} else { } else {
qualityString = stream.getFormat().getSuffix(); if (mediaFormat == null) {
qualityString = context.getString(R.string.unknown_quality);
} else {
qualityString = mediaFormat.getSuffix();
}
} }
if (streamsWrapper.getSizeInBytes(position) > 0) { if (streamsWrapper.getSizeInBytes(position) > 0) {
final SecondaryStreamHelper<U> secondary = secondaryStreams == null ? null final SecondaryStreamHelper<U> secondary = secondaryStreams == null ? null
: secondaryStreams.get(position); : secondaryStreams.get(position);
if (secondary != null) { if (secondary != null) {
final long size final long size = secondary.getSizeInBytes()
= secondary.getSizeInBytes() + streamsWrapper.getSizeInBytes(position); + streamsWrapper.getSizeInBytes(position);
sizeView.setText(Utility.formatBytes(size)); sizeView.setText(Utility.formatBytes(size));
} else { } else {
sizeView.setText(streamsWrapper.getFormattedSize(position)); sizeView.setText(streamsWrapper.getFormattedSize(position));
@ -164,11 +179,15 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
if (stream instanceof SubtitlesStream) { if (stream instanceof SubtitlesStream) {
formatNameView.setText(((SubtitlesStream) stream).getLanguageTag()); formatNameView.setText(((SubtitlesStream) stream).getLanguageTag());
} else if (stream.getFormat() == MediaFormat.WEBMA_OPUS) { } else {
if (mediaFormat == null) {
formatNameView.setText(context.getString(R.string.unknown_format));
} else if (mediaFormat == MediaFormat.WEBMA_OPUS) {
// noinspection AndroidLintSetTextI18n // noinspection AndroidLintSetTextI18n
formatNameView.setText("opus"); formatNameView.setText("opus");
} else { } else {
formatNameView.setText(stream.getFormat().getName()); formatNameView.setText(mediaFormat.getName());
}
} }
qualityView.setText(qualityString); qualityView.setText(qualityString);
@ -233,6 +252,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
* @param streamsWrapper the wrapper * @param streamsWrapper the wrapper
* @return a {@link Single} that returns a boolean indicating if any elements were changed * @return a {@link Single} that returns a boolean indicating if any elements were changed
*/ */
@NonNull
public static <X extends Stream> Single<Boolean> fetchSizeForWrapper( public static <X extends Stream> Single<Boolean> fetchSizeForWrapper(
final StreamSizeWrapper<X> streamsWrapper) { final StreamSizeWrapper<X> streamsWrapper) {
final Callable<Boolean> fetchAndSet = () -> { final Callable<Boolean> fetchAndSet = () -> {
@ -243,7 +263,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
} }
final long contentLength = DownloaderImpl.getInstance().getContentLength( final long contentLength = DownloaderImpl.getInstance().getContentLength(
stream.getUrl()); stream.getContent());
streamsWrapper.setSize(stream, contentLength); streamsWrapper.setSize(stream, contentLength);
hasChanged = true; hasChanged = true;
} }

View file

@ -3,7 +3,7 @@ package org.schabi.newpipe.util;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
/** /**
* Utility class for {@link org.schabi.newpipe.extractor.stream.StreamType}. * Utility class for {@link StreamType}.
*/ */
public final class StreamTypeUtil { public final class StreamTypeUtil {
private StreamTypeUtil() { private StreamTypeUtil() {
@ -11,11 +11,37 @@ public final class StreamTypeUtil {
} }
/** /**
* Checks if the streamType is a livestream. * Check if the {@link StreamType} of a stream is a livestream.
* *
* @param streamType * @param streamType the stream type of the stream
* @return <code>true</code> when the streamType is a * @return whether the stream type is {@link StreamType#AUDIO_STREAM},
* {@link StreamType#LIVE_STREAM} or {@link StreamType#AUDIO_LIVE_STREAM} * {@link StreamType#AUDIO_LIVE_STREAM} or {@link StreamType#POST_LIVE_AUDIO_STREAM}
*/
public static boolean isAudio(final StreamType streamType) {
return streamType == StreamType.AUDIO_STREAM
|| streamType == StreamType.AUDIO_LIVE_STREAM
|| streamType == StreamType.POST_LIVE_AUDIO_STREAM;
}
/**
* Check if the {@link StreamType} of a stream is a livestream.
*
* @param streamType the stream type of the stream
* @return whether the stream type is {@link StreamType#VIDEO_STREAM},
* {@link StreamType#LIVE_STREAM} or {@link StreamType#POST_LIVE_STREAM}
*/
public static boolean isVideo(final StreamType streamType) {
return streamType == StreamType.VIDEO_STREAM
|| streamType == StreamType.LIVE_STREAM
|| streamType == StreamType.POST_LIVE_STREAM;
}
/**
* Check if the {@link StreamType} of a stream is a livestream.
*
* @param streamType the stream type of the stream
* @return whether the stream type is {@link StreamType#LIVE_STREAM} or
* {@link StreamType#AUDIO_LIVE_STREAM}
*/ */
public static boolean isLiveStream(final StreamType streamType) { public static boolean isLiveStream(final StreamType streamType) {
return streamType == StreamType.LIVE_STREAM return streamType == StreamType.LIVE_STREAM

View file

@ -23,14 +23,17 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.TypedValue; import android.util.TypedValue;
import androidx.annotation.AttrRes; import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StyleRes; import androidx.annotation.StyleRes;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@ -227,6 +230,20 @@ public final class ThemeHelper {
return value.data; return value.data;
} }
/**
* Resolves a {@link Drawable} by it's id.
*
* @param context Context
* @param attrResId Resource id
* @return the {@link Drawable}
*/
public static Drawable resolveDrawable(@NonNull final Context context,
@AttrRes final int attrResId) {
final TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(attrResId, typedValue, true);
return AppCompatResources.getDrawable(context, typedValue.resourceId);
}
private static String getSelectedThemeKey(final Context context) { private static String getSelectedThemeKey(final Context context) {
final String themeKey = context.getString(R.string.theme_key); final String themeKey = context.getString(R.string.theme_key);
final String defaultTheme = context.getResources().getString(R.string.default_theme_value); final String defaultTheme = context.getResources().getString(R.string.default_theme_value);

View file

@ -1,5 +1,7 @@
package org.schabi.newpipe.util.external_communication; package org.schabi.newpipe.util.external_communication;
import static org.schabi.newpipe.MainActivity.DEBUG;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
@ -7,17 +9,28 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.PicassoHelper;
import java.io.File;
import java.io.FileOutputStream;
public final class ShareUtils { public final class ShareUtils {
private static final String TAG = ShareUtils.class.getSimpleName();
private ShareUtils() { private ShareUtils() {
} }
@ -231,9 +244,11 @@ public final class ShareUtils {
/** /**
* Open the android share sheet to share a content. * Open the android share sheet to share a content.
* *
* <p>
* For Android 10+ users, a content preview is shown, which includes the title of the shared * For Android 10+ users, a content preview is shown, which includes the title of the shared
* content. * content and an image preview the content, if its URL is not null or empty and its
* Support sharing the image of the content needs to done, if possible. * corresponding image is in the image cache.
* </p>
* *
* @param context the context to use * @param context the context to use
* @param title the title of the content * @param title the title of the content
@ -252,13 +267,20 @@ public final class ShareUtils {
shareIntent.putExtra(Intent.EXTRA_SUBJECT, title); shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
} }
/* TODO: add the image of the content to Android share sheet with setClipData after // Content preview in the share sheet has been added in Android 10, so it's not needed to
generating a content URI of this image, then use ClipData.newUri(the content resolver, // set a content preview which will be never displayed
null, the content URI) and set the ClipData to the share intent with // See https://developer.android.com/training/sharing/send#adding-rich-content-previews
shareIntent.setClipData(generated ClipData). // If loading of images has been disabled, don't try to generate a content preview
if (!imagePreviewUrl.isEmpty()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
//shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); && !TextUtils.isEmpty(imagePreviewUrl)
}*/ && PicassoHelper.getShouldLoadImages()) {
final ClipData clipData = generateClipDataForImagePreview(context, imagePreviewUrl);
if (clipData != null) {
shareIntent.setClipData(clipData);
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
openAppChooser(context, shareIntent, false); openAppChooser(context, shareIntent, false);
} }
@ -266,11 +288,11 @@ public final class ShareUtils {
/** /**
* Open the android share sheet to share a content. * Open the android share sheet to share a content.
* *
* For Android 10+ users, a content preview is shown, which includes the title of the shared
* content.
* <p> * <p>
* This calls {@link #shareText(Context, String, String, String)} with an empty string for the * This calls {@link #shareText(Context, String, String, String)} with an empty string for the
* imagePreviewUrl parameter. * {@code imagePreviewUrl} parameter. This method should be used when the shared content has no
* preview thumbnail.
* </p>
* *
* @param context the context to use * @param context the context to use
* @param title the title of the content * @param title the title of the content
@ -301,4 +323,81 @@ public final class ShareUtils {
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text)); clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
} }
/**
* Generate a {@link ClipData} with the image of the content shared, if it's in the app cache.
*
* <p>
* In order not to worry about network issues (timeouts, DNS issues, low connection speed, ...)
* when sharing a content, only images in the {@link com.squareup.picasso.LruCache LruCache}
* used by the Picasso library inside {@link PicassoHelper} are used as preview images. If the
* thumbnail image is not in the cache, no {@link ClipData} will be generated and {@code null}
* will be returned.
* </p>
*
* <p>
* In order to display the image in the content preview of the Android share sheet, an URI of
* the content, accessible and readable by other apps has to be generated, so a new file inside
* the application cache will be generated, named {@code android_share_sheet_image_preview.jpg}
* (if a file under this name already exists, it will be overwritten). The thumbnail will be
* compressed in JPEG format, with a {@code 90} compression level.
* </p>
*
* <p>
* Note that if an exception occurs when generating the {@link ClipData}, {@code null} is
* returned.
* </p>
*
* <p>
* This method will call {@link PicassoHelper#getImageFromCacheIfPresent(String)} to get the
* thumbnail of the content in the {@link com.squareup.picasso.LruCache LruCache} used by
* the Picasso library inside {@link PicassoHelper}.
* </p>
*
* <p>
* Using the result of this method when sharing has only an effect on the system share sheet (if
* OEMs didn't change Android system standard behavior) on Android API 29 and higher.
* </p>
*
* @param context the context to use
* @param thumbnailUrl the URL of the content thumbnail
* @return a {@link ClipData} of the content thumbnail, or {@code null}
*/
@Nullable
private static ClipData generateClipDataForImagePreview(
@NonNull final Context context,
@NonNull final String thumbnailUrl) {
try {
final Bitmap bitmap = PicassoHelper.getImageFromCacheIfPresent(thumbnailUrl);
if (bitmap == null) {
return null;
}
// Save the image in memory to the application's cache because we need a URI to the
// image to generate a ClipData which will show the share sheet, and so an image file
final Context applicationContext = context.getApplicationContext();
final String appFolder = applicationContext.getCacheDir().getAbsolutePath();
final File thumbnailPreviewFile = new File(appFolder
+ "/android_share_sheet_image_preview.jpg");
// Any existing file will be overwritten with FileOutputStream
final FileOutputStream fileOutputStream = new FileOutputStream(thumbnailPreviewFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream);
fileOutputStream.close();
final ClipData clipData = ClipData.newUri(applicationContext.getContentResolver(), "",
FileProvider.getUriForFile(applicationContext,
BuildConfig.APPLICATION_ID + ".provider",
thumbnailPreviewFile));
if (DEBUG) {
Log.d(TAG, "ClipData successfully generated for Android share sheet: " + clipData);
}
return clipData;
} catch (final Exception e) {
Log.w(TAG, "Error when setting preview image for share sheet", e);
return null;
}
}
} }

View file

@ -6,6 +6,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
import org.schabi.newpipe.extractor.stream.StreamExtractor; import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
@ -131,31 +132,38 @@ public class DownloadMissionRecover extends Thread {
switch (mRecovery.getKind()) { switch (mRecovery.getKind()) {
case 'a': case 'a':
for (AudioStream audio : mExtractor.getAudioStreams()) { for (final AudioStream audio : mExtractor.getAudioStreams()) {
if (audio.getAverageBitrate() == mRecovery.getDesiredBitrate() && audio.getFormat() == mRecovery.getFormat()) { if (audio.getAverageBitrate() == mRecovery.getDesiredBitrate()
url = audio.getUrl(); && audio.getFormat() == mRecovery.getFormat()
&& audio.getDeliveryMethod() == DeliveryMethod.PROGRESSIVE_HTTP) {
url = audio.getContent();
break; break;
} }
} }
break; break;
case 'v': case 'v':
List<VideoStream> videoStreams; final List<VideoStream> videoStreams;
if (mRecovery.isDesired2()) if (mRecovery.isDesired2())
videoStreams = mExtractor.getVideoOnlyStreams(); videoStreams = mExtractor.getVideoOnlyStreams();
else else
videoStreams = mExtractor.getVideoStreams(); videoStreams = mExtractor.getVideoStreams();
for (VideoStream video : videoStreams) { for (final VideoStream video : videoStreams) {
if (video.resolution.equals(mRecovery.getDesired()) && video.getFormat() == mRecovery.getFormat()) { if (video.getResolution().equals(mRecovery.getDesired())
url = video.getUrl(); && video.getFormat() == mRecovery.getFormat()
&& video.getDeliveryMethod() == DeliveryMethod.PROGRESSIVE_HTTP) {
url = video.getContent();
break; break;
} }
} }
break; break;
case 's': case 's':
for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.getFormat())) { for (final SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery
.getFormat())) {
String tag = subtitles.getLanguageTag(); String tag = subtitles.getLanguageTag();
if (tag.equals(mRecovery.getDesired()) && subtitles.isAutoGenerated() == mRecovery.isDesired2()) { if (tag.equals(mRecovery.getDesired())
url = subtitles.getUrl(); && subtitles.isAutoGenerated() == mRecovery.isDesired2()
&& subtitles.getDeliveryMethod() == DeliveryMethod.PROGRESSIVE_HTTP) {
url = subtitles.getContent();
break; break;
} }
} }

View file

@ -11,23 +11,23 @@ import java.io.Serializable
@Parcelize @Parcelize
class MissionRecoveryInfo( class MissionRecoveryInfo(
var format: MediaFormat, var format: MediaFormat?,
var desired: String? = null, var desired: String? = null,
var isDesired2: Boolean = false, var isDesired2: Boolean = false,
var desiredBitrate: Int = 0, var desiredBitrate: Int = 0,
var kind: Char = Char.MIN_VALUE, var kind: Char = Char.MIN_VALUE,
var validateCondition: String? = null var validateCondition: String? = null
) : Serializable, Parcelable { ) : Serializable, Parcelable {
constructor(stream: Stream) : this(format = stream.getFormat()!!) { constructor(stream: Stream) : this(format = stream.format) {
when (stream) { when (stream) {
is AudioStream -> { is AudioStream -> {
desiredBitrate = stream.averageBitrate desiredBitrate = stream.getAverageBitrate()
isDesired2 = false isDesired2 = false
kind = 'a' kind = 'a'
} }
is VideoStream -> { is VideoStream -> {
desired = stream.resolution desired = stream.getResolution()
isDesired2 = stream.isVideoOnly isDesired2 = stream.isVideoOnly()
kind = 'v' kind = 'v'
} }
is SubtitlesStream -> { is SubtitlesStream -> {
@ -62,7 +62,7 @@ class MissionRecoveryInfo(
} }
} }
str.append(" format=") str.append(" format=")
.append(format.getName()) .append(format?.getName())
.append(' ') .append(' ')
.append(info) .append(info)
.append('}') .append('}')

View file

@ -30,7 +30,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import androidx.core.view.ViewCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -900,7 +899,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
super(view); super(view);
progress = new ProgressDrawable(); progress = new ProgressDrawable();
ViewCompat.setBackground(itemView.findViewById(R.id.item_bkg), progress); itemView.findViewById(R.id.item_bkg).setBackground(progress);
status = itemView.findViewById(R.id.item_status); status = itemView.findViewById(R.id.item_status);
name = itemView.findViewById(R.id.item_name); name = itemView.findViewById(R.id.item_name);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:tint="@color/defaultIconTint" >
<path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.49 10,-10S17.52,2 12,2L12,2z"
android:fillColor="#FF000000" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/defaultIconTint">
<path
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96z"
android:fillColor="#FF000000" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportHeight="97.75"
android:viewportWidth="97.75"
android:tint="@color/defaultIconTint">
<path
android:pathData="M48.875,0C21.882,0 0,21.882 0,48.875S21.882,97.75 48.875,97.75 97.75,75.868 97.75,48.875 75.868,0 48.875,0zM64.835,70.857L12.593,70.857l20.32,-43.965h52.244L64.835,70.857z"
android:fillColor="#FF000000" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="26.458319"
android:viewportHeight="26.458332"
android:tint="@color/defaultIconTint">
<path
android:pathData="M13.23,0.302C6.07,0.302 0.264,6.107 0.264,13.267a12.965,12.965 0,0 0,0.847 4.595c0.19,-0.497 0.408,-0.982 0.682,-1.438 0.14,-0.232 0.294,-0.457 0.396,-0.707 0.103,-0.25 0.15,-0.533 0.072,-0.792a1.362,1.362 0,0 0,-0.22 -0.404c-0.092,-0.123 -0.192,-0.24 -0.275,-0.37a1.662,1.662 0,0 1,-0.255 -1.12,1.5 1.5,0 0,1 0.58,-0.987c0.28,-0.208 0.635,-0.3 0.985,-0.288a1.757,1.757 0,0 1,0.346 0.048c0.452,0.11 0.852,0.393 1.148,0.75 0.368,0.447 0.584,1.01 0.637,1.586a3.574,3.574 0,0 1,-0.275 1.693c-0.4,0.955 -1.15,1.725 -1.565,2.673 -0.338,0.775 -0.435,1.638 -0.39,2.483 0.007,0.077 0.018,0.155 0.025,0.234a12.965,12.965 0,0 0,3.62 3.18,17.63 17.63,0 0,1 -0.13,-2.11c0.002,-0.56 0.03,-1.12 0.085,-1.675 -0.34,-0.236 -0.65,-0.51 -0.87,-0.86 -0.392,-0.62 -0.466,-1.408 -0.305,-2.124 0.16,-0.717 0.54,-1.37 0.997,-1.945a7.833,7.833 0,0 1,2.835 -2.223,10.305 10.305,0 0,1 -0.09,-0.126 4.854,4.854 0,0 1,-0.702 -2.176c-0.06,-0.777 0.064,-1.554 0.115,-2.33 0.037,-0.543 0.04,-1.085 0.07,-1.627 0.038,-0.627 0.114,-1.255 0.29,-1.858a2.36,2.36 0,0 1,0.266 -0.63,1.4 1.4,0 0,1 0.594,-0.514c0.274,-0.108 0.51,-0.132 0.776,-0.087 0.22,0.046 0.425,0.156 0.604,0.294 0.18,0.138 0.335,0.304 0.48,0.477a7.298,7.298 0,0 1,1.04 1.617,3.57 3.57,0 0,1 1.09,0 7.287,7.287 0,0 1,1.04 -1.616,3.21 3.21,0 0,1 0.48,-0.476c0.18,-0.14 0.383,-0.248 0.604,-0.295a1.268,1.268 0,0 1,0.78 0.086,1.402 1.402,0 0,1 0.595,0.517c0.124,0.19 0.202,0.408 0.266,0.626 0.175,0.602 0.252,1.23 0.29,1.856 0.03,0.543 0.033,1.087 0.07,1.628 0.05,0.777 0.175,1.554 0.116,2.33a4.855,4.855 0,0 1,-0.705 2.178c-0.03,0.05 -0.07,0.096 -0.103,0.145 0.247,0.278 0.598,0.513 0.898,0.614a1.956,1.956 0,0 0,1.05 0.044,1.65 1.65,0 0,0 0.533,-0.226 1.253,1.253 0,0 0,0.397 -0.418c0.118,-0.21 0.166,-0.45 0.192,-0.687 0.067,-0.61 0,-1.224 -0.05,-1.835 -0.034,-0.396 -0.062,-0.8 0.027,-1.187 0.06,-0.26 0.177,-0.518 0.373,-0.7a1.106,1.106 0,0 1,0.465 -0.255,1.312 1.312,0 0,1 0.53,-0.03c0.38,0.057 0.736,0.274 0.948,0.594 0.12,0.18 0.194,0.39 0.238,0.604 0.044,0.213 0.06,0.43 0.072,0.648 0.04,0.76 0.04,1.522 0.018,2.284 -0.018,0.665 -0.055,1.348 -0.32,1.957 -0.343,0.782 -1.032,1.366 -1.775,1.786a7.052,7.052 0,0 1,-1.588 0.647c0.482,1.54 0.733,3.24 0.733,4.968a17.6,17.6 0,0 1,-0.135 2.125,12.964 12.964,0 0,0 6.384,-11.152c0,-7.16 -5.806,-12.965 -12.965,-12.965zM9.602,16.284v1.483a1.88,1.88 0,0 1,1.083 0.362,1.738 1.738,0 0,1 0.556,0.68c0.122,0.27 0.166,0.576 0.116,0.868a1.493,1.493 0,0 1,-0.332 0.708,1.647 1.647,0 0,1 -0.635,0.458 1.738,1.738 0,0 1,-0.787 0.122v3.73l7.762,-4.208 -7.762,-4.204z"
android:fillColor="#FF000000" />
</vector>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:tint="@color/defaultIconTint">
<path
android:pathData="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM9.5 16.5v-9l7 4.5-7 4.5z"
android:fillColor="#FF000000"/>
</vector>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -29,9 +30,9 @@
<RelativeLayout <RelativeLayout
android:id="@+id/tempoControl" android:id="@+id/tempoControl"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="wrap_content"
android:layout_below="@id/tempoControlText" android:layout_below="@id/tempoControlText"
android:layout_marginTop="3dp" android:layout_marginTop="1dp"
android:orientation="horizontal"> android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
@ -39,7 +40,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
@ -57,9 +57,7 @@
android:layout_marginLeft="4dp" android:layout_marginLeft="4dp"
android:layout_marginRight="4dp" android:layout_marginRight="4dp"
android:layout_toStartOf="@id/tempoStepUp" android:layout_toStartOf="@id/tempoStepUp"
android:layout_toLeftOf="@id/tempoStepUp"
android:layout_toEndOf="@id/tempoStepDown" android:layout_toEndOf="@id/tempoStepDown"
android:layout_toRightOf="@id/tempoStepDown"
android:orientation="horizontal"> android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
@ -67,9 +65,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:gravity="center" android:gravity="center"
android:text="-.--x" android:text="-.--x"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
@ -93,9 +89,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:gravity="center" android:gravity="center"
android:text="---%" android:text="---%"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
@ -108,7 +102,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/tempoCurrentText" android:layout_below="@id/tempoCurrentText"
android:paddingBottom="4dp" android:paddingBottom="2dp"
tools:progress="50" /> tools:progress="50" />
</RelativeLayout> </RelativeLayout>
@ -117,10 +111,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
@ -138,9 +130,9 @@
android:layout_height="1dp" android:layout_height="1dp"
android:layout_below="@id/tempoControl" android:layout_below="@id/tempoControl"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginTop="6dp" android:layout_marginTop="5dp"
android:layout_marginEnd="6dp" android:layout_marginEnd="12dp"
android:layout_marginBottom="6dp" android:layout_marginBottom="5dp"
android:background="?attr/separator_color" /> android:background="?attr/separator_color" />
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
@ -154,20 +146,72 @@
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
android:textStyle="bold" /> android:textStyle="bold" />
<RelativeLayout <ImageView
android:id="@+id/pitchControl" android:id="@+id/pitchToogleControlModes"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_below="@id/separatorPitch"
android:layout_alignParentEnd="true"
android:clickable="true"
android:focusable="true"
app:srcCompat="@drawable/ic_expand_more"
tools:ignore="ContentDescription" />
<LinearLayout
android:id="@+id/pitchControlModeTabs"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="22dp"
android:layout_below="@id/pitchControlText" android:layout_below="@id/pitchControlText"
android:layout_marginTop="3dp" android:layout_marginStart="22dp"
android:layout_marginEnd="22dp"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/pitchControlModePercent"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:text="@string/percent"
android:textColor="?attr/colorAccent" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/pitchControlModeSemitone"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:text="@string/semitone"
android:textColor="?attr/colorAccent" />
</LinearLayout>
<RelativeLayout
android:id="@+id/pitchControlContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/pitchControlModeTabs"
android:layout_marginTop="1dp">
<RelativeLayout
android:id="@+id/pitchPercentControl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"> android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/pitchStepDown" android:id="@+id/pitchPercentStepDown"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
@ -180,25 +224,21 @@
tools:text="-5%" /> tools:text="-5%" />
<RelativeLayout <RelativeLayout
android:id="@+id/pitchDisplay" android:id="@+id/pitchPercentDisplay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginLeft="4dp" android:layout_marginLeft="4dp"
android:layout_marginRight="4dp" android:layout_marginRight="4dp"
android:layout_toStartOf="@+id/pitchStepUp" android:layout_toStartOf="@+id/pitchPercentStepUp"
android:layout_toLeftOf="@+id/pitchStepUp" android:layout_toEndOf="@+id/pitchPercentStepDown"
android:layout_toEndOf="@+id/pitchStepDown"
android:layout_toRightOf="@+id/pitchStepDown"
android:orientation="horizontal"> android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/pitchMinimumText" android:id="@+id/pitchPercentMinimumText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:gravity="center" android:gravity="center"
android:text="---%" android:text="---%"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
@ -206,7 +246,7 @@
tools:text="25%" /> tools:text="25%" />
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/pitchCurrentText" android:id="@+id/pitchPercentCurrentText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
@ -218,13 +258,11 @@
tools:text="100%" /> tools:text="100%" />
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/pitchMaximumText" android:id="@+id/pitchPercentMaximumText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:gravity="center" android:gravity="center"
android:text="---%" android:text="---%"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
@ -232,24 +270,22 @@
tools:text="300%" /> tools:text="300%" />
<androidx.appcompat.widget.AppCompatSeekBar <androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/pitchSeekbar" android:id="@+id/pitchPercentSeekbar"
style="@style/Widget.AppCompat.SeekBar" style="@style/Widget.AppCompat.SeekBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/pitchCurrentText" android:layout_below="@+id/pitchPercentCurrentText"
android:paddingBottom="4dp" android:paddingBottom="2dp"
tools:progress="50" /> tools:progress="50" />
</RelativeLayout> </RelativeLayout>
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/pitchStepUp" android:id="@+id/pitchPercentStepUp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
@ -262,19 +298,17 @@
</RelativeLayout> </RelativeLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/semitoneControl" android:id="@+id/pitchSemitoneControl"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="match_parent"
android:layout_below="@id/pitchControlText" android:orientation="horizontal"
android:layout_marginTop="4dp" tools:visibility="gone">
android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/semitoneStepDown" android:id="@+id/pitchSemitoneStepDown"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
@ -287,73 +321,66 @@
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<RelativeLayout <RelativeLayout
android:id="@+id/semitoneDisplay" android:id="@+id/pitchSemitoneDisplay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginLeft="4dp" android:layout_marginLeft="4dp"
android:layout_marginRight="4dp" android:layout_marginRight="4dp"
android:layout_toStartOf="@+id/semitoneStepUp" android:layout_toStartOf="@+id/pitchSemitoneStepUp"
android:layout_toLeftOf="@+id/semitoneStepUp" android:layout_toEndOf="@+id/pitchSemitoneStepDown"
android:layout_toEndOf="@+id/semitoneStepDown"
android:layout_toRightOf="@+id/semitoneStepDown"
android:orientation="horizontal"> android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/semitoneMinimumText" android:id="@+id/pitchSemitoneMinimumText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:gravity="center" android:gravity="center"
android:text="-12" android:text="-12"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
tools:ignore="HardcodedText"/> tools:ignore="HardcodedText" />
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/semitoneCurrentText" android:id="@+id/pitchSemitoneCurrentText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:gravity="center" android:gravity="center"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
android:textStyle="bold" android:textStyle="bold"
tools:text="0" /> tools:text="0"
tools:ignore="RelativeOverlap" />
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/semitoneMaximumText" android:id="@+id/pitchSemitoneMaximumText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:gravity="center" android:gravity="center"
android:text="+12" android:text="+12"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatSeekBar <androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/semitoneSeekbar" android:id="@+id/pitchSemitoneSeekbar"
style="@style/Widget.AppCompat.SeekBar" style="@style/Widget.AppCompat.SeekBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/semitoneCurrentText" android:layout_below="@+id/pitchSemitoneCurrentText"
android:max="24" android:max="24"
android:paddingBottom="4dp" android:paddingBottom="2dp"
android:progress="12" /> android:progress="12" />
</RelativeLayout> </RelativeLayout>
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/semitoneStepUp" android:id="@+id/pitchSemitoneStepUp"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
@ -364,22 +391,23 @@
android:textStyle="bold" android:textStyle="bold"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
</RelativeLayout> </RelativeLayout>
</RelativeLayout>
<View <View
android:id="@+id/separatorStepSizeSelector" android:id="@+id/separatorStepSizeSelector"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_below="@+id/semitoneControl" android:layout_below="@+id/pitchControlContainer"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginTop="6dp" android:layout_marginTop="5dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:layout_marginBottom="6dp" android:layout_marginBottom="5dp"
android:background="?attr/separator_color" /> android:background="?attr/separator_color" />
<LinearLayout <LinearLayout
android:id="@+id/stepSizeSelector" android:id="@+id/stepSizeSelector"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="32dp" android:layout_height="30dp"
android:layout_below="@id/separatorStepSizeSelector" android:layout_below="@id/separatorStepSizeSelector"
android:orientation="horizontal"> android:orientation="horizontal">
@ -403,7 +431,8 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center" android:gravity="center"
android:textColor="?attr/colorAccent" /> android:textColor="?attr/colorAccent"
tools:text="1%" />
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/stepSizeFivePercent" android:id="@+id/stepSizeFivePercent"
@ -414,7 +443,8 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center" android:gravity="center"
android:textColor="?attr/colorAccent" /> android:textColor="?attr/colorAccent"
tools:text="5%" />
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/stepSizeTenPercent" android:id="@+id/stepSizeTenPercent"
@ -425,7 +455,8 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center" android:gravity="center"
android:textColor="?attr/colorAccent" /> android:textColor="?attr/colorAccent"
tools:text="10%" />
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/stepSizeTwentyFivePercent" android:id="@+id/stepSizeTwentyFivePercent"
@ -436,9 +467,10 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center" android:gravity="center"
android:textColor="?attr/colorAccent" /> android:textColor="?attr/colorAccent"
tools:text="25%" />
<TextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/stepSizeOneHundredPercent" android:id="@+id/stepSizeOneHundredPercent"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -447,7 +479,8 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center" android:gravity="center"
android:textColor="?attr/colorAccent" /> android:textColor="?attr/colorAccent"
tools:text="100%" />
</LinearLayout> </LinearLayout>
<View <View
@ -456,9 +489,9 @@
android:layout_height="1dp" android:layout_height="1dp"
android:layout_below="@+id/stepSizeSelector" android:layout_below="@+id/stepSizeSelector"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginTop="6dp" android:layout_marginTop="5dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:layout_marginBottom="6dp" android:layout_marginBottom="5dp"
android:background="?attr/separator_color" /> android:background="?attr/separator_color" />
<LinearLayout <LinearLayout
@ -486,17 +519,6 @@
android:focusable="true" android:focusable="true"
android:text="@string/skip_silence_checkbox" /> android:text="@string/skip_silence_checkbox" />
<CheckBox
android:id="@+id/adjustBySemitonesCheckbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/skipSilenceCheckbox"
android:layout_centerHorizontal="true"
android:checked="false"
android:clickable="true"
android:focusable="true"
android:maxLines="1"
android:text="@string/adjust_by_semitones_checkbox" />
</LinearLayout> </LinearLayout>
<!-- END HERE --> <!-- END HERE -->

View file

@ -82,13 +82,14 @@
android:text="@string/msg_threads" /> android:text="@string/msg_threads" />
<LinearLayout <LinearLayout
android:id="@+id/threads_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/threads_text_view" android:layout_below="@+id/threads_text_view"
android:layout_marginLeft="24dp" android:layout_marginLeft="24dp"
android:layout_marginRight="24dp" android:layout_marginRight="24dp"
android:orientation="horizontal" android:layout_marginBottom="12dp"
android:paddingBottom="12dp"> android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/threads_count" android:id="@+id/threads_count"
@ -106,4 +107,16 @@
android:max="31" android:max="31"
android:progress="3" /> android:progress="3" />
</LinearLayout> </LinearLayout>
<org.schabi.newpipe.views.NewPipeTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/threads_layout"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="12dp"
android:gravity="center"
android:text="@string/streams_not_yet_supported_removed"
android:textSize="12sp" />
</RelativeLayout> </RelativeLayout>

View file

@ -86,7 +86,7 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
app:tint="@color/drawer_header_font_color" app:tint="@color/drawer_header_font_color"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:srcCompat="@drawable/place_holder_youtube" /> tools:srcCompat="@drawable/ic_smart_display" />
<org.schabi.newpipe.views.NewPipeTextView <org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/drawer_header_service_view" android:id="@+id/drawer_header_service_view"

View file

@ -1,114 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="14dp"
android:background="?attr/contrast_background_color">
<View
android:layout_width="match_parent"
android:layout_height="5dp"
android:background="?attr/toolbar_shadow" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/separator_color" />
<LinearLayout
android:id="@+id/import_export"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingLeft="16dp"
android:paddingTop="8dp"
android:paddingRight="16dp"
android:paddingBottom="8dp"
android:text="@string/tab_subscriptions"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/import_export_expand_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="16dp"
app:srcCompat="@drawable/ic_expand_more"
tools:ignore="ContentDescription,RtlHardcoded" />
</LinearLayout>
<org.schabi.newpipe.views.CollapsibleView
android:id="@+id/import_export_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="6dp"
tools:ignore="RtlSymmetry">
<org.schabi.newpipe.views.NewPipeTextView
android:layout_width="match_parent"
android:layout_height="@dimen/subscription_import_export_title_height"
android:gravity="left|center"
android:maxLines="1"
android:paddingLeft="36dp"
android:text="@string/import_from"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="13sp"
tools:ignore="RtlHardcoded" />
<LinearLayout
android:id="@+id/import_from_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:layout_marginLeft="36dp"
android:orientation="vertical" />
<org.schabi.newpipe.views.NewPipeTextView
android:layout_width="match_parent"
android:layout_height="@dimen/subscription_import_export_title_height"
android:background="?attr/selectableItemBackground"
android:gravity="left|center"
android:maxLines="1"
android:paddingLeft="36dp"
android:text="@string/export_to"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="13sp"
tools:ignore="RtlHardcoded" />
<LinearLayout
android:id="@+id/export_to_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:orientation="vertical" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="8dp"
android:background="?attr/separator_color" />
</org.schabi.newpipe.views.CollapsibleView>
</LinearLayout>

View file

@ -26,7 +26,7 @@
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
tools:ignore="ContentDescription,RtlHardcoded" tools:ignore="ContentDescription,RtlHardcoded"
tools:src="@drawable/place_holder_peertube" /> tools:src="@drawable/ic_placeholder_peertube" />
<TextView <TextView
android:id="@+id/instanceName" android:id="@+id/instanceName"

View file

@ -1,6 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!-- -->
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadeScrollbars="false">
<RadioGroup
android:id="@android:id/list" android:id="@android:id/list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="?attr/listPreferredItemPaddingLeft" /> android:paddingTop="?attr/listPreferredItemPaddingLeft" />
</ScrollView>

View file

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal">
<ImageView
android:id="@android:id/icon1"
android:layout_width="48dp"
android:layout_height="@dimen/subscription_import_export_item_icon_size"
android:layout_marginTop="@dimen/subscription_import_export_item_icon_margin"
android:layout_marginBottom="@dimen/subscription_import_export_item_icon_margin"
android:scaleType="fitCenter"
tools:ignore="ContentDescription,RtlHardcoded"
tools:src="@drawable/place_holder_youtube" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="@dimen/subscription_import_export_item_height"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="13sp"
tools:text="@string/youtube" />
</LinearLayout>

View file

@ -28,4 +28,10 @@
android:orderInCategory="2" android:orderInCategory="2"
android:title="@string/open_in_browser" android:title="@string/open_in_browser"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/menu_item_append_playlist"
android:orderInCategory="3"
android:title="@string/add_to_playlist"
app:showAsAction="never" />
</menu> </menu>

View file

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

View file

@ -23,7 +23,7 @@
<string name="light_theme_title">فاتح</string> <string name="light_theme_title">فاتح</string>
<string name="network_error">خطأ في الشبكة</string> <string name="network_error">خطأ في الشبكة</string>
<string name="no_player_found">لم يتم العثور على مشغل بث. تثبيت VLC؟</string> <string name="no_player_found">لم يتم العثور على مشغل بث. تثبيت VLC؟</string>
<string name="open_in_browser">افتح في المتصفح</string> <string name="open_in_browser">فتح في المتصفح</string>
<string name="play_audio">الصوت</string> <string name="play_audio">الصوت</string>
<string name="play_with_kodi_title">تشغيل بواسطة كودي</string> <string name="play_with_kodi_title">تشغيل بواسطة كودي</string>
<string name="search">البحث</string> <string name="search">البحث</string>
@ -730,11 +730,8 @@
<string name="show_error_snackbar">إظهار خطأ snackbar</string> <string name="show_error_snackbar">إظهار خطأ snackbar</string>
<string name="no_appropriate_file_manager_message_android_10">لم يتم العثور على مدير ملفات مناسب لهذا الإجراء. <string name="no_appropriate_file_manager_message_android_10">لم يتم العثور على مدير ملفات مناسب لهذا الإجراء.
\nالرجاء تثبيت مدير ملفات متوافق مع Storage Access Framework.</string> \nالرجاء تثبيت مدير ملفات متوافق مع Storage Access Framework.</string>
<string name="background_player_already_playing_toast">يتم تشغيله في الخلفية</string>
<string name="detail_pinned_comment_view_description">تعليق مثبت</string> <string name="detail_pinned_comment_view_description">تعليق مثبت</string>
<string name="leak_canary_not_available">LeakCanary غير متوفر</string> <string name="leak_canary_not_available">LeakCanary غير متوفر</string>
<string name="adjust_by_semitones_checkbox">ضبط الصوت من خلال النغمات الموسيقية النصفية</string>
<string name="playback_tempo_step">خطوة الإيقاع</string>
<string name="progressive_load_interval_exoplayer_default">الافتراضي ExoPlayer</string> <string name="progressive_load_interval_exoplayer_default">الافتراضي ExoPlayer</string>
<string name="progressive_load_interval_summary">تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل.</string> <string name="progressive_load_interval_summary">تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل.</string>
<string name="settings_category_player_notification_summary">تكوين إشعار مشغل البث الحالي</string> <string name="settings_category_player_notification_summary">تكوين إشعار مشغل البث الحالي</string>
@ -763,4 +760,14 @@
<item quantity="many">%s دفق جديد</item> <item quantity="many">%s دفق جديد</item>
<item quantity="other">%s دفق جديد</item> <item quantity="other">%s دفق جديد</item>
</plurals> </plurals>
<string name="percent">النسبة المئوية</string>
<string name="semitone">سيميتون</string>
<string name="streams_not_yet_supported_removed">لا يتم عرض التدفقات التي لم يدعمها برنامج التنزيل بعد</string>
<string name="selected_stream_external_player_not_supported">الدفق المحدد غير مدعوم من قبل المشغلون الخارجيون</string>
<string name="no_audio_streams_available_for_external_players">لا توجد تدفقات صوتية متاحة للمشغلات الخارجية</string>
<string name="no_video_streams_available_for_external_players">لا تتوفر تدفقات فيديو للاعبين الخارجيين</string>
<string name="select_quality_external_players">حدد الجودة للمشغلين الخارجيين</string>
<string name="unknown_format">تنسيق غير معروف</string>
<string name="unknown_quality">جودة غير معروفة</string>
<string name="progressive_load_interval_title">حجم الفاصل الزمني لتحميل التشغيل</string>
</resources> </resources>

View file

@ -2,145 +2,145 @@
<resources> <resources>
<string name="main_bg_subtitle">Başlamaq üçün \"Axtarış\" bölməsinə toxunun.</string> <string name="main_bg_subtitle">Başlamaq üçün \"Axtarış\" bölməsinə toxunun.</string>
<string name="upload_date_text">%1$s tarixində yayımlanıb</string> <string name="upload_date_text">%1$s tarixində yayımlanıb</string>
<string name="no_player_found">Axın pleyeri tapılmadı. \"VLC\" yüklənilsin\?</string> <string name="no_player_found">Yayım oynadıcı tapılmadı. \"VLC\" yüklənilsin\?</string>
<string name="no_player_found_toast">Axın pleyeri tapılmadı (baxmaq üçün \"VLC\"ni yükləyə bilərsiniz).</string> <string name="no_player_found_toast">Yayım oynadıcı tapılmadı (baxmaq üçün \"VLC\"\'ni yükləyə bilərsiniz).</string>
<string name="install">Quraşdır</string> <string name="install">Yükləyin</string>
<string name="cancel">İmtina</string> <string name="cancel">İmtina</string>
<string name="open_in_browser">Brauzerdə aç</string> <string name="open_in_browser">Brauzerdə açın</string>
<string name="share">Paylaş</string> <string name="share">Paylaşın</string>
<string name="download">Yüklə</string> <string name="download">Endirin</string>
<string name="controls_download_desc">Axın faylını endirin</string> <string name="controls_download_desc">Yayım faylını endirin</string>
<string name="search">Axtarış</string> <string name="search">Axtarın</string>
<string name="settings">Ayarlar</string> <string name="settings">Tənzimləmələr</string>
<string name="did_you_mean">Bunu demək istədiniz: \"%1$s\"\?</string> <string name="did_you_mean">Bunu demək istədiniz: \"%1$s\"\?</string>
<string name="share_dialog_title">Paylaş</string> <string name="share_dialog_title">...ilə paylaşın</string>
<string name="use_external_video_player_title">Kənar video oynadıcı istifadə et</string> <string name="use_external_video_player_title">Xarici video oynadıcı istifadə edin</string>
<string name="use_external_video_player_summary">Bəzi görüntü keyfiyyətlərində səs itir</string> <string name="use_external_video_player_summary">Bəzi keyfiyyət seçimlərində səsi silir</string>
<string name="use_external_audio_player_title">Kənar audio oynadıcı istifadə et</string> <string name="use_external_audio_player_title">Xarici səs oynadıcı istifadə edin</string>
<string name="subscribe_button_title">Abunə ol</string> <string name="subscribe_button_title">Abunə Olun</string>
<string name="subscribed_button_title">Abunə olundu</string> <string name="subscribed_button_title">Abunə olundu</string>
<string name="channel_unsubscribed">Kanal abunəliyindən çıxıldı</string> <string name="channel_unsubscribed">Kanal abunəliyindən çıxıldı</string>
<string name="show_info">Məlumat göstər</string> <string name="show_info">Məlumat göstər</string>
<string name="tab_subscriptions">Abunəliklər</string> <string name="tab_subscriptions">Abunəliklər</string>
<string name="tab_bookmarks">Saxlanmış Oxutma Siyahıları</string> <string name="tab_bookmarks">Əlfəcinlənmiş Pleylistlər</string>
<string name="fragment_feed_title">Yeni nə var</string> <string name="fragment_feed_title">Yeniliklər</string>
<string name="controls_background_title">Fon</string> <string name="controls_background_title">Arxa Fon</string>
<string name="download_path_title">Video yükləmə qovluğu</string> <string name="download_path_title">Video endirmə qovluğu</string>
<string name="download_path_summary">Yüklənmiş videolar burada saxlanılır</string> <string name="download_path_summary">Endirilmiş video fayllar burada saxlanılır</string>
<string name="download_path_dialog_title">Video faylları üçün yükləmə qovluğunu seçin</string> <string name="download_path_dialog_title">Video faylları üçün endirmə qovluğunu seçin</string>
<string name="download_path_audio_title">Audio yükləmə qovluğu</string> <string name="download_path_audio_title">Səs endirmə qovluğu</string>
<string name="download_path_audio_summary">Yüklənmiş audio faylları burada saxlanılır</string> <string name="download_path_audio_summary">Endirilmiş səs faylları burada saxlanılır</string>
<string name="download_path_audio_dialog_title">Audio faylları üçün yükləmə qovluğunu seçin</string> <string name="download_path_audio_dialog_title">Səs faylları üçün endirmə qovluğunu seçin</string>
<string name="default_resolution_title">Standard görüntü keyfiyyəti</string> <string name="default_resolution_title">Defolt keyfiyyət</string>
<string name="show_higher_resolutions_title">Daha böyük ölçüləri göstər</string> <string name="show_higher_resolutions_title">Daha böyük keyfiyyət seçimləri göstər</string>
<string name="play_with_kodi_title">\"Kodi\" ilə oxut</string> <string name="play_with_kodi_title">\"Kodi\" ilə Oynat</string>
<string name="kore_not_found">Çatışmayan \"Kore\" tətbiqi yüklənilsin\?</string> <string name="kore_not_found">Çatışmayan \"Kore\" tətbiqi yüklənilsin\?</string>
<string name="show_play_with_kodi_title">\"Kodi ilə oxut\" seçimini göstər</string> <string name="show_play_with_kodi_title">\"Kodi ilə Oynat\" seçimini göstər</string>
<string name="show_play_with_kodi_summary">Videonu Kodi media center ilə oynatmaq üçün seçim göstər</string> <string name="show_play_with_kodi_summary">Videonu Kodi media mərkəzi ilə oynatmaq üçün seçim göstər</string>
<string name="play_audio">Audio</string> <string name="play_audio">Səs</string>
<string name="default_audio_format_title">Standart səs formatı</string> <string name="default_audio_format_title">Defolt səs formatı</string>
<string name="default_video_format_title">Standart video formatı</string> <string name="default_video_format_title">Defolt video formatı</string>
<string name="theme_title">Mövzu</string> <string name="theme_title">Mövzu</string>
<string name="light_theme_title">ıq</string> <string name="light_theme_title">İşıqlı</string>
<string name="dark_theme_title">Qaranlıq</string> <string name="dark_theme_title">Qaranlıq</string>
<string name="black_theme_title">Qara</string> <string name="black_theme_title">Qara</string>
<string name="unsubscribe">Abunəlikdən çıx</string> <string name="unsubscribe">Abunəlikdən çıxın</string>
<string name="open_in_popup_mode">Ani pəncərə rejimində aç</string> <string name="open_in_popup_mode">Ani pəncərə rejimində açın</string>
<string name="autoplay_title">Avto-oxutma</string> <string name="autoplay_title">Avto-oynatma</string>
<string name="download_dialog_title">Yüklə</string> <string name="download_dialog_title">Endirin</string>
<string name="resume_on_audio_focus_gain_summary">Kəsintilərdən sonra (məs. telefon zəngi) oxutmağa davam et</string> <string name="resume_on_audio_focus_gain_summary">Fasilələrdən sonra (məsələn, telefon zəngləri) oynatmağa davam etdirin</string>
<string name="resume_on_audio_focus_gain_title">Oxutmağa davam et</string> <string name="resume_on_audio_focus_gain_title">Oynatmanı davam etdir</string>
<string name="enable_watch_history_summary">İzlənmiş videoları qeyd et</string> <string name="enable_watch_history_summary">Baxılmış videoların saxlanılması</string>
<string name="settings_category_clear_data_title">Verilənləri təmizlə</string> <string name="settings_category_clear_data_title">Məlumat təmizləmə</string>
<string name="enable_playback_state_lists_summary">Oxutma siyahılarındakı oxutma mövqeyi göstəricisini nümayiş et</string> <string name="enable_playback_state_lists_summary">Siyahılarda oynatma mövqelərini göstərin</string>
<string name="enable_playback_state_lists_title">Siyahılardakı mövqelər</string> <string name="enable_playback_state_lists_title">Siyahılardakı mövqelər</string>
<string name="enable_playback_resume_summary">Video oxutmanı axırıncı qaldığı yerdən bərpa et</string> <string name="enable_playback_resume_summary">Son oynatma mövqeyinə qaytarın</string>
<string name="enable_playback_resume_title">Oxutmanı davam etdir</string> <string name="enable_playback_resume_title">Oynatmanı davam etdir</string>
<string name="enable_watch_history_title">İzləmə tarixçəsi</string> <string name="enable_watch_history_title">Baxış tarixçəsi</string>
<string name="enable_search_history_summary">Axtarış sorğularını lokal olaraq saxlayın</string> <string name="enable_search_history_summary">Axtarış sorğularını yerli olaraq saxlayın</string>
<string name="enable_search_history_title">Axtarış tarixçəsi</string> <string name="enable_search_history_title">Axtarış tarixçəsi</string>
<string name="show_search_suggestions_summary">Axtarış edərkən göstəriləcək təklifləri seçin</string> <string name="show_search_suggestions_summary">Axtarış edərkən göstəriləcək təklifləri seçin</string>
<string name="show_search_suggestions_title">Axtarış təklifləri</string> <string name="show_search_suggestions_title">Axtarış təklifləri</string>
<string name="brightness_gesture_control_summary">Pleyerin parlaqlığını idarə etmək üçün jestlərdən istifadə edin</string> <string name="brightness_gesture_control_summary">Oynadıcının parlaqlığını nizamlamaq üçün jestlərdən istifadə edin</string>
<string name="brightness_gesture_control_title">Parlaqlığın jestlə idarə edilməsi</string> <string name="brightness_gesture_control_title">Parlaqlığı jestlə nizamlama</string>
<string name="volume_gesture_control_summary">Pleyerin səsini idarə etmək üçün jestlərdən istifadə edin</string> <string name="volume_gesture_control_summary">Oynadıcının səsini nizamlamaq üçün jestlərdən istifadə edin</string>
<string name="volume_gesture_control_title">Səsin jestlə idarə edilməsi</string> <string name="volume_gesture_control_title">Səsi jestlə nizamlama</string>
<string name="auto_queue_toggle">Avto-növbələmə</string> <string name="auto_queue_toggle">Avto-növbələmə</string>
<string name="auto_queue_title">Növbəti axını avtomatik olaraq növbəyə əlavə et</string> <string name="auto_queue_title">Növbəti Yayımı Avto-növbələmə</string>
<string name="metadata_cache_wipe_complete_notice">Metadata keşi silindi</string> <string name="metadata_cache_wipe_complete_notice">Üst məlumat keşi silindi</string>
<string name="metadata_cache_wipe_summary">Keşlənmiş bütün veb-səhifə verilənlərini sil</string> <string name="metadata_cache_wipe_summary">Keşlənmiş bütün veb-səhifə məlumatlarını silin</string>
<string name="metadata_cache_wipe_title">Keşlənmiş metadatanı təmizlə</string> <string name="metadata_cache_wipe_title">Keşlənmiş üst məlumatı təmizləyin</string>
<string name="thumbnail_cache_wipe_complete_notice">Şəkil keşi silindi</string> <string name="thumbnail_cache_wipe_complete_notice">Şəkil keşi silindi</string>
<string name="show_comments_summary">Şərhləri gizlətmək üçün söndürün</string> <string name="show_comments_summary">Şərhləri gizlətmək üçün söndürün</string>
<string name="show_comments_title">Şərhləri göstər</string> <string name="show_comments_title">Şərhləri göstər</string>
<string name="clear_queue_confirmation_description">Aktiv pleyerin növbəsi dəyişdiriləcək</string> <string name="clear_queue_confirmation_description">Aktiv oynadıcının növbəsi dəyişdiriləcək</string>
<string name="clear_queue_confirmation_summary">Bir pleyerdən digərinə keçmək növbənizi dəyişdirə bilər</string> <string name="clear_queue_confirmation_summary">Bir oynadıcıdan digərinə keçid növbənizi dəyişdirə bilər</string>
<string name="clear_queue_confirmation_title">Növbəni təmizləmədən öncə təsdiq üçün soruş</string> <string name="clear_queue_confirmation_title">Növbəni təmizləməzdən əvvəl təsdiq üçün soruş</string>
<string name="use_inexact_seek_title">Qeyri-dəqiq axtarış (videonu irəli/geri çəkmə) istifadə edin</string> <string name="use_inexact_seek_title">Sürətli qeyri-dəqiq axtarışdan istifadə edin</string>
<string name="use_inexact_seek_summary">Qeyri-dəqiq axtarış oynadıcıya azaldılmış həssaslıqla mövqeləri daha sürətlə axtarmağa imkan verir. 5, 15 və ya 25 saniyəlik axtarış bununla işləmir</string> <string name="use_inexact_seek_summary">Qeyri-dəqiq axtarış oynadıcıya azaldılmış dəqiqliklə mövqeləri daha sürətli axtarmağa imkan verir. 5, 15 və ya 25 saniyəlik axtarış bununla işləmir</string>
<string name="seek_duration_title">Cəld irəli/geri çəkmə müddəti</string> <string name="seek_duration_title">Sürətli irəli/geri çəkmə axtarış müddəti</string>
<string name="notification_action_nothing">Heç nə</string> <string name="notification_action_nothing">Heç nə</string>
<string name="notification_action_buffering">Buferizasiya olunur</string> <string name="notification_action_buffering">Buferlənir</string>
<string name="notification_action_shuffle">Qarışdır</string> <string name="notification_action_shuffle">Qarışdır</string>
<string name="notification_action_repeat">Təkrarla</string> <string name="notification_action_repeat">Təkrarla</string>
<string name="notification_action_4_title">Beşinci hərəkət düyməsi</string> <string name="notification_action_4_title">Beşinci fəaliyyət düyməsi</string>
<string name="notification_action_3_title">Dördüncü hərəkət düyməsi</string> <string name="notification_action_3_title">Dördüncü fəaliyyət düyməsi</string>
<string name="notification_action_2_title">Üçüncü hərəkət düyməsi</string> <string name="notification_action_2_title">Üçüncü fəaliyyət düyməsi</string>
<string name="notification_action_1_title">İkinci hərəkət düyməsi</string> <string name="notification_action_1_title">İkinci fəaliyyət düyməsi</string>
<string name="notification_action_0_title">Birinci hərəkət düyməsi</string> <string name="notification_action_0_title">Birinci fəaliyyət düyməsi</string>
<string name="show_higher_resolutions_summary">Yalnız bəzi cihazlar 2K/4K videoları oxuda bilir</string> <string name="show_higher_resolutions_summary">Yalnız bəzi cihazlar 2K/4K videoları oynada bilir</string>
<string name="default_popup_resolution_title">Ani pəncərədə standart görüntü keyfiyyəti</string> <string name="default_popup_resolution_title">Defolt ani pəncərə keyfiyyəti</string>
<string name="controls_add_to_playlist_title">Əlavə et</string> <string name="controls_add_to_playlist_title">Əlavə Edin</string>
<string name="controls_popup_title">Ani pəncərə (popup)</string> <string name="controls_popup_title">Ani Pəncərə</string>
<string name="tab_choose">Tab-vərəqəni Seçin</string> <string name="tab_choose">Tabı Seçin</string>
<string name="subscription_update_failed">Abunəlik yenilənmədi</string> <string name="subscription_update_failed">Abunəliyi yeniləmək alınmadı</string>
<string name="subscription_change_failed">Abunəlik dəyişdirilmədi</string> <string name="subscription_change_failed">Abunəliyi dəyişdirmək alınmadı</string>
<string name="search_showing_result_for">Nəticələr göstərilir: %s</string> <string name="search_showing_result_for">Nəticələr göstərilir: %s</string>
<string name="channels">Kanallar</string> <string name="channels">Kanallar</string>
<string name="video_detail_by">%s tərəfindən</string> <string name="video_detail_by">%s tərəfindən</string>
<string name="youtube_restricted_mode_enabled_title">\"Youtube\"un \"Məhdudiyyətli Rejimi\"ni aktivləşdir</string> <string name="youtube_restricted_mode_enabled_title">YouTube\'un \"Məhdud Rejimi\"ni açın</string>
<string name="show_age_restricted_content_summary">Yaş limiti olduğuna görə (məs. 18+) böyük ehtimal uşaqlar üçün uyğun olmayan məzmunu göstər</string> <string name="show_age_restricted_content_summary">Yaş həddi səbəbiylə (məsələn, 18+) uşaqlar üçün uyğun olmayan məzmunu göstərin</string>
<string name="show_age_restricted_content_title">Yaş məhdudiyyətli məzmunu göstər</string> <string name="show_age_restricted_content_title">Yaş məhdudiyyətli məzmunu göstər</string>
<string name="content">Məzmun</string> <string name="content">Məzmun</string>
<string name="popup_playing_toast">Ani pəncərədə oxudulur</string> <string name="popup_playing_toast">Ani pəncərədə oynadılır</string>
<string name="background_player_playing_toast">Fonda oxudulur</string> <string name="background_player_playing_toast">Arxa fonda oynadılır</string>
<string name="settings_category_updates_title">Yeniləmələr</string> <string name="settings_category_updates_title">Yeniləmələr</string>
<string name="settings_category_debug_title">Sazlama</string> <string name="settings_category_debug_title">Sazlama</string>
<string name="settings_category_appearance_title">Görünüş</string> <string name="settings_category_appearance_title">Görünüş</string>
<string name="settings_category_history_title">Tarix və keş</string> <string name="settings_category_history_title">Tarixçə və keş</string>
<string name="settings_category_video_audio_title">Video və səs</string> <string name="settings_category_video_audio_title">Video və səs</string>
<string name="settings_category_player_behavior_title">Davranış</string> <string name="settings_category_player_behavior_title">Davranış</string>
<string name="settings_category_player_title">Pleyer</string> <string name="settings_category_player_title">Oynadıcı</string>
<string name="content_language_title">İlkin məzmun dili</string> <string name="content_language_title">Defolt məzmun dili</string>
<string name="default_content_country_title">Məzmun üçün ilkin ölkə</string> <string name="default_content_country_title">Defolt məzmun ölkəsi</string>
<string name="unsupported_url_dialog_message">URL tanınmadı. Başqa bir tətbiq ilə açılsın\?</string> <string name="unsupported_url_dialog_message">URL\'i tanımaq olmadı. Başqa tətbiqlə açılsın\?</string>
<string name="unsupported_url">Dəstəklənməyən URL</string> <string name="unsupported_url">Dəstəklənməyən URL\'i</string>
<string name="show_hold_to_append_title">\"Əlavə etmək üçün basılı tutun\" məsləhətini göstər</string> <string name="show_hold_to_append_title">\"Növbəyə əlavə etmək üçün basılı saxla\" ipucusun göstər</string>
<string name="show_next_and_similar_title">\"Növbəti\" və \"Bənzər\" videoları göstər</string> <string name="show_next_and_similar_title">\"Növbəti\" və \"Bənzər\" videoları göstər</string>
<string name="export_data_summary">Tarixçəni, abunəlikləri və oxutma siyahılarını ixrac et</string> <string name="export_data_summary">Tarixçəni, abunəlikləri, pleylistləri və tənzimləmələri ixrac edin</string>
<string name="import_data_summary">"Cari tarixçə, abunəliklər, pleylistlər və (istəyə görə) ayarlarınızın üzərinə yazır"</string> <string name="import_data_summary">Cari tarixçənizi, abunəliklərinizi, pleylistlərinizi və (istəyə görə) tənzimləmələrinizi etibarsız edir</string>
<string name="recaptcha_cookies_cleared">reCAPTCHA çərəzləri təmizləndi</string> <string name="recaptcha_cookies_cleared">reCAPTCHA kukiləri təmizləndi</string>
<string name="clear_cookie_title">reCAPTCHA çərəzlərini təmizlə</string> <string name="clear_cookie_title">reCAPTCHA kukilərini təmizləyin</string>
<string name="export_data_title">Məlumat bazasını ixrac et</string> <string name="export_data_title">Məlumat bazasını ixrac edin</string>
<string name="import_data_title">Məlumat bazasını idxal et</string> <string name="import_data_title">Məlumat bazasını idxal edin</string>
<string name="switch_to_main">Əsas Görünüşə Keç</string> <string name="switch_to_main">Əsas Görünüşə Keçid</string>
<string name="switch_to_popup">Ani Pəncərəyə Keç</string> <string name="switch_to_popup">Ani Pəncərəyə Keçid</string>
<string name="switch_to_background">Fona Keç</string> <string name="switch_to_background">Arxa Fona Keçid</string>
<string name="unknown_content">[Bilinməyən]</string> <string name="unknown_content">[Naməlum]</string>
<string name="app_update_notification_channel_description">Yeni \"NewPipe\" versiyası üçün bildirişlər</string> <string name="app_update_notification_channel_description">Yeni \"NewPipe\" versiyası üçün bildirişlər</string>
<string name="app_update_notification_channel_name">Tətbiq Yeniləmə Bildirişi</string> <string name="app_update_notification_channel_name">Tətbiq yeniləmə bildirişi</string>
<string name="notification_channel_description">Fon və ani pəncərə pleyerləri üçün \"NewPipe\" bildirişləri</string> <string name="notification_channel_description">NewPipe oynadıcısı üçün bildirişlər</string>
<string name="all">Hamısı</string> <string name="all">Hamısı</string>
<string name="error_report_title">Xəta hesabatı</string> <string name="error_report_title">Xəta hesabatı</string>
<string name="downloads_title">Yükləmələr</string> <string name="downloads_title">Endirmələr</string>
<string name="downloads">Yükləmələr</string> <string name="downloads">Endirmələr</string>
<string name="duration_live">Canlı</string> <string name="duration_live">Canlı</string>
<string name="restricted_video">Bu video yaş məhdudiyyətlidir. <string name="restricted_video">Bu video yaş məhdudiyyətlidir.
\n \n
\nGörmək istəyirsinizsə, ayarlarda \"%1$s\" özəlliyini yandırın.</string> \nOnu görmək istəyirsinizsə, tənzimləmələrdə \"%1$s\" seçimini aktivləşdirin.</string>
<string name="youtube_restricted_mode_enabled_summary">\"YouTube\" böyüklər üçün potensial məzmunu gizləyən \"Məhdud Rejim\" təmin edir.</string> <string name="youtube_restricted_mode_enabled_summary">\"YouTube\" böyüklər üçün potensial məzmunu gizləyən \"Məhdud Rejim\" təmin edir</string>
<string name="peertube_instance_url_title">\"PeerTube\" nümunələri</string> <string name="peertube_instance_url_title">\"PeerTube\" serverləri</string>
<string name="download_thumbnail_title">Kiçik təsvirləri yüklə</string> <string name="download_thumbnail_title">Miniatürləri yükləyin</string>
<string name="notification_actions_at_most_three">Siz yığcam bildirişdə göstərilməsi üçün ən çoxu üç fəaliyyət seçə bilərsiniz!</string> <string name="notification_actions_at_most_three">Siz yığcam bildirişdə göstərilməsi üçün ən çoxu üç fəaliyyət seçə bilərsiniz!</string>
<string name="feed_update_threshold_option_always_update">Həmişə yenilə</string> <string name="feed_update_threshold_option_always_update">Həmişə yenilə</string>
<string name="settings_category_feed_title">Axın</string> <string name="settings_category_feed_title">Axın</string>
@ -152,75 +152,75 @@
<item quantity="one">%d seçildi</item> <item quantity="one">%d seçildi</item>
<item quantity="other">%d seçildi</item> <item quantity="other">%d seçildi</item>
</plurals> </plurals>
<string name="feed_group_dialog_empty_selection">Abunəlik seçilmədi</string> <string name="feed_group_dialog_empty_selection">Abunəlik seçilməyib</string>
<string name="feed_group_dialog_select_subscriptions">Abunəlikləri seçin</string> <string name="feed_group_dialog_select_subscriptions">Abunəlikləri seçin</string>
<string name="feed_processing_message">Axın emal edilir…</string> <string name="feed_processing_message">Axın emal edilir…</string>
<string name="feed_notification_loading">Axın yüklənir…</string> <string name="feed_notification_loading">Axın yüklənir…</string>
<string name="feed_subscription_not_loaded_count">Yüklənmədi: %d</string> <string name="feed_subscription_not_loaded_count">Yüklənmədi: %d</string>
<string name="playlist_page_summary">Oxutma siyahısı səhifəsi</string> <string name="playlist_page_summary">Pleylist səhifəsi</string>
<string name="notification_channel_name">\"Newpipe\" Bildirişi</string> <string name="notification_channel_name">\"Newpipe\" bildirişi</string>
<string name="file">Fayl</string> <string name="file">Fayl</string>
<string name="just_once">Yalnız Bir Dəfə</string> <string name="just_once">Yalnız Bir Dəfə</string>
<string name="always">Həmişə</string> <string name="always">Həmişə</string>
<string name="play_all">Hamısını Oxut</string> <string name="play_all">Hamısını Oynat</string>
<string name="file_deleted">Fayl silindi</string> <string name="file_deleted">Fayl silindi</string>
<string name="undo">Geri qaytar</string> <string name="undo">Geri qaytar</string>
<string name="best_resolution">Ən yaxşı görüntü keyfiyyəti</string> <string name="best_resolution">Ən yaxşı keyfiyyət</string>
<string name="clear">Təmizlə</string> <string name="clear">Təmizləyin</string>
<string name="disabled">Deaktiv edilib</string> <string name="disabled">Qeyri-aktivdir</string>
<string name="artists">İfaçılar</string> <string name="artists">Rəssamlar</string>
<string name="albums">Albomlar</string> <string name="albums">Albomlar</string>
<string name="songs">Mahnılar</string> <string name="songs">Mahnılar</string>
<string name="events">Hadisələr</string> <string name="events">Hadisələr</string>
<string name="users">İstifadəçilər</string> <string name="users">İstifadəçilər</string>
<string name="tracks">Treklər</string> <string name="tracks">Treklər</string>
<string name="videos_string">Videolar</string> <string name="videos_string">Videolar</string>
<string name="playlists">Oxutma siyahıları</string> <string name="playlists">Pleylistlər</string>
<string name="general_error">Xəta</string> <string name="general_error">Xəta</string>
<string name="help">Kömək</string> <string name="help">Kömək</string>
<string name="search_history_deleted">Axtarış tarixçəsi silindi.</string> <string name="search_history_deleted">Axtarış tarixçəsi silindi</string>
<string name="delete_search_history_alert">Bütün axtarış tarixçəsi silinsin\?</string> <string name="delete_search_history_alert">Bütün axtarış tarixçəsi silinsin\?</string>
<string name="clear_search_history_summary">Axtarışda işlədilmiş açar sözlərin tarixçəsini siləcək</string> <string name="clear_search_history_summary">Axtarışdakı açar sözlərin tarixçəsini silir</string>
<string name="clear_search_history_title">Axtarış tarixçəsini təmizlə</string> <string name="clear_search_history_title">Axtarış tarixçəsini silin</string>
<string name="watch_history_states_deleted">Oxutma mövqeləri silindi.</string> <string name="watch_history_states_deleted">Oynatma mövqeləri silindi</string>
<string name="delete_playback_states_alert">Bütün oxutma mövqeləri silinsin\?</string> <string name="delete_playback_states_alert">Bütün oynatma mövqeləri silinsin\?</string>
<string name="clear_playback_states_summary">Bütün oxutma mövqelərini siləcək</string> <string name="clear_playback_states_summary">Bütün oynatma mövqelərini siləcək</string>
<string name="clear_playback_states_title">Oxutma mövqelərini sil</string> <string name="clear_playback_states_title">Oynatma mövqelərini silin</string>
<string name="watch_history_deleted">Baxış tarixçəsi silindi.</string> <string name="watch_history_deleted">Baxış tarixçəsi silindi</string>
<string name="delete_view_history_alert">Bütün baxış tarixçəsi silinsin\?</string> <string name="delete_view_history_alert">Bütün baxış tarixçəsi silinsin\?</string>
<string name="clear_views_history_title">Baxış tarixçəsini təmizlə</string> <string name="clear_views_history_title">Baxış tarixçəsini təmizlə</string>
<string name="clear_cookie_summary">\"ReCAPTCHA\" həll edilərkən \"NewPipe\"ın saxladığı çərəzləri təmizlə</string> <string name="clear_cookie_summary">reCAPTCHA həll edərkən NewPipe\'ın saxladığı kukiləri silin</string>
<string name="channel_created_by">%s tərəfindən yaradıldı</string> <string name="channel_created_by">%s tərəfindən yaradıldı</string>
<string name="resize_zoom">Yaxınlaşdır</string> <string name="resize_zoom">Yaxınlaşdır</string>
<string name="resize_fill">Doldur</string> <string name="resize_fill">Doldur</string>
<string name="resize_fit">Dart</string> <string name="resize_fit">Dart</string>
<string name="caption_none">Altyazı yoxdur</string> <string name="caption_none">Altyazı Yoxdur</string>
<string name="delete">Sil</string> <string name="delete">Silin</string>
<string name="no_channel_subscribed_yet">Hələ ki kanal abunəliyi yoxdur</string> <string name="no_channel_subscribed_yet">Hələ ki, kanal abunəliyi yoxdur</string>
<string name="select_a_channel">Kanal seçin</string> <string name="select_a_channel">Kanal seçin</string>
<string name="channel_page_summary">Kanal Səhifəsi</string> <string name="channel_page_summary">Kanal Səhifəsi</string>
<string name="default_kiosk_page_summary">İlkin Köşk</string> <string name="default_kiosk_page_summary">İlkin Köşk</string>
<string name="kiosk_page_summary">Köşk Səhifəsi</string> <string name="kiosk_page_summary">Köşk Səhifəsi</string>
<string name="blank_page_summary">Boş Səhifə</string> <string name="blank_page_summary">Boş Səhifə</string>
<string name="main_page_content_summary">Ana səhifədə hansı tab-vərəqələr göstərilir</string> <string name="main_page_content_summary">Ana səhifədə hansı tablar göstərilir</string>
<string name="main_page_content">Ana səhifənin məzmunu</string> <string name="main_page_content">Ana səhifənin məzmunu</string>
<string name="updates_setting_description">Yeni versiya mövcud olanda tətbiqi yeniləməyi xatırlatmaq üçün bildiriş göstər</string> <string name="updates_setting_description">Yeni versiya mövcud olduqda tətbiq yeniləməsini xatırlatmaq üçün bildiriş göstər</string>
<string name="updates_setting_title">Yeniləmələr</string> <string name="updates_setting_title">Yeniləmələr</string>
<string name="limit_mobile_data_usage_title">Mobil internet istifadə edərkən görüntü keyfiyyətini məhdudlaşdırın</string> <string name="limit_mobile_data_usage_title">Mobil internet istifadə edərkən görüntü keyfiyyətini məhdudlaşdırın</string>
<string name="limit_data_usage_none_description">Limitsiz</string> <string name="limit_data_usage_none_description">Limitsiz</string>
<string name="one_item_deleted">1 element silindi.</string> <string name="one_item_deleted">1 element silindi.</string>
<string name="peertube_instance_add_title">Nümunə əlavə et</string> <string name="peertube_instance_add_title">Server əlavə edin</string>
<string name="peertube_instance_url_summary">Sevimli \"PeerTube\" nümunələrinizi seçin</string> <string name="peertube_instance_url_summary">Sevimli \"PeerTube\" serverlərinizi seçin</string>
<string name="delete_downloaded_files">Yüklənmiş faylları sil</string> <string name="delete_downloaded_files">Endirilmiş faylları silin</string>
<string name="confirm_prompt">Yükləmə tarixçənizi təmizləmək və ya yüklənmiş bütün faylları silmək istəyirsiniz\?</string> <string name="confirm_prompt">Endirmə tarixçənizi təmizləmək və ya endirilmiş bütün faylları silmək istəyirsiniz\?</string>
<string name="clear_download_history">Yükləmə tarixçəsini təmizlə</string> <string name="clear_download_history">Endirmə tarixçəsini təmizlə</string>
<string name="start_downloads">Yükləmələrə başla</string> <string name="start_downloads">Endirmələrə başla</string>
<string name="pause_downloads">Yükləmələrə fasilə ver</string> <string name="pause_downloads">Endirmələrə fasilə verin</string>
<string name="downloads_storage_ask_title">Haraya yüklənəcəyini soruş</string> <string name="downloads_storage_ask_title">Haraya endiriləcəyini soruş</string>
<string name="downloads_storage_ask_summary">Hər yükləmədə harada saxlanılacağı soruşulacaq</string> <string name="downloads_storage_ask_summary">Sizdən hər endirmənin harada saxlanacağı soruşulacaq.
<string name="downloads_storage_use_saf_summary">\'Saxlanca Müraciət Çərçivəsi\' xarici SD karta yükləməyə imkan verir. \nXarici SD karta yükləmək istəyirsinizsə, sistem qovluğu seçicisini (SAF) aktiv edin</string>
\nBəzi cihazlar uyğun deyil</string> <string name="downloads_storage_use_saf_summary">\'Yaddaş Giriş Çərçivəsi \' xarici SD karta endirməyə imkan verir</string>
<string name="systems_language">Sistem öncülü</string> <string name="systems_language">Sistem defoltu</string>
<string name="app_language_title">Tətbiq dili</string> <string name="app_language_title">Tətbiq dili</string>
<plurals name="days"> <plurals name="days">
<item quantity="one">%d gün</item> <item quantity="one">%d gün</item>
@ -238,11 +238,11 @@
<item quantity="one">%d saniyə</item> <item quantity="one">%d saniyə</item>
<item quantity="other">%d saniyə</item> <item quantity="other">%d saniyə</item>
</plurals> </plurals>
<string name="feed_oldest_subscription_update">Axın sonuncu dəfə güncəlləndi: %s</string> <string name="feed_oldest_subscription_update">Axın sonuncu dəfə yeniləndi: %s</string>
<string name="feed_update_threshold_title">Axın güncəlləmə astanası</string> <string name="feed_update_threshold_title">Axın yeniləmə astanası</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Sürətli rejimi aktiv et</string> <string name="feed_use_dedicated_fetch_method_enable_button">Sürətli rejimi aktivləşdir</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Sürətli rejimi deaktiv et</string> <string name="feed_use_dedicated_fetch_method_disable_button">Sürətli rejimi deaktiv et</string>
<string name="feed_use_dedicated_fetch_method_help_text">Axının çox yavaş yükləndiyini düşünürsünüz\? Əgər elədirsə, sürətli yükləməni işə salmağı sınayın (ayarlardan dəyişə və ya aşağıdakı düyməni basa bilərsiniz). <string name="feed_use_dedicated_fetch_method_help_text">Axının çox yavaş yükləndiyini düşünürsünüz\? Əgər elədirsə, sürətli yükləməni işə salmağı sınayın (tənzimləmələrdən dəyişə və ya aşağıdakı düyməni basa bilərsiniz).
\n \n
\nNewPipe axını yükləmək üçün 2 metod təklif edir: \nNewPipe axını yükləmək üçün 2 metod təklif edir:
\n• Bütün abunəlik kanallarını gətirtmək, bu yavaş olsa da tamdır; \n• Bütün abunəlik kanallarını gətirtmək, bu yavaş olsa da tamdır;
@ -253,43 +253,469 @@
\nYouTube öz RSS axını ilə bu sürətli metodu təklif edən xidmətlərdən biridir. \nYouTube öz RSS axını ilə bu sürətli metodu təklif edən xidmətlərdən biridir.
\n \n
\nBeləliklə, seçim sizin nəyə üstünlük verməyinizdən asılıdır: sürət yoxsa dəqiq məlumat.</string> \nBeləliklə, seçim sizin nəyə üstünlük verməyinizdən asılıdır: sürət yoxsa dəqiq məlumat.</string>
<string name="player_stream_failure">Bu axını oxutmaq olmadı</string> <string name="player_stream_failure">Bu yayımı oynatmaq alınmadı</string>
<string name="app_ui_crash">Tətbiq/UI çökdü</string> <string name="app_ui_crash">Tətbiq/UI çökdü</string>
<string name="could_not_setup_download_menu">Yükləmə menyusu qurulmadı</string> <string name="could_not_setup_download_menu">Endirmə menyusunu qurmaq mümkün olmadı</string>
<string name="content_not_available">Məzmun əlçatmazdır</string> <string name="content_not_available">Məzmun əlçatmazdır</string>
<string name="could_not_load_thumbnails">Kiçik təsvirlərin hamısı yüklənmədi</string> <string name="could_not_load_thumbnails">Bütün miniatürləri yükləmək alınmadı</string>
<string name="network_error">Şəbəkə xətası</string> <string name="network_error">Şəbəkə xətası</string>
<string name="download_to_sdcard_error_message">Xarici SD karta yükləmək mümkün deyil. Yükləmə qovluğu üçün təyin edilmiş yer sıfırlansın\?</string> <string name="download_to_sdcard_error_message">Xarici SD karta endirmək mümkün deyil. Endirmə qovluğunun yeri sıfırlansın\?</string>
<string name="download_to_sdcard_error_title">Xarici saxlanc əlçatmazdır</string> <string name="download_to_sdcard_error_title">Xarici yaddaş əlçatan deyil</string>
<string name="clear_views_history_summary">Oxutma axının tarixçəsini və oxutma mövqelərini siləcək</string> <string name="clear_views_history_summary">Oynadılmış yayımların tarixçəsini və oynatma mövqelərini silir</string>
<string name="show_meta_info_title">Üst bilgini göstər</string> <string name="show_meta_info_title">Üst məlumatı göstər</string>
<string name="show_description_summary">Video təsvirini və əlavə bilgiləri gizlətmək üçün söndürün</string> <string name="show_description_summary">Video ıqlamasını və əlavə məlumatı gizlətmək üçün söndürün</string>
<string name="show_description_title">Təsviri göstər</string> <string name="show_description_title">ıqlamanı göstər</string>
<string name="notification_colorize_title">Bildirişləri rənglə</string> <string name="notification_colorize_title">Bildirişi rəngləyin</string>
<string name="invalid_directory">Belə qovluq yoxdur</string> <string name="invalid_directory">Belə qovluq yoxdur</string>
<string name="start_main_player_fullscreen_title">Əsas oynadıcını (pleyeri) tam ekranda başlat</string> <string name="start_main_player_fullscreen_title">Əsas oynadıcını tam ekranda başlat</string>
<string name="external_player_unsupported_link_type">Xarici oynadıcılar bu cür linkləri dəstəkləmir</string> <string name="external_player_unsupported_link_type">Xarici oynadıcılar bu cür linkləri dəstəkləmir</string>
<string name="local_search_suggestions">Yerli axtarış təklifləri</string> <string name="local_search_suggestions">Yerli axtarış təklifləri</string>
<string name="video">Video</string> <string name="video">Video</string>
<string name="related_items_tab_description">Əlaqəli videolar</string> <string name="related_items_tab_description">Əlaqədar yayımlar</string>
<string name="mark_as_watched">İzlənmiş kimi işarələ</string> <string name="mark_as_watched">Baxılmış kimi işarələ</string>
<string name="open_with">Aşağıdakılardan biri ilə aç</string> <string name="open_with">...ilə açın</string>
<string name="night_theme_title">Gecə Mövzusu</string> <string name="night_theme_title">Gecə Mövzusu</string>
<string name="popup_remember_size_pos_title">Ani ılan pəncərə (popup) xüsusiyyətlərini xatırla</string> <string name="popup_remember_size_pos_title">Ani pəncərə xüsusiyyətlərini xatırla</string>
<string name="popup_remember_size_pos_summary">Ani ılan pəncərənin son ölçüsü və mövqeyini xatırla</string> <string name="popup_remember_size_pos_summary">Ani pəncərənin son ölçüsü və mövqeyini xatırla</string>
<string name="video_streams_empty">Video yayımı tapılmadı</string> <string name="video_streams_empty">Video yayımı tapılmadı</string>
<string name="comments_tab_description">Şərhlər</string> <string name="comments_tab_description">Şərhlər</string>
<string name="description_tab_description">Təsvir</string> <string name="description_tab_description">ıqlama</string>
<string name="empty_subscription_feed_subtitle"></string> <string name="empty_subscription_feed_subtitle">Burada kriketlərdən başqa heç nə yoxdur</string>
<string name="search_no_results">Nəticə yoxdur</string> <string name="search_no_results">Nəticə yoxdur</string>
<string name="restore_defaults">İlkin ayarları qaytar</string> <string name="restore_defaults">İlkin tənzimləmələri qaytarın</string>
<string name="missing_file">Fayl köçürüldü və ya silindi</string> <string name="missing_file">Fayl köçürüldü və ya silindi</string>
<string name="player_recoverable_failure">Oynadıcı xətası düzəldilir</string> <string name="player_recoverable_failure">Oynadıcı xətası bərpa edilir</string>
<string name="player_unrecoverable_failure">Bərpa oluna bilməyən oynadıcı xətası baş verdi</string> <string name="player_unrecoverable_failure">Bərpa olunmayan oynatma xətası baş verdi</string>
<string name="ok">Oldu</string> <string name="ok">Oldu</string>
<string name="restricted_video_no_stream">Bu video yaş məhdudiyyətlidir. <string name="restricted_video_no_stream">Bu video yaş məhdudiyyətidir.
\n\"YouTube\"un yeni yaş məhdudiyyətli videolar siyasətinə görə \"NewPipe\" bu cür videoların yayımını əldə edə bilmir, beləliklə, videonu oynatmaq mümkün deyil.</string> \nYaş məhdudiyyəti olan videolarla bağlı yeni YouTube siyasətlərinə görə, NewPipe bu cür video yayımlara daxil ola və oynada bilməz.</string>
<string name="audio_streams_empty">Səs yayımı tapılmadı</string> <string name="audio_streams_empty">Səs yayımı tapılmadı</string>
<string name="permission_display_over_apps">Başqa proqramların üzərində göstərmə icazəsi ver</string> <string name="permission_display_over_apps">Digər tətbiqlərin üzərində göstərməyə icazə verin</string>
<string name="restore_defaults_confirmation">İlkin ayarları qaytarmaq istəyirsiniz\?</string> <string name="restore_defaults_confirmation">İlkin tənzimləmələri qaytarmaq istəyirsiniz\?</string>
<string name="download_thumbnail_summary">Miniatürlərin yüklənməsini, dataya qənaət etmək və yaddaşdan istifadəni azaltmaq üçün söndürün. Dəyişikliklər həm yaddaşdaxili, həm də diskdə olan təsvir keşini təmizləyir</string>
<string name="enqueue_next_stream">Növbətini sıraya salın</string>
<string name="retry">Yenidən Cəhd Edin</string>
<string name="settings_category_player_notification_summary">Cari oynatma yayımı bildirişini konfiqurasiya edin</string>
<string name="notifications">Bildirişlər</string>
<string name="hash_channel_name">Video fayl xülasəsi bildirişi</string>
<string name="streams_notification_channel_description">Abunəliklər üçün yeni yayımlar haqqında bildirişlər</string>
<string name="error_report_channel_description">Xəta hesabatları üçün bildirişlər</string>
<string name="file_name_empty_error">Fayl adı boş ola bilməz</string>
<string name="saved_tabs_invalid_json">Yadda saxlanmış tabları oxumaq mümkün olmadı, buna görə defolt tablardan istifadə edin</string>
<string name="error_report_notification_title">NewPipe xəta ilə qarşılaşdı, bildirmək üçün toxunun</string>
<string name="sorry_string">Bağışlayın, belə olmamalı idi.</string>
<string name="error_report_button_text">Bu xətanı e-poçt vasitəsilə bildirin</string>
<string name="error_report_open_issue_button_text">GitHub\'da Hesabat Verin</string>
<string name="error_report_open_github_notice">Zəhmət olmasa, xətanızı müzakirə edən məsələnin mövcud olub-olmadığını yoxlayın. Dublikat biletləri yaradarkən, bizdən faktiki səhvi düzəltməyə sərf edəcəyimiz vaxt alırsınız.</string>
<string name="error_snackbar_action">Hesabat Bildirin</string>
<string name="what_device_headline">Məlumat:</string>
<string name="what_happened_headline">Nə baş verdi:</string>
<string name="detail_uploader_thumbnail_view_description">Yükləyənin avatar miniatürü</string>
<string name="detail_likes_img_view_description">Bəyəni</string>
<string name="detail_dislikes_img_view_description">Bəyənməmə</string>
<string name="detail_drag_description">Yenidən sıralamaq üçün sürüşdürün</string>
<string name="short_thousand">m</string>
<string name="short_million">M</string>
<string name="short_billion">M</string>
<string name="drawer_header_description">Xidməti dəyişin,hazırda seçilmiş:</string>
<string name="no_subscribers">Abunəçi yoxdur</string>
<string name="no_views">Baxış yoxdur</string>
<string name="no_one_watching">Heç kim izləmir</string>
<string name="no_one_listening">Heç kim dinləmir</string>
<string name="no_videos">Video yoxdur</string>
<string name="comments_are_disabled">Şərhlər qeyri-aktivdir</string>
<string name="start">Başladın</string>
<string name="pause">Dayandırın</string>
<string name="checksum">Təsdiqləmə</string>
<string name="dismiss">İmtina</string>
<string name="msg_error">Xəta</string>
<string name="msg_running_detail">Detallar üçün toxunun</string>
<string name="msg_wait">Zəhmət olmasa, gözləyin…</string>
<string name="no_dir_yet">Hələ endirmə qovluğu təyin edilməyib, indi defolt endirmə qovluğunu seçin</string>
<string name="title_activity_recaptcha">reCAPTCHA çağırışı</string>
<string name="recaptcha_request_toast">reCAPTCHA sorğusu göndərildi</string>
<string name="recaptcha_done_button">Bitdi</string>
<string name="settings_file_replacement_character_summary">Etibarsız simvollar bu dəyərlə əvəz olunur</string>
<string name="settings_file_replacement_character_title">Əvəzedici xarakter</string>
<string name="charset_most_special_characters">Ən xüsusi simvollar</string>
<string name="title_licenses">Üçüncü Tərəf Lisenziyaları</string>
<string name="tab_about">Haqqında</string>
<string name="contribution_title">Töhfə Verin</string>
<string name="contribution_encouragement">Fikirlərinizin olub-olmaması, tərcümə, dizayn dəyişiklikləri, kodun təmizlənməsi və ya real ağırlıqlı kod dəyişiklikləri və.s kömək həmişə xoşdur. Nə qədər çox edilsə, bir o qədər yaxşı olar!</string>
<string name="give_back">İanə Edin</string>
<string name="website_title">Veb sayt</string>
<string name="website_encouragement">Əlavə məlumat və xəbərlər üçün NewPipe Veb saytına daxil olun.</string>
<string name="privacy_policy_title">NewPipe\'ın Məxfilik Siyasəti</string>
<string name="privacy_policy_encouragement">NewPipe layihəsi məxfiliyinizə çox ciddi yanaşır. Buna görə də, tətbiq sizin razılığınız olmadan heç bir məlumat toplamır.
\nNewPipe\'ın məxfilik siyasəti qəza hesabatı göndərdiyiniz zaman hansı məlumatların göndərildiyini və saxlandığını ətraflı izah edir.</string>
<string name="read_privacy_policy">Məxfilik siyasətini oxuyun</string>
<string name="app_license_title">NewPipe\'ın Lisenziyası</string>
<string name="action_history">Tarixçə</string>
<string name="delete_item_search_history">Bu elementi axtarış tarixçəsindən silmək istəyirsiniz\?</string>
<string name="title_last_played">Son Oynadılan</string>
<string name="title_most_played">Ən çox oynadılan</string>
<string name="select_a_kiosk">Köşk seçin</string>
<string name="import_complete_toast">İdxal edildi</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="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="new_and_hot">Yeni və populyar</string>
<string name="local">Yerli</string>
<string name="recently_added">Son əlavə edilən</string>
<string name="conferences">Konfranslar</string>
<string name="title_activity_play_queue">Oynatma növbəsi</string>
<string name="play_queue_stream_detail">Detallar</string>
<string name="show_channel_details">Kanal təfərrüatlarını göstərin</string>
<string name="start_here_on_popup">Ani pəncərədə oynatmağa başlayın</string>
<string name="preferred_open_action_settings_title">\"Açıq\" fəaliyyətə üstünlük verilir</string>
<string name="background_player">Arxa Fon oynadıcı</string>
<string name="always_ask_open_action">Həmişə soruşun</string>
<string name="preferred_player_fetcher_notification_message">Tələb olunan məzmun yüklənir</string>
<string name="create_playlist">Yeni Pleylist</string>
<string name="rename_playlist">Adını dəyişdirin</string>
<string name="add_to_playlist">Pleylistə əlavə edin</string>
<string name="processing_may_take_a_moment">Emal edilir... Bir az vaxt ala bilər</string>
<string name="unmute">Səsi aç</string>
<string name="bookmark_playlist">Pleylisti Əlfəcinlə</string>
<string name="unbookmark_playlist">Əlfəcini Silin</string>
<string name="delete_playlist_prompt">Bu pleylist silinsin\?</string>
<string name="playlist_creation_success">Pleylist yaradıldı</string>
<string name="playlist_thumbnail_change_success">Pleylist miniatürü dəyişdirildi.</string>
<string name="playlist_no_uploader">Avtomatik yaradıldı (heç bir yükləyici tapılmadı)</string>
<string name="caption_auto_generated">Avtomatik yaradıldı</string>
<string name="caption_setting_title">Altyazılar</string>
<string name="leak_canary_not_available">LeakCanary yoxdur</string>
<string name="enable_leak_canary_summary">Yaddaş sızmasının monitorinqi yığın boşaltma zamanı tətbiqin cavab verməməsinə səbəb ola bilər</string>
<string name="show_memory_leaks">Yaddaş sızmalarını göstərin</string>
<string name="enable_disposed_exceptions_summary">Utilizasiyadan sonra fraqment və ya fəaliyyətin yaşam dövründən kənarda çatdırıla bilməyən Rx istisnaları barədə hesabat verməyə məcbur edin</string>
<string name="show_original_time_ago_summary">Xidmətlərdən alınmış orijinal mətnlər yayım elementlərində görünəcək</string>
<string name="check_new_streams">Yeni yayımları yoxlayın</string>
<string name="import_soundcloud_instructions">URL və ya ID\'nizi daxil etməklə SoundCloud profilini idxal edin:
\n
\n1. Veb-brauzerdə \"iş masası rejimini\" aktiv edin (sayt mobil cihazlar üçün mövcud deyil)
\n2. Bu URL\'ə keçin: %1$s
\n3. Soruşulduqda daxil olun
\n4. Yönləndirildiyiniz profilin URL\'sini kopyalayın.</string>
<string name="import_soundcloud_instructions_hint">ID\'niz, soundcloud.com/ID\'niz</string>
<string name="import_network_expensive_warning">Unutmayın ki, bu əməliyyat şəbəkəyə ağır yük ola bilər.
\n
\nDavam etmək istəyirsiniz\?</string>
<string name="skip_silence_checkbox">Sükut zamanı sürətlə irəlilə</string>
<string name="enable_streams_notifications_title">Yeni yayım bildirişləri</string>
<string name="enable_streams_notifications_summary">Abunəliklərdən yeni yayımlar haqqında bildiriş göndər</string>
<string name="streams_notifications_interval_title">Tezliyin yoxlanılması</string>
<string name="streams_notifications_network_title">Tələb olunan şəbəkə bağlantısı</string>
<string name="any_network">İstənilən şəbəkə</string>
<string name="minimize_on_exit_title">Tətbiq keçidində kiçildin</string>
<string name="minimize_on_exit_background_description">Arxa Fon oynadıcısına kiçildin</string>
<string name="minimize_on_exit_popup_description">Ani-pəncərə oynadıcısına kiçildin</string>
<string name="autoplay_summary">Oynatmağa avtomatik başlayın — %s</string>
<string name="low_quality_smaller">Aşağı keyfiyyətli (daha kiçik)</string>
<string name="dont_show">Göstərməyin</string>
<string name="download_failed">Endirmə uğursuz oldu</string>
<string name="error_http_unsupported_range">Server çox iş parçalı endirmələri qəbul etmir, @string/msg_threads = 1 ilə yenidən cəhd edin</string>
<string name="delete_downloaded_files_confirm">Bütün endirilmiş fayllar diskdən silinsin\?</string>
<string name="max_retry_msg">Maksimum təkrar cəhdlər</string>
<string name="remove_watched_popup_warning">Pleylistə əlavə olunandan əvvəl və sonra baxılmış videolar silinəcək.
\nSiz əminsiniz\? Bu geri qaytarıla bilməz!</string>
<string name="feed_groups_header_title">Kanal qrupları</string>
<string name="feed_new_items">Yeni axın elementləri</string>
<string name="feed_update_threshold_summary">Abunəlik köhnəlmiş hesab edilənə qədərki son yeniləmədən sonrakı vaxt — %s</string>
<string name="feed_load_error">Axın yükləmə xətası</string>
<string name="content_not_supported">Bu məzmun hələ NewPipe tərəfindən dəstəklənmir.
\n
\nÜmid edirik ki, gələcək versiyada dəstəklənəcək.</string>
<string name="show_thumbnail_summary">Həm kilid ekranı fonu, həm də bildirişlər üçün miniatürdən istifadə edin</string>
<string name="recent">Ən Yeni</string>
<string name="georestricted_content">Bu məzmun ölkənizdə mövcud deyil.</string>
<string name="paid_content">Bu məzmun yalnız ödəniş etmiş istifadəçilər üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlana və ya endirilə bilməz.</string>
<string name="auto_device_theme_title">Avtomatik (cihaz mövzusu)</string>
<string name="night_theme_summary">Sevimli gecə mövzusunu seçin — %s</string>
<string name="detail_pinned_comment_view_description">Sabitlənmiş şərh</string>
<string name="notifications_disabled">Bildirişlər deaktiv edilib</string>
<string name="get_notified">Bildiriş alın</string>
<string name="you_successfully_subscribed">Artıq bu kanala abunə oldunuz</string>
<string name="enumeration_comma">,</string>
<string name="toggle_all">Hamısını dəyişdirin</string>
<string name="msg_name">Fayl adı</string>
<string name="recaptcha_solve">Həll edin</string>
<string name="subscriptions_export_unsuccessful">Abunəlikləri ixrac etmək mümkün olmadı</string>
<plurals name="watching">
<item quantity="one">%s izləyici</item>
<item quantity="other">%s izləyici</item>
</plurals>
<string name="manual_update_description">Yeni versiyaları əlinizlə yoxlayın</string>
<plurals name="listening">
<item quantity="one">%s dinləyici</item>
<item quantity="other">%s dinləyici</item>
</plurals>
<plurals name="videos">
<item quantity="one">%s video</item>
<item quantity="other">%s video</item>
</plurals>
<string name="manual_update_title">Yeniləmələri yoxlayın</string>
<string name="seekbar_preview_thumbnail_title">Axtarış çubuğunun miniatür önizləməsi</string>
<string name="permission_denied">Əməliyyat sistem tərəfindən ləğv edildi</string>
<string name="auto">Avto</string>
<string name="error_http_not_found">Tapılmadı</string>
<string name="error_http_no_content">Server məlumat göndərmir</string>
<string name="error_download_resource_gone">Bu endirməni bərpa etmək mümkün deyil</string>
<string name="downloads_storage_ask_summary_no_saf_notice">Sizdən hər endirmənin harada saxlanacağı soruşulacaq</string>
<string name="downloads_storage_use_saf_summary_api_19">\'Yaddaş Giriş Çərçivəsi\' Android KitKat və ondan aşağı versiyalarda dəstəklənmir</string>
<string name="downloads_storage_use_saf_summary_api_29">\"Yaddaş Giriş Çərçivəsi\"yalnız Android 10\'dan başlayaraq dəstəklənir</string>
<string name="detail_sub_channel_thumbnail_view_description">Kanalın avatar miniatürü</string>
<string name="select_night_theme_toast">Sevdiyiniz gecə mövzusunu aşağıda seçə bilərsiniz</string>
<string name="notification_colorize_summary">Android\'in bildiriş rəngini miniatürdəki əsas rəngə uyğun fərdiləşdirməsini təmin edin(qeyd edək ki, bu, bütün cihazlarda mövcud deyil)</string>
<string name="view_on_github">GitHub\'da Baxın</string>
<string name="donation_title">İanə Edin</string>
<string name="donation_encouragement">NewPipe, sizə ən yaxşı istifadəçi təcrübəsini göstərmək üçün boş vaxtlarını sərf edən könüllülər tərəfindən hazırlanmışdır. Tərtibatçılara bir fincan qəhvə içərkən NewPipe\'ı daha da yaxşılaşdırmağa kömək etmək üçün kömək edin.</string>
<string name="most_liked">Ən çox bəyənildi</string>
<string name="enqueued">Növbəyə salındı</string>
<string name="preferred_open_action_settings_summary">Məzmunu açarkən defolt hərəkət — %s</string>
<string name="name">Ad</string>
<string name="set_as_playlist_thumbnail">Pleylist miniatürü kimi təyin edin</string>
<string name="wifi_only">Yalnız Wi-Fi\'da</string>
<string name="never">Heç vaxt</string>
<string name="list_view_mode">Siyahı görünüş rejimi</string>
<string name="list">Siyahı</string>
<string name="grid">Tor</string>
<string name="high_quality_larger">Yüksək keyfiyyətli (daha böyük)</string>
<string name="missions_header_pending">Gözlənilir</string>
<string name="queued">növbədə</string>
<string name="post_processing">son proseslər tətbiq olunur</string>
<string name="checking_updates_toast">Yeniləmələr yoxlanılır…</string>
<string name="app_update_notification_content_title">NewPipe yeniləməsi mövcuddur!</string>
<string name="metadata_licence">Lisenziya</string>
<string name="feed_load_error_terminated">Müəllifin hesabı bağlanıb.
\nNewPipe gələcəkdə bu axını yükləyə bilməyəcək.
\nBu kanala abunəlikdən çıxmaq istəyirsiniz\?</string>
<string name="feed_toggle_show_played_items">Baxılmış elementləri göstərin</string>
<string name="featured">Seçilmiş</string>
<string name="drawer_close">Çəkməcəni Bağlayın</string>
<string name="video_player">Video oynadıcı</string>
<string name="hash_channel_description">Video fayl xülasəsi prosesi üçün bildirişlər</string>
<string name="on">ın</string>
<string name="notification_scale_to_square_image_title">Kiçik şəkili 1:1 aspekt nisbətinə ölçün</string>
<string name="progressive_load_interval_summary">Yükləmə intervalının həcmini dəyişdirin (hazırda %s). Daha aşağı dəyər ilkin video yükləməni sürətləndirə bilər. Dəyişikliklər oynadıcının yenidən başladılmasını tələb edir.</string>
<string name="show_meta_info_summary">Yayım yaradıcısı, məzmunu və ya axtarış sorğusu haqqında əlavə məlumat olan üst məlumat qutularını gizlətmək üçün söndürün</string>
<string name="auto_queue_summary">Əlaqədar yayımı əlavə etməklə (təkrar etməyən) sonlanacaq oynatma sırasını davam etdir</string>
<string name="remote_search_suggestions">Kənar axtarış təklifləri</string>
<string name="peertube_instance_add_exists">Server artıq mövcuddur</string>
<string name="start_main_player_fullscreen_summary">Videoları mini oynadıcıda başlatma, avtomatik fırlatma kilidlidirsə, birbaşa tam ekran rejiminə keçid. Siz hələ də tam ekran rejimindən çıxmaqla mini pleyerə daxil ola bilərsiniz</string>
<string name="more_than_100_videos">100+ video</string>
<string name="infinite_videos">∞ video</string>
<string name="no_comments">Şərhlər yoxdur</string>
<string name="subtitle_activity_recaptcha">Həll edildikdə \"Bitdi\" düyməsini basın</string>
<string name="select_a_playlist">Pleylist seçin</string>
<string name="error_unable_to_load_comments">Şərhləri yükləmək mümkün olmadı</string>
<string name="trending">Populyar</string>
<string name="play_queue_audio_settings">Səs Tənzimləmələri</string>
<string name="preferred_player_fetcher_notification_title">Məlumat əldə edilir…</string>
<string name="show_original_time_ago_title">Elementlərdə əvvəlki vaxtı göstərin</string>
<string name="enable_disposed_exceptions_title">Yaşam dövrəsi xaricindəki xətaları bildirin</string>
<string name="show_image_indicators_title">Şəkil göstəricilərini göstərin</string>
<string name="show_image_indicators_summary">Şəkillərin üzərində mənbəsini göstərən Pikasso rəngli lentləri göstərin: şəbəkə üçün qırmızı, disk üçün mavi və yaddaş üçün yaşıl</string>
<string name="pause_downloads_on_mobile_desc">Bəzi endirmələri dayandırmaq mümkün olmasa da, mobil dataya keçərkən faydalıdır</string>
<string name="close">Bağla</string>
<string name="error_progress_lost">Fayl silindiyi üçün irəliləyiş itirildi</string>
<string name="max_retry_desc">Endirməni ləğv etməzdən əvvəl ümumi cəhdlərin sayı</string>
<string name="pause_downloads_on_mobile">Ölçülmüş şəbəkələrdə dayandır</string>
<string name="enable_queue_limit">Endirmə növbəsini məhdudlaşdırın</string>
<string name="enable_queue_limit_desc">Eyni vaxtda ancaq bir endirmə həyata keçiriləcək</string>
<string name="account_terminated">Hesab ləğv edildi</string>
<string name="service_provides_reason">%s bu səbəbi təmin edir:</string>
<string name="download_has_started">Endirmə başladı</string>
<string name="description_select_disable">ıqlamadakı mətni seçməyi deaktiv edin</string>
<string name="metadata_category">Kateqoriya</string>
<string name="metadata_privacy_internal">Daxili</string>
<string name="description_select_enable">ıqlamadakı mətni seçməyi aktivləşdirin</string>
<string name="metadata_tags">Teqlər</string>
<string name="tablet_mode_title">Planşet rejimi</string>
<string name="off">Bağlayın</string>
<string name="detail_heart_img_view_description">Yaradıcısından ürəkləndi</string>
<string name="open_website_license">Veb saytıın</string>
<plurals name="views">
<item quantity="one">%s baxış</item>
<item quantity="other">%s baxış</item>
</plurals>
<string name="playback_step">Addım</string>
<plurals name="new_streams">
<item quantity="one">%s yeni yayım</item>
<item quantity="other">%s yeni yayım</item>
</plurals>
<string name="playback_reset">Sıfırlayın</string>
<string name="percent">Faiz</string>
<string name="semitone">Yarımton</string>
<plurals name="download_finished_notification">
<item quantity="one">Endirmə tamamlandı</item>
<item quantity="other">%s endirmələr tamamlandı</item>
</plurals>
<string name="progressive_load_interval_exoplayer_default">Defolt ExoPlayer</string>
<string name="feed_use_dedicated_fetch_method_title">Mövcud olduqda xüsusi axından alın</string>
<string name="remove_watched_popup_title">Baxılmış videolar silinsin\?</string>
<string name="remove_watched">İzlənilmişi silin</string>
<string name="downloads_storage_use_saf_title">Sistem qovluğu seçicisini (SAF) istifadə edin</string>
<string name="error_timeout">Bağlantı fasiləsi</string>
<string name="error_insufficient_storage">Cihazda yer qalmayıb</string>
<string name="error_postprocessing_stopped">Fayl üzərində işləyərkən NewPipe bağlandı</string>
<string name="error_postprocessing_failed">Emaldan sonra uğursuz oldu</string>
<string name="error_connect_host">Serverə qoşulmaq mümkün deyil</string>
<string name="error_unknown_host">Serveri tapmaq olmadı</string>
<string name="error_ssl_exception">Təhlükəsiz əlaqə qurmaq olmadı</string>
<string name="error_file_creation">Fayl yaradıla bilməz</string>
<string name="download_already_running">Bu adla bir endirmə davam edir</string>
<string name="overwrite_failed">faylın üzərinə yazıla bilməz</string>
<string name="overwrite_finished_warning">Bu adda endirilmiş fayl artıq mövcuddur</string>
<string name="overwrite">Üzərinə yazın</string>
<string name="enqueue">Növbəyə qoyun</string>
<string name="recovering">bərpa olunur</string>
<string name="paused">dayandırıldı</string>
<string name="missions_header_finished">Bitdi</string>
<string name="app_update_notification_content_text">Endirmək üçün toxunun</string>
<string name="minimize_on_exit_none_description">Heç biri</string>
<string name="minimize_on_exit_summary">Əsas video oynadıcıdan digər tətbiqə keçid zamanı hərəkət — %s</string>
<string name="decline">İmtina</string>
<string name="accept">Razıyam</string>
<string name="playback_tempo">Sürət</string>
<string name="import_title">İdxal</string>
<string name="previous_export">Əvvəlki ixrac</string>
<string name="export_ongoing">İxrac edilir…</string>
<string name="import_ongoing">İdxal edilir…</string>
<string name="playlist_add_stream_success">Pleylistə salındı</string>
<string name="mute">Səsi bağla</string>
<string name="popup_player">Ani pəncərə oynadıcı</string>
<string name="drawer_open">Çəkməcəni Açın</string>
<string name="hold_to_append">Növbəyə saxlamaq üçün basılı tutun</string>
<string name="play_queue_remove">Silin</string>
<string name="app_description">Android\'də pulsuz yüngül yayımlayıcı.</string>
<string name="copyright">© %1$s, %2$s tərəfindən %3$s altında</string>
<string name="toast_no_player">Bu faylı oynatmaq üçün heç bir tətbiq quraşdırılmayıb</string>
<string name="settings_category_downloads_title">Endirmə</string>
<string name="msg_popup_permission">Bu icazə, ani pəncərə rejimində
\naçmaq üçün lazımdır</string>
<string name="msg_copied">Buferə kopyalandı</string>
<string name="msg_threads">Parçalar</string>
<string name="rename">Adını dəyişdir</string>
<string name="create">Yaradın</string>
<plurals name="subscribers">
<item quantity="one">%s abunəçi</item>
<item quantity="other">%s abunəçi</item>
</plurals>
<string name="audio">Səs</string>
<string name="error_details_headline">Təfərrüatlar:</string>
<string name="info_labels">Nə:\\nTələb:\\nMəzmun Dili:\\nMəzmun Ölkəsi:\\nTətbiq Dili:\\nXidmət:\\nGMT Saatı:\\nPaket:\\nVersiya:\\nƏS versiyası:</string>
<string name="error_snackbar_message">Bağışlayın, nəsə xəta baş verdi.</string>
<string name="copy_for_github">Formatlanmış hesabatı kopyalayın</string>
<string name="peertube_instance_add_help">Server URL\'sini daxil edin</string>
<string name="peertube_instance_add_fail">Serveri doğrulamaq mümkün olmadı</string>
<string name="peertube_instance_url_help">%s-də bəyəndiyiniz serverləri tapın</string>
<string name="show_hold_to_append_summary">Video \"Təfsilatlar:\"səhifəsində arxa fon və ya ani pəncərə düyməsini basarkən ipucu göstər</string>
<string name="caption_setting_description">Oynadıcı altyazı miqyasını və arxa fon üslublarını dəyişdirin. Effektiv olması üçün tətbiqin yenidən başladılması tələb olunur</string>
<string name="error_occurred_detail">Xəta baş verdi: %1$s</string>
<string name="invalid_file">Fayl mövcud deyil, yaxud oxumaq və ya yazmaq icazəsi yoxdur</string>
<string name="parsing_error">Veb saytı təhlil etmək alınmadı</string>
<string name="playback_pitch">Səs ucalığı</string>
<string name="radio">Radio</string>
<string name="show_crash_the_player_title">\"Oynadıcını çökdür\" Göstərin</string>
<string name="show_crash_the_player_summary">Oynadıcıdan istifadə edərkən çökdürmə seçimini göstərin</string>
<string name="show_error_snackbar">Xəta balonu göstər</string>
<string name="create_error_notification">Xəta bildirişi yaradın</string>
<string name="import_from">Burdan idxal edin</string>
<string name="export_to">Bura ixrac edin</string>
<string name="import_file_title">Faylı idxal edin</string>
<string name="subscriptions_import_unsuccessful">Abunəlikləri idxal etmək mümkün olmadı</string>
<string name="start_accept_privacy_policy">Avropa Ümumi Məlumat Mühafizəsi Qaydasına (GDPR) riayət etmək üçün diqqətinizi NewPipe\'ın məxfilik siyasətinə cəlb edirik. Zəhmət olmasa, diqqətlə oxuyun. Xəta hesabatını bizə göndərmək üçün onu qəbul etməlisiniz.</string>
<string name="overwrite_unrelated_warning">Bu adda fayl artıq mövcuddur</string>
<string name="download_already_pending">Bu adla gözlənilən bir endirmə var</string>
<string name="error_path_creation">Təyinat qovluğu yaradıla bilməz</string>
<string name="generate_unique_name">Bənzərsiz ad yaradın</string>
<string name="chapters">Bölmələr</string>
<string name="no_app_to_open_intent">Cihazınızdakı heç bir tətbiq bunu aça bilməz</string>
<string name="show_thumbnail_title">Miniatürü göstərin</string>
<string name="soundcloud_go_plus_content">Bu, ən azı sizin ölkənizdə olan SoundCloud Go+ trekidir, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.</string>
<string name="private_content">Bu məzmun şəxsidir, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.</string>
<string name="metadata_support">Dəstək</string>
<string name="metadata_privacy">Məxfilik</string>
<string name="metadata_host">Sahib</string>
<string name="metadata_privacy_unlisted">Siyahıdan kənar</string>
<string name="metadata_privacy_private">Şəxsi</string>
<string name="metadata_thumbnail_url">Miniatür URL</string>
<string name="metadata_age_limit">Yaş həddi</string>
<string name="metadata_language">Dil</string>
<string name="metadata_privacy_public">İctimai</string>
<string name="subscribers_count_not_available">Abunəçi sayı əlçatan deyil</string>
<string name="read_full_license">Lisenziyanı oxuyun</string>
<string name="title_activity_history">Tarixçə</string>
<string name="charset_letters_and_digits">Hərflər və rəqəmlər</string>
<string name="crash_the_player">Oynadıcını çökdür</string>
<string name="peertube_instance_add_https_only">Yalnız HTTPS URL\'ləri dəstəklənir</string>
<string name="settings_category_player_notification_title">Oynadıcı bildirişi</string>
<string name="streams_notification_channel_name">Yeni yayımlar</string>
<string name="error_report_channel_name">Xəta hesabatı bildirişi</string>
<string name="youtube_signature_deobfuscation_error">Video URL\'i imzasının şifrəsi qırılmadı</string>
<string name="no_streams_available_download">Endirmək üçün heç bir yayım yoxdur</string>
<string name="error_report_notification_toast">Xəta baş verdi, bildirişə baxın</string>
<string name="your_comment">Şərhiniz (İngilis dilində):</string>
<string name="detail_thumbnail_view_description">Videonu oynadın, müddət:</string>
<string name="no_available_dir">Zəhmət olmasa, daha sonra tənzimləmələrdə endirmə qovluğunu təyin edin</string>
<string name="msg_running">NewPipe Endirilir</string>
<string name="msg_calculating_hash">Hash hesablanır</string>
<string name="settings_file_charset_title">Fayl adlarında icazə verilən simvollar</string>
<string name="title_activity_about">NewPipe Haqqında</string>
<string name="tab_licenses">Lisenziyalar</string>
<string name="app_license">NewPipe müəllif hüquqlu sərbəst tətbiqdir: Siz onu istədiyiniz zaman istifadə edə, öyrənə, paylaşa və təkmilləşdirə bilərsiniz. Xüsusilə, siz Lisenziyanın 3-cü versiyası və ya (sizin seçiminizə görə) hər hansı sonrakı versiyada Azad Proqram Təminatı Fondu tərəfindən dərc edilən GNU Ümumi İctimai Lisenziyasının şərtlərinə uyğun olaraq onu yenidən paylaya və/yaxud dəyişdirə bilərsiniz.</string>
<string name="export_complete_toast">İxrac edildi</string>
<string name="main_page_content_swipe_remove">Elementləri silmək üçün sürüşdürün</string>
<string name="no_playlist_bookmarked_yet">Hələ,əlfəcinlənmiş pleylistlər yoxdur</string>
<string name="override_current_data">Bu, cari quraşdırmanızı ləğv edəcək.</string>
<string name="enqueue_stream">Növbəyə qoy</string>
<string name="disable_media_tunneling_summary">Qara ekranla qarşılaşsanız və ya videonu oynatdıqda səs qırılarsa, media tunelini deaktiv edin</string>
<string name="enqueued_next">Növbəti sıraya salındı</string>
<string name="start_here_on_background">Arxa fonda oynatmağa başlayın</string>
<string name="loading_stream_details">Yayım təfərrüatları yüklənir…</string>
<string name="disable_media_tunneling_title">Media tunelini deaktiv edin</string>
<string name="crash_the_app">Tətbiq çökdü</string>
<string name="import_youtube_instructions">YouTube abunəliklərini Google takeout\'dan
\nidxal edin:
\n
\n1. Bu URL\'ə keçin: %1$s
\n2. Soruşulduqda daxil olun
\n3.\"Bütün Məlumatlar Daxildir\",sonra \"Heçbirini Seçmə\", yalnız \"abunəliklər\"i seçin və \"OK\" kliklə
\n4. \"Növbəti addım\"üzərinə klikləyin, sonra isə \"İxrac Yarat\" üzərinə klikləyin
\n5. Görünəndən sonra \"Endirin\"düyməsini basın
\n6. Aşağıdakı FAYLI İDXAL ET düyməsinə klikləyin və endirilmiş .zip faylını seçin
\n7. [Əgər .zip faylı idxalı uğursuz olsa] .csv faylını çıxarın(adətən\"YouTubevəYouTubeMusic/subscriptions/subscriptions.csv\" altında),aşağıda İDXAL EDİLƏN FAYL-ı klikləyin və çıxarılmış csv faylını seçin</string>
<string name="playback_speed_control">Oynatma Sürəti Nizamlamaları</string>
<string name="unhook_checkbox">Ayır (pozuntuya səbəb ola bilər)</string>
<string name="show_error">Xətanı göstər</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Bəli və qismən baxılmış videolar</string>
<plurals name="deleted_downloads_toast">
<item quantity="one">%1$s endirməsi silindi</item>
<item quantity="other">%1$s endirmə silindi</item>
</plurals>
<string name="stop">Dayandır</string>
<string name="choose_instance_prompt">Nümunə seçin</string>
<string name="feed_load_error_fast_unknown">Sürətli axın rejimi bu barədə əlavə məlumat vermir.</string>
<string name="new_seek_duration_toast">ExoPlayer məhdudiyyətlərinə görə axtarış müddəti %d saniyəyə təyin edildi</string>
<string name="feed_use_dedicated_fetch_method_summary">Bəzi xidmətlərdə mövcuddur, adətən daha sürətli olur, lakin məhdud sayda elementləri və çox vaxt natamam məlumatı qaytara bilər (məsələn, müddət, element növü, canlı status yoxdur)</string>
<string name="no_appropriate_file_manager_message">Bu əməliyyat üçün uyğun fayl meneceri tapılmadı.
\nZəhmət olmasa, fayl menecerini quraşdırın və ya endirmə tənzimləmələrində \'%s\'-i deaktiv etməyə çalışın.</string>
<string name="feed_load_error_account_info">\'%s\' üçün axın yükləmək mümkün olmadı.</string>
<string name="no_appropriate_file_manager_message_android_10">Bu əməliyyat üçün uyğun fayl meneceri tapılmadı.
\nZəhmət olmasa ,Yaddaş Giriş Çərçivəsinə uyğun fayl menecerini quraşdırın.</string>
<string name="youtube_music_premium_content">Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.</string>
<string name="description_select_note">İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə bilər və keçidlər kliklənməyə bilər.</string>
<string name="notification_scale_to_square_image_summary">Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 nisbətinə qədər ölçün (pozuntulara səbəb ola bilər)</string>
<string name="notification_actions_summary">Aşağıdakı bildiriş fəaliyyətini hər birinin üzərinə toxunaraq redaktə edin. Sağdakı təsdiq qutularından istifadə edərək yığcam bildirişdə göstərilməsi üçün onlardan üçə qədərini seçin</string>
<string name="invalid_source">Belə fayl/məzmun mənbəyi yoxdur</string>
<string name="selected_stream_external_player_not_supported">Seçilmiş yayım xarici oynadıcılar tərəfindən dəstəklənmir</string>
<string name="streams_not_yet_supported_removed">Yükləyici tərəfindən hələ dəstəklənməyən yayımlar göstərilmir</string>
<string name="no_audio_streams_available_for_external_players">Xarici oynadıcılar üçün heç bir səs yayımı yoxdur</string>
<string name="no_video_streams_available_for_external_players">Xarici oynadıcılar üçün heç bir video yayımı yoxdur</string>
<string name="select_quality_external_players">Xarici oynadıcılar üçün keyfiyyət seçin</string>
<string name="unknown_format">Naməlum format</string>
<string name="unknown_quality">Naməlum keyfiyyət</string>
<string name="progressive_load_interval_title">Oynatma yükləmə intervalı həcmi</string>
</resources> </resources>

View file

@ -670,12 +670,9 @@
<string name="no_appropriate_file_manager_message_android_10">找不到适合此操作的文件管理器。 <string name="no_appropriate_file_manager_message_android_10">找不到适合此操作的文件管理器。
\n请安装与存储访问框架(SAF)兼容的文件管理器。</string> \n请安装与存储访问框架(SAF)兼容的文件管理器。</string>
<string name="error_report_notification_title">NewPipe 遇到了一个错误,点击此处报告此错误</string> <string name="error_report_notification_title">NewPipe 遇到了一个错误,点击此处报告此错误</string>
<string name="background_player_already_playing_toast">已经在后台播放</string>
<string name="detail_pinned_comment_view_description">置顶评论</string> <string name="detail_pinned_comment_view_description">置顶评论</string>
<string name="leak_canary_not_available">LeakCanary 不可用</string> <string name="leak_canary_not_available">LeakCanary 不可用</string>
<string name="adjust_by_semitones_checkbox">以音乐半音调整音高</string> <string name="progressive_load_interval_summary">更改加载间隔的大小(当前为 %s较低的值可以加快视频的首次加载速度。更改需要重启播放器。</string>
<string name="playback_tempo_step">节奏步长</string>
<string name="progressive_load_interval_summary">改变加载间隔的大小(当前%s较低的值可以加快初始的视频加载速度改变需要重启播放器。</string>
<string name="progressive_load_interval_exoplayer_default">ExoPlayer 默认</string> <string name="progressive_load_interval_exoplayer_default">ExoPlayer 默认</string>
<string name="settings_category_player_notification_summary">配置当前正在播放的串流的通知</string> <string name="settings_category_player_notification_summary">配置当前正在播放的串流的通知</string>
<string name="enable_streams_notifications_title">新串流通知</string> <string name="enable_streams_notifications_title">新串流通知</string>
@ -699,4 +696,13 @@
<string name="delete_downloaded_files_confirm">清除所有下载的文件?</string> <string name="delete_downloaded_files_confirm">清除所有下载的文件?</string>
<string name="get_notified">获取通知</string> <string name="get_notified">获取通知</string>
<string name="enable_streams_notifications_summary">来自订阅的新串流的通知</string> <string name="enable_streams_notifications_summary">来自订阅的新串流的通知</string>
<string name="semitone">半音</string>
<string name="percent">百分比</string>
<string name="unknown_format">未知格式</string>
<string name="no_audio_streams_available_for_external_players">没有音频流可用于外部播放器</string>
<string name="select_quality_external_players">选择外部播放器画质</string>
<string name="selected_stream_external_player_not_supported">外部播放器不支持所选串流</string>
<string name="no_video_streams_available_for_external_players">没有视频流可用于外部播放器</string>
<string name="streams_not_yet_supported_removed">不显示下载器尚不支持的串流</string>
<string name="unknown_quality">未知画质</string>
</resources> </resources>

View file

@ -1,45 +1,45 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="main_bg_subtitle">অনুসন্ধান এ চাপ দিয়ে শুরু করুন</string> <string name="main_bg_subtitle">শুরু করতে আতস কাঁচটিতে টাচ করুন।</string>
<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>
<string name="open_in_popup_mode">পপ-আপ মোডে ওপেন করো</string> <string name="open_in_popup_mode">পপ-আপ মোডে ওপেন করো</string>
<string name="share">শেয়ার</string> <string name="share">শেয়ার</string>
<string name="download">ডাউনলো</string> <string name="download">ডাউনলোড</string>
<string name="search">খুঁজুন</string> <string name="search">খুঁজুন</string>
<string name="settings">সেটিংস</string> <string name="settings">সেটিংস</string>
<string name="did_you_mean">আপনি কি বুঝিয়েছেনঃ %1$s\?</string> <string name="did_you_mean">আপনি কি %1$s বুঝিয়েছেন\?</string>
<string name="share_dialog_title">শেয়ার করুন</string> <string name="share_dialog_title">শেয়ার করুন</string>
<string name="use_external_video_player_title">বাইরের ভিডিও প্লেয়ার ব্যবহার করুন</string> <string name="use_external_video_player_title">বাইরের ভিডিও প্লেয়ার ব্যবহার করুন</string>
<string name="use_external_audio_player_title">বহির্গত অডিও প্লেয়ার ব্যবহার করুন</string> <string name="use_external_audio_player_title">অন্য অডিও প্লেয়ার ব্যবহার করুন</string>
<string name="controls_background_title">ব্যাকগ্রাউন্ড</string> <string name="controls_background_title">ব্যাকগ্রাউন্ড</string>
<string name="controls_popup_title">পপআপ</string> <string name="controls_popup_title">পপআপ</string>
<string name="download_path_title">ভিডিও ডাউনলোড করার ফোল্ডার</string> <string name="download_path_title">ভিডিও ডাউনলোড ফোল্ডার</string>
<string name="download_path_summary">ডাউনলোড করা ভিডিওগুলো এখানে থাকে</string> <string name="download_path_summary">ডাউনলোড করা ভিডিওগুলো এখানে থাকে</string>
<string name="download_path_dialog_title">ভিডিওগুলির জন্য ডাউনলোডের পাথ নির্বাচন কর</string> <string name="download_path_dialog_title">ভিডিও ফাইলের জন্য ডাউনলোড ফোল্ডার বাছুন</string>
<string name="download_path_audio_title">অডিও ডাউনলোড ফোল্ডার</string> <string name="download_path_audio_title">অডিও ডাউনলোড ফোল্ডার</string>
<string name="download_path_audio_summary">ডাউনলোড করা অডিও এখানে রাখা হয়</string> <string name="download_path_audio_summary">ডাউনলোড করা অডিও ফাইল এখানে জমা হয়</string>
<string name="download_path_audio_dialog_title">অডিও ফাইলগুলির জন্য ডাউনলোডের ফোল্ডার নির্বাচন করুন</string> <string name="download_path_audio_dialog_title">অডিও ফাইলের জন্য ডাউনলোড ফোল্ডার বাছুন</string>
<string name="default_resolution_title">ডিফল্ট রেজোল্যুশন</string> <string name="default_resolution_title">ডিফল্ট রেজ্যুলেশন</string>
<string name="default_popup_resolution_title">ডিফল্ট পপআপ রেজোল্যুশন</string> <string name="default_popup_resolution_title">ডিফল্ট পপআপ রেজ্যুলেশন</string>
<string name="show_higher_resolutions_title">উচ্চ রেজোল্যুশন দেখাও</string> <string name="show_higher_resolutions_title">উচ্চ রেজ্যুলেশন দেখাও</string>
<string name="show_higher_resolutions_summary">শুধুমাত্র কিছু ডিভাইস 2K/4K ভিডিও চালাতে পারে</string> <string name="show_higher_resolutions_summary">শুধু কিছু ডিভাইস 2K/4K ভিডিও চালাতে পারে</string>
<string name="play_with_kodi_title">Kodi এর মাধ্যমে চালাও</string> <string name="play_with_kodi_title">Kodi এর মাধ্যমে চালাও</string>
<string name="kore_not_found">হারানো কোর ইনস্টল করবেন\?</string> <string name="kore_not_found">হারানো কোর ইনস্টল করবেন\?</string>
<string name="show_play_with_kodi_title">দেখাও \"Kodi এর মাধ্যমে চালাও \" বিকল্প</string> <string name="show_play_with_kodi_title">\"Kodi দিয়ে চালাও\" অপশন দেখাও</string>
<string name="show_play_with_kodi_summary">Kodi মিডিয়া সেন্টারে এর মাধ্যমে ভিডিও প্লে করার জন্য একটি বিকল্প প্রদর্শন কর</string> <string name="show_play_with_kodi_summary">Kodi মিডিয়া সেন্টারে এর মাধ্যমে ভিডিও প্লে করার জন্য একটি বিকল্প দেখাও</string>
<string name="play_audio">অডিও</string> <string name="play_audio">অডিও</string>
<string name="default_audio_format_title">ডিফল্ট অডিও ফরম্যাট</string> <string name="default_audio_format_title">ডিফল্ট অডিও ফরম্যাট</string>
<string name="default_video_format_title">পছন্দসই ভিডিও ফরম্যাট</string> <string name="default_video_format_title">ডিফল্ট ভিডিও ফরম্যাট</string>
<string name="theme_title">থিম</string> <string name="theme_title">থিম</string>
<string name="light_theme_title">উজ্জ্বল</string> <string name="light_theme_title">উজ্জ্বল</string>
<string name="dark_theme_title">অন্ধকার</string> <string name="dark_theme_title">অন্ধকার</string>
<string name="black_theme_title">কালো</string> <string name="black_theme_title">কালো</string>
<string name="popup_remember_size_pos_title">পপআপ আকার এবং অবস্থান মনে রাখো</string> <string name="popup_remember_size_pos_title">পপআপ আকার এবং অবস্থান মনে রাখো</string>
<string name="popup_remember_size_pos_summary">শেষ আকার এবং পপআপ সেট অবস্থান মনে রাখো</string> <string name="popup_remember_size_pos_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="unsupported_url">URL সমর্থিত নয়</string> <string name="unsupported_url">URL সমর্থিত নয়</string>
@ -49,7 +49,7 @@
<string name="background_player_playing_toast">ব্যাকগ্রাউন্ডে চলছে</string> <string name="background_player_playing_toast">ব্যাকগ্রাউন্ডে চলছে</string>
<string name="popup_playing_toast">পপআপ মোডে চলছে</string> <string name="popup_playing_toast">পপআপ মোডে চলছে</string>
<string name="content">কন্টেন্ট</string> <string name="content">কন্টেন্ট</string>
<string name="show_age_restricted_content_title">য়স সীমাবদ্ধ কন্টেন্ট দেখাও</string> <string name="show_age_restricted_content_title">য়সের অনুপযোগী কন্টেন্ট দেখাও</string>
<string name="duration_live">লাইভ</string> <string name="duration_live">লাইভ</string>
<string name="downloads">ডাউনলোডগুলি</string> <string name="downloads">ডাউনলোডগুলি</string>
<string name="downloads_title">ডাউনলোডগুলি</string> <string name="downloads_title">ডাউনলোডগুলি</string>
@ -76,16 +76,16 @@
<string name="your_comment">তোমার মন্তব্য (ইংরেজিতে):</string> <string name="your_comment">তোমার মন্তব্য (ইংরেজিতে):</string>
<string name="error_details_headline">বর্ণনা:</string> <string name="error_details_headline">বর্ণনা:</string>
<!-- Content descriptions (for better accessibility) --> <!-- Content descriptions (for better accessibility) -->
<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="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">K</string> <string name="short_thousand">হা</string>
<string name="short_million">M</string> <string name="short_million">M</string>
<string name="short_billion">B</string> <string name="short_billion">বি</string>
<!-- Missions --> <!-- Missions -->
<string name="start">শুরু</string> <string name="start">শুরু</string>
<string name="pause">বিরতি</string> <string name="pause">বিরতি</string>
@ -101,18 +101,19 @@
<string name="msg_running_detail">বিস্তারিত জানার জন্য আলতো চাপ</string> <string name="msg_running_detail">বিস্তারিত জানার জন্য আলতো চাপ</string>
<string name="msg_wait">অনুগ্রহপূর্বক অপেক্ষা করো…</string> <string name="msg_wait">অনুগ্রহপূর্বক অপেক্ষা করো…</string>
<string name="msg_copied">ক্লিপবোর্ডে অনুলিপি করা হয়েছে</string> <string name="msg_copied">ক্লিপবোর্ডে অনুলিপি করা হয়েছে</string>
<string name="no_available_dir">অনুগ্রহ করে একটি উপলব্ধ ডাউনলোড ডিরেক্টরি নির্বাচন করো।</string> <string name="no_available_dir">পরে সেটিংস থেকে একটি ডাউনলোড ফোল্ডার নির্ধারণ করে দিন</string>
<string name="msg_popup_permission">এই অনুমতিটি পপআপ মোডে খুলতে প্রয়োজন</string> <string name="msg_popup_permission">পপআপ মোডে চালু হতে
\nএই অনুমতির প্রয়োজন আছে</string>
<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>
<!-- End of GigaGet's Strings --> <!-- End of GigaGet's Strings -->
<string name="info_labels">কি:\\nঅনুরোধ:\\nকন্টেন্ট ভাষা:\\nসার্ভিস:\\nসময়(GMT এ):\\nপ্যাকেজ:\\nসংস্করণ:\\nওএস সংস্করণ:\\nআইপি পরিসর:</string> <string name="info_labels">কি:\\nঅনুরোধ:\\nকন্টেন্ট ভাষা:\\nসার্ভিস:\\nসময়(GMT এ):\\nপ্যাকেজ:\\nসংস্করণ:\\nওএস সংস্করণ:\\nআইপি পরিসর:</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="fragment_feed_title">নতুন যা কিছু</string> <string name="fragment_feed_title">নতুন যা কিছু</string>
<string name="controls_add_to_playlist_title">যুক্ত করুন</string> <string name="controls_add_to_playlist_title">যুক্ত করুন</string>
<string name="enable_search_history_title">খোজ ইতিহাস</string> <string name="enable_search_history_title">অনুসন্ধানের ইতিহাস</string>
<string name="enable_watch_history_title">ইতিহাস</string> <string name="enable_watch_history_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>
<string name="settings_category_history_title">ইতিহাস</string> <string name="settings_category_history_title">ইতিহাস</string>
@ -123,13 +124,13 @@
<string name="no_views">কোন ভিউ নেই</string> <string name="no_views">কোন ভিউ নেই</string>
<string name="rename">নাম পরিবর্তন করুন</string> <string name="rename">নাম পরিবর্তন করুন</string>
<string name="website_title">ওয়েবসাইট</string> <string name="website_title">ওয়েবসাইট</string>
<string name="no_player_found_toast">কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি (প্লে করতে VLC ইন্সটল করতে পারেন)</string> <string name="no_player_found_toast">কোন স্ট্রিম প্লেয়ার নেই (প্লে করতে VLC ইন্সটল করতে পারেন)</string>
<string name="use_external_video_player_summary">কিছু কিছু রেজোলিউশনে অডিও বন্ধ করে দেয়</string> <string name="use_external_video_player_summary">কিছু কিছু রেজ্যুলেশনে অডিও বন্ধ করে দেয়</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>
<string name="channel_unsubscribed">চ্যানেল থেকে আনসাবস্ক্রাইব্ড</string> <string name="channel_unsubscribed">চ্যানেল থেকে আনসাবস্ক্রাইব্ড</string>
<string name="subscription_change_failed">সাবস্ক্রিপশন পরিবর্তন করা যায়নি</string> <string name="subscription_change_failed">সাবস্ক্রিপশন পরিবর্তন করা যায়নি</string>
<string name="subscription_update_failed">সাবস্ক্রিপশন আপডেটে ব্যার্থ</string> <string name="subscription_update_failed">সাবস্ক্রিপশন আপডেট করা যায়নি</string>
<string name="tab_subscriptions">সাবস্ক্রিপশন</string> <string name="tab_subscriptions">সাবস্ক্রিপশন</string>
<string name="tab_bookmarks">বুকমার্ককৃত প্লেলিস্টসমূহ</string> <string name="tab_bookmarks">বুকমার্ককৃত প্লেলিস্টসমূহ</string>
<string name="use_inexact_seek_title">দ্রুত টানা ব্যাবহার করুন</string> <string name="use_inexact_seek_title">দ্রুত টানা ব্যাবহার করুন</string>
@ -137,28 +138,28 @@
<string name="tab_choose">ট্যাব পছন্দ করুন</string> <string name="tab_choose">ট্যাব পছন্দ করুন</string>
<string name="use_inexact_seek_summary">অনির্দিষ্ট সন্ধান প্লেয়ারকে আরো দ্রুত গতিতে সন্ধান করার সুবিধা দেয়, কিন্তু এটি সম্পূর্ণ নির্ভুল নাও হতে পারে ৷ ৫, ১৫ ও ২৫ সেকেন্ডের জন্য এটা কাজ করবে না ৷</string> <string name="use_inexact_seek_summary">অনির্দিষ্ট সন্ধান প্লেয়ারকে আরো দ্রুত গতিতে সন্ধান করার সুবিধা দেয়, কিন্তু এটি সম্পূর্ণ নির্ভুল নাও হতে পারে ৷ ৫, ১৫ ও ২৫ সেকেন্ডের জন্য এটা কাজ করবে না ৷</string>
<string name="seek_duration_title">দ্রুত-ফরওয়ার্ড/-পুনরায় সন্ধান সময়কাল</string> <string name="seek_duration_title">দ্রুত-ফরওয়ার্ড/-পুনরায় সন্ধান সময়কাল</string>
<string name="show_comments_summary">তামত প্রদর্শন বন্ধ করতে অপশনটি বন্ধ করুন</string> <string name="show_comments_summary">ন্তব্যসমূহ লুকাতে বন্ধ করুন</string>
<string name="show_comments_title">তামত প্রদর্শন করুন</string> <string name="show_comments_title">ন্তব্যসমূহ দেখাও</string>
<string name="download_thumbnail_title">থাম্বনেইল লোড করুন</string> <string name="download_thumbnail_title">থাম্বনেইল লোড কর</string>
<string name="download_thumbnail_summary">থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি‌ পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে।</string> <string name="download_thumbnail_summary">থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি‌ পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে।</string>
<string name="thumbnail_cache_wipe_complete_notice">ছবির ক্যাশ মুছে ফেলা হয়েছে</string> <string name="thumbnail_cache_wipe_complete_notice">ছবির ক্যাশ মোছা হয়েছে</string>
<string name="metadata_cache_wipe_summary">সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো</string> <string name="metadata_cache_wipe_summary">সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো</string>
<string name="metadata_cache_wipe_title">ক্যাশ করা মেটাডেটা মুছ</string> <string name="metadata_cache_wipe_title">ক্যাশ করা মেটাডেটা মো</string>
<string name="metadata_cache_wipe_complete_notice">মেটাডেটা ক্যাশ মুছে ফেলা হয়েছে</string> <string name="metadata_cache_wipe_complete_notice">মেটাডেটা ক্যাশ মোছা হয়েছে</string>
<string name="auto_queue_title">পরবর্তী স্ট্রিম স্বয়ংক্রিয়ংভাবে সংযোজন করুন</string> <string name="auto_queue_title">পরবর্তী স্ট্রিম স্বয়ংক্রিয়ংভাবে সংযোজন কর</string>
<string name="brightness_gesture_control_summary">প্লেয়ারের উজ্জ্বলতা নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো</string> <string name="brightness_gesture_control_summary">প্লেয়ারের উজ্জ্বলতা নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো</string>
<string name="brightness_gesture_control_title">উজ্জ্বলতার নিয়ন্ত্রণ সংকেত</string> <string name="brightness_gesture_control_title">উজ্জ্বলতার নিয়ন্ত্রণ সংকেত</string>
<string name="volume_gesture_control_summary">প্লেয়ারের ভলিউম নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো</string> <string name="volume_gesture_control_summary">প্লেয়ারের ভলিউম নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো</string>
<string name="volume_gesture_control_title">ভলিউম সংকেত নিয়ন্ত্রণ</string> <string name="volume_gesture_control_title">ভলিউম সংকেত নিয়ন্ত্রণ</string>
<string name="missions_header_finished">সম্পূর্ণ</string> <string name="missions_header_finished">সম্পন্ন</string>
<string name="list">তালিকা</string> <string name="list">তালিকা</string>
<string name="enable_playback_state_lists_title">তালিকাতে পজিশন</string> <string name="enable_playback_state_lists_title">তালিকা আকারে সাজাও</string>
<string name="enable_playback_resume_summary">শেষ প্লেব্যাক পজিশন এ যাও</string> <string name="enable_playback_resume_summary">শেষ প্লেব্যাক পজিশন এ যাও</string>
<string name="enable_playback_resume_title">পুনরায় প্লে ব্যাক চালু করো</string> <string name="enable_playback_resume_title">পুনরায় প্লে ব্যাক চালু করো</string>
<string name="enable_search_history_summary">সার্চগুলো স্থানীয়ভাবে জমা করো</string> <string name="enable_search_history_summary">অনুসন্ধানের বিষয়বস্তু স্থানীয়ভাবে জমা করো</string>
<string name="show_search_suggestions_summary">সার্চের সময় পরামর্শ দেখাও</string> <string name="show_search_suggestions_summary">অনুসন্ধানকালীন কী পরামর্শ দেওয়া হবে তা বাছুন</string>
<string name="show_search_suggestions_title">সার্চ পরামর্শ</string> <string name="show_search_suggestions_title">অনুসন্ধানের পরামর্শ</string>
<string name="search_showing_result_for">রেজাল্ট দেখানো হচ্ছেঃ %s</string> <string name="search_showing_result_for">ফলাফল দেখাচ্ছে: %s এর জন্যে</string>
<string name="tab_about">সম্পর্কিত</string> <string name="tab_about">সম্পর্কিত</string>
<string name="title_activity_about">নিউপাইপ এর সম্বন্ধে</string> <string name="title_activity_about">নিউপাইপ এর সম্বন্ধে</string>
<string name="trending">ট্রেন্ডিং</string> <string name="trending">ট্রেন্ডিং</string>
@ -175,11 +176,11 @@
<string name="error_http_not_found">পাওয়া যায় নি</string> <string name="error_http_not_found">পাওয়া যায় নি</string>
<string name="error_unknown_host">সার্ভার পাওয়া যায় নি</string> <string name="error_unknown_host">সার্ভার পাওয়া যায় নি</string>
<string name="show_error">এরর দেখান</string> <string name="show_error">এরর দেখান</string>
<string name="download_failed">ডাউন লোড হয় নি</string> <string name="download_failed">ডাউনলোড ব্যর্থ হয়েছে</string>
<string name="paused">পজ হয়েছে</string> <string name="paused">স্থগিত</string>
<string name="app_update_notification_content_text">ডাউন লোড করার জন্য চাপ দিন</string> <string name="app_update_notification_content_text">ডাউনলোড করতে টোকা দিন</string>
<string name="auto">অটো</string> <string name="auto">অটো</string>
<string name="limit_data_usage_none_description">কোন সীমা নেই</string> <string name="limit_data_usage_none_description">সীমাহীন</string>
<string name="caption_none">কোন ক্যাপশন নেই</string> <string name="caption_none">কোন ক্যাপশন নেই</string>
<string name="playlist_creation_success">প্লে লিস্ট তৈরি হয়েছে</string> <string name="playlist_creation_success">প্লে লিস্ট তৈরি হয়েছে</string>
<string name="delete_playlist_prompt">প্লে লিস্ট ডিলিট করতে চান\?</string> <string name="delete_playlist_prompt">প্লে লিস্ট ডিলিট করতে চান\?</string>
@ -231,15 +232,15 @@
<string name="player_stream_failure">স্ট্রিম টি চালানো গেল না</string> <string name="player_stream_failure">স্ট্রিম টি চালানো গেল না</string>
<string name="download_to_sdcard_error_title">বাহ্যিক স্টোরেজ নেই</string> <string name="download_to_sdcard_error_title">বাহ্যিক স্টোরেজ নেই</string>
<string name="help">সাহায্য</string> <string name="help">সাহায্য</string>
<string name="search_history_deleted">সার্চ ইতিহাস ডিলিট হয়েছে।</string> <string name="search_history_deleted">অনুসন্ধান ইতিহাস মোছা হয়েছে</string>
<string name="delete_search_history_alert">সমগ্র সার্চ ইতিহাস মুছবেন\?</string> <string name="delete_search_history_alert">সমগ্র সার্চ ইতিহাস মুছবেন\?</string>
<string name="clear_search_history_summary">সার্চের ইতিহাস মোছা হয়</string> <string name="clear_search_history_summary">সার্চের ইতিহাস মোছা হয়</string>
<string name="clear_search_history_title">সার্চ ইতিহাস মুছুন</string> <string name="clear_search_history_title">সার্চ ইতিহাস মুছুন</string>
<string name="watch_history_states_deleted">প্লে ব্যাক এর অবস্থান মোছা হয়েছে</string> <string name="watch_history_states_deleted">প্লেব্যাক অবস্থানসমূহ মোছা হয়েছে</string>
<string name="delete_playback_states_alert">সমস্ত প্লে লিস্ট এর অবস্থান মুছবেন\?</string> <string name="delete_playback_states_alert">সমস্ত প্লে লিস্ট এর অবস্থান মুছবেন\?</string>
<string name="clear_playback_states_summary">সমস্ত প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string> <string name="clear_playback_states_summary">সমস্ত প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string>
<string name="clear_playback_states_title">প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string> <string name="clear_playback_states_title">প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string>
<string name="watch_history_deleted">দেখার ইতিহাস মুছে গেছে।</string> <string name="watch_history_deleted">দেখা ভিডিওর ইতিহাস মোছা হয়েছে</string>
<string name="delete_view_history_alert">সম্পূর্ণ দেখার ইতিহাস মুছে ফেলুন\?</string> <string name="delete_view_history_alert">সম্পূর্ণ দেখার ইতিহাস মুছে ফেলুন\?</string>
<string name="clear_views_history_title">দেখার ইতিহাস মুছে ফেলুন</string> <string name="clear_views_history_title">দেখার ইতিহাস মুছে ফেলুন</string>
<string name="export_data_title">ডাটা বেস এক্সপোর্ট করুন</string> <string name="export_data_title">ডাটা বেস এক্সপোর্ট করুন</string>
@ -247,9 +248,9 @@
<string name="switch_to_main">মেন এ ফিরে যান</string> <string name="switch_to_main">মেন এ ফিরে যান</string>
<string name="switch_to_popup">পপ-আপ এ খুলুন</string> <string name="switch_to_popup">পপ-আপ এ খুলুন</string>
<string name="switch_to_background">পেছনে নিয়ে যান</string> <string name="switch_to_background">পেছনে নিয়ে যান</string>
<string name="app_update_notification_channel_description">িউ পাইপ এর নতুন ভার্সন এর সূচনা</string> <string name="app_update_notification_channel_description">তুন নিউপাইপ ভার্সন এর নোটিফিকেশন</string>
<string name="app_update_notification_channel_name">অ্যাপ আপডেট এর সূচনা</string> <string name="app_update_notification_channel_name">অ্যাপ আপডেট নোটিফিকেশন</string>
<string name="notification_channel_name">নিউ পাইপ এর সূচনা</string> <string name="notification_channel_name">নিউ পাইপ নোটিফিকেশন</string>
<string name="play_all">সব চালু করুন</string> <string name="play_all">সব চালু করুন</string>
<string name="file_deleted">ফাইল ডিলিট হয়েছে</string> <string name="file_deleted">ফাইল ডিলিট হয়েছে</string>
<string name="best_resolution">সেরা রেজুলিউসন</string> <string name="best_resolution">সেরা রেজুলিউসন</string>
@ -257,7 +258,7 @@
<string name="albums">অ্যালবাম গুলি</string> <string name="albums">অ্যালবাম গুলি</string>
<string name="songs">গান গুলি</string> <string name="songs">গান গুলি</string>
<string name="videos_string">ভিডিও গুলি</string> <string name="videos_string">ভিডিও গুলি</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">শুধুমাত্র HTTPS URL গুলি সাপোর্ট করে</string> <string name="peertube_instance_add_https_only">শুধুমাত্র HTTPS URL গুলি সাপোর্ট করে</string>
<string name="peertube_instance_add_help">ইন্সটান্স এর ইউ আর এল</string> <string name="peertube_instance_add_help">ইন্সটান্স এর ইউ আর এল</string>
<string name="peertube_instance_add_title">ইন্সটান্স যোগ করুন</string> <string name="peertube_instance_add_title">ইন্সটান্স যোগ করুন</string>
@ -295,14 +296,14 @@
<string name="dismiss">সরাও</string> <string name="dismiss">সরাও</string>
<string name="create">তৈরি করো</string> <string name="create">তৈরি করো</string>
<string name="undo">ফিরে যাও</string> <string name="undo">ফিরে যাও</string>
<string name="autoplay_title">স্বয়ংক্রিয়</string> <string name="autoplay_title">স্বয়ংক্রিয় প্লে</string>
<string name="resume_on_audio_focus_gain_title">পুনরায় চালু করো</string> <string name="resume_on_audio_focus_gain_title">পুনরায় চালু করো</string>
<string name="enable_watch_history_summary">দেখা ভিডিওগুলোর হিসেব</string> <string name="enable_watch_history_summary">দেখা ভিডিওগুলোর হিসেব</string>
<string name="settings_category_clear_data_title">ডাটা মুছে ফেলুন</string> <string name="settings_category_clear_data_title">ডাটা মুছে ফেল</string>
<string name="notification_action_nothing">কিছুই না</string> <string name="notification_action_nothing">কিছুই না</string>
<string name="notification_action_repeat">পুনরায়</string> <string name="notification_action_repeat">পুনরায়</string>
<string name="notification_action_0_title">প্রথম ক্রিয়া বোতাম</string> <string name="notification_action_0_title">প্রথম ক্রিয়া বোতাম</string>
<string name="notification_scale_to_square_image_title">থাম্বনেল ১:১ অনুপাতে করো</string> <string name="notification_scale_to_square_image_title">থাম্বনেল ১:১ অনুপাতে সেট করো</string>
<string name="systems_language">সিস্টেম ডিফল্ট</string> <string name="systems_language">সিস্টেম ডিফল্ট</string>
<string name="toast_no_player">এ ফাইলটি চালানোর জন্য কোন অ্যাপ ইন্সটলকৃত নেই</string> <string name="toast_no_player">এ ফাইলটি চালানোর জন্য কোন অ্যাপ ইন্সটলকৃত নেই</string>
<string name="bookmark_playlist">প্লেলিস্ট বুকমার্ক করুন</string> <string name="bookmark_playlist">প্লেলিস্ট বুকমার্ক করুন</string>
@ -351,4 +352,111 @@
<string name="metadata_language">ভাষা</string> <string name="metadata_language">ভাষা</string>
<string name="metadata_age_limit">বয়স সীমা</string> <string name="metadata_age_limit">বয়স সীমা</string>
<string name="main_page_content">প্রধান পাতার উপাদান সমূহ</string> <string name="main_page_content">প্রধান পাতার উপাদান সমূহ</string>
<string name="no_one_listening">কেউ শুনছে না</string>
<string name="start_main_player_fullscreen_title">মূল প্লেয়ার পুরো পর্দাজুড়ে চালাও</string>
<string name="description_tab_description">বর্ণনা</string>
<string name="settings_category_player_notification_title">প্লেয়ার নোটিফিকেশন</string>
<string name="youtube_restricted_mode_enabled_summary">ইউটিউব একটি \"সীমিত ধরন\" সরবরাহ করে যেটি সম্ভাব্য প্রাপ্তবয়স্ক কন্টেন্ট লুকিয়ে রাখে</string>
<string name="hash_channel_name">ভিডিও হ্যাশ নোটিফিকেশন</string>
<string name="streams_notification_channel_name">নতুন স্ট্রিমসমূহ</string>
<string name="recaptcha_cookies_cleared">রিক্যাপচা কুকিজ মোছা হয়েছে</string>
<string name="permission_display_over_apps">অন্যান্য অ্যাপের উপরে হাজির হতে অনুমতি দিন</string>
<string name="error_report_notification_toast">সমস্যা হয়েছে, নোটিফিকেশন দেখুন</string>
<string name="copy_for_github">তৈরিকৃত রিপোর্ট কপি করো</string>
<string name="related_items_tab_description">সম্পর্কিত আইটেমসমূহ</string>
<string name="empty_subscription_feed_subtitle">ঝিঁঝিঁপোকা ছাড়া এখানে কিছু নেই</string>
<string name="detail_drag_description">পুনর্বিন্যাস্ত করতে টান দিন</string>
<string name="subscribers_count_not_available">গ্রাহকসংখ্যা সুলভ নয়</string>
<string name="no_one_watching">কেউ দেখছে না</string>
<plurals name="videos">
<item quantity="one">%s ভিডিও</item>
<item quantity="other">%s ভিডিও</item>
</plurals>
<plurals name="listening">
<item quantity="one">%s জন শ্রোতা</item>
<item quantity="other">%s জন শ্রোতা</item>
</plurals>
<string name="infinite_videos">∞ ভিডিও</string>
<string name="recaptcha_solve">সমাধান করো</string>
<string name="enable_streams_notifications_title">নতুন স্ট্রিমের নোটিফিকেশন</string>
<string name="any_network">যেকোন নেটওয়ার্ক</string>
<string name="updates_setting_title">আপডেটসমূহ</string>
<string name="wifi_only">শুধু ওয়াই-ফাই তে</string>
<string name="never">কখনো না</string>
<string name="missions_header_pending">অপেক্ষমাণ</string>
<string name="checking_updates_toast">আপডেট চেক করা হচ্ছে …</string>
<string name="enumeration_comma">,</string>
<string name="night_theme_title">রাত্রিকালীন থিম</string>
<string name="show_description_title">বর্ণনা দেখাও</string>
<string name="notification_action_1_title">দ্বিতীয় পদক্ষেপ বোতাম</string>
<string name="notification_action_4_title">পঞ্চম পদক্ষেপ বোতাম</string>
<string name="show_description_summary">ভিডিও বর্ণনা ও বাড়তি তথ্য লুকাতে বন্ধ করুন</string>
<string name="dont_show">দেখিও না</string>
<string name="app_update_notification_content_title">নিউ পাইপ আপডেট এসেছে!</string>
<string name="comments_are_disabled">মন্তব্যসমূহ নিষ্ক্রিয় আছে</string>
<plurals name="views">
<item quantity="one">%s বার দেখেছে</item>
<item quantity="other">%s বার দেখেছে</item>
</plurals>
<string name="overwrite">ওভাররাইট</string>
<string name="notification_action_2_title">তৃতীয় পদক্ষেপ বোতাম</string>
<string name="local_search_suggestions">স্থানীয় অনুসন্ধানের পরামর্শ</string>
<string name="limit_mobile_data_usage_title">মোবাইল ডেটা ব্যবহারের সময় রেজুলেশন সীমিত করো</string>
<string name="queued">অপেক্ষমাণ</string>
<string name="clear_queue_confirmation_summary">এক প্লেয়ার বদলে অন্য প্লেয়ারে যাওয়া আপনার অপেক্ষমাণ সারিকে প্রতিস্থাপন করতে পারে</string>
<string name="clear_queue_confirmation_description">সক্রিয় প্লেয়ারের অপেক্ষমাণ সারি প্রতিস্থাপিত হবে</string>
<string name="resume_on_audio_focus_gain_summary">বিঘ্ন ঘটার পরে চালানো অব্যাহত রাখো (যেমন. ফোনকল)</string>
<string name="enable_playback_state_lists_summary">প্লেব্যাক অবস্থানের চিহ্নসমূহ তালিকায় দেখাও</string>
<string name="unsupported_url_dialog_message">URL টি বোঝা যায়নি। অন্য অ্যাপ দিয়ে খুলবো\?</string>
<string name="default_content_country_title">কনটেন্টের জন্য পূর্বনির্ধারিত দেশ</string>
<string name="external_player_unsupported_link_type">বাইরের প্লেয়ারসমূহ এ ধরনের লিঙ্কসমূহ সমর্থন করে না</string>
<string name="msg_calculating_hash">হ্যাশ হিসাব করা হচ্ছে</string>
<string name="manual_update_title">আপডেট চেক করো</string>
<plurals name="watching">
<item quantity="one">%s জন দেখছে</item>
<item quantity="other">%s জন দেখছে</item>
</plurals>
<plurals name="new_streams">
<item quantity="one">%s টি নতুন স্ট্রিম</item>
<item quantity="other">%s টি নতুন স্ট্রিম</item>
</plurals>
<plurals name="download_finished_notification">
<item quantity="one">ডাউনলোড সম্পন্ন</item>
<item quantity="other">%s ডাউনলোড হয়েছে</item>
</plurals>
<string name="more_than_100_videos">১০০+ ভিডিও</string>
<string name="users">ব্যবহারকারীগণ</string>
<plurals name="subscribers">
<item quantity="one">%s জন গ্রাহক</item>
<item quantity="other">%s জন গ্রাহক</item>
</plurals>
<string name="show_meta_info_title">আনুষঙ্গিক তথ্য দেখাও</string>
<string name="streams_notifications_network_title">নেটওয়ার্ক সংযোগ দরকার</string>
<string name="high_quality_larger">উচ্চ মান (বড় আকারের)</string>
<string name="low_quality_smaller">নিম্ন মান (ছোট আকার)</string>
<string name="notification_action_3_title">চতুর্থ পদক্ষেপ বোতাম</string>
<string name="notification_action_shuffle">এলোমেলো</string>
<string name="notification_action_buffering">বাফারিং</string>
<string name="notification_colorize_title">নোটিফিকেশন বর্ণিল করো</string>
<string name="restricted_video">এই ভিডিও বয়স দ্বারা সীমিত।
\n
\nএটি দেখতে চাইলে সেটিংস থেকে \"%1$s\" চালু করুন।</string>
<string name="toggle_all">সব টগল করো</string>
<string name="you_successfully_subscribed">আপনি এখন এই চ্যানেলের গ্রাহক</string>
<string name="clear_queue_confirmation_title">অপেক্ষমাণ সারি বাদ দেওয়ার আগে নিশ্চিত হতে জিজ্ঞেস করো</string>
<string name="show_age_restricted_content_summary">শিশুদের জন্য বয়সসীমার কারণে অনুপযোগী হতে পারে এমন কন্টেন্ট দেখাও (যেমন ১৮+)</string>
<string name="clear_views_history_summary">প্লে করা স্ট্রিমসমূহের ইতিহাস এবং প্লেব্যাক অবস্থানসমূহ মুছে দেবে</string>
<string name="notifications">নোটিফিকেশনসমূহ</string>
<string name="error_report_channel_name">ত্রুটি প্রতিবেদন এর নোটিফিকেশন</string>
<string name="export_data_summary">ইতিহাস, সাবস্ক্রিপশন্স, প্লেলিস্ট ও সেটিংস রপ্তানি করো</string>
<string name="no_dir_yet">কোনো ডাউনলোড ফোল্ডার ঠিক করা নেই, এখন ডিফল্ট ডাউনলোড ফোল্ডার বাছুন</string>
<string name="auto_device_theme_title">স্বয়ংক্রিয় (ডিভাইস থিম)</string>
<string name="night_theme_summary">প্রিয় রাত্রিকালীন থিম বেছে নিন — %s</string>
<string name="metadata_licence">লাইসেন্স</string>
<string name="metadata_privacy">গোপনতা</string>
<string name="select_night_theme_toast">নিচ থেকে আপনার পছন্দের রাত্রিকালীন থিম বেছে নিতে পারেন</string>
<string name="download_has_started">ডাউনলোড শুরু হয়েছে</string>
<string name="mark_as_watched">দেখা হিসেবে মার্ক করো</string>
<string name="metadata_tags">ট্যাগসমূহ</string>
<string name="notification_colorize_summary">অ্যান্ড্রয়েডকে থাম্বনেইলের প্রধান রং অনুযায়ী রঙিন করুন (উল্লেখ্য যে, এটি সব ডিভাইসে উপলব্ধ নয়)</string>
</resources> </resources>

View file

@ -33,7 +33,7 @@
<string name="detail_thumbnail_view_description">ভিডিও প্রাকদর্শন, সময়ঃ</string> <string name="detail_thumbnail_view_description">ভিডিও প্রাকদর্শন, সময়ঃ</string>
<string name="error_details_headline">বর্ণনা:</string> <string name="error_details_headline">বর্ণনা:</string>
<string name="your_comment">আপনার মন্তব্য (ইংরেজিতে):</string> <string name="your_comment">আপনার মন্তব্য (ইংরেজিতে):</string>
<string name="info_labels">কি:\\nঅনুরোধ:\\nকন্টেন্ট ভাষা:\\nসার্ভিস:\\nসময়(GMT এ):\\nপ্যাকেজ:\\nসংস্করণ:\\nওএস সংস্করণ:\\nআইপি পরিসর:</string> <string name="info_labels">কি:\\nঅনুরোধ:\\nকন্টেন্ট ভাষা:\\nকন্টেন্ট দেশ:\\nঅ্যাপ ভাষা:\\nসার্ভিস:\\nসময়(GMT এ):\\nপ্যাকেজ:\\nসংস্করণ:\\nওএস সংস্করণ:</string>
<string name="what_happened_headline">কি হয়েছিল:</string> <string name="what_happened_headline">কি হয়েছিল:</string>
<string name="what_device_headline">তথ্য:</string> <string name="what_device_headline">তথ্য:</string>
<string name="error_snackbar_action">প্রতিবেদন</string> <string name="error_snackbar_action">প্রতিবেদন</string>
@ -83,7 +83,7 @@
<string name="metadata_cache_wipe_summary">সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো</string> <string name="metadata_cache_wipe_summary">সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো</string>
<string name="metadata_cache_wipe_title">ক্যাশ করা মেটাডেটা মুছো</string> <string name="metadata_cache_wipe_title">ক্যাশ করা মেটাডেটা মুছো</string>
<string name="thumbnail_cache_wipe_complete_notice">ছবির ক্যাশ মুছে ফেলা হয়েছে</string> <string name="thumbnail_cache_wipe_complete_notice">ছবির ক্যাশ মুছে ফেলা হয়েছে</string>
<string name="download_thumbnail_summary">থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি‌ পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে</string> <string name="download_thumbnail_summary">থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি‌ পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে</string>
<string name="show_comments_summary">মতামত প্রদর্শন বন্ধ করতে অপশনটি বন্ধ করুন</string> <string name="show_comments_summary">মতামত প্রদর্শন বন্ধ করতে অপশনটি বন্ধ করুন</string>
<string name="show_comments_title">মতামত প্রদর্শন করুন</string> <string name="show_comments_title">মতামত প্রদর্শন করুন</string>
<string name="download_thumbnail_title">থাম্বনেইল লোড করুন</string> <string name="download_thumbnail_title">থাম্বনেইল লোড করুন</string>
@ -142,7 +142,7 @@
<string name="no_player_found_toast">কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি (প্লে করতে VLC ইন্সটল করতে পারেন)।</string> <string name="no_player_found_toast">কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি (প্লে করতে VLC ইন্সটল করতে পারেন)।</string>
<string name="no_player_found">কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। VLC ইনস্টল করতে চান\?</string> <string name="no_player_found">কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। VLC ইনস্টল করতে চান\?</string>
<string name="upload_date_text">প্রকাশকাল %1$s</string> <string name="upload_date_text">প্রকাশকাল %1$s</string>
<string name="main_bg_subtitle">অনুসন্ধান এ চাপ দিয়ে শুরু করুন</string> <string name="main_bg_subtitle">অনুসন্ধান শুরু করুন</string>
<string name="feed_create_new_group_button_title">নতুন</string> <string name="feed_create_new_group_button_title">নতুন</string>
<string name="fragment_feed_title">নতুন কি</string> <string name="fragment_feed_title">নতুন কি</string>
<string name="app_language_title">অ্যাপ এর ভাষা</string> <string name="app_language_title">অ্যাপ এর ভাষা</string>
@ -224,7 +224,7 @@
<string name="player_recoverable_failure">প্লেয়ার এর এরর থেকে বেরিয়ে আসুন</string> <string name="player_recoverable_failure">প্লেয়ার এর এরর থেকে বেরিয়ে আসুন</string>
<string name="player_stream_failure">স্ট্রিম টি চালানো গেল না</string> <string name="player_stream_failure">স্ট্রিম টি চালানো গেল না</string>
<string name="clear_search_history_summary">সার্চের ইতিহাস মোছা হয়</string> <string name="clear_search_history_summary">সার্চের ইতিহাস মোছা হয়</string>
<string name="watch_history_states_deleted">প্লে ব্যাক এর অবস্থান মোছা হয়েছে</string> <string name="watch_history_states_deleted">প্লে ব্যাক এর অবস্থান মোছা হয়েছে</string>
<string name="delete_playback_states_alert">সমস্ত প্লে লিস্ট এর অবস্থান মুছবেন\?</string> <string name="delete_playback_states_alert">সমস্ত প্লে লিস্ট এর অবস্থান মুছবেন\?</string>
<string name="clear_playback_states_summary">সমস্ত প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string> <string name="clear_playback_states_summary">সমস্ত প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string>
<string name="clear_playback_states_title">প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string> <string name="clear_playback_states_title">প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string>
@ -300,4 +300,20 @@
<string name="notification_action_0_title">প্রথম অ্যাকশান বোতাম</string> <string name="notification_action_0_title">প্রথম অ্যাকশান বোতাম</string>
<string name="notification_action_1_title">দ্বিতীয় অ্যাকশান বোতাম</string> <string name="notification_action_1_title">দ্বিতীয় অ্যাকশান বোতাম</string>
<string name="streams_notification_channel_name">নতুন স্ট্রিম</string> <string name="streams_notification_channel_name">নতুন স্ট্রিম</string>
<string name="wifi_only">শুধুমাত্র ওয়াইফাইএ</string>
<string name="metadata_language">ভাষা</string>
<string name="unsupported_url_dialog_message">URL বোঝা যায় নি, অন্য অ্যাপ এ খুলুন\?</string>
<string name="error_file_creation">ফাইল তৈরি করা যাচ্ছে না</string>
<string name="show_description_summary">ভিডিও বিবরণ ও বাড়তি তথ্য বন্ধ করুন</string>
<string name="start_main_player_fullscreen_title">মূল প্লেয়ার ফুল স্ক্রীন এ শুরু করুন</string>
<string name="peertube_instance_add_fail">" "</string>
<string name="error_connect_host">সার্ভার এর সাথে যোগাযোগ করা যাচ্ছে না</string>
<string name="notification_action_buffering">বাফার হচ্ছে</string>
<string name="download_has_started">ডাউনলোড শুরু হয়েছে</string>
<string name="open_with">এটি দিয়ে খুলুন</string>
<string name="mark_as_watched">দেখা হিসাবে চিহ্নিত করুন</string>
<string name="notification_scale_to_square_image_title">থাম্বনেল 1:1 আকৃতির অনুপাতের করুন</string>
<string name="notification_scale_to_square_image_summary">বিজ্ঞপ্তিতে প্রদর্শিত ভিডিও থাম্বনেল 16:9 থেকে 1:1 অনুপাতের করুন (বিকৃতি দেখা যেতে পারে)</string>
<string name="notification_action_shuffle">অদলবদল</string>
<string name="notification_action_nothing">কিছু না</string>
</resources> </resources>

View file

@ -44,7 +44,7 @@
<string name="always_ask_open_action">সবসময় জিজ্ঞেস করুন</string> <string name="always_ask_open_action">সবসময় জিজ্ঞেস করুন</string>
<string name="video_player">ভিডিও প্লেয়ার</string> <string name="video_player">ভিডিও প্লেয়ার</string>
<string name="drawer_close">ড্রয়ার বন্ধ করুন</string> <string name="drawer_close">ড্রয়ার বন্ধ করুন</string>
<string name="drawer_open">ড্রয়ার খুল</string> <string name="drawer_open">ড্রয়ার খুল</string>
<string name="play_queue_audio_settings">অডিও সেটিং</string> <string name="play_queue_audio_settings">অডিও সেটিং</string>
<string name="play_queue_stream_detail">বিবরণ</string> <string name="play_queue_stream_detail">বিবরণ</string>
<string name="play_queue_remove">সরাও</string> <string name="play_queue_remove">সরাও</string>
@ -126,7 +126,7 @@
<string name="error_report_open_issue_button_text">গিটহাব এ এরর রিপোর্ট করুন</string> <string name="error_report_open_issue_button_text">গিটহাব এ এরর রিপোর্ট করুন</string>
<string name="error_report_button_text">মেইলের মাধ্যমে ত্রুটি প্রতিবেদন করুন</string> <string name="error_report_button_text">মেইলের মাধ্যমে ত্রুটি প্রতিবেদন করুন</string>
<string name="sorry_string">দুঃখিত, এটা ঘটা উচিত ছিল না।</string> <string name="sorry_string">দুঃখিত, এটা ঘটা উচিত ছিল না।</string>
<string name="restore_defaults_confirmation">আপনি কি ডিফল্ট এ ফিরতে চান\?</string> <string name="restore_defaults_confirmation">তুমি কি এই সহজাত পছন্দ ফিরত চাও\?</string>
<string name="restore_defaults">ডিফল্ট এ ফিরে যান</string> <string name="restore_defaults">ডিফল্ট এ ফিরে যান</string>
<string name="no_streams_available_download">ডাউন লোড এর জন্য কোন স্ট্রিম নেই</string> <string name="no_streams_available_download">ডাউন লোড এর জন্য কোন স্ট্রিম নেই</string>
<string name="error_occurred_detail">একটা এরর হয়েছেঃ %1$s</string> <string name="error_occurred_detail">একটা এরর হয়েছেঃ %1$s</string>
@ -147,15 +147,15 @@
<string name="download_to_sdcard_error_title">বাহ্যিক স্টোরেজ নেই</string> <string name="download_to_sdcard_error_title">বাহ্যিক স্টোরেজ নেই</string>
<string name="general_error">ত্রুটি</string> <string name="general_error">ত্রুটি</string>
<string name="help">সাহায্য</string> <string name="help">সাহায্য</string>
<string name="search_history_deleted">সার্চ ইতিহাস ডিলিট হয়েছে</string> <string name="search_history_deleted">সার্চ ইতিহাস ডিলিট হয়েছে</string>
<string name="delete_search_history_alert">সমগ্র সার্চ ইতিহাস মুছবেন\?</string> <string name="delete_search_history_alert">সমগ্র সার্চ ইতিহাস মুছবেন\?</string>
<string name="clear_search_history_summary">সার্চের ইতিহাস মোছা হয়</string> <string name="clear_search_history_summary">সার্চের ইতিহাস মোছা হয়</string>
<string name="clear_search_history_title">সার্চ ইতিহাস মুছুন</string> <string name="clear_search_history_title">সার্চ ইতিহাস মুছুন</string>
<string name="watch_history_states_deleted">প্লে ব্যাক এর অবস্থান মোছা হয়েছে</string> <string name="watch_history_states_deleted">প্লে ব্যাক এর অবস্থান মোছা হয়েছে</string>
<string name="delete_playback_states_alert">সমস্ত প্লে লিস্ট এর অবস্থান মুছবেন\?</string> <string name="delete_playback_states_alert">সমস্ত প্লে লিস্ট এর অবস্থান মুছবেন\?</string>
<string name="clear_playback_states_summary">সমস্ত প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string> <string name="clear_playback_states_summary">সমস্ত প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string>
<string name="clear_playback_states_title">প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string> <string name="clear_playback_states_title">প্লে লিস্ট এর অবস্থান মুছে ফেলুন</string>
<string name="watch_history_deleted">দেখার ইতিহাস মুছে গেছে</string> <string name="watch_history_deleted">দেখার ইতিহাস মুছে গেছে</string>
<string name="delete_view_history_alert">সম্পূর্ণ দেখার ইতিহাস মুছে ফেলুন\?</string> <string name="delete_view_history_alert">সম্পূর্ণ দেখার ইতিহাস মুছে ফেলুন\?</string>
<string name="clear_views_history_title">দেখার ইতিহাস মুছে ফেলুন</string> <string name="clear_views_history_title">দেখার ইতিহাস মুছে ফেলুন</string>
<string name="export_data_title">ডাটা বেস এক্সপোর্ট করুন</string> <string name="export_data_title">ডাটা বেস এক্সপোর্ট করুন</string>
@ -189,7 +189,7 @@
<string name="downloads_title">ডাউনলোডগুলি</string> <string name="downloads_title">ডাউনলোডগুলি</string>
<string name="downloads">ডাউনলোডগুলি</string> <string name="downloads">ডাউনলোডগুলি</string>
<string name="duration_live">লাইভ</string> <string name="duration_live">লাইভ</string>
<string name="youtube_restricted_mode_enabled_title">YouTube \"নিষিদ্ধ মোড\" চালু করুন</string> <string name="youtube_restricted_mode_enabled_title">ইউটিউব ‘সীমিত মোড’ চালু করো</string>
<string name="show_age_restricted_content_title">বয়স সীমাবদ্ধ কন্টেন্ট দেখাও</string> <string name="show_age_restricted_content_title">বয়স সীমাবদ্ধ কন্টেন্ট দেখাও</string>
<string name="content">কন্টেন্ট</string> <string name="content">কন্টেন্ট</string>
<string name="popup_playing_toast">পপআপ মোডে চলছে</string> <string name="popup_playing_toast">পপআপ মোডে চলছে</string>
@ -231,12 +231,12 @@
<string name="metadata_cache_wipe_summary">সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো</string> <string name="metadata_cache_wipe_summary">সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো</string>
<string name="metadata_cache_wipe_title">ক্যাশ করা মেটাডেটা মুছো</string> <string name="metadata_cache_wipe_title">ক্যাশ করা মেটাডেটা মুছো</string>
<string name="thumbnail_cache_wipe_complete_notice">ছবির ক্যাশ মুছে ফেলা হয়েছে</string> <string name="thumbnail_cache_wipe_complete_notice">ছবির ক্যাশ মুছে ফেলা হয়েছে</string>
<string name="download_thumbnail_summary">থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি‌ পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে</string> <string name="download_thumbnail_summary">থাম্বনেইল প্রদর্শন বন্ধ করার মাধ্যমে, ডাটা এবং মেমোরি সংরক্ষণ করুন। অপশনটি‌ পরিবর্তনে ইন-মেমোরি এবং অন-ডিস্ক ইমেজ ক্যাশ উভয়ই মুছে যাবে</string>
<string name="show_comments_summary">মতামত প্রদর্শন বন্ধ করতে অপশনটি বন্ধ করুন</string> <string name="show_comments_summary">মতামত প্রদর্শন বন্ধ করতে অপশনটি বন্ধ করুন</string>
<string name="show_comments_title">মতামত প্রদর্শন করুন</string> <string name="show_comments_title">মতামত প্রদর্শন করুন</string>
<string name="download_thumbnail_title">থাম্বনেইল লোড করুন</string> <string name="download_thumbnail_title">থাম্বনেইল লোড করুন</string>
<string name="seek_duration_title">দ্রুত-ফরওয়ার্ড/-পুনরায় সন্ধান সময়কাল</string> <string name="seek_duration_title">দ্রুত-ফরওয়ার্ড/-পুনরায় সন্ধান সময়কাল</string>
<string name="use_inexact_seek_summary">অনির্দিষ্ট সন্ধান প্লেয়ারকে আরো দ্রুত গতিতে সন্ধান করার সুবিধা দেয়, কিন্তু এটি সম্পূর্ণ নির্ভুল নাও হতে পারে ৷ ৫, ১৫ ও ২৫ সেকেন্ডের জন্য এটা কাজ করবে না</string> <string name="use_inexact_seek_summary">অনির্দিষ্ট সন্ধান, চালককে আরো দ্রুত গতিতে সন্ধান করার সুবিধা দেয়, কিন্তু এটি সম্পূর্ণ নির্ভুল নাও হতে পারে ৷ ৫, ১৫ ও ২৫ সেকেন্ডের জন্য এটা কাজ করবে না</string>
<string name="use_inexact_seek_title">দ্রুত টানা ব্যাবহার করুন</string> <string name="use_inexact_seek_title">দ্রুত টানা ব্যাবহার করুন</string>
<string name="popup_remember_size_pos_summary">শেষ আকার এবং পপআপ সেট অবস্থান মনে রাখো</string> <string name="popup_remember_size_pos_summary">শেষ আকার এবং পপআপ সেট অবস্থান মনে রাখো</string>
<string name="popup_remember_size_pos_title">পপআপ আকার এবং অবস্থান মনে রাখো</string> <string name="popup_remember_size_pos_title">পপআপ আকার এবং অবস্থান মনে রাখো</string>
@ -253,12 +253,12 @@
<string name="notification_scale_to_square_image_title">থাম্বনেলে ১:১ অনুপাতে করো</string> <string name="notification_scale_to_square_image_title">থাম্বনেলে ১:১ অনুপাতে করো</string>
<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">\"Kodi দ্বারা চালান\" বিকল্পটি প্রদর্শন কর</string> <string name="show_play_with_kodi_title">\"Kodi দ্বারা চালান\" বিকল্পটি প্রদর্শন কর</string>
<string name="kore_not_found">হারানো কোর ইনস্টল করবে\?</string> <string name="kore_not_found">হারানো কোর ইনস্টল করবে\?</string>
<string name="play_with_kodi_title">Kodi দ্বারা চালাও</string> <string name="play_with_kodi_title">Kodi দ্বারা চালাও</string>
<string name="show_higher_resolutions_summary">শুধুমাত্র কিছু ডিভাইস 2K/4K ভিডিও চালাতে পারে</string> <string name="show_higher_resolutions_summary">শুধুমাত্র কিছু ডিভাইস 2K/4K ভিডিও চালাতে পারে</string>
<string name="show_higher_resolutions_title">উচ্চতর রেজুলেশন প্রদর্শন করা হবে</string> <string name="show_higher_resolutions_title">উচ্চতর রেজুলেশন প্রদর্শন করা হবে</string>
<string name="default_popup_resolution_title">ডিফল্ট পপ-আপ রেজোল্যুশন</string> <string name="default_popup_resolution_title">সহজাত ভাসমান আকার</string>
<string name="default_resolution_title">ডিফল্ট রেজোল্যুশন</string> <string name="default_resolution_title">সহজাত আকার</string>
<string name="download_path_audio_dialog_title">অডিও ফাইলগুলির জন্য ডাউনলোডের ফোল্ডার নির্বাচন করুন</string> <string name="download_path_audio_dialog_title">অডিও ফাইলগুলির জন্য ডাউনলোডের ফোল্ডার নির্বাচন করুন</string>
<string name="download_path_audio_summary">ডাউনলোড করা অডিও ফাইলগুলি এখানে সঞ্চিত থাকে</string> <string name="download_path_audio_summary">ডাউনলোড করা অডিও ফাইলগুলি এখানে সঞ্চিত থাকে</string>
<string name="download_path_audio_title">অডিও ডাউনলোড ফোল্ডার</string> <string name="download_path_audio_title">অডিও ডাউনলোড ফোল্ডার</string>
@ -283,20 +283,20 @@
<string name="use_external_video_player_title">বাইরের ভিডিও প্লেয়ার ব্যবহার করুন</string> <string name="use_external_video_player_title">বাইরের ভিডিও প্লেয়ার ব্যবহার করুন</string>
<string name="share_dialog_title">শেয়ার করুন</string> <string name="share_dialog_title">শেয়ার করুন</string>
<string name="search_showing_result_for">রেজাল্ট দেখানো হচ্ছেঃ %s</string> <string name="search_showing_result_for">রেজাল্ট দেখানো হচ্ছেঃ %s</string>
<string name="did_you_mean">আপনি কি বুঝিয়েছেনঃ %1$s\?</string> <string name="did_you_mean">তুমি কি বুঝিয়েছো %1$s\?</string>
<string name="settings">সেটিংস</string> <string name="settings">সেটিংস</string>
<string name="search">খুঁজুন</string> <string name="search">খুঁজুন</string>
<string name="controls_download_desc">স্ট্রিম ফাইল ডাউনলোড করুন</string> <string name="controls_download_desc">স্ট্রিম ফাইল ডাউনলোড করুন</string>
<string name="download">ডাউনলোউড</string> <string name="download">ডাউনলোউড</string>
<string name="share">শেয়ার</string> <string name="share">শেয়ার</string>
<string name="open_in_popup_mode">পপ-আপ মোডে ওপেন কর</string> <string name="open_in_popup_mode">ভাসমান অবস্থায় খুল</string>
<string name="open_in_browser">ব্রাউজারে ওপেন কর</string> <string name="open_in_browser">ব্রাউজারে খুল</string>
<string name="cancel">বাতিল</string> <string name="cancel">বাতিল</string>
<string name="install">ইনস্টল</string> <string name="install">ইনস্টল</string>
<string name="no_player_found_toast">কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি (প্লে করতে VLC ইন্সটল করতে পারেন).</string> <string name="no_player_found_toast">কোনো ধারা চালক পাওয়া যায়নি (প্লে করতে VLC ইন্সটল করতে পারো)।</string>
<string name="no_player_found">কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। VLC ইনস্টল করতে চান\?</string> <string name="no_player_found">কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। VLC ইনস্টল করতে চান\?</string>
<string name="upload_date_text">প্রকাশকাল %1$s</string> <string name="upload_date_text">প্রকাশকাল %1$s</string>
<string name="main_bg_subtitle">\"অনুসন্ধান\" এ চাপ দিয়ে শুরু করুন</string> <string name="main_bg_subtitle">আতশী কাঁচে টিপ দিয়ে শুরু করো।</string>
<string name="notification_action_buffering">বাফারিং</string> <string name="notification_action_buffering">বাফারিং</string>
<string name="notification_action_shuffle">সাফল</string> <string name="notification_action_shuffle">সাফল</string>
<string name="notification_action_4_title">পঞ্চম অ্যাকশন বাটন</string> <string name="notification_action_4_title">পঞ্চম অ্যাকশন বাটন</string>
@ -308,7 +308,7 @@
<string name="clear_queue_confirmation_summary">এক প্লেয়ার থেকে অন্য প্লেয়ারে পরিবর্তন করলে তোমার সারি প্রতিস্থাপিত হতে পারে</string> <string name="clear_queue_confirmation_summary">এক প্লেয়ার থেকে অন্য প্লেয়ারে পরিবর্তন করলে তোমার সারি প্রতিস্থাপিত হতে পারে</string>
<string name="clear_queue_confirmation_title">কিউ মোছার আগে নিশ্চিত করো</string> <string name="clear_queue_confirmation_title">কিউ মোছার আগে নিশ্চিত করো</string>
<string name="notification_actions_at_most_three">কমপ্যাক্ট বিজ্ঞপ্তিতে প্রদর্শন করতে তুমি সর্বাধিক তিনটি ক্রিয়া নির্বাচন করতে পারো!</string> <string name="notification_actions_at_most_three">কমপ্যাক্ট বিজ্ঞপ্তিতে প্রদর্শন করতে তুমি সর্বাধিক তিনটি ক্রিয়া নির্বাচন করতে পারো!</string>
<string name="notification_actions_summary">নিচের প্রতিটি প্রজ্ঞাপন ক্রিয়া সম্পাদনা করো। ডান দিকের চেকবাক্স ব্যবহার করে কম্প্যাক্ট নোটিফিকেশনে দেখানোর জন্য তিনটি পর্যন্ত নির্বাচন করো</string> <string name="notification_actions_summary">নিচের প্রতিটি প্রজ্ঞাপন ক্রিয়া সম্পাদনা করো। ডান দিকের চেকবাক্স ব্যবহার করে কম্প্যাক্ট নোটিফিকেশনে দেখানোর জন্য তিনটি পর্যন্ত নির্বাচন করো</string>
<string name="notification_scale_to_square_image_summary">১৬:৯ থেকে ১:১অনুপাতে প্রদর্শিত ভিডিও থাম্বনেইল পরিবর্তন করো (বিকৃতি প্রবর্তন করতে পারে)</string> <string name="notification_scale_to_square_image_summary">১৬:৯ থেকে ১:১অনুপাতে প্রদর্শিত ভিডিও থাম্বনেইল পরিবর্তন করো (বিকৃতি প্রবর্তন করতে পারে)</string>
<string name="settings_category_feed_title">ফিড</string> <string name="settings_category_feed_title">ফিড</string>
<string name="overwrite">ওভাররাইট</string> <string name="overwrite">ওভাররাইট</string>
@ -441,7 +441,7 @@
<string name="chapters">অধ্যায়</string> <string name="chapters">অধ্যায়</string>
<string name="comments_tab_description">মতামত</string> <string name="comments_tab_description">মতামত</string>
<string name="description_tab_description">বর্ণনা</string> <string name="description_tab_description">বর্ণনা</string>
<string name="open_with">দিয়ে খুলুন</string> <string name="open_with">দিয়ে খুল</string>
<string name="feed_update_threshold_title">ফিড হালনাগাদ সীমা</string> <string name="feed_update_threshold_title">ফিড হালনাগাদ সীমা</string>
<string name="feed_group_dialog_empty_name">খালি গ্রুপ নাম</string> <string name="feed_group_dialog_empty_name">খালি গ্রুপ নাম</string>
<string name="feed_group_dialog_empty_selection">কোনো সদস্যতা নির্বাচিত হয়নি</string> <string name="feed_group_dialog_empty_selection">কোনো সদস্যতা নির্বাচিত হয়নি</string>
@ -454,7 +454,7 @@
<string name="wifi_only">শুধুমাত্র ওয়াই-ফাই-তে</string> <string name="wifi_only">শুধুমাত্র ওয়াই-ফাই-তে</string>
<string name="skip_silence_checkbox">নীরবতার সময় দ্রুত আগাও</string> <string name="skip_silence_checkbox">নীরবতার সময় দ্রুত আগাও</string>
<string name="playback_speed_control">প্লেব্যাক গতি নিয়ন্ত্রণ</string> <string name="playback_speed_control">প্লেব্যাক গতি নিয়ন্ত্রণ</string>
<string name="preferred_open_action_settings_title">পছন্দসই \'মুক্ত\' ক্রিয়া</string> <string name="preferred_open_action_settings_title">পছন্দসই \'খোলার\' ক্রিয়া</string>
<string name="enable_disposed_exceptions_title">আউট-অফ-লাইফসাইকেল ত্রুটি প্রতিবেদন করো</string> <string name="enable_disposed_exceptions_title">আউট-অফ-লাইফসাইকেল ত্রুটি প্রতিবেদন করো</string>
<string name="playlist_thumbnail_change_success">প্লে-তালিকার থাম্বনেইল পরিবর্তিত হয়েছে।</string> <string name="playlist_thumbnail_change_success">প্লে-তালিকার থাম্বনেইল পরিবর্তিত হয়েছে।</string>
<string name="preferred_player_fetcher_notification_message">অনুরোধকৃত তথ্য লোড হচ্ছে</string> <string name="preferred_player_fetcher_notification_message">অনুরোধকৃত তথ্য লোড হচ্ছে</string>
@ -484,14 +484,14 @@
<string name="detail_sub_channel_thumbnail_view_description">চ্যানেলের অবতারের প্রতিচ্ছবি</string> <string name="detail_sub_channel_thumbnail_view_description">চ্যানেলের অবতারের প্রতিচ্ছবি</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_enable_button">দ্রুত মোড চালু করো</string> <string name="feed_use_dedicated_fetch_method_enable_button">দ্রুত মোড চালু করো</string>
<string name="error_file_creation">ফাইল বাানো যায়নি</string> <string name="error_file_creation">ফাইল বাানো যায়নি</string>
<string name="limit_mobile_data_usage_title">মোবাইল ডাটা ব্যবহারের সময় আকার সীমিত রাখো</string> <string name="limit_mobile_data_usage_title">মোবাইল ডাটা ব্যবহারের সময় আকার সীমিত রাখো</string>
<string name="show_original_time_ago_title">ভুক্তিতে আসল সময় দেখাও</string> <string name="show_original_time_ago_title">ভুক্তিতে আসল সময় দেখাও</string>
<string name="radio">রেডিও</string> <string name="radio">রেডিও</string>
<string name="featured">বিশেষ</string> <string name="featured">বিশেষ</string>
<string name="recaptcha_solve">সমাধান করো</string> <string name="recaptcha_solve">সমাধান করো</string>
<string name="feed_update_threshold_summary">শেষ হালনাগাদের পর একটি সাবস্ক্রিপশনের আগের সময় সেকেলে বিবেচিত — %s</string> <string name="feed_update_threshold_summary">শেষ হালনাগাদের পর একটি সাবস্ক্রিপশনের আগের সময় সেকেলে বিবেচিত — %s</string>
<string name="feed_group_dialog_delete_message">আপনি কি এ গ্রুপটি মুছতে চান\?</string> <string name="feed_group_dialog_delete_message">তুমি কি এ গ্রুপটি মুছতে চাও\?</string>
<string name="website_encouragement">আরও তথ্য এবং খবরের জন্য নিউপাইপ ওয়েবসাইট দেখো।</string> <string name="website_encouragement">আরও তথ্য এবং খবরের জন্য নিউপাইপ ওয়েবসাইট দেখো।</string>
<string name="toast_no_player">এ ফাইলটি চালানোর জন্য কোন অ্যাপ ইন্সটলকৃত নেই</string> <string name="toast_no_player">এ ফাইলটি চালানোর জন্য কোন অ্যাপ ইন্সটলকৃত নেই</string>
<string name="override_current_data">এতে তোমার বর্তমান অবস্থা সরানো হবে।</string> <string name="override_current_data">এতে তোমার বর্তমান অবস্থা সরানো হবে।</string>
@ -516,14 +516,14 @@
<string name="import_settings">পছন্দসমূহ কি আমদানি করতে চাও\?</string> <string name="import_settings">পছন্দসমূহ কি আমদানি করতে চাও\?</string>
<string name="settings_file_replacement_character_summary">অবৈধ অক্ষরগুলো এই মান দ্বারা প্রতিস্থাপিত</string> <string name="settings_file_replacement_character_summary">অবৈধ অক্ষরগুলো এই মান দ্বারা প্রতিস্থাপিত</string>
<string name="permission_display_over_apps">অন্য অ্যাপের উপরে দেখাতে অনুমতি দাও</string> <string name="permission_display_over_apps">অন্য অ্যাপের উপরে দেখাতে অনুমতি দাও</string>
<string name="peertube_instance_url_help">%s-এ আপনার পছন্দের ইন্সট্যান্স খুঁজুন</string> <string name="peertube_instance_url_help">%s-এ তোমার পছন্দের ইন্সট্যান্স খুজো</string>
<string name="clear_views_history_summary">প্লে করা স্ট্রিমের ইতিহাস এবং প্লেব্যাক অবস্থানগুলি মুছে দেয়</string> <string name="clear_views_history_summary">প্লে করা স্ট্রিমের ইতিহাস এবং প্লেব্যাক অবস্থানগুলি মুছে দেয়</string>
<string name="restricted_video">এই ভিডিওটি বয়সসীমাবদ্ধ । <string name="restricted_video">এই ভিডিওটি বয়সসীমাবদ্ধ ।
\n \n
\nআপনি এটি দেখতে চাইলে সেটটিংসে \"%1$s\" চালু করুন </string> \nতুমি এটি দেখতে চাইলে পছন্দসমূহে \"%1$s\" চালু করো</string>
<string name="youtube_restricted_mode_enabled_summary">Youtube একটি \"সীমাবদ্ধ মোড\" সরবরাহ করে যা সম্ভাব্য বয়সসীমাবদ্ধ বিষয়গুলি গুপ্ত রাখে</string> <string name="youtube_restricted_mode_enabled_summary">Youtube একটি \"সীমাবদ্ধ মোড\" সরবরাহ করে যা সম্ভাব্য বয়সসীমাবদ্ধ বিষয়গুলি গুপ্ত রাখে</string>
<string name="show_age_restricted_content_summary">শিশুদের জন্যে সম্ভবত অনুপযুক্ত বিষয়গুলোও দেখান যেগুলির একটি বয়সসীমা রয়েছে (যেমন ১৮+ বিষয়সমূহ)</string> <string name="show_age_restricted_content_summary">শিশুদের জন্যে সম্ভবত অনুপযুক্ত বিষয়গুলোও দেখান যেগুলির একটি বয়সসীমা রয়েছে (যেমন ১৮+ বিষয়সমূহ)</string>
<string name="unsupported_url_dialog_message">ইউআরএলটি চিন্থিত করা যায়নি | অন্য এপ্লিকেশন এ খুলতে চান \?</string> <string name="unsupported_url_dialog_message">ইউআরএলটি চিহ্নিত করা যায়নি। অন্য অ্যাপ্লিকেশনে খুলতে চাও\?</string>
<string name="error_postprocessing_stopped">এই ফাইলে কাজ করার সময় নিউপাইপ বন্ধ করা হয়েছে</string> <string name="error_postprocessing_stopped">এই ফাইলে কাজ করার সময় নিউপাইপ বন্ধ করা হয়েছে</string>
<string name="download_already_pending">এই নামের একটি ডাউনলোড প্রক্রিয়ারত</string> <string name="download_already_pending">এই নামের একটি ডাউনলোড প্রক্রিয়ারত</string>
<string name="saved_tabs_invalid_json">সংরক্ষিত ট্যাব পড়া যায় নি, তাই সহজাতটি ব্যবহার করা হচ্ছে</string> <string name="saved_tabs_invalid_json">সংরক্ষিত ট্যাব পড়া যায় নি, তাই সহজাতটি ব্যবহার করা হচ্ছে</string>
@ -543,7 +543,7 @@
<string name="delete_item_search_history">অনুসন্ধান ইতিহাস থেকে এই ভুক্তিটি মুছবে\?</string> <string name="delete_item_search_history">অনুসন্ধান ইতিহাস থেকে এই ভুক্তিটি মুছবে\?</string>
<string name="downloads_storage_ask_summary">প্রত্যেক ডাউনলোড কোথায় রাখা হবে তা জিজ্ঞেস করা হবে</string> <string name="downloads_storage_ask_summary">প্রত্যেক ডাউনলোড কোথায় রাখা হবে তা জিজ্ঞেস করা হবে</string>
<string name="download_already_running">এই নামের একটি ডাউনলোড চলমান</string> <string name="download_already_running">এই নামের একটি ডাউনলোড চলমান</string>
<string name="localization_changes_requires_app_restart">অ্যাপ আবার শুরু হলে ভাষা পাল্টাবে</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>
@ -555,7 +555,7 @@
<string name="description_select_enable">বর্ণনার লেখা নির্বাচন করা সক্ষম করো</string> <string name="description_select_enable">বর্ণনার লেখা নির্বাচন করা সক্ষম করো</string>
<string name="service_provides_reason">%s এই কারণ বলছে:</string> <string name="service_provides_reason">%s এই কারণ বলছে:</string>
<string name="feed_load_error">প্রক্রিয়াকরণ ফিডে ত্রুটি</string> <string name="feed_load_error">প্রক্রিয়াকরণ ফিডে ত্রুটি</string>
<string name="open_website_license">মুক্ত ওয়েবসাইট</string> <string name="open_website_license">ওয়েবসাইট খুলুন</string>
<string name="account_terminated">অ্যাকাউন্ট ধ্বংসকৃত</string> <string name="account_terminated">অ্যাকাউন্ট ধ্বংসকৃত</string>
<string name="metadata_thumbnail_url">প্রতিচ্ছবি সংযোগ</string> <string name="metadata_thumbnail_url">প্রতিচ্ছবি সংযোগ</string>
<string name="metadata_age_limit">বয়সসীমা</string> <string name="metadata_age_limit">বয়সসীমা</string>
@ -582,4 +582,44 @@
<item quantity="other">%sটি ডাউনলোড সমাপ্ত</item> <item quantity="other">%sটি ডাউনলোড সমাপ্ত</item>
</plurals> </plurals>
<string name="mark_as_watched">দেখা হয়েছে চিহ্নিত করো</string> <string name="mark_as_watched">দেখা হয়েছে চিহ্নিত করো</string>
<string name="settings_category_player_notification_title">চালক বিজ্ঞপ্তি</string>
<string name="low_quality_smaller">নিম্ন মান(ছোট)</string>
<string name="detail_heart_img_view_description">মূল তৈরিকারকের পছন্দ করা</string>
<string name="enumeration_comma">,</string>
<string name="get_notified">বিজ্ঞপ্তি পাঠাও</string>
<string name="toggle_all">সব পরিবর্তন করো</string>
<string name="high_quality_larger">উচ্চতর মান (বৃহত্তর)</string>
<string name="comments_are_disabled">মন্তব্য নিষ্ক্রিয়</string>
<string name="local_search_suggestions">স্থানীয় অনুসন্ধানের পরামর্শ</string>
<string name="remote_search_suggestions">দূর অনুসন্ধানে পরামর্শ</string>
<string name="percent">শতাংশ</string>
<string name="semitone">সেমিটোন</string>
<string name="progressive_load_interval_exoplayer_default">বাইরের চালক সহজাত</string>
<string name="enqueue_next_stream">পরেরটা ক্রমে রাখো</string>
<string name="crash_the_player">চালক থামাও</string>
<string name="streams_notification_channel_name">নতুন ধারা</string>
<string name="streams_notifications_interval_title">কম্পাঙ্ক দেখো</string>
<string name="seekbar_preview_thumbnail_title">পূর্বদর্শন রেখার মাধ্যমে প্রাকদর্শন</string>
<string name="show_image_indicators_title">ছবিরূপ সূচক দেখাও</string>
<string name="dont_show">দেখিও না</string>
<string name="any_network">যেকোনো নেটওয়ার্ক</string>
<string name="enqueued_next">পরেরটা ক্রমে রাখা হয়েছে</string>
<string name="detail_pinned_comment_view_description">পিনকৃত মন্তব্য</string>
<string name="notifications">বিজ্ঞপ্তি</string>
<string name="checking_updates_toast">হালনাগাদ দেখা হচ্ছে …</string>
<string name="manual_update_title">হালনাগাদ আছে কিনা দেখো</string>
<string name="start_main_player_fullscreen_title">মূল প্লেয়ার ফুল স্ক্রীন এ শুরু করুন</string>
<string name="feed_new_items">ধারার নতুন ভুক্তি</string>
<string name="error_report_channel_name">ত্রুটি প্রতিবেদন এর বিজ্ঞপ্তি</string>
<string name="show_hold_to_append_summary">পটভূমি বা ভিডিওর ‘বিস্তারিত: এর ভাসমান বোতাম টিপলে একটা তথ্য দেখাও</string>
<plurals name="new_streams">
<item quantity="one">%s টি নতুন ধারা</item>
<item quantity="other">%s টি নতুন ধারা</item>
</plurals>
<string name="notification_colorize_summary">প্রতিচ্ছবিত প্রধান রঙ অনুসারে অ্যান্ড্রয়েডকে বিজ্ঞপ্তির রঙ কাস্টমাইজ করতে দাও (দ্রষ্টব্য যে এটা সমস্ত ডিভাইসে উপলব্ধ নয়)</string>
<string name="unknown_format">অজ্ঞাত ফরম্যাট</string>
<string name="select_quality_external_players">বাহ্যিক প্লেয়ারের জন্য মান নির্বাচন করুন</string>
<string name="no_audio_streams_available_for_external_players">বাহ্যিক প্লেয়ারের জন্য কোনো অডিও স্ট্রিম নেই</string>
<string name="no_video_streams_available_for_external_players">বাহ্যিক প্লেয়ারের জন্য কোনো ভিডিও স্ট্রিম নেই</string>
<string name="unknown_quality">অজ্ঞাত মান</string>
</resources> </resources>

View file

@ -650,7 +650,6 @@
<string name="start_main_player_fullscreen_title">Inicia el reproductor principal en pantalla completa</string> <string name="start_main_player_fullscreen_title">Inicia el reproductor principal en pantalla completa</string>
<string name="main_page_content_swipe_remove">Llisqueu els elements per eliminar-los</string> <string name="main_page_content_swipe_remove">Llisqueu els elements per eliminar-los</string>
<string name="start_main_player_fullscreen_summary">Si la rotació automàtica està bloquejada, no inicieu vídeos al mini reproductor, sinó que aneu directament al mode de pantalla completa. Podeu accedir igualment al mini reproductor sortint de pantalla completa</string> <string name="start_main_player_fullscreen_summary">Si la rotació automàtica està bloquejada, no inicieu vídeos al mini reproductor, sinó que aneu directament al mode de pantalla completa. Podeu accedir igualment al mini reproductor sortint de pantalla completa</string>
<string name="background_player_already_playing_toast">Ja s\'està reproduint en segon pla</string>
<string name="error_report_channel_name">Notificació d\'informe d\'error</string> <string name="error_report_channel_name">Notificació d\'informe d\'error</string>
<string name="crash_the_player">Tancar abruptament el reproductor</string> <string name="crash_the_player">Tancar abruptament el reproductor</string>
<string name="manual_update_title">Comprovar si hi ha actualitzacions</string> <string name="manual_update_title">Comprovar si hi ha actualitzacions</string>

View file

@ -11,7 +11,7 @@
\nیوتیوب نموونەیە لەم خزمەتگوزارییە کە ڕێگەی خێرا بەکاردەبات بەهۆی پیشاندەری RSS. \nیوتیوب نموونەیە لەم خزمەتگوزارییە کە ڕێگەی خێرا بەکاردەبات بەهۆی پیشاندەری RSS.
\n \n
\nبۆیە هەڵژرادن بۆ خۆت دەگەڕێتەوە: زانیاری تەواو یان خێرا.</string> \nبۆیە هەڵژرادن بۆ خۆت دەگەڕێتەوە: زانیاری تەواو یان خێرا.</string>
<string name="app_license">نیوپایپ نه‌رمه‌والایه‌كی سەرچاوە کراوەیە : دەتوانیت بەکاریبهێنیت، بیخوێنیتەوە و هاوبەشی پێبکەیت و بەرەوپێشی ببەیت. بەتایبەتی دەتوانی دابەشیبکەیتەوە یاخوود بگۆڕیت بەپێی مەرجەکانی GNU مۆڵەتنامەی گشتی وەک نه‌رمه‌واڵایه‌كی بڵاوی خۆڕایی, بەهۆی وەشانی ٣ ی مۆڵەتنامە، یان هەر وەشانێکی دوواتر.</string> <string name="app_license">نیوپایپ نه‌رمه‌والایه‌كی سەرچاوە کراوەیە : دەتوانیت بەکاریبهێنیت، بیخوێنیتەوە، هاوبەشی پێبکەیت ،بەرەوپێشی ببەیت. بەتایبەتی دەتوانی دابەشیبکەیتەوە یاخوود بگۆڕیت بەپێی مەرجەکانی GNU مۆڵەتنامەی گشتی وەک نه‌رمه‌واڵایه‌كی بڵاوی خۆڕایی, بەهۆی وەشانی ٣ ی مۆڵەتنامە، یان هەر وەشانێکی دوواتر.</string>
<string name="info_labels">چی:\\nداواكاری:\\nزمانی بابەت:\\nوڵاتی بابەت:\\nزمانی به‌رنامه‌:\\nخزمهتگوزاری:\\nGMT كات:\\nپاكێج:\\nوهشان:\\nOS وه‌شان:</string> <string name="info_labels">چی:\\nداواكاری:\\nزمانی بابەت:\\nوڵاتی بابەت:\\nزمانی به‌رنامه‌:\\nخزمهتگوزاری:\\nGMT كات:\\nپاكێج:\\nوهشان:\\nOS وه‌شان:</string>
<string name="privacy_policy_encouragement">پڕۆژەی نیوپایپ زانیارییە تایبەتییەکانت بە وردی دەپارێزێت. هەروەها به‌رنامه‌كه‌ هیچ زانایارییەکت بەبێ ئاگاداری تۆ بەکارنابات. <string name="privacy_policy_encouragement">پڕۆژەی نیوپایپ زانیارییە تایبەتییەکانت بە وردی دەپارێزێت. هەروەها به‌رنامه‌كه‌ هیچ زانایارییەکت بەبێ ئاگاداری تۆ بەکارنابات.
\nسیاسەتی تایبەتی نیوپایپ بە وردی ڕوونکردنەوەت دەداتێ لەسەر ئەو زانیاریانەی وەریاندەگرێت و بەکاریاندەبات.</string> \nسیاسەتی تایبەتی نیوپایپ بە وردی ڕوونکردنەوەت دەداتێ لەسەر ئەو زانیاریانەی وەریاندەگرێت و بەکاریاندەبات.</string>
@ -672,7 +672,6 @@
<string name="show_crash_the_player_title">پیشاندانی ”کڕاش کردنی لێدەرەکە“</string> <string name="show_crash_the_player_title">پیشاندانی ”کڕاش کردنی لێدەرەکە“</string>
<string name="create_error_notification">سازاندنی پەیامی کێشەیەک</string> <string name="create_error_notification">سازاندنی پەیامی کێشەیەک</string>
<string name="manual_update_title">پشکنین بۆ نوێکردنەوە</string> <string name="manual_update_title">پشکنین بۆ نوێکردنەوە</string>
<string name="background_player_already_playing_toast">وا لە پاشبنەمادا لێدەدرێت</string>
<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="feed_new_items">بابەتە نوێیەکانی فیید</string> <string name="feed_new_items">بابەتە نوێیەکانی فیید</string>
@ -684,4 +683,29 @@
<string name="leak_canary_not_available">LeakCanary بەردەست نییە</string> <string name="leak_canary_not_available">LeakCanary بەردەست نییە</string>
<string name="no_appropriate_file_manager_message_android_10">هیچ ڕێکخەرێکی فایلی گونجاو نەدۆزرایەوە بۆ ئەم کردارە. <string name="no_appropriate_file_manager_message_android_10">هیچ ڕێکخەرێکی فایلی گونجاو نەدۆزرایەوە بۆ ئەم کردارە.
\nتکایە ڕێکخەرێکی فایلی دابمەزرێنە کە گونجاوبێت لەگەڵ دەسەڵاتی گەیشتن بە بیرگە.</string> \nتکایە ڕێکخەرێکی فایلی دابمەزرێنە کە گونجاوبێت لەگەڵ دەسەڵاتی گەیشتن بە بیرگە.</string>
<string name="check_new_streams">پشکنین کردن بۆ پەخشی نوێ</string>
<string name="enable_streams_notifications_title">پەیامەکانی پەخشە نوێیەکان</string>
<string name="enable_streams_notifications_summary">پەیام بکرێم لەکاتی هەبوونی پەخشی نوێی بەژدارییەکان</string>
<string name="streams_notifications_interval_title">فریکوێنسی دەپشکنرێت</string>
<string name="streams_notifications_network_title">پەیوەندی تۆڕ داواکراوە</string>
<string name="any_network">هەر تۆڕێک</string>
<string name="you_successfully_subscribed">تۆ ئێستا ئەم چەناڵەت بەژداری کردووە</string>
<string name="enumeration_comma">،</string>
<string name="streams_notification_channel_name">پەخشە نوێیەکان</string>
<string name="settings_category_player_notification_title">پەیامی لێدەر</string>
<string name="notifications">پەیامەکان</string>
<string name="streams_notification_channel_description">پەیامەکان بۆ پەخشە نوێیەکانی بەژدارییەکانت</string>
<string name="loading_stream_details">وردەکاری پەخش باردەکرێت…</string>
<plurals name="new_streams">
<item quantity="one">%s پەخشی نوێ</item>
<item quantity="other">%s پەخشانی نوێ</item>
</plurals>
<string name="settings_category_player_notification_summary">پەیامی ئێستای لێدانی پەخش ڕێکبخە</string>
<string name="delete_downloaded_files_confirm">هەموو فایلە دابەزێنراوەکان لە دیسک بسڕدرێتەوە؟</string>
<string name="notifications_disabled">پەیامەکان ناکاراکراون</string>
<string name="get_notified">پەیامم بکە</string>
<string name="progressive_load_interval_summary">"قەبارەی نێوان بارکردنەکە بگۆڕە (لە ئێستادا %s) . بەهایەکی کەمتر لەوانەیە بارکردنی ڤیدیۆی سەرەتایی خێراتر بکات. گۆڕانکارییەکان پێویستیان بە داگیرساندنەوەی لێدەر هەیە"</string>
<string name="percent">لەسەدا</string>
<string name="semitone">نیمچەتەن</string>
<string name="progressive_load_interval_exoplayer_default">بنەڕەتی ExoPlayer</string>
</resources> </resources>

View file

@ -684,7 +684,6 @@
<string name="create_error_notification">Vytvořit oznámení o chybě</string> <string name="create_error_notification">Vytvořit oznámení o chybě</string>
<string name="checking_updates_toast">Kontrola aktualizací…</string> <string name="checking_updates_toast">Kontrola aktualizací…</string>
<string name="show_crash_the_player_title">Ukázat „Shodit přehrávač“</string> <string name="show_crash_the_player_title">Ukázat „Shodit přehrávač“</string>
<string name="background_player_already_playing_toast">Hraje již v pozadí</string>
<string name="feed_new_items">Nové položky feedů</string> <string name="feed_new_items">Nové položky feedů</string>
<string name="no_appropriate_file_manager_message_android_10">Pro tuto akci nebyl nalezen žádný vhodný správce souborů. <string name="no_appropriate_file_manager_message_android_10">Pro tuto akci nebyl nalezen žádný vhodný správce souborů.
\nProsím, nainstalujte správce souborů kompatibilní se Storage Access Framework.</string> \nProsím, nainstalujte správce souborů kompatibilní se Storage Access Framework.</string>
@ -698,7 +697,30 @@
<string name="crash_the_player">Shodit přehrávač</string> <string name="crash_the_player">Shodit přehrávač</string>
<string name="progressive_load_interval_summary">Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače.</string> <string name="progressive_load_interval_summary">Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače.</string>
<string name="leak_canary_not_available">LeakCanary není dostupné</string> <string name="leak_canary_not_available">LeakCanary není dostupné</string>
<string name="adjust_by_semitones_checkbox">Upravit výšku tónů po půltónech</string>
<string name="playback_tempo_step">Krok tempa</string>
<string name="progressive_load_interval_exoplayer_default">Výchozí ExoPlayer</string> <string name="progressive_load_interval_exoplayer_default">Výchozí ExoPlayer</string>
<string name="settings_category_player_notification_summary">Nastavit oznámení k právě přehrávanému strýmu</string>
<string name="enable_streams_notifications_title">Oznámení o nových strýmech</string>
<string name="enable_streams_notifications_summary">Oznámit o nových strýmech k objednání</string>
<string name="streams_notifications_interval_title">Frekvence kontroly</string>
<string name="any_network">Jakákoli síť</string>
<string name="streams_notifications_network_title">Nutné síťové připojení</string>
<string name="delete_downloaded_files_confirm">Smazat všechny stažené soubory z disku\?</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="streams_notification_channel_name">Nové strýmy</string>
<string name="streams_notification_channel_description">Oznámení o nových strýmech k objednání</string>
<string name="check_new_streams">Spustit kontrolu nových strýmů</string>
<string name="settings_category_player_notification_title">Oznámení přehrávače</string>
<string name="notifications">Oznámení</string>
<string name="loading_stream_details">Načítám podrobnosti o strýmu…</string>
<string name="notifications_disabled">Oznámení jsou vypnuta</string>
<string name="get_notified">Přijímat oznámení</string>
<string name="enumeration_comma">,</string>
<plurals name="new_streams">
<item quantity="one">%s nový strým</item>
<item quantity="few">%s nové strýmy</item>
<item quantity="other">%s nových strýmů</item>
</plurals>
<string name="percent">Procento</string>
<string name="semitone">Půltón</string>
</resources> </resources>

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="main_bg_subtitle">Tryk på forstørrelsesglasset for at komme i gang.</string> <string name="main_bg_subtitle">Tryk på forstørrelsesglasset for at komme i gang.</string>
<string name="upload_date_text">Udgivet %1$s</string> <string name="upload_date_text">Udgivet den %1$s</string>
<string name="no_player_found">Ingen streamafspiller blev fundet. Installer VLC\?</string> <string name="no_player_found">Ingen streamafspiller blev fundet. Installér VLC\?</string>
<string name="no_player_found_toast">Ingen streamafspiller fundet (du kan installere VLC for at afspille den).</string> <string name="no_player_found_toast">Ingen streamafspiller fundet (du kan installere VLC for at afspille den).</string>
<string name="install">Installer</string> <string name="install">Installer</string>
<string name="cancel">Annuller</string> <string name="cancel">Annuller</string>
@ -13,13 +13,13 @@
<string name="controls_download_desc">Download stream-fil</string> <string name="controls_download_desc">Download stream-fil</string>
<string name="search">Søg</string> <string name="search">Søg</string>
<string name="settings">Indstillinger</string> <string name="settings">Indstillinger</string>
<string name="did_you_mean">Mente du: %1$s\?</string> <string name="did_you_mean">Mente du \"%1$s\"\?</string>
<string name="share_dialog_title">Del med</string> <string name="share_dialog_title">Del med</string>
<string name="use_external_video_player_title">Benyt ekstern videoafspiller</string> <string name="use_external_video_player_title">Benyt ekstern videoafspiller</string>
<string name="use_external_video_player_summary">Fjerner lyd ved NOGLE opløsninger</string> <string name="use_external_video_player_summary">Fjerner lyd ved nogle opløsninger</string>
<string name="use_external_audio_player_title">Brug ekstern lydafspiller</string> <string name="use_external_audio_player_title">Brug ekstern lydafspiller</string>
<string name="subscribe_button_title">Abonner</string> <string name="subscribe_button_title">Abonner</string>
<string name="subscribed_button_title">Abonneret</string> <string name="subscribed_button_title">Abonnerer</string>
<string name="unsubscribe">Afmeld abonnement</string> <string name="unsubscribe">Afmeld abonnement</string>
<string name="channel_unsubscribed">Abonnement afmeldt</string> <string name="channel_unsubscribed">Abonnement afmeldt</string>
<string name="subscription_change_failed">Kunne ikke ændre abonnement</string> <string name="subscription_change_failed">Kunne ikke ændre abonnement</string>
@ -33,17 +33,17 @@
<string name="controls_popup_title">Pop op</string> <string name="controls_popup_title">Pop op</string>
<string name="controls_add_to_playlist_title">Føj til</string> <string name="controls_add_to_playlist_title">Føj til</string>
<string name="download_path_title">Placering af videodownloads</string> <string name="download_path_title">Placering af videodownloads</string>
<string name="download_path_summary">Mappe som videoer skal downloades til</string> <string name="download_path_summary">Downloadede videoer gemmes her</string>
<string name="download_path_dialog_title">Angiv downloadmappe for videoer</string> <string name="download_path_dialog_title">Angiv downloadmappe for videofiler</string>
<string name="download_path_audio_title">Downloadmappe for lydfiler</string> <string name="download_path_audio_title">Downloadmappe for lydfiler</string>
<string name="download_path_audio_summary">Downloadede lydfiler bliver gemt her</string> <string name="download_path_audio_summary">Downloadede lydfiler gemmes her</string>
<string name="download_path_audio_dialog_title">Angiv downloadmappe for lydfiler</string> <string name="download_path_audio_dialog_title">Angiv downloadmappe for lydfiler</string>
<string name="default_resolution_title">Standardopløsning</string> <string name="default_resolution_title">Standardopløsning</string>
<string name="default_popup_resolution_title">Standardopløsning for pop op</string> <string name="default_popup_resolution_title">Standardopløsning for pop op</string>
<string name="show_higher_resolutions_title">Vis højere opløsninger</string> <string name="show_higher_resolutions_title">Vis højere opløsninger</string>
<string name="show_higher_resolutions_summary">Ikke alle enheder understøtter afspilning af 2K/4K-videoer</string> <string name="show_higher_resolutions_summary">Kun nogle enheder kan afspille 2K-/4K-videoer</string>
<string name="play_with_kodi_title">Afspil med Kodi</string> <string name="play_with_kodi_title">Afspil med Kodi</string>
<string name="kore_not_found">Kore-appen ikke fundet. Installer den\?</string> <string name="kore_not_found">Installer manglede Kore-app\?</string>
<string name="show_play_with_kodi_title">Vis valgmuligheden \"Afspil med Kodi\"</string> <string name="show_play_with_kodi_title">Vis valgmuligheden \"Afspil med Kodi\"</string>
<string name="show_play_with_kodi_summary">Vis en knap til at afspille en video via Kodi</string> <string name="show_play_with_kodi_summary">Vis en knap til at afspille en video via Kodi</string>
<string name="play_audio">Lyd</string> <string name="play_audio">Lyd</string>
@ -56,30 +56,30 @@
<string name="popup_remember_size_pos_title">Husk størrelse og placering af pop op</string> <string name="popup_remember_size_pos_title">Husk størrelse og placering af pop op</string>
<string name="popup_remember_size_pos_summary">Husk sidste størrelse og placering af pop op-afspiller</string> <string name="popup_remember_size_pos_summary">Husk sidste størrelse og placering af pop op-afspiller</string>
<string name="use_inexact_seek_title">Brug hurtig og upræcis søgning</string> <string name="use_inexact_seek_title">Brug hurtig og upræcis søgning</string>
<string name="use_inexact_seek_summary">Upræcis søgning lader afspilleren finde placeringer hurtigere, men mindre præcist</string> <string name="use_inexact_seek_summary">Upræcis søgning lader afspilleren finde placeringer hurtigere, men mindre præcist. Søgninger på 5, 15 eller 25 sekunder fungerer ikke med denne indstilling, slået til</string>
<string name="download_thumbnail_title">Indlæs miniaturebilleder</string> <string name="download_thumbnail_title">Indlæs miniaturebilleder</string>
<string name="download_thumbnail_summary">Slå fra for at undgå indlæsning af billeder, hvorved der spares data og hukommelse. Ændringer sletter billedcachen i både ram og lager.</string> <string name="download_thumbnail_summary">Slå fra for at undgå indlæsning af billeder, hvorved der spares data og hukommelse. Ændringer sletter billedcachen i både ram og lager</string>
<string name="thumbnail_cache_wipe_complete_notice">Billedcache slettet</string> <string name="thumbnail_cache_wipe_complete_notice">Billedcache slettet</string>
<string name="metadata_cache_wipe_title">Slet metadata-cachen</string> <string name="metadata_cache_wipe_title">Slet metadata-cachen</string>
<string name="metadata_cache_wipe_summary">Slet alle websidedata fra cachen</string> <string name="metadata_cache_wipe_summary">Slet alle websidedata fra cachen</string>
<string name="metadata_cache_wipe_complete_notice">Metadata-cache slettet</string> <string name="metadata_cache_wipe_complete_notice">Metadata-cache slettet</string>
<string name="auto_queue_title">Føj automatisk næste stream til køen</string> <string name="auto_queue_title">Føj automatisk næste stream til køen</string>
<string name="auto_queue_summary">Føj automatisk relaterede streams til køen når den sidste stream i en ikke-repeterende kø afspilles.</string> <string name="auto_queue_summary">Fortsæt nedlukningen af en (ikke-gentagende) playback kø ved at tilføje et relateret stream</string>
<string name="volume_gesture_control_title">Juster lydstyrke ved hjælp af fingerbevægelser</string> <string name="volume_gesture_control_title">Juster lydstyrke ved hjælp af fingerbevægelser</string>
<string name="volume_gesture_control_summary">Brug fingerbevægelser til at kontrollere lydstyrke</string> <string name="volume_gesture_control_summary">Brug fingerbevægelser til at kontrollere afspillerens lydstyrke</string>
<string name="brightness_gesture_control_title">Styr lysstyrken med fingerbevægelser</string> <string name="brightness_gesture_control_title">Styr lysstyrken med fingerbevægelser</string>
<string name="brightness_gesture_control_summary">Brug fingerbevægelser til at justere afspillerens lysstyrke</string> <string name="brightness_gesture_control_summary">Brug fingerbevægelser til at justere afspillerens lysstyrke</string>
<string name="show_search_suggestions_title">Søgeforslag</string> <string name="show_search_suggestions_title">Søgeforslag</string>
<string name="show_search_suggestions_summary">Vis forslag når der søges</string> <string name="show_search_suggestions_summary">Vælg forslagene, der vises, når der søges</string>
<string name="enable_search_history_title">Søgehistorik</string> <string name="enable_search_history_title">Søgehistorik</string>
<string name="enable_search_history_summary">Gem søgninger lokalt</string> <string name="enable_search_history_summary">Gem søgninger lokalt</string>
<string name="enable_watch_history_title">Historik og cache</string> <string name="enable_watch_history_title">Visningshistorik</string>
<string name="enable_watch_history_summary">Husk sete videoer</string> <string name="enable_watch_history_summary">Husk sete videoer</string>
<string name="resume_on_audio_focus_gain_title">Fortsæt når appen kommer i fokus</string> <string name="resume_on_audio_focus_gain_title">Fortsæt afspilning</string>
<string name="resume_on_audio_focus_gain_summary">Fortsæt afspilning efter afbrydelser (fx telefonopkald)</string> <string name="resume_on_audio_focus_gain_summary">Fortsæt afspilning efter afbrydelser (fx telefonopkald)</string>
<string name="download_dialog_title">Download</string> <string name="download_dialog_title">Download</string>
<string name="show_next_and_similar_title">Vis \'Næste\' og \'Lignende\' videoer</string> <string name="show_next_and_similar_title">Vis \'Næste\' og \'Lignende\' videoer</string>
<string name="show_hold_to_append_title">Vis \"Hold for at tilføje\"-tip</string> <string name="show_hold_to_append_title">Vis \"Hold for at sætte i kø\"-tip</string>
<string name="show_hold_to_append_summary">Vis et tip når der trykkes på baggrunds- eller pop op-knappen på siden med videodetaljer</string> <string name="show_hold_to_append_summary">Vis et tip når der trykkes på baggrunds- eller pop op-knappen på siden med videodetaljer</string>
<string name="unsupported_url">Denne webadresse er ikke understøttet</string> <string name="unsupported_url">Denne webadresse er ikke understøttet</string>
<string name="default_content_country_title">Standardland for indhold</string> <string name="default_content_country_title">Standardland for indhold</string>
@ -94,8 +94,8 @@
<string name="background_player_playing_toast">Afspiller i baggrunden</string> <string name="background_player_playing_toast">Afspiller i baggrunden</string>
<string name="popup_playing_toast">Afspiller i pop op-tilstand</string> <string name="popup_playing_toast">Afspiller i pop op-tilstand</string>
<string name="content">Indhold</string> <string name="content">Indhold</string>
<string name="show_age_restricted_content_title">Aldersbegrænset indhold</string> <string name="show_age_restricted_content_title">Vis aldersbegrænset indhold</string>
<string name="duration_live">LIVE</string> <string name="duration_live">Live</string>
<string name="downloads">Downloads</string> <string name="downloads">Downloads</string>
<string name="downloads_title">Downloads</string> <string name="downloads_title">Downloads</string>
<string name="error_report_title">Fejlrapport</string> <string name="error_report_title">Fejlrapport</string>
@ -117,29 +117,29 @@
<string name="always">Altid</string> <string name="always">Altid</string>
<string name="just_once">Kun én gang</string> <string name="just_once">Kun én gang</string>
<string name="file">Fil</string> <string name="file">Fil</string>
<string name="notification_channel_name">NewPipe-notifikation</string> <string name="notification_channel_name">NewPipe notifikation</string>
<string name="notification_channel_description">Notifikationer for NewPipes baggrunds- og pop op-afspillere</string> <string name="notification_channel_description">Notifikationer for NewPipes afspiller</string>
<string name="app_update_notification_channel_name">Notifikation om opdatering af app</string> <string name="app_update_notification_channel_name">Notifikation om opdatering af app</string>
<string name="app_update_notification_channel_description">Notifikationer for nye NewPipe-versioner</string> <string name="app_update_notification_channel_description">Notifikationer for nye NewPipe versioner</string>
<string name="unknown_content">[Ukendt]</string> <string name="unknown_content">[Ukendt]</string>
<string name="switch_to_background">Skift til baggrund</string> <string name="switch_to_background">Skift til baggrund</string>
<string name="switch_to_popup">Skift til pop op</string> <string name="switch_to_popup">Skift til pop op</string>
<string name="switch_to_main">Skift til hovedafspiller</string> <string name="switch_to_main">Skift til hovedafspiller</string>
<string name="import_data_title">Importer database</string> <string name="import_data_title">Importer database</string>
<string name="export_data_title">Eksporter database</string> <string name="export_data_title">Eksporter database</string>
<string name="import_data_summary">Overskriver din nuværende historik og abonnementer</string> <string name="import_data_summary">Overskriver din nuværende historik, abonnementer, spillelister og (hvis det ønskes) indstillinger</string>
<string name="export_data_summary">Eksporter historik, abonnementer og spillelister</string> <string name="export_data_summary">Eksporter historik, abonnementer, spillelister og indstillinger</string>
<string name="clear_views_history_title">Slet visningshistorik</string> <string name="clear_views_history_title">Slet visningshistorik</string>
<string name="clear_views_history_summary">Sletter historikken for viste videoer</string> <string name="clear_views_history_summary">Sletter historikken og positioner af tidligere viste videoer</string>
<string name="delete_view_history_alert">Slet hele visningshistorikken\?</string> <string name="delete_view_history_alert">Slet hele visningshistorikken\?</string>
<string name="watch_history_deleted">Visningshistorikken blev slettet.</string> <string name="watch_history_deleted">Visningshistorikken blev slettet</string>
<string name="clear_search_history_title">Slet søgehistorik</string> <string name="clear_search_history_title">Slet søgehistorik</string>
<string name="clear_search_history_summary">Sletter historikken for søgeord</string> <string name="clear_search_history_summary">Sletter historikken for søgeord</string>
<string name="delete_search_history_alert">Slet hele søgehistorikken\?</string> <string name="delete_search_history_alert">Slet hele søgehistorikken\?</string>
<string name="search_history_deleted">Søgehistorik slettet.</string> <string name="search_history_deleted">Søgehistorikken blev slettet</string>
<string name="general_error">Fejl</string> <string name="general_error">Fejl</string>
<string name="download_to_sdcard_error_title">Eksternt lager utilgængeligt</string> <string name="download_to_sdcard_error_title">Eksternt lager utilgængeligt</string>
<string name="download_to_sdcard_error_message">Download til eksternt SD-kort er endnu ikke muligt. Nulstil placering af download-mappe\?</string> <string name="download_to_sdcard_error_message">Det er endnu ikke muligt at downloade til et eksternt SD-kort. Nulstil download-mappens placering\?</string>
<string name="network_error">Netværksfejl</string> <string name="network_error">Netværksfejl</string>
<string name="could_not_load_thumbnails">Kunne ikke indlæse alle miniaturebilleder</string> <string name="could_not_load_thumbnails">Kunne ikke indlæse alle miniaturebilleder</string>
<string name="youtube_signature_deobfuscation_error">Kunne ikke dekryptere URL-signatur for video</string> <string name="youtube_signature_deobfuscation_error">Kunne ikke dekryptere URL-signatur for video</string>
@ -161,16 +161,16 @@
<string name="no_streams_available_download">Ingen streams er tilgængelige for download</string> <string name="no_streams_available_download">Ingen streams er tilgængelige for download</string>
<string name="saved_tabs_invalid_json">Bruger standardfaner pga. fejl ved indlæsning af gemte faner</string> <string name="saved_tabs_invalid_json">Bruger standardfaner pga. fejl ved indlæsning af gemte faner</string>
<string name="restore_defaults">Genskab standardindstillinger</string> <string name="restore_defaults">Genskab standardindstillinger</string>
<string name="restore_defaults_confirmation">Vil du genskabe standardindstillingerne\?</string> <string name="restore_defaults_confirmation">Vil du genoprette standardindstillingerne\?</string>
<string name="sorry_string">Undskyld, dette skulle ikke være sket.</string> <string name="sorry_string">Undskyld, dette skulle ikke være sket.</string>
<string name="error_report_button_text">Rapporter fejl via e-mail</string> <string name="error_report_button_text">Rapporter denne fejl via e-mail</string>
<string name="error_snackbar_message">Undskyld, nogle fejl opstod.</string> <string name="error_snackbar_message">Beklager, noget gik galt.</string>
<string name="error_snackbar_action">RAPPORTER</string> <string name="error_snackbar_action">Rapporter</string>
<string name="what_device_headline">Information:</string> <string name="what_device_headline">Information:</string>
<string name="what_happened_headline">Hvad skete der:</string> <string name="what_happened_headline">Hvad skete der:</string>
<string name="your_comment">Din kommentar (på engelsk):</string> <string name="your_comment">Din kommentar (på engelsk):</string>
<string name="error_details_headline">Detaljer:</string> <string name="error_details_headline">Detaljer:</string>
<string name="detail_thumbnail_view_description">Videominiaturebillede</string> <string name="detail_thumbnail_view_description">Afspil video, længde:</string>
<string name="detail_uploader_thumbnail_view_description">Uploaders profilbillede</string> <string name="detail_uploader_thumbnail_view_description">Uploaders profilbillede</string>
<string name="detail_likes_img_view_description">Synes godt om</string> <string name="detail_likes_img_view_description">Synes godt om</string>
<string name="detail_dislikes_img_view_description">Kan ikke lide</string> <string name="detail_dislikes_img_view_description">Kan ikke lide</string>
@ -201,8 +201,9 @@
<string name="msg_running_detail">Tryk for detaljer</string> <string name="msg_running_detail">Tryk for detaljer</string>
<string name="msg_wait">Vent venligst…</string> <string name="msg_wait">Vent venligst…</string>
<string name="msg_copied">Kopieret til udklipsholderen</string> <string name="msg_copied">Kopieret til udklipsholderen</string>
<string name="no_available_dir">Vælg venligst en tilgængelig downloadmappe</string> <string name="no_available_dir">Vælg senere en tilgængelig downloadmappe i indstillingerne</string>
<string name="msg_popup_permission">Denne tilladelse er nødvendig for at kunne åbne i pop op-tilstand</string> <string name="msg_popup_permission">Denne tilladelse behøves for
\nat åbne i pop op-tilstand</string>
<string name="one_item_deleted">1 element slettet.</string> <string name="one_item_deleted">1 element slettet.</string>
<string name="title_activity_recaptcha">reCAPTCHA-udfordring</string> <string name="title_activity_recaptcha">reCAPTCHA-udfordring</string>
<string name="recaptcha_request_toast">Der blev anmodet om en reCAPTCHA-udfordring</string> <string name="recaptcha_request_toast">Der blev anmodet om en reCAPTCHA-udfordring</string>
@ -223,7 +224,7 @@
<string name="contribution_encouragement">Hvad enten du har idéer til oversættelse, designændringer, kodeoprydning eller virkelig tunge kodeændringer, så er hjælp altid velkommen. Jo mere der bliver gjort, jo bedre bliver det!</string> <string name="contribution_encouragement">Hvad enten du har idéer til oversættelse, designændringer, kodeoprydning eller virkelig tunge kodeændringer, så er hjælp altid velkommen. Jo mere der bliver gjort, jo bedre bliver det!</string>
<string name="view_on_github">Se på GitHub</string> <string name="view_on_github">Se på GitHub</string>
<string name="donation_title">Doner</string> <string name="donation_title">Doner</string>
<string name="donation_encouragement">NewPipe er udviklet af frivillige der bruger tid på at give dig den bedste oplevelse. Giv noget tilbage for at hjælpe NewPipes udviklere til at gøre appen endnu bedre, mens de nyder en kop kaffe.</string> <string name="donation_encouragement">NewPipe er udviklet af frivillige, der bruger deres fritid på at give dig den bedst mulige brugeroplevelse. Giv noget tilbage for at hjælpe NewPipes udviklere til at gøre appen endnu bedre, mens de nyder en kop kaffe.</string>
<string name="give_back">Giv noget tilbage</string> <string name="give_back">Giv noget tilbage</string>
<string name="website_title">Websted</string> <string name="website_title">Websted</string>
<string name="website_encouragement">Besøg NewPipes websted for mere information og nyheder.</string> <string name="website_encouragement">Besøg NewPipes websted for mere information og nyheder.</string>
@ -292,7 +293,7 @@
<string name="missions_header_finished">Færdig</string> <string name="missions_header_finished">Færdig</string>
<string name="missions_header_pending">Afventning</string> <string name="missions_header_pending">Afventning</string>
<string name="post_processing">efterbehandling</string> <string name="post_processing">efterbehandling</string>
<string name="enqueue">Kø</string> <string name="enqueue">Læg i kø</string>
<string name="permission_denied">Handling afvist af systemet</string> <string name="permission_denied">Handling afvist af systemet</string>
<string name="download_failed">Download fejlede</string> <string name="download_failed">Download fejlede</string>
<string name="generate_unique_name">Generer unikt navn</string> <string name="generate_unique_name">Generer unikt navn</string>
@ -303,9 +304,9 @@
<string name="show_error">Vis fejl</string> <string name="show_error">Vis fejl</string>
<string name="error_file_creation">Filen kan ikke oprettes</string> <string name="error_file_creation">Filen kan ikke oprettes</string>
<string name="error_path_creation">Destinationsmappen kan ikke oprettes</string> <string name="error_path_creation">Destinationsmappen kan ikke oprettes</string>
<string name="error_ssl_exception">Sikker forbindelse fejlede</string> <string name="error_ssl_exception">Kunne ikke etablere en sikker forbindelse</string>
<string name="error_unknown_host">Kunne ikke finde serveren</string> <string name="error_unknown_host">Kunne ikke finde serveren</string>
<string name="error_connect_host">Kan ikke forbinde til serveren</string> <string name="error_connect_host">Kan ikke oprette forbindelse til serveren</string>
<string name="error_http_no_content">Serveren sender ikke data</string> <string name="error_http_no_content">Serveren sender ikke data</string>
<string name="error_http_unsupported_range">Serveren accepterer ikke multitrådede downloads; prøv igen med @string/msg_threads = 1</string> <string name="error_http_unsupported_range">Serveren accepterer ikke multitrådede downloads; prøv igen med @string/msg_threads = 1</string>
<string name="error_http_not_found">Ikke fundet</string> <string name="error_http_not_found">Ikke fundet</string>
@ -313,7 +314,7 @@
<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_subscription_feed_subtitle">Intet at se her</string> <string name="empty_subscription_feed_subtitle">Intet at se her</string>
<string name="short_thousand">T</string> <string name="short_thousand">t</string>
<string name="short_million">mio.</string> <string name="short_million">mio.</string>
<string name="short_billion">mia.</string> <string name="short_billion">mia.</string>
<plurals name="subscribers"> <plurals name="subscribers">
@ -324,18 +325,18 @@
<string name="subscriptions_import_unsuccessful">Kunne ikke importere abonnementer</string> <string name="subscriptions_import_unsuccessful">Kunne ikke importere abonnementer</string>
<string name="subscriptions_export_unsuccessful">Kunne ikke eksportere abonnementer</string> <string name="subscriptions_export_unsuccessful">Kunne ikke eksportere abonnementer</string>
<string name="conferences">Konferencer</string> <string name="conferences">Konferencer</string>
<string name="start_here_on_background">Start her når i baggrunden</string> <string name="start_here_on_background">Start afspilningen i baggrunden</string>
<string name="start_here_on_popup">Start her ved ny pop op</string> <string name="start_here_on_popup">Start afspilning i et pop op</string>
<string name="drawer_open">Åbn skuffe</string> <string name="drawer_open">Åbn skuffe</string>
<string name="drawer_close">Luk skuffe</string> <string name="drawer_close">Luk skuffe</string>
<string name="info_labels">Hvad:\\nForespørgsel:\\nIndholdssprog:\\nTjeneste:\\nGMT-tid:\\nPakke:\\nVersion:\\nOS-version:</string> <string name="info_labels">Hvad:\\nForespørgsel:\\nIndholdssprog:\\nIndholdsland:\\nAppsprog:\\nTjeneste:\\nGMT-tid:\\nPakke:\\nVersion:\\nOS-version:</string>
<string name="preferred_open_action_settings_summary">Standardhandling ved åbning af indhold — %s</string> <string name="preferred_open_action_settings_summary">Standardhandling når indhold åbnes %s</string>
<string name="set_as_playlist_thumbnail">Angiv som miniaturebillede for spilleliste</string> <string name="set_as_playlist_thumbnail">Anvend som playlistens miniature</string>
<string name="bookmark_playlist">Bogmærk spilleliste</string> <string name="bookmark_playlist">Bogmærk spilleliste</string>
<string name="unbookmark_playlist">Fjern bogmærke</string> <string name="unbookmark_playlist">Fjern bogmærke</string>
<string name="playlist_add_stream_success">Føjet til spillelisten</string> <string name="playlist_add_stream_success">Føjet til spillelisten</string>
<string name="playlist_thumbnail_change_success">Miniaturebillede for spilleliste ændret.</string> <string name="playlist_thumbnail_change_success">Miniaturebillede for spilleliste ændret.</string>
<string name="caption_setting_description">Ændr undertekststørrelse og baggrundsstil. Kræver genstart af appen for at træde i kraft.</string> <string name="caption_setting_description">Ændr undertekststørrelse og baggrundsstil. Kræver genstart af appen for at træde i kraft</string>
<string name="enable_leak_canary_summary">Monitorering for hukommelseslækager kan få appen til ikke at svare under heap dumping</string> <string name="enable_leak_canary_summary">Monitorering for hukommelseslækager kan få appen til ikke at svare under heap dumping</string>
<string name="enable_disposed_exceptions_title">Rapporter out-of-lifecycle-fejl</string> <string name="enable_disposed_exceptions_title">Rapporter out-of-lifecycle-fejl</string>
<string name="import_title">Importer</string> <string name="import_title">Importer</string>
@ -348,7 +349,11 @@
\n \n
\n1. Gå til denne webadresse: %1$s \n1. Gå til denne webadresse: %1$s
\n2. Log ind når du bliver bedt om det \n2. Log ind når du bliver bedt om det
\n3. En download bør starte (det er eksportfilen)</string> \n3. Klik på \"Alle Youtube-data medtages\" og fravælg alt bortset fra \"abonnementer\".
\n4. Klik på \"Næste trin\" og derefter \"Opret eksport\".
\n5. Klik på \"Download\" knappen efter den popper frem.
\n6. Klik på \"IMPORTER FIL\" nederst på denne side og vælg den downloadede .zip fil.
\n7. [Såfremt .zip-importeringen slår fejl] Uddrag .csv filen (som normalt findes i \"YouTube og YouTube Music/abonnementer/abonnementer.csv\"). Klik på \"IMPORTER FIL\" nederst på denne side, og vælg den uddragede .csv fil</string>
<string name="import_soundcloud_instructions_hint">ditID, soundcloud.com/ditID</string> <string name="import_soundcloud_instructions_hint">ditID, soundcloud.com/ditID</string>
<string name="import_network_expensive_warning">Bemærk at denne operation kan kræve meget netværkstrafik. <string name="import_network_expensive_warning">Bemærk at denne operation kan kræve meget netværkstrafik.
\n \n
@ -364,37 +369,288 @@
<string name="minimize_on_exit_none_description">Ingen</string> <string name="minimize_on_exit_none_description">Ingen</string>
<string name="minimize_on_exit_background_description">Minimer til baggrundsafspiller</string> <string name="minimize_on_exit_background_description">Minimer til baggrundsafspiller</string>
<string name="minimize_on_exit_popup_description">Minimer til pop op-afspiller</string> <string name="minimize_on_exit_popup_description">Minimer til pop op-afspiller</string>
<string name="app_update_notification_content_title">NewPipe-opdatering tilgængelig!</string> <string name="app_update_notification_content_title">En NewPipe-opdatering er tilgængelig!</string>
<string name="paused">sat på pause</string> <string name="paused">sat på pause</string>
<string name="queued">sat i kø</string> <string name="queued">sat i kø</string>
<string name="max_retry_msg">Maksimalt antal genforsøg</string> <string name="max_retry_msg">Maksimalt antal genforsøg</string>
<string name="max_retry_desc">Maksimalt antal forsøg før downloaden opgives</string> <string name="max_retry_desc">Maksimalt antal forsøg før downloaden opgives</string>
<string name="pause_downloads_on_mobile">Sæt på pause ved skift til mobildata</string> <string name="pause_downloads_on_mobile">Afbryd på forbrugsafregnede netværk</string>
<string name="pause_downloads_on_mobile_desc">Downloads som ikke kan sættes på pause vil blive genstartet</string> <string name="pause_downloads_on_mobile_desc">Nyttigt ved skift til mobildata, selv om nogle downloads ikke kan sættes på pause</string>
<string name="peertube_instance_add_https_only">Kun HTTPS URL-er understøttet</string> <string name="peertube_instance_add_https_only">Kun HTTPS adresser understøttes</string>
<string name="peertube_instance_add_exists">Instansen findes allerede</string> <string name="peertube_instance_add_exists">Instansen findes allerede</string>
<string name="peertube_instance_add_fail">Kunde ikke bekræfte instans</string> <string name="peertube_instance_add_fail">Kunne ikke validere instansen</string>
<string name="peertube_instance_add_help">Skriv ind instans-URL</string> <string name="peertube_instance_add_help">Skriv instansens adresse</string>
<string name="peertube_instance_add_title">Tilføj instans</string> <string name="peertube_instance_add_title">Tilføj instans</string>
<string name="peertube_instance_url_help">Finn instanserne du liger på %s</string> <string name="peertube_instance_url_help">Find de instanserne du kan lide på %s</string>
<string name="peertube_instance_url_summary">Vælg dine favorit-PeerTube-instanser</string> <string name="peertube_instance_url_summary">Vælg dine yndlings PeerTube-instanser</string>
<string name="peertube_instance_url_title">PeerTube-instanser</string> <string name="peertube_instance_url_title">PeerTube-instanser</string>
<string name="autoplay_title">Automatisk afspilning</string> <string name="autoplay_title">Afspil automatisk</string>
<string name="settings_category_clear_data_title">Tøm data</string> <string name="settings_category_clear_data_title">Ryd data</string>
<string name="enable_playback_state_lists_title">Positioner i lister</string> <string name="enable_playback_state_lists_title">Positioner i lister</string>
<string name="enable_playback_resume_summary">Genopret forrige afspilningsposition</string> <string name="enable_playback_resume_summary">Genopret forrige afspilningsposition</string>
<string name="enable_playback_resume_title">Fortsæt afspilning</string> <string name="enable_playback_resume_title">Fortsæt afspilning</string>
<string name="show_comments_summary">Skru av for at skjule kommentarer</string> <string name="show_comments_summary">Slå fra for at skjule kommentarer</string>
<string name="show_comments_title">Vis kommentarer</string> <string name="show_comments_title">Vis kommentarer</string>
<string name="notification_action_nothing">Ingenting</string> <string name="notification_action_nothing">Ingenting</string>
<string name="notification_action_repeat">Gentagelse</string> <string name="notification_action_repeat">Gentag</string>
<string name="notification_action_4_title">Femte handlingstast</string> <string name="notification_action_4_title">Femte handlingstast</string>
<string name="notification_action_3_title">Fjerde handlingstast</string> <string name="notification_action_3_title">Fjerde handlingstast</string>
<string name="notification_action_0_title">Første handlingstast</string> <string name="notification_action_0_title">Første handlingstast</string>
<string name="notification_action_1_title">Andre handlingstast</string> <string name="notification_action_1_title">Anden handlingstast</string>
<string name="notification_action_2_title">Tredje handlingstast</string> <string name="notification_action_2_title">Tredje handlingstast</string>
<string name="search_showing_result_for">Viser resultater for: %s</string> <string name="search_showing_result_for">Viser resultater for: %s</string>
<string name="open_with">Åben med</string> <string name="open_with">Åben med</string>
<string name="leak_canary_not_available">LeakCanary er ikke tilgængelig</string> <string name="leak_canary_not_available">LeakCanary er ikke tilgængelig</string>
<string name="mark_as_watched">Markér som set</string> <string name="mark_as_watched">Markér som set</string>
<string name="description_tab_description">Beskrivelse</string>
<string name="chapters">Kapitler</string>
<string name="notifications">Notifikationer</string>
<string name="help">Hjælp</string>
<string name="artists">Kunstnere</string>
<string name="close">Luk</string>
<string name="radio">Radio</string>
<string name="metadata_category">Kategori</string>
<string name="metadata_privacy">Privatliv</string>
<string name="percent">Procent</string>
<string name="feed_create_new_group_button_title">Ny</string>
<string name="systems_language">Systemstandard</string>
<string name="albums">Album</string>
<string name="songs">Sange</string>
<string name="videos_string">Videoer</string>
<string name="dont_show">Vis ikke</string>
<string name="notification_action_shuffle">Bland</string>
<string name="show_description_title">Vis beskrivelse</string>
<string name="open_website_license">Åbn hjemmeside</string>
<string name="metadata_language">Sprog</string>
<string name="low_quality_smaller">Lav kvalitet (mindre)</string>
<string name="autoplay_summary">Start afspilning automatisk — %s</string>
<string name="never">Aldrig</string>
<string name="wifi_only">Kun på Wi-Fi</string>
<plurals name="seconds">
<item quantity="one">%d sekund</item>
<item quantity="other">%d sekunder</item>
</plurals>
<plurals name="deleted_downloads_toast">
<item quantity="one">%1$s download slettet</item>
<item quantity="other">%1$s downloads slettet</item>
</plurals>
<string name="delete_downloaded_files_confirm">Slet alle downloadede filer fra drevet\?</string>
<string name="pause_downloads">Sæt downloads på pause</string>
<string name="start_main_player_fullscreen_title">Start hovedafspilleren i fuldskærmstilstand</string>
<string name="no_dir_yet">Downloadmappe endnu ikke valgt. Vælg standardmappen nu</string>
<string name="auto_queue_toggle">Læg automatisk i kø</string>
<string name="settings_category_player_notification_summary">Konfigurer det spillende streams notifikation</string>
<string name="show_age_restricted_content_summary">Vis aldersbegrænset indhold (f.eks. 18+)</string>
<string name="youtube_restricted_mode_enabled_title">Slå YouTube \"begrænset tilstand\" til</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube har en \"begrænset tilstand\" der skjuler videoer som potientielt er skadelige for børn</string>
<string name="restricted_video">Denne video er aldersbegrænset.
\n
\nSlå \"%1$s\" fra i indstillingerne hvis du vil se den.</string>
<string name="streams_notification_channel_name">Nye streams</string>
<string name="streams_notification_channel_description">Notifikationer om nye streams fra abonnementer</string>
<string name="recaptcha_cookies_cleared">reCAPTCHA cookies er ryddet</string>
<string name="delete_playback_states_alert">Slet alle playback positioner\?</string>
<string name="missing_file">Filen er flyttet eller slettet</string>
<string name="error_report_notification_title">NewPipe stødte ind i en fejl, tryk for at rapportere</string>
<string name="error_report_open_issue_button_text">Rapporter på GitHub</string>
<string name="high_quality_larger">Høj kvalitet (større)</string>
<string name="enable_queue_limit">Begræns downloadkøen</string>
<string name="clear_cookie_summary">Ryd de cookies som NewPipe opbevarer når du løser en reCAPTCHA</string>
<string name="notification_colorize_title">Farvelæg notifikationen</string>
<string name="settings_category_player_notification_title">Afspillernotifikation</string>
<string name="error_report_notification_toast">En fejl opstod, se notifikationen</string>
<string name="show_description_summary">Slå fra for at skjule videobeskrivelsen og yderligere information</string>
<string name="show_meta_info_summary">Slå fra for at gemme metainformationskasser med yderligere information om streammets skaber, streammets indhold eller en søgeforespørgsel</string>
<plurals name="download_finished_notification">
<item quantity="one">Download fuldført</item>
<item quantity="other">%s downloads fuldført</item>
</plurals>
<string name="progressive_load_interval_summary">Lav indlæsningsintervallets størrelse, (som nu ligger på %s) om. En højere værdi kan øge videoindlæsningshastigheden. Ændringer af værdien kræver genstart.</string>
<string name="clear_queue_confirmation_description">Den aktive spilleliste bliver udskiftet</string>
<string name="clear_queue_confirmation_summary">At skifte fra en afspiller til en anden kan udskifte din kø</string>
<string name="show_meta_info_title">Vis metainformation</string>
<string name="local_search_suggestions">Lokale søgeforslag</string>
<string name="remote_search_suggestions">Fjerne søgeforslag</string>
<string name="start_main_player_fullscreen_summary">Start ikke videoer i miniafspilleren, men gå direkte til fuldskærmstilstand, hvis automatisk rotering er låst. Du kan stadig se miniafspilleren, hvis du går ud af fuldskærmstilstand</string>
<string name="unsupported_url_dialog_message">Kunne ikke genkende addressen. Vil du åbne den i en anden app\?</string>
<string name="hash_channel_name">Videohashfunktion notifikation</string>
<string name="hash_channel_description">Notifikationer om videohashfunktioners status</string>
<string name="error_report_channel_name">Fejlrapport-notifikation</string>
<string name="error_report_channel_description">Notifikationer for at rapportere fejl</string>
<string name="clear_playback_states_title">Slet playback positioner</string>
<string name="clear_playback_states_summary">Sletter alle playback positioner</string>
<string name="downloads_storage_ask_title">Spørg hvor filen skal downloades</string>
<string name="enable_queue_limit_desc">Et download ad gangen</string>
<string name="delete_downloaded_files">Slet downloadede filer</string>
<string name="confirm_prompt">Vil du rydde din download historik eller slette alle downloadede filer\?</string>
<string name="error_download_resource_gone">Kan ikke gendanne dette download</string>
<string name="clear_download_history">Ryd download historik</string>
<string name="privacy_policy_encouragement">NewPipe projektet tager dit privatliv seriøst. Derfor samler appen intet data uden dit samtykke.
\nNewPipes fortrolighedspolitik forklarer i detaljer, hvilke data der bliver sendt og opbevaret når du sender en nedbrudsrapport.</string>
<string name="copy_for_github">Kopier en formatteret rapport</string>
<string name="permission_display_over_apps">Giv tilladelse til at vise over andre apps</string>
<string name="enable_playback_state_lists_summary">Vis playback positionsvisere i lister</string>
<string name="watch_history_states_deleted">Playback positioner slettet</string>
<string name="clear_cookie_title">Ryd reCAPTCHA cookies</string>
<string name="download_already_pending">Der er en afventende download med dette navn</string>
<string name="start_downloads">Start downloads</string>
<string name="notification_scale_to_square_image_title">Skaler miniaturebilledet til 1:1 format</string>
<string name="notification_scale_to_square_image_summary">Skaler notifikationsminiaturebillederne fra 16:9 til 1:1 format (dette kan medføre forvrængninger)</string>
<string name="notification_actions_summary">Rediger hver eneste varselshandling nedenunder ved at trykke på dem. Vælg op til tre af dem som bliver vist i den lille notifikation, via kasserne til højre</string>
<string name="notification_actions_at_most_three">Du kan kun vælge op til tre handlinger som kan vises i den lille notifikation!</string>
<string name="notification_action_buffering">Buffer</string>
<string name="notification_colorize_summary">Få Android til at vælge notifikationens farve ud fra den primære farve i miniaturebilledet (virker ikke på alle enheder)</string>
<string name="night_theme_title">Nattetema</string>
<string name="seek_duration_title">Frem- og tilbagesøgningstid</string>
<string name="restricted_video_no_stream">Denne video er aldersbegrænset.
\nPga. YouTubes politik om aldersbegrænsede videoer har NewPipe ikke adgang til videoen.</string>
<string name="crash_the_player">Crash afspilleren</string>
<string name="clear_queue_confirmation_title">Spørg om bekræftelse før du tømmer en kø</string>
<string name="seekbar_preview_thumbnail_title">Forhåndsvisning af miniaturebilleder på statuslinjen</string>
<string name="enqueue_next_stream">Sæt i kø som næste</string>
<string name="enqueued_next">Er sat som næste i køen</string>
<string name="download_has_started">Download er begyndt</string>
<string name="show_thumbnail_summary">Vis miniaturebilleder på både låseskærmen og notifikationer</string>
<string name="recent">Nylige</string>
<string name="notifications_disabled">Notifikationer er slået fra</string>
<string name="comments_tab_description">Kommentarer</string>
<string name="related_items_tab_description">Relaterede objekter</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="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">Spillekø</string>
<string name="show_channel_details">Vis kanalens detaljer</string>
<string name="enqueue_stream">Sæt i kø</string>
<string name="enqueued">Sat i kø</string>
<string name="loading_stream_details">Loader streamets detaljer…</string>
<string name="processing_may_take_a_moment">Processere... Det kan tage et øjeblik</string>
<string name="show_memory_leaks">Vis hukommelsestab</string>
<string name="disable_media_tunneling_title">Deaktiver medietunneler</string>
<string name="show_image_indicators_title">Vis billedindikatorer</string>
<string name="streams_notifications_network_title">Netværkskrav</string>
<string name="any_network">Alle netværk</string>
<string name="streams_notifications_interval_title">Kontrolfrekvens</string>
<string name="enable_streams_notifications_title">Notifikationer ved nye streams</string>
<string name="enable_streams_notifications_summary">Notifikationer om ny streams fra abonnomenter</string>
<string name="manual_update_description">Tjek manuelt efter opdateringer</string>
<string name="checking_updates_toast">Tjekker efter opdateringer…</string>
<string name="recovering">Gendanner</string>
<string name="feed_load_error_fast_unknown">\"Hurtig feed\"-tilstand viser ikke mere information om dette.</string>
<string name="manual_update_title">Tjek efter opdateringer</string>
<string name="remove_watched_popup_title">Fjern sete videoer\?</string>
<string name="disable_media_tunneling_summary">Deaktiver medietunneler hvis du oplever en sort skærm eller hak ved videoafspilning</string>
<string name="error_report_open_github_notice">Venligst tjek om der allerede eksisterer en problemrapport som diskuterer dit crash. Hvis du opretter duplikatrapporter, tager du tid fra os som vi kunne bruge på at fikse fejlen.</string>
<string name="check_new_streams">Tjek efter nye streams</string>
<string name="create_error_notification">Lav en fejlnotifikation</string>
<string name="local">Lokale</string>
<string name="feed_load_error_terminated">Udgiverens bruger er blevet slettet.
\nNewpipe kan ikke indlæse dette feed i fremtiden.
\nVil du fjerne dit abonnement på denne kanal\?</string>
<string name="feed_oldest_subscription_update">Feedet blev sidst opdateret for %s</string>
<string name="feed_subscription_not_loaded_count">Ikke indlæst: %d</string>
<string name="feed_notification_loading">Indlæser feed…</string>
<string name="feed_new_items">Nye feed elementer</string>
<string name="feed_update_threshold_summary">Tid siden sidste opdatering for at et abonnoment bliver forældet - %s</string>
<string name="feed_update_threshold_option_always_update">Altid opdater</string>
<string name="feed_group_dialog_select_subscriptions">Vælg abonnementer</string>
<string name="feed_toggle_show_played_items">Vis sete elementer</string>
<string name="georestricted_content">Dette indhold er ikke tilgængeligt i dit land</string>
<string name="video_detail_by">Af %s</string>
<string name="remove_watched_popup_warning">Videoer på playlisten som allerede er blevet set fjernes.
\nDette kan ikke fortrydes!</string>
<string name="show_thumbnail_title">Vis miniaturebillede</string>
<string name="metadata_tags">Tags</string>
<string name="metadata_age_limit">Aldersbegrænsning</string>
<string name="content_not_supported">Dette indhold er ikke understøttet af NewPipe.
\n
\nVi håber at kunne understøtte det i en fremtiden.</string>
<string name="paid_content">Dette indhold er kun tilgængeligt for brugere som har betalt for det. Det kan ikke blive streamet eller downloadet af NewPipe.</string>
<string name="account_terminated">Bruger slettet</string>
<string name="private_content">Dette indhold er privat, så det jan ikke blive streamet eller downloadet af NewPipe.</string>
<string name="recently_added">Nyligt tilføjede</string>
<string name="featured">Fremhævede</string>
<string name="service_provides_reason">%s giver denne grund:</string>
<plurals name="listening">
<item quantity="one">%s lytter</item>
<item quantity="other">%s lyttere</item>
</plurals>
<string name="more_than_100_videos">100+ videoer</string>
<string name="msg_calculating_hash">Udregner hash</string>
<string name="recaptcha_solve">Løs</string>
<string name="feed_group_dialog_empty_selection">Ingen abonnementer valgt</string>
<string name="feed_group_dialog_delete_message">Vil du slette denne gruppe\?</string>
<string name="metadata_licence">Licens</string>
<plurals name="new_streams">
<item quantity="one">%s nyt stream</item>
<item quantity="other">%s nye streams</item>
</plurals>
<string name="semitone">Semitone</string>
<plurals name="hours">
<item quantity="one">%d time</item>
<item quantity="other">%d timer</item>
</plurals>
<plurals name="days">
<item quantity="one">%d dag</item>
<item quantity="other">%d dage</item>
</plurals>
<string name="channel_created_by">Lavet af %s</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Slå hurtigtilstand fra</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Slå hurtigtilstand til</string>
<string name="feed_use_dedicated_fetch_method_title">Hent fra det dedikerede feed når det er muligt</string>
<string name="feed_update_threshold_title">Feed opdateringsgrænse</string>
<string name="settings_category_feed_title">Feed</string>
<plurals name="feed_group_dialog_selection_count">
<item quantity="one">%d valgt</item>
<item quantity="other">%d valgte</item>
</plurals>
<string name="feed_processing_message">Processerer feed…</string>
<string name="feed_groups_header_title">Kanalgrupper</string>
<plurals name="minutes">
<item quantity="one">%d minut</item>
<item quantity="other">%d minutter</item>
</plurals>
<string name="remove_watched">Fjern sete</string>
<string name="choose_instance_prompt">Vælg en instans</string>
<string name="error_timeout">Forbindelse afbrudt</string>
<string name="error_progress_lost">Fremskridt tabt fordi filen blev slettet</string>
<string name="error_postprocessing_stopped">NewPipe blev lukket under arbejde på filen</string>
<string name="overwrite_failed">Kan ikke overskrive filen</string>
<string name="start_accept_privacy_policy">For at være i overenstemmelse med GDPR fanger vi din opmærksomhed hentil NewPipes privatpolitik. Venligst læs den med omhu.
\nDu skal acceptere den for at sende os en fejlrapport.</string>
<string name="unhook_checkbox">Aflænk (kan skabe forvrængning)</string>
<string name="import_soundcloud_instructions">Importer en SoundCloud profil ved at skrive enten dit URL eller ID:
\n
\n1. Slå \"desktop-version\" til i mobilbrowsere.
\n2. Gå til denne adresse: %1$s
\n3. Log ind når du bliver spurgt
\n4. Kopier adressen på den profil du bliver henstillet til.</string>
<string name="show_original_time_ago_title">Vis den oprindelige tidsforskel på elementer</string>
<string name="playlist_no_uploader">Autogenereret (ingen uploader fundet)</string>
<string name="unmute">Slå lyd til</string>
<string name="mute">Sæt på lydløs</string>
<string name="most_liked">Mest likede</string>
<string name="error_unable_to_load_comments">Kunne ikke indlæse kommentarer</string>
<string name="default_kiosk_page_summary">Standard Kiosk</string>
<string name="recaptcha_done_button">Færdig</string>
<string name="subtitle_activity_recaptcha">Tryk på \"Færdig\" når den er løst</string>
<string name="no_comments">Ingen kommentarer</string>
<string name="infinite_videos">∞ videoer</string>
<string name="no_one_listening">Ingen lyttere</string>
<plurals name="watching">
<item quantity="one">%s seer</item>
<item quantity="other">%s seere</item>
</plurals>
<string name="no_one_watching">Ingen seere</string>
<string name="drawer_header_description">Skift service, nuværende valg:</string>
<string name="comments_are_disabled">Kommentarer er slået fra</string>
<string name="no_app_to_open_intent">Ingen apps på din enhed kan åbne dette</string>
<string name="error_insufficient_storage">Ingen ledig plads på enheden</string>
<string name="app_language_title">App sprog</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Ja, og delvist sete videoer</string>
<string name="feed_load_error">Fejl ved indlæsning af feed</string>
<string name="feed_load_error_account_info">Kunne ikke indlæse feed for \'%s\'.</string>
<string name="show_crash_the_player_title">Vis \"crash afspilleren\"</string>
<string name="crash_the_app">Crash appen</string>
<string name="show_crash_the_player_summary">Vis et crash alternativ når afspilleren er i brug</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">Veröffentlicht am %1$s</string> <string name="upload_date_text">Veröffentlicht am %1$s</string>
<string name="no_player_found">Kein Stream-Player gefunden. Möchtest du VLC installieren\?</string> <string name="no_player_found">Kein Stream-Player gefunden. Möchtest du den VLC installieren\?</string>
<string name="install">Installieren</string> <string name="install">Installieren</string>
<string name="cancel">Abbrechen</string> <string name="cancel">Abbrechen</string>
<string name="open_in_browser">Im Browser öffnen</string> <string name="open_in_browser">Im Browser öffnen</string>
@ -373,7 +373,7 @@
<string name="post_processing">Nachbearbeitung</string> <string name="post_processing">Nachbearbeitung</string>
<string name="enqueue">In Wiedergabe einreihen</string> <string name="enqueue">In Wiedergabe einreihen</string>
<string name="permission_denied">System verweigert den Zugriff</string> <string name="permission_denied">System verweigert den Zugriff</string>
<string name="download_failed">Herunterladen fehlgeschlagen</string> <string name="download_failed">Download fehlgeschlagen</string>
<string name="generate_unique_name">Eindeutigen Namen erzeugen</string> <string name="generate_unique_name">Eindeutigen Namen erzeugen</string>
<string name="overwrite">Überschreiben</string> <string name="overwrite">Überschreiben</string>
<string name="overwrite_unrelated_warning">Eine Datei mit diesem Namen existiert bereits</string> <string name="overwrite_unrelated_warning">Eine Datei mit diesem Namen existiert bereits</string>
@ -684,12 +684,9 @@
\nBitte installiere einen Dateimanager oder versuche, \'%s\' in den Downloadeinstellungen zu deaktivieren.</string> \nBitte installiere einen Dateimanager oder versuche, \'%s\' in den Downloadeinstellungen zu deaktivieren.</string>
<string name="no_appropriate_file_manager_message_android_10">Es wurde kein geeigneter Dateimanager für diese Aktion gefunden. <string name="no_appropriate_file_manager_message_android_10">Es wurde kein geeigneter Dateimanager für diese Aktion gefunden.
\nBitte installiere einen Storage Access Framework kompatiblen Dateimanager.</string> \nBitte installiere einen Storage Access Framework kompatiblen Dateimanager.</string>
<string name="background_player_already_playing_toast">Wird bereits im Hintergrund abgespielt</string>
<string name="detail_pinned_comment_view_description">Angehefteter Kommentar</string> <string name="detail_pinned_comment_view_description">Angehefteter Kommentar</string>
<string name="leak_canary_not_available">LeakCanary ist nicht verfügbar</string> <string name="leak_canary_not_available">LeakCanary ist nicht verfügbar</string>
<string name="adjust_by_semitones_checkbox">Tonhöhe nach musikalischen Halbtönen anpassen</string>
<string name="progressive_load_interval_summary">Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players.</string> <string name="progressive_load_interval_summary">Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players.</string>
<string name="playback_tempo_step">Geschwindigkeitsstufe</string>
<string name="progressive_load_interval_exoplayer_default">ExoPlayer Standard</string> <string name="progressive_load_interval_exoplayer_default">ExoPlayer Standard</string>
<string name="notifications">Benachrichtigungen</string> <string name="notifications">Benachrichtigungen</string>
<string name="streams_notification_channel_description">Benachrichtigen über neue abonnierbare Streams</string> <string name="streams_notification_channel_description">Benachrichtigen über neue abonnierbare Streams</string>
@ -710,5 +707,15 @@
<string name="delete_downloaded_files_confirm">Alle heruntergeladenen Dateien von der Festplatte löschen\?</string> <string name="delete_downloaded_files_confirm">Alle heruntergeladenen Dateien von der Festplatte löschen\?</string>
<string name="you_successfully_subscribed">Du hast jetzt diesen Kanal abonniert</string> <string name="you_successfully_subscribed">Du hast jetzt diesen Kanal abonniert</string>
<string name="toggle_all">Alle umschalten</string> <string name="toggle_all">Alle umschalten</string>
<string name="streams_notifications_interval_title">Aktualisierungsintervall</string> <string name="streams_notifications_interval_title">Prüfintervall</string>
<string name="percent">Prozent</string>
<string name="semitone">Halbton</string>
<string name="no_video_streams_available_for_external_players">Keine Videostreams für externe Player verfügbar</string>
<string name="select_quality_external_players">Qualität für externe Player auswählen</string>
<string name="unknown_format">Unbekanntes Format</string>
<string name="no_audio_streams_available_for_external_players">Keine Audiostreams für externe Player verfügbar</string>
<string name="unknown_quality">Unbekannte Qualität</string>
<string name="streams_not_yet_supported_removed">Streams, die noch nicht vom Downloader unterstützt werden, werden nicht angezeigt</string>
<string name="selected_stream_external_player_not_supported">Der ausgewählte Stream wird von externen Playern nicht unterstützt</string>
<string name="progressive_load_interval_title">Größe des Ladeintervalls für die Wiedergabe</string>
</resources> </resources>

View file

@ -682,11 +682,8 @@
\nΕγκαταστήστε έναν συμβατό με το Πλαίσιο Πρόσβασης Αποθήκευσης.</string> \nΕγκαταστήστε έναν συμβατό με το Πλαίσιο Πρόσβασης Αποθήκευσης.</string>
<string name="error_report_notification_title">Το NewPipe παρουσίασε ένα σφάλμα. Πατήστε για αναφορά</string> <string name="error_report_notification_title">Το NewPipe παρουσίασε ένα σφάλμα. Πατήστε για αναφορά</string>
<string name="show_error_snackbar">Εμφάνιση μιας snackbar σφάλματος</string> <string name="show_error_snackbar">Εμφάνιση μιας snackbar σφάλματος</string>
<string name="background_player_already_playing_toast">Αναπαράγεται ήδη στο παρασκήνιο</string>
<string name="detail_pinned_comment_view_description">Καρφιτσωμένο σχόλιο</string> <string name="detail_pinned_comment_view_description">Καρφιτσωμένο σχόλιο</string>
<string name="leak_canary_not_available">Το LeakCanary δεν είναι διαθέσιμο</string> <string name="leak_canary_not_available">Το LeakCanary δεν είναι διαθέσιμο</string>
<string name="adjust_by_semitones_checkbox">Προσαρμόστε τον τόνο με βάση τα μουσικά ημιτόνια</string>
<string name="playback_tempo_step">Βήμα τέμπο</string>
<string name="progressive_load_interval_exoplayer_default">Εξ\' ορισμού ExoPlayer</string> <string name="progressive_load_interval_exoplayer_default">Εξ\' ορισμού ExoPlayer</string>
<string name="progressive_load_interval_summary">Αλλάξτε το μέγεθος του διαστήματος φόρτωσης (επί του παρόντος είναι %s). Μια χαμηλότερη τιμή μπορεί να επιταχύνει την αρχική φόρτωση βίντεο. Οι αλλαγές απαιτούν επανεκκίνηση της εφαρμογής.</string> <string name="progressive_load_interval_summary">Αλλάξτε το μέγεθος του διαστήματος φόρτωσης (επί του παρόντος είναι %s). Μια χαμηλότερη τιμή μπορεί να επιταχύνει την αρχική φόρτωση βίντεο. Οι αλλαγές απαιτούν επανεκκίνηση της εφαρμογής.</string>
<string name="notifications">Ειδοποιήσεις</string> <string name="notifications">Ειδοποιήσεις</string>
@ -711,4 +708,13 @@
<string name="get_notified">Λάβετε ειδοποίηση</string> <string name="get_notified">Λάβετε ειδοποίηση</string>
<string name="you_successfully_subscribed">Έχετε εγγραφεί τώρα σε αυτό το κανάλι</string> <string name="you_successfully_subscribed">Έχετε εγγραφεί τώρα σε αυτό το κανάλι</string>
<string name="toggle_all">Εναλλαγή όλων</string> <string name="toggle_all">Εναλλαγή όλων</string>
<string name="percent">Τοις εκατό</string>
<string name="semitone">Ημιτόνιο</string>
<string name="streams_not_yet_supported_removed">Οι ροές που δεν υποστηρίζονται ακόμα από τον λήπτη δεν εμφανίζονται</string>
<string name="selected_stream_external_player_not_supported">Η επιλεγμένη ροή δεν υποστηρίζεται από εξωτερικούς αναπαραγωγούς</string>
<string name="no_audio_streams_available_for_external_players">Δεν διατίθενται ροές ήχου για εξωτερικούς αναπαραγωγούς</string>
<string name="no_video_streams_available_for_external_players">Δεν διατίθενται ροές βίντεο για εξωτερικούς αναπαραγωγούς</string>
<string name="select_quality_external_players">Επιλογή ποιότητας εξωτερικών αναπαραγωγών</string>
<string name="unknown_format">Άγνωστος τύπος αρχείου</string>
<string name="unknown_quality">Άγνωστη ποιότητα</string>
</resources> </resources>

View file

@ -292,7 +292,7 @@
<string name="metadata_cache_wipe_summary">Quitar todos los datos guardados de páginas web</string> <string name="metadata_cache_wipe_summary">Quitar todos los datos guardados de páginas web</string>
<string name="metadata_cache_wipe_complete_notice">Se vació la caché de metadatos</string> <string name="metadata_cache_wipe_complete_notice">Se vació la caché de metadatos</string>
<string name="playback_speed_control">Controles de velocidad de reproducción</string> <string name="playback_speed_control">Controles de velocidad de reproducción</string>
<string name="playback_tempo">Tiempo</string> <string name="playback_tempo">Tempo</string>
<string name="playback_pitch">Tono</string> <string name="playback_pitch">Tono</string>
<string name="unhook_checkbox">Desvincular (puede causar distorsión)</string> <string name="unhook_checkbox">Desvincular (puede causar distorsión)</string>
<string name="no_streams_available_download">No hay streams disponibles para descargar</string> <string name="no_streams_available_download">No hay streams disponibles para descargar</string>
@ -558,7 +558,7 @@
<string name="notification_action_buffering">Almacenar en memoria (búfer)</string> <string name="notification_action_buffering">Almacenar en memoria (búfer)</string>
<string name="notification_action_repeat">Repetir</string> <string name="notification_action_repeat">Repetir</string>
<string name="notification_actions_at_most_three">¡Puedes seleccionar como máximo tres acciones para mostrar en la notificación compacta!</string> <string name="notification_actions_at_most_three">¡Puedes seleccionar como máximo tres acciones para mostrar en la notificación compacta!</string>
<string name="notification_actions_summary">Edita cada acción de la notificación pulsando sobre ella. Selecciona hasta tres de ellas para mostrarlas en la notificación compacta usando las casillas de verificación a la derecha</string> <string name="notification_actions_summary">Edita cada acción de notificación debajo pulsando sobre ella. Selecciona hasta tres de ellas para que aparezcan en la notificación compacta usando las casillas de verificación a la derecha</string>
<string name="notification_action_4_title">Botón de quinta acción</string> <string name="notification_action_4_title">Botón de quinta acción</string>
<string name="notification_action_3_title">Botón de cuarta acción</string> <string name="notification_action_3_title">Botón de cuarta acción</string>
<string name="notification_action_2_title">Botón de tercera acción</string> <string name="notification_action_2_title">Botón de tercera acción</string>
@ -686,16 +686,40 @@
<string name="no_appropriate_file_manager_message_android_10">No se encontró ningún gestor de archivos adecuado para esta acción. <string name="no_appropriate_file_manager_message_android_10">No se encontró ningún gestor de archivos adecuado para esta acción.
\nPor favor instale un gestor de archivos compatible con \"Sistema de Acceso al Almacenamiento\".</string> \nPor favor instale un gestor de archivos compatible con \"Sistema de Acceso al Almacenamiento\".</string>
<string name="detail_pinned_comment_view_description">Comentario fijado</string> <string name="detail_pinned_comment_view_description">Comentario fijado</string>
<string name="background_player_already_playing_toast">Ya se reproduce en segundo plano</string>
<string name="leak_canary_not_available">LeakCanary no está disponible</string> <string name="leak_canary_not_available">LeakCanary no está disponible</string>
<string name="progressive_load_interval_exoplayer_default">ExoPlayer valor por defecto</string> <string name="progressive_load_interval_exoplayer_default">ExoPlayer valor por defecto</string>
<string name="playback_tempo_step">Paso de tempo</string>
<string name="progressive_load_interval_summary">Cambia el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial del vídeo. Los cambios requieren un reinicio del reproductor.</string> <string name="progressive_load_interval_summary">Cambia el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial del vídeo. Los cambios requieren un reinicio del reproductor.</string>
<string name="adjust_by_semitones_checkbox">Ajustar el tono por semitonos musicales</string>
<string name="notifications">Notificaciones</string> <string name="notifications">Notificaciones</string>
<string name="streams_notification_channel_name">Nuevos streams</string> <string name="streams_notification_channel_name">Nuevos streams</string>
<string name="settings_category_player_notification_title">Notificación del reproductor</string> <string name="settings_category_player_notification_title">Notificación del reproductor</string>
<string name="settings_category_player_notification_summary">Configurar notificación de la reproducción en curso</string> <string name="settings_category_player_notification_summary">Configurar notificación de la reproducción en curso</string>
<string name="you_successfully_subscribed">Ahora estás suscrito a este canal</string>
<string name="any_network">Cualquier red</string>
<string name="enumeration_comma">,</string>
<string name="check_new_streams">Comprobar la existencia de nuevos directos</string>
<string name="enable_streams_notifications_title">Notificaciones de nuevos directos</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="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="get_notified">Recibir notificaciones</string>
<string name="toggle_all">Conmutar todo</string>
<string name="loading_stream_details">Cargando detalles del directo…</string>
<string name="streams_notification_channel_description">Notificaciones sobre nuevos directos para suscriptores</string>
<string name="streams_notifications_network_title">Se requiere conexión a red</string>
<plurals name="new_streams">
<item quantity="one">%s nuevo directo</item>
<item quantity="other">%s nuevos directos</item>
</plurals>
<string name="percent">Porcentaje</string>
<string name="semitone">Semitono</string>
<string name="streams_not_yet_supported_removed">No se muestran flujos cuya descarga aún no está soportada</string>
<string name="selected_stream_external_player_not_supported">El flujo seleccionado no es soportado por reproductores externos</string>
<string name="no_audio_streams_available_for_external_players">No hay flujos de audio disponibles para reproductores externos</string>
<string name="no_video_streams_available_for_external_players">No hay flujos de video disponibles para reproductores externos</string>
<string name="select_quality_external_players">Elija la calidad para reproductores externos</string>
<string name="unknown_format">Formato desconocido</string>
<string name="unknown_quality">Calidad desconocida</string>
<!-- SponsorBlock --> <!-- SponsorBlock -->
<string name="sponsor_block">SponsorBlock</string> <string name="sponsor_block">SponsorBlock</string>
<string name="sponsor_block_home_page_title">Ver Sitio Web</string> <string name="sponsor_block_home_page_title">Ver Sitio Web</string>

View file

@ -2,8 +2,8 @@
<resources> <resources>
<string name="main_bg_subtitle">Alustamiseks toksa suurendusklaasi ikooni.</string> <string name="main_bg_subtitle">Alustamiseks toksa suurendusklaasi ikooni.</string>
<string name="upload_date_text">Avaldatud %1$s</string> <string name="upload_date_text">Avaldatud %1$s</string>
<string name="no_player_found">Voogesituseks puudub pleier. Kas paigaldada VLC?</string> <string name="no_player_found">Voogesituseks puudub pleier. Kas paigaldame VLC\?</string>
<string name="no_player_found_toast">Voogesituseks puudub pleier (selleks võib paigaldada VLC).</string> <string name="no_player_found_toast">Voogesituseks puudub pleier (selleks võid paigaldada VLC).</string>
<string name="install">Paigalda</string> <string name="install">Paigalda</string>
<string name="cancel">Tühista</string> <string name="cancel">Tühista</string>
<string name="open_in_browser">Ava veebilehitsejas</string> <string name="open_in_browser">Ava veebilehitsejas</string>
@ -305,7 +305,7 @@
<string name="title_activity_recaptcha">reCAPTCHA nõue</string> <string name="title_activity_recaptcha">reCAPTCHA nõue</string>
<string name="recaptcha_request_toast">reCAPTCHA nõude taotlus</string> <string name="recaptcha_request_toast">reCAPTCHA nõude taotlus</string>
<string name="copyright" formatted="true">© %1$s %2$s %3$s alla</string> <string name="copyright" formatted="true">© %1$s %2$s %3$s alla</string>
<string name="app_description">Vaba kergekaaluline Androidi voogesitus.</string> <string name="app_description">Vaba ja lihtne voogesitus Androidis.</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="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 arendajad on vabatahtlikud, kes kulutavad oma vaba aega, toomaks sulle parimat kasutamise kogemust. On aeg anda tagasi aidates arendajaid ja muuta NewPipe veel paremaks, nautides ise tassi kohvi.</string> <string name="donation_encouragement">NewPipe arendajad on vabatahtlikud, kes kulutavad oma vaba aega, toomaks sulle parimat kasutamise kogemust. 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>
@ -674,7 +674,6 @@
<string name="error_report_notification_title">NewPipe töös tekkis viga, sellest teavitamiseks klõpsi</string> <string name="error_report_notification_title">NewPipe töös tekkis viga, sellest teavitamiseks klõpsi</string>
<string name="crash_the_player">Jooksuta meediamängija kokku</string> <string name="crash_the_player">Jooksuta meediamängija kokku</string>
<string name="show_error_snackbar">Näita veateate akent</string> <string name="show_error_snackbar">Näita veateate akent</string>
<string name="background_player_already_playing_toast">Meedia esitamine taustal toimib juba</string>
<string name="error_report_channel_name">Teavitus vigadest</string> <string name="error_report_channel_name">Teavitus vigadest</string>
<string name="error_report_channel_description">Teavitused vigadest informeerimiseks</string> <string name="error_report_channel_description">Teavitused vigadest informeerimiseks</string>
<string name="error_report_notification_toast">Tekkis viga, vaata vastavat teadet</string> <string name="error_report_notification_toast">Tekkis viga, vaata vastavat teadet</string>
@ -709,6 +708,13 @@
<string name="you_successfully_subscribed">Sa oled nüüd selle kanali tellija</string> <string name="you_successfully_subscribed">Sa oled nüüd selle kanali tellija</string>
<string name="enumeration_comma">,</string> <string name="enumeration_comma">,</string>
<string name="toggle_all">Lülita kõik sisse</string> <string name="toggle_all">Lülita kõik sisse</string>
<string name="adjust_by_semitones_checkbox">Reguleeri helikõrgust muusikaliste pooltoonide kaupa</string> <string name="no_audio_streams_available_for_external_players">Välise pleieri jaoks ei leidu sobilikke helivoogusid</string>
<string name="playback_tempo_step">Tempo samm</string> <string name="streams_not_yet_supported_removed">Need meediavood, mida allalaadija ei oska kasutada, on peidetud</string>
<string name="no_video_streams_available_for_external_players">Välise pleieri jaoks ei leidu sobilikke videovoogusid</string>
<string name="select_quality_external_players">Vali välis pleieri jaoks sobilik kvaliteet</string>
<string name="unknown_format">Tundmatu vorming</string>
<string name="unknown_quality">Teadmata kvaliteet</string>
<string name="selected_stream_external_player_not_supported">Valitud meediavood ei ole toetatud välises pleieris</string>
<string name="percent">Protsent</string>
<string name="semitone">Pooltoon</string>
</resources> </resources>

View file

@ -666,7 +666,6 @@
<string name="enqueue_next_stream">Gehitu bideo hau isatsari</string> <string name="enqueue_next_stream">Gehitu bideo hau isatsari</string>
<string name="show_crash_the_player_title">Erakutsi \"Itxi erreproduzigailua\"</string> <string name="show_crash_the_player_title">Erakutsi \"Itxi erreproduzigailua\"</string>
<string name="processing_may_take_a_moment">Prozesatzen... Itxoin mesedez</string> <string name="processing_may_take_a_moment">Prozesatzen... Itxoin mesedez</string>
<string name="background_player_already_playing_toast">Atzeko planoan erreproduzitzen dagoeneko</string>
<string name="error_report_channel_name">Erroreen txostenen jakinarazpena</string> <string name="error_report_channel_name">Erroreen txostenen jakinarazpena</string>
<string name="error_report_channel_description">Jakinarazpenak erroreen berri emateko</string> <string name="error_report_channel_description">Jakinarazpenak erroreen berri emateko</string>
<string name="error_report_notification_title">NewPipe-k errore bat aurkitu du, sakatu berri emateko</string> <string name="error_report_notification_title">NewPipe-k errore bat aurkitu du, sakatu berri emateko</string>
@ -695,8 +694,6 @@
<string name="you_successfully_subscribed">Kanal honetara harpidetu zara</string> <string name="you_successfully_subscribed">Kanal honetara harpidetu zara</string>
<string name="enumeration_comma">,</string> <string name="enumeration_comma">,</string>
<string name="toggle_all">Txandakatu denak</string> <string name="toggle_all">Txandakatu denak</string>
<string name="adjust_by_semitones_checkbox">Doitu tonua semitono musikalen arabera</string>
<string name="playback_tempo_step">Tempo urratsa</string>
<string name="progressive_load_interval_summary">Aldatu karga maiztasun tamaina (unean %s). Balio txikiago batek bideoaren hasierako karga azkartu dezake. Erreproduzigailuaren berrabiarazte bat behar du.</string> <string name="progressive_load_interval_summary">Aldatu karga maiztasun tamaina (unean %s). Balio txikiago batek bideoaren hasierako karga azkartu dezake. Erreproduzigailuaren berrabiarazte bat behar du.</string>
<string name="enable_streams_notifications_summary">Harpidetzen jario berriei buruz jakinarazi</string> <string name="enable_streams_notifications_summary">Harpidetzen jario berriei buruz jakinarazi</string>
<string name="delete_downloaded_files_confirm">Ezabatu deskargatutako fitxategi guztiak biltegitik\?</string> <string name="delete_downloaded_files_confirm">Ezabatu deskargatutako fitxategi guztiak biltegitik\?</string>
@ -711,4 +708,6 @@
<string name="get_notified">Jakinarazi</string> <string name="get_notified">Jakinarazi</string>
<string name="progressive_load_interval_exoplayer_default">ExoPlayer lehenetsia</string> <string name="progressive_load_interval_exoplayer_default">ExoPlayer lehenetsia</string>
<string name="streams_notifications_network_title">Beharrezko sare konexioa</string> <string name="streams_notifications_network_title">Beharrezko sare konexioa</string>
<string name="percent">Portzentaia</string>
<string name="semitone">Semitonoa</string>
</resources> </resources>

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