1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-04-16 07:43:18 +00:00

Merge branch 'dev' into Merge-dev-to-refactor

# Conflicts:
#	app/build.gradle
#	app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
#	app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt
#	app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
#	app/src/main/java/org/schabi/newpipe/player/PlayerService.java
#	app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java
#	app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java
#	app/src/main/res/values-is/strings.xml
This commit is contained in:
Isira Seneviratne 2025-04-08 05:17:03 +05:30
commit 6486f2de56
131 changed files with 1986 additions and 677 deletions

View File

@ -5,10 +5,17 @@
## Rules for NewPipeExtractor
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
## Rules for Rhino and Rhino Engine
-keep class org.mozilla.javascript.* { *; }
-keep class org.mozilla.javascript.** { *; }
-keep class org.mozilla.javascript.engine.** { *; }
-keep class org.mozilla.classfile.ClassFileWriter
-dontwarn org.mozilla.javascript.JavaToJSONConverters
-dontwarn org.mozilla.javascript.tools.**
-keep class javax.script.** { *; }
-dontwarn javax.script.**
-keep class jdk.dynalink.** { *; }
-dontwarn jdk.dynalink.**
## Rules for ExoPlayer
-keep class com.google.android.exoplayer2.** { *; }

View File

@ -64,6 +64,9 @@
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<activity
@ -429,5 +432,10 @@
<meta-data
android:name="com.samsung.android.multidisplay.keep_process_alive"
android:value="true" />
<!-- Android Auto -->
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@mipmap/ic_launcher" />
</application>
</manifest>

View File

@ -38,6 +38,7 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
@ -136,6 +137,19 @@ public class MainActivity extends AppCompatActivity {
ThemeHelper.setDayNightMode(this);
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
// Fixes text color turning black in dark/black mode:
// https://github.com/TeamNewPipe/NewPipe/issues/12016
// For further reference see: https://issuetracker.google.com/issues/37124582
if (DeviceUtils.supportsWebView()) {
try {
new WebView(this);
} catch (final Throwable e) {
if (DEBUG) {
Log.e(TAG, "Failed to create WebView", e);
}
}
}
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState);
@ -826,7 +840,8 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onReceive(final Context context, final Intent intent) {
if (Objects.equals(intent.getAction(),
VideoDetailFragment.ACTION_PLAYER_STARTED)) {
VideoDetailFragment.ACTION_PLAYER_STARTED)
&& PlayerHolder.getInstance().isPlayerOpen()) {
openMiniPlayerIfMissing();
// At this point the player is added 100%, we can unregister. Other actions
// are useless since the fragment will not be removed after that.
@ -838,6 +853,10 @@ public class MainActivity extends AppCompatActivity {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED);
registerReceiver(broadcastReceiver, intentFilter);
// If the PlayerHolder is not bound yet, but the service is running, try to bind to it.
// Once the connection is established, the ACTION_PLAYER_STARTED will be sent.
PlayerHolder.getInstance().tryBindIfNeeded(this);
}
}

View File

@ -3,6 +3,8 @@ package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo
import androidx.room.Embedded
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.image.ImageStrategy
import java.time.OffsetDateTime
data class StreamHistoryEntry(
@ -27,4 +29,17 @@ data class StreamHistoryEntry(
return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId &&
accessDate.isEqual(other.accessDate)
}
fun toStreamInfoItem(): StreamInfoItem =
StreamInfoItem(
streamEntity.serviceId,
streamEntity.url,
streamEntity.title,
streamEntity.streamType,
).apply {
duration = streamEntity.duration
uploaderName = streamEntity.uploader
uploaderUrl = streamEntity.uploaderUrl
thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
}
}

View File

@ -1,5 +1,7 @@
package org.schabi.newpipe.database.playlist;
import androidx.annotation.Nullable;
import org.schabi.newpipe.database.LocalItem;
public interface PlaylistLocalItem extends LocalItem {
@ -10,4 +12,7 @@ public interface PlaylistLocalItem extends LocalItem {
long getUid();
void setDisplayIndex(long displayIndex);
@Nullable
String getThumbnailUrl();
}

View File

@ -9,6 +9,8 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
import androidx.annotation.Nullable;
public class PlaylistMetadataEntry implements PlaylistLocalItem {
public static final String PLAYLIST_STREAM_COUNT = "streamCount";
@ -71,4 +73,10 @@ public class PlaylistMetadataEntry implements PlaylistLocalItem {
public void setDisplayIndex(final long displayIndex) {
this.displayIndex = displayIndex;
}
@Nullable
@Override
public String getThumbnailUrl() {
return thumbnailUrl;
}
}

View File

@ -34,7 +34,7 @@ public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
+ REMOTE_PLAYLIST_ID + " = :playlistId")
Flowable<List<PlaylistRemoteEntity>> getPlaylist(long playlistId);
Flowable<PlaylistRemoteEntity> getPlaylist(long playlistId);
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
+ REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")

View File

@ -2,6 +2,7 @@ package org.schabi.newpipe.database.playlist.model;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
@ -134,6 +135,8 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
this.name = name;
}
@Nullable
@Override
public String getThumbnailUrl() {
return thumbnailUrl;
}

View File

@ -95,8 +95,7 @@ import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.PlayerService;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.event.PlayerHolderLifecycleEventListener;
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
@ -137,8 +136,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
public final class VideoDetailFragment
extends BaseStateFragment<StreamInfo>
implements BackPressable,
PlayerServiceEventListener,
PlayerHolderLifecycleEventListener,
PlayerServiceExtendedEventListener,
OnKeyDownListener {
public static final String KEY_SWITCHING_PLAYERS = "switching_players";
@ -236,10 +234,14 @@ public final class VideoDetailFragment
// Service management
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onServiceConnected(final PlayerService connectedPlayerService,
final boolean playAfterConnect) {
player = connectedPlayerService.getPlayer();
public void onServiceConnected(@NonNull final PlayerService connectedPlayerService) {
playerService = connectedPlayerService;
}
@Override
public void onPlayerConnected(@NonNull final Player connectedPlayer,
final boolean playAfterConnect) {
player = connectedPlayer;
// It will do nothing if the player is not in fullscreen mode
hideSystemUiIfNeeded();
@ -271,11 +273,18 @@ public final class VideoDetailFragment
updateOverlayPlayQueueButtonVisibility();
}
@Override
public void onPlayerDisconnected() {
player = null;
// the binding could be null at this point, if the app is finishing
if (binding != null) {
restoreDefaultBrightness();
}
}
@Override
public void onServiceDisconnected() {
playerService = null;
player = null;
restoreDefaultBrightness();
}
@ -394,7 +403,7 @@ public final class VideoDetailFragment
if (activity.isFinishing() && isPlayerAvailable() && player.videoPlayerSelected()) {
playerHolder.stopService();
} else {
playerHolder.unsetListeners();
playerHolder.setListener(null);
}
PreferenceManager.getDefaultSharedPreferences(activity)
@ -659,10 +668,10 @@ public final class VideoDetailFragment
});
setupBottomPlayer();
if (playerHolder.isNotBoundYet()) {
if (!playerHolder.isBound()) {
setHeightThumbnail();
} else {
playerHolder.startService(false, this, this);
playerHolder.startService(false, this);
}
}
@ -1053,7 +1062,7 @@ public final class VideoDetailFragment
// See UI changes while remote playQueue changes
if (!isPlayerAvailable()) {
playerHolder.startService(false, this, this);
playerHolder.startService(false, this);
} else {
// FIXME Workaround #7427
player.setRecovery();
@ -1116,7 +1125,7 @@ public final class VideoDetailFragment
private void openNormalBackgroundPlayer(final boolean append) {
// See UI changes while remote playQueue changes
if (!isPlayerAvailable()) {
playerHolder.startService(false, this, this);
playerHolder.startService(false, this);
}
final PlayQueue queue = setupPlayQueueForIntent(append);
@ -1130,7 +1139,7 @@ public final class VideoDetailFragment
private void openMainPlayer() {
if (!isPlayerServiceAvailable()) {
playerHolder.startService(autoPlayEnabled, this, this);
playerHolder.startService(autoPlayEnabled, this);
return;
}
if (currentInfo == null) {
@ -1385,11 +1394,9 @@ public final class VideoDetailFragment
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
// Rebound to the service if it was closed via notification or mini player
if (playerHolder.isNotBoundYet()) {
if (!playerHolder.isBound()) {
playerHolder.startService(
false,
VideoDetailFragment.this,
VideoDetailFragment.this);
false, VideoDetailFragment.this);
}
break;
}
@ -1838,13 +1845,16 @@ public final class VideoDetailFragment
@Override
public void onServiceStopped() {
setOverlayPlayPauseImage(false);
if (currentInfo != null) {
updateOverlayData(currentInfo.getName(),
currentInfo.getUploaderName(),
currentInfo.getThumbnails());
// the binding could be null at this point, if the app is finishing
if (binding != null) {
setOverlayPlayPauseImage(false);
if (currentInfo != null) {
updateOverlayData(currentInfo.getName(),
currentInfo.getUploaderName(),
currentInfo.getThumbnails());
}
updateOverlayPlayQueueButtonVisibility();
}
updateOverlayPlayQueueButtonVisibility();
}
@Override

View File

@ -121,67 +121,6 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
menuProvider = new MenuProvider() {
@Override
public void onCreateMenu(@NonNull final Menu menu,
@NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.menu_channel, menu);
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
}
@Override
public void onPrepareMenu(@NonNull final Menu menu) {
menuRssButton = menu.findItem(R.id.menu_item_rss);
menuNotifyButton = menu.findItem(R.id.menu_item_notify);
updateRssButton();
updateNotifyButton(channelSubscription);
}
@Override
public boolean onMenuItemSelected(@NonNull final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_notify:
final boolean value = !item.isChecked();
item.setEnabled(false);
setNotify(value);
break;
case R.id.action_settings:
NavigationHelper.openSettings(requireContext());
break;
case R.id.menu_item_rss:
if (currentInfo != null) {
ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl());
}
break;
case R.id.menu_item_openInBrowser:
if (currentInfo != null) {
ShareUtils.openUrlInBrowser(requireContext(),
currentInfo.getOriginalUrl());
}
break;
case R.id.menu_item_share:
if (currentInfo != null) {
ShareUtils.shareText(requireContext(), name,
currentInfo.getOriginalUrl(), currentInfo.getAvatars());
}
break;
default:
return false;
}
return true;
}
};
activity.addMenuProvider(menuProvider);
}
@Override
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
@ -196,6 +135,67 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
menuProvider = new MenuProvider() {
@Override
public void onCreateMenu(@NonNull final Menu menu,
@NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.menu_channel, menu);
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
}
@Override
public void onPrepareMenu(@NonNull final Menu menu) {
menuRssButton = menu.findItem(R.id.menu_item_rss);
menuNotifyButton = menu.findItem(R.id.menu_item_notify);
updateRssButton();
updateNotifyButton(channelSubscription);
}
@Override
public boolean onMenuItemSelected(@NonNull final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_notify:
final boolean value = !item.isChecked();
item.setEnabled(false);
setNotify(value);
break;
case R.id.action_settings:
NavigationHelper.openSettings(requireContext());
break;
case R.id.menu_item_rss:
if (currentInfo != null) {
ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl());
}
break;
case R.id.menu_item_openInBrowser:
if (currentInfo != null) {
ShareUtils.openUrlInBrowser(requireContext(),
currentInfo.getOriginalUrl());
}
break;
case R.id.menu_item_share:
if (currentInfo != null) {
ShareUtils.shareText(requireContext(), name,
currentInfo.getOriginalUrl(), currentInfo.getAvatars());
}
break;
default:
return false;
}
return true;
}
};
activity.addMenuProvider(menuProvider);
}
@Override // called from onViewCreated in BaseFragment.onViewCreated
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
@ -238,6 +238,14 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
binding.subChannelTitleView.setOnClickListener(openSubChannel);
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (menuProvider != null) {
activity.removeMenuProvider(menuProvider);
}
}
@Override
public void onDestroy() {
super.onDestroy();
@ -246,7 +254,6 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
}
disposables.clear();
binding = null;
activity.removeMenuProvider(menuProvider);
menuProvider = null;
}

View File

@ -7,3 +7,16 @@ import java.io.Serializable
inline fun <reified T : Serializable> Bundle.serializable(key: String?): T? {
return BundleCompat.getSerializable(this, key, T::class.java)
}
fun Bundle?.toDebugString(): String {
if (this == null) {
return "null"
}
val string = StringBuilder("Bundle{")
for (key in this.keySet()) {
@Suppress("DEPRECATION") // we want this[key] to return items of any type
string.append(" ").append(key).append(" => ").append(this[key]).append(";")
}
string.append(" }")
return string.toString()
}

View File

@ -17,8 +17,10 @@ import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import org.schabi.newpipe.MainActivity
// logs in this class are disabled by default since it's usually not useful,
// you can enable them by setting this flag to MainActivity.DEBUG
private const val DEBUG = false
private const val TAG = "ViewUtils"
/**
@ -38,7 +40,7 @@ fun View.animate(
delay: Long = 0,
execOnEnd: Runnable? = null
) {
if (MainActivity.DEBUG) {
if (DEBUG) {
val id = try {
resources.getResourceEntryName(id)
} catch (e: Exception) {
@ -51,7 +53,7 @@ fun View.animate(
Log.d(TAG, "animate(): $msg")
}
if (isVisible && enterOrExit) {
if (MainActivity.DEBUG) {
if (DEBUG) {
Log.d(TAG, "animate(): view was already visible > view = [$this]")
}
animate().setListener(null).cancel()
@ -60,7 +62,7 @@ fun View.animate(
execOnEnd?.run()
return
} else if ((isGone || isInvisible) && !enterOrExit) {
if (MainActivity.DEBUG) {
if (DEBUG) {
Log.d(TAG, "animate(): view was already gone > view = [$this]")
}
animate().setListener(null).cancel()
@ -89,7 +91,7 @@ fun View.animate(
* @param colorEnd the background color to end with
*/
fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) {
if (MainActivity.DEBUG) {
if (DEBUG) {
Log.d(
TAG,
"animateBackgroundColor() called with: view = [$this], duration = [$duration], " +
@ -109,7 +111,7 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo
}
fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator {
if (MainActivity.DEBUG) {
if (DEBUG) {
Log.d(TAG, "animateHeight: duration = [$duration], from $height to → $targetHeight in: $this")
}
val animator = ValueAnimator.ofFloat(height.toFloat(), targetHeight.toFloat())
@ -127,7 +129,7 @@ fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator {
}
fun View.animateRotation(duration: Long, targetRotation: Int) {
if (MainActivity.DEBUG) {
if (DEBUG) {
Log.d(TAG, "animateRotation: duration = [$duration], from $rotation to → $targetRotation in: $this")
}
animate().setListener(null).cancel()

View File

@ -0,0 +1,72 @@
package org.schabi.newpipe.local.playlist
import android.content.Context
import org.schabi.newpipe.R
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry
import org.schabi.newpipe.extractor.exceptions.ParsingException
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory
import org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS
import org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES
import org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST
fun export(
shareMode: PlayListShareMode,
playlist: List<PlaylistStreamEntry>,
context: Context
): String {
return when (shareMode) {
WITH_TITLES -> exportWithTitles(playlist, context)
JUST_URLS -> exportJustUrls(playlist)
YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(playlist)
}
}
fun exportWithTitles(
playlist: List<PlaylistStreamEntry>,
context: Context
): String {
return playlist.asSequence()
.map { it.streamEntity }
.map { entity ->
context.getString(
R.string.video_details_list_item,
entity.title,
entity.url
)
}
.joinToString(separator = "\n")
}
fun exportJustUrls(playlist: List<PlaylistStreamEntry>): String {
return playlist.asSequence()
.map { it.streamEntity.url }
.joinToString(separator = "\n")
}
fun exportAsYoutubeTempPlaylist(playlist: List<PlaylistStreamEntry>): String {
val videoIDs = playlist.asReversed().asSequence()
.map { it.streamEntity.url }
.mapNotNull(::getYouTubeId)
.take(50) // YouTube limitation: temp playlists can't have more than 50 items
.toList()
.asReversed()
.joinToString(separator = ",")
return "https://www.youtube.com/watch_videos?video_ids=$videoIDs"
}
val linkHandler: YoutubeStreamLinkHandlerFactory = YoutubeStreamLinkHandlerFactory.getInstance()
/**
* Gets the video id from a YouTube URL.
*
* @param url YouTube URL
* @return the video id
*/
fun getYouTubeId(url: String): String? {
return try { linkHandler.getId(url) } catch (e: ParsingException) { null }
}

View File

@ -2,8 +2,13 @@ package org.schabi.newpipe.local.playlist;
import static org.schabi.newpipe.error.ErrorUtil.showUiErrorSnackbar;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.local.playlist.ExportPlaylistKt.export;
import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS;
import static org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES;
import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST;
import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
@ -27,7 +32,6 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding;
import com.evernote.android.state.State;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.schabi.newpipe.NewPipeDatabase;
@ -385,34 +389,41 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
/**
* Shares the playlist as a list of stream URLs if {@code shouldSharePlaylistDetails} is
* set to {@code false}. Shares the playlist name along with a list of video titles and URLs
* if {@code shouldSharePlaylistDetails} is set to {@code true}.
* Shares the playlist in one of 3 ways, depending on the value of {@code shareMode}:
* <ul>
* <li>{@code JUST_URLS}: shares the URLs only.</li>
* <li>{@code WITH_TITLES}: each entry in the list is accompanied by its title.</li>
* <li>{@code YOUTUBE_TEMP_PLAYLIST}: shares as a YouTube temporary playlist.</li>
* </ul>
*
* @param shouldSharePlaylistDetails Whether the playlist details should be included in the
* shared content.
* @param shareMode The way the playlist should be shared.
*/
private void sharePlaylist(final boolean shouldSharePlaylistDetails) {
private void sharePlaylist(final PlayListShareMode shareMode) {
final Context context = requireContext();
disposables.add(playlistManager.getPlaylistStreams(playlistId)
.flatMapSingle(playlist -> Single.just(playlist.stream()
.map(PlaylistStreamEntry::getStreamEntity)
.map(streamEntity -> {
if (shouldSharePlaylistDetails) {
return context.getString(R.string.video_details_list_item,
streamEntity.getTitle(), streamEntity.getUrl());
} else {
return streamEntity.getUrl();
}
})
.collect(Collectors.joining("\n"))))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(urlsText -> ShareUtils.shareText(
context, name, shouldSharePlaylistDetails
? context.getString(R.string.share_playlist_content_details,
name, urlsText) : urlsText),
throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable)));
.flatMapSingle(playlist -> Single.just(export(
shareMode,
playlist,
context
)))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
urlsText -> {
final String content = shareMode == WITH_TITLES
? context.getString(R.string.share_playlist_content_details,
name,
urlsText
)
: urlsText;
ShareUtils.shareText(context, name, content);
},
throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable)
)
);
}
public void removeWatchedStreams(final boolean removePartiallyWatched) {
@ -872,13 +883,15 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private void createShareConfirmationDialog() {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.share_playlist)
.setMessage(R.string.share_playlist_with_titles_message)
.setCancelable(true)
.setPositiveButton(R.string.share_playlist_with_titles, (dialog, which) ->
sharePlaylist(/* shouldSharePlaylistDetails= */ true)
sharePlaylist(WITH_TITLES)
)
.setNeutralButton(R.string.share_playlist_as_youtube_temporary_playlist,
(dialog, which) -> sharePlaylist(YOUTUBE_TEMP_PLAYLIST)
)
.setNegativeButton(R.string.share_playlist_with_list, (dialog, which) ->
sharePlaylist(/* shouldSharePlaylistDetails= */ false)
sharePlaylist(JUST_URLS)
)
.show();
}

View File

@ -0,0 +1,8 @@
package org.schabi.newpipe.local.playlist;
public enum PlayListShareMode {
JUST_URLS,
WITH_TITLES,
YOUTUBE_TEMP_PLAYLIST
}

View File

@ -26,6 +26,10 @@ public class RemotePlaylistManager {
return playlistRemoteTable.getPlaylists().subscribeOn(Schedulers.io());
}
public Flowable<PlaylistRemoteEntity> getPlaylist(final long playlistId) {
return playlistRemoteTable.getPlaylist(playlistId).subscribeOn(Schedulers.io());
}
public Flowable<List<PlaylistRemoteEntity>> getPlaylist(final PlaylistInfo info) {
return playlistRemoteTable.getPlaylist(info.getServiceId(), info.getUrl())
.subscribeOn(Schedulers.io());

View File

@ -183,7 +183,10 @@ public final class PlayQueueActivity extends AppCompatActivity
////////////////////////////////////////////////////////////////////////////
private void bind() {
// Note: this code should not really exist, and PlayerHolder should be used instead, but
// it will be rewritten when NewPlayer will replace the current player.
final Intent bindIntent = new Intent(this, PlayerService.class);
bindIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION);
final boolean success = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);
if (!success) {
unbindService(serviceConnection);
@ -217,16 +220,11 @@ public final class PlayQueueActivity extends AppCompatActivity
}
@Override
public void onServiceConnected(final ComponentName name, final IBinder binder) {
public void onServiceConnected(final ComponentName name, final IBinder service) {
Log.d(TAG, "Player service is connected");
if (binder instanceof PlayerService.LocalBinder localBinder) {
final @Nullable PlayerService s = localBinder.getService();
if (s == null) {
player = null;
} else {
player = s.getPlayer();
}
if (service instanceof PlayerService.LocalBinder) {
player = ((PlayerService.LocalBinder) service).getService().getPlayer();
}
if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) {

View File

@ -55,6 +55,7 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.support.v4.media.session.MediaSessionCompat;
import android.util.Log;
import android.view.LayoutInflater;
@ -71,6 +72,7 @@ import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player.PositionInfo;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.text.CueGroup;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
@ -263,7 +265,16 @@ public final class Player implements PlaybackListener, Listener {
//////////////////////////////////////////////////////////////////////////*/
//region Constructor
public Player(@NonNull final PlayerService service) {
/**
* @param service the service this player resides in
* @param mediaSession used to build the {@link MediaSessionPlayerUi}, lives in the service and
* could possibly be reused with multiple player instances
* @param sessionConnector used to build the {@link MediaSessionPlayerUi}, lives in the service
* and could possibly be reused with multiple player instances
*/
public Player(@NonNull final PlayerService service,
@NonNull final MediaSessionCompat mediaSession,
@NonNull final MediaSessionConnector sessionConnector) {
this.service = service;
context = service;
prefs = PreferenceManager.getDefaultSharedPreferences(context);
@ -294,7 +305,7 @@ public final class Player implements PlaybackListener, Listener {
// notification ui in the UIs list, since the notification depends on the media session in
// PlayerUi#initPlayer(), and UIs.call() guarantees UI order is preserved.
UIs = new PlayerUiList(
new MediaSessionPlayerUi(this),
new MediaSessionPlayerUi(this, mediaSession, sessionConnector),
new NotificationPlayerUi(this)
);
}
@ -637,7 +648,7 @@ public final class Player implements PlaybackListener, Listener {
Log.d(TAG, "onPlaybackShutdown() called");
}
// destroys the service, which in turn will destroy the player
service.stopService();
service.destroyPlayerAndStopService();
}
public void smoothStopForImmediateReusing() {
@ -709,7 +720,7 @@ public final class Player implements PlaybackListener, Listener {
pause();
break;
case ACTION_CLOSE:
service.stopService();
service.destroyPlayerAndStopService();
break;
case ACTION_PLAY_PAUSE:
playPause();
@ -1356,6 +1367,19 @@ public final class Player implements PlaybackListener, Listener {
public void onCues(@NonNull final CueGroup cueGroup) {
UIs.call(playerUi -> playerUi.onCues(cueGroup.cues));
}
/**
* To be called when the {@code PlaybackPreparer} set in the {@link MediaSessionConnector}
* receives an {@code onPrepare()} call. This function allows restoring the default behavior
* that would happen if there was no playback preparer set, i.e. to just call
* {@code player.prepare()}. You can find the default behavior in `onPlay()` inside the
* {@link MediaSessionConnector} file.
*/
public void onPrepare() {
if (!exoPlayerIsNull()) {
simpleExoPlayer.prepare();
}
}
//endregion

View File

@ -21,82 +21,142 @@ package org.schabi.newpipe.player;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ServiceCompat;
import androidx.media.MediaBrowserServiceCompat;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import org.schabi.newpipe.ktx.BundleKt;
import org.schabi.newpipe.player.mediabrowser.MediaBrowserImpl;
import org.schabi.newpipe.player.mediabrowser.MediaBrowserPlaybackPreparer;
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
import org.schabi.newpipe.player.notification.NotificationPlayerUi;
import org.schabi.newpipe.util.ThemeHelper;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.function.Consumer;
/**
* One background service for our player. Even though the player has multiple UIs
* (e.g. the audio-only UI, the main UI, the pulldown-menu UI),
* this allows us to keep playing even when switching between the different UIs.
* One service for all players.
*/
public final class PlayerService extends Service {
public final class PlayerService extends MediaBrowserServiceCompat {
private static final String TAG = PlayerService.class.getSimpleName();
private static final boolean DEBUG = Player.DEBUG;
public static final String SHOULD_START_FOREGROUND_EXTRA = "should_start_foreground_extra";
public static final String BIND_PLAYER_HOLDER_ACTION = "bind_player_holder_action";
// These objects are used to cleanly separate the Service implementation (in this file) and the
// media browser and playback preparer implementations. At the moment the playback preparer is
// only used in conjunction with the media browser.
private MediaBrowserImpl mediaBrowserImpl;
private MediaBrowserPlaybackPreparer mediaBrowserPlaybackPreparer;
// these are instantiated in onCreate() as per
// https://developer.android.com/training/cars/media#browser_workflow
private MediaSessionCompat mediaSession;
private MediaSessionConnector sessionConnector;
@Nullable
private Player player;
private final IBinder mBinder = new PlayerService.LocalBinder(this);
public Player getPlayer() {
return player;
}
/**
* The parameter taken by this {@link Consumer} can be null to indicate the player is being
* stopped.
*/
@Nullable
private Consumer<Player> onPlayerStartedOrStopped = null;
/*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
//region Service lifecycle
@Override
public void onCreate() {
super.onCreate();
if (DEBUG) {
Log.d(TAG, "onCreate() called");
}
assureCorrectAppLanguage(this);
ThemeHelper.setTheme(this);
player = new Player(this);
/*
Create the player notification and start immediately the service in foreground,
otherwise if nothing is played or initializing the player and its components (especially
loading stream metadata) takes a lot of time, the app would crash on Android 8+ as the
service would never be put in the foreground while we said to the system we would do so
*/
player.UIs().get(NotificationPlayerUi.class)
.ifPresent(NotificationPlayerUi::createNotificationAndStartForeground);
mediaBrowserImpl = new MediaBrowserImpl(this, this::notifyChildrenChanged);
// see https://developer.android.com/training/cars/media#browser_workflow
mediaSession = new MediaSessionCompat(this, "MediaSessionPlayerServ");
setSessionToken(mediaSession.getSessionToken());
sessionConnector = new MediaSessionConnector(mediaSession);
sessionConnector.setMetadataDeduplicationEnabled(true);
mediaBrowserPlaybackPreparer = new MediaBrowserPlaybackPreparer(
this,
sessionConnector::setCustomErrorMessage,
() -> sessionConnector.setCustomErrorMessage(null),
(playWhenReady) -> {
if (player != null) {
player.onPrepare();
}
}
);
sessionConnector.setPlaybackPreparer(mediaBrowserPlaybackPreparer);
// Note: you might be tempted to create the player instance and call startForeground here,
// but be aware that the Android system might start the service just to perform media
// queries. In those cases creating a player instance is a waste of resources, and calling
// startForeground means creating a useless empty notification. In case it's really needed
// the player instance can be created here, but startForeground() should definitely not be
// called here unless the service is actually starting in the foreground, to avoid the
// useless notification.
}
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (DEBUG) {
Log.d(TAG, "onStartCommand() called with: intent = [" + intent
+ "], extras = [" + BundleKt.toDebugString(intent.getExtras())
+ "], flags = [" + flags + "], startId = [" + startId + "]");
}
/*
Be sure that the player notification is set and the service is started in foreground,
otherwise, the app may crash on Android 8+ as the service would never be put in the
foreground while we said to the system we would do so
The service is always requested to be started in foreground, so always creating a
notification if there is no one already and starting the service in foreground should
not create any issues
If the service is already started in foreground, requesting it to be started shouldn't
do anything
*/
if (player != null) {
// All internal NewPipe intents used to interact with the player, that are sent to the
// PlayerService using startForegroundService(), will have SHOULD_START_FOREGROUND_EXTRA,
// to ensure startForeground() is called (otherwise Android will force-crash the app).
if (intent.getBooleanExtra(SHOULD_START_FOREGROUND_EXTRA, false)) {
final boolean playerWasNull = (player == null);
if (playerWasNull) {
// make sure the player exists, in case the service was resumed
player = new Player(this, mediaSession, sessionConnector);
}
// Be sure that the player notification is set and the service is started in foreground,
// otherwise, the app may crash on Android 8+ as the service would never be put in the
// foreground while we said to the system we would do so. The service is always
// requested to be started in foreground, so always creating a notification if there is
// no one already and starting the service in foreground should not create any issues.
// If the service is already started in foreground, requesting it to be started
// shouldn't do anything.
player.UIs().get(NotificationPlayerUi.class)
.ifPresent(NotificationPlayerUi::createNotificationAndStartForeground);
if (playerWasNull && onPlayerStartedOrStopped != null) {
// notify that a new player was created (but do it after creating the foreground
// notification just to make sure we don't incur, due to slowness, in
// "Context.startForegroundService() did not then call Service.startForeground()")
onPlayerStartedOrStopped.accept(player);
}
}
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
@ -107,7 +167,7 @@ public final class PlayerService extends Service {
Stop the service in this case, which will be removed from the foreground and its
notification cancelled in its destruction
*/
stopSelf();
destroyPlayerAndStopService();
return START_NOT_STICKY;
}
@ -149,35 +209,86 @@ public final class PlayerService extends Service {
if (DEBUG) {
Log.d(TAG, "destroy() called");
}
super.onDestroy();
cleanup();
mediaBrowserPlaybackPreparer.dispose();
mediaSession.release();
mediaBrowserImpl.dispose();
}
private void cleanup() {
if (player != null) {
if (onPlayerStartedOrStopped != null) {
// notify that the player is being destroyed
onPlayerStartedOrStopped.accept(null);
}
player.destroy();
player = null;
}
// Should already be handled by MediaSessionPlayerUi, but just to be sure.
mediaSession.setActive(false);
// Should already be handled by NotificationUtil.cancelNotificationAndStopForeground() in
// NotificationPlayerUi, but let's make sure that the foreground service is stopped.
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
}
public void stopService() {
/**
* Destroys the player and allows the player instance to be garbage collected. Sets the media
* session to inactive. Stops the foreground service and removes the player notification
* associated with it. Tries to stop the {@link PlayerService} completely, but this step will
* have no effect in case some service connection still uses the service (e.g. the Android Auto
* system accesses the media browser even when no player is running).
*/
public void destroyPlayerAndStopService() {
if (DEBUG) {
Log.d(TAG, "destroyPlayerAndStopService() called");
}
cleanup();
stopSelf();
// This only really stops the service if there are no other service connections (see docs):
// for example the (Android Auto) media browser binder will block stopService().
// This is why we also stopForeground() above, to make sure the notification is removed.
// If we were to call stopSelf(), then the service would be surely stopped (regardless of
// other service connections), but this would be a waste of resources since the service
// would be immediately restarted by those same connections to perform the queries.
stopService(new Intent(this, PlayerService.class));
}
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
}
//endregion
//region Bind
@Override
public IBinder onBind(final Intent intent) {
return mBinder;
if (DEBUG) {
Log.d(TAG, "onBind() called with: intent = [" + intent
+ "], extras = [" + BundleKt.toDebugString(intent.getExtras()) + "]");
}
if (BIND_PLAYER_HOLDER_ACTION.equals(intent.getAction())) {
// Note that this binder might be reused multiple times while the service is alive, even
// after unbind() has been called: https://stackoverflow.com/a/8794930 .
return mBinder;
} else if (MediaBrowserServiceCompat.SERVICE_INTERFACE.equals(intent.getAction())) {
// MediaBrowserService also uses its own binder, so for actions related to the media
// browser service, pass the onBind to the superclass.
return super.onBind(intent);
} else {
// This is an unknown request, avoid returning any binder to not leak objects.
return null;
}
}
/**
* Allows us this {@link org.schabi.newpipe.player.PlayerService} over the Service boundary
* back to our {@link org.schabi.newpipe.player.helper.PlayerHolder}.
*/
public static class LocalBinder extends Binder {
private final WeakReference<PlayerService> playerService;
@ -185,12 +296,55 @@ public final class PlayerService extends Service {
this.playerService = new WeakReference<>(playerService);
}
/**
* Get the PlayerService object itself.
* @return this
* */
public @Nullable PlayerService getService() {
public PlayerService getService() {
return playerService.get();
}
}
/**
* @return the current active player instance. May be null, since the player service can outlive
* the player e.g. to respond to Android Auto media browser queries.
*/
@Nullable
public Player getPlayer() {
return player;
}
/**
* Sets the listener that will be called when the player is started or stopped. If a
* {@code null} listener is passed, then the current listener will be unset. The parameter taken
* by the {@link Consumer} can be null to indicate that the player is stopping.
* @param listener the listener to set or unset
*/
public void setPlayerListener(@Nullable final Consumer<Player> listener) {
this.onPlayerStartedOrStopped = listener;
if (listener != null) {
// if there is no player, then `null` will be sent here, to ensure the state is synced
listener.accept(player);
}
}
//endregion
//region Media browser
@Override
public BrowserRoot onGetRoot(@NonNull final String clientPackageName,
final int clientUid,
@Nullable final Bundle rootHints) {
// TODO check if the accessing package has permission to view data
return mediaBrowserImpl.onGetRoot(clientPackageName, clientUid, rootHints);
}
@Override
public void onLoadChildren(@NonNull final String parentId,
@NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
mediaBrowserImpl.onLoadChildren(parentId, result);
}
@Override
public void onSearch(@NonNull final String query,
final Bundle extras,
@NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
mediaBrowserImpl.onSearch(query, result);
}
//endregion
}

View File

@ -1,10 +0,0 @@
package org.schabi.newpipe.player.event;
import org.schabi.newpipe.player.PlayerService;
/** Gets signalled if our PlayerHolder (dis)connects from the PlayerService. */
public interface PlayerHolderLifecycleEventListener {
void onServiceConnected(PlayerService playerService,
boolean playAfterConnect);
void onServiceDisconnected();
}

View File

@ -0,0 +1,48 @@
package org.schabi.newpipe.player.event;
import androidx.annotation.NonNull;
import org.schabi.newpipe.player.PlayerService;
import org.schabi.newpipe.player.Player;
/**
* In addition to {@link PlayerServiceEventListener}, provides callbacks for service and player
* connections and disconnections. "Connected" here means that the service (resp. the
* player) is running and is bound to {@link org.schabi.newpipe.player.helper.PlayerHolder}.
* "Disconnected" means that either the service (resp. the player) was stopped completely, or that
* {@link org.schabi.newpipe.player.helper.PlayerHolder} is not bound.
*/
public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener {
/**
* The player service just connected to {@link org.schabi.newpipe.player.helper.PlayerHolder},
* but the player may not be active at this moment, e.g. in case the service is running to
* respond to Android Auto media browser queries without playing anything.
* {@link #onPlayerConnected(Player, boolean)} will be called right after this function if there
* is a player.
*
* @param playerService the newly connected player service
*/
void onServiceConnected(@NonNull PlayerService playerService);
/**
* The player service is already connected and the player was just started.
*
* @param player the newly connected or started player
* @param playAfterConnect whether to open the video player in the video details fragment
*/
void onPlayerConnected(@NonNull Player player, boolean playAfterConnect);
/**
* The player got disconnected, for one of these reasons: the player is getting closed while
* leaving the service open for future media browser queries, the service is stopping
* completely, or {@link org.schabi.newpipe.player.helper.PlayerHolder} is unbinding.
*/
void onPlayerDisconnected();
/**
* The service got disconnected from {@link org.schabi.newpipe.player.helper.PlayerHolder},
* either because {@link org.schabi.newpipe.player.helper.PlayerHolder} is unbinding or because
* the service is stopping completely.
*/
void onServiceDisconnected();
}

View File

@ -7,7 +7,6 @@ import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
@ -17,17 +16,17 @@ import com.google.android.exoplayer2.PlaybackParameters;
import org.schabi.newpipe.App;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.PlayerService;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
import org.schabi.newpipe.player.event.PlayerHolderLifecycleEventListener;
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.Optional;
import java.util.function.Consumer;
/**
* Singleton that manages a `PlayerService`
* and can be used to control the player instance through the service.
*/
public final class PlayerHolder {
private PlayerHolder() {
@ -44,14 +43,21 @@ public final class PlayerHolder {
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = PlayerHolder.class.getSimpleName();
@Nullable private PlayerServiceEventListener listener;
@Nullable private PlayerHolderLifecycleEventListener holderListener;
@Nullable private PlayerServiceExtendedEventListener listener;
private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection();
private boolean bound;
@Nullable private PlayerService playerService;
@Nullable private Player player;
private Optional<Player> getPlayer() {
return Optional.ofNullable(playerService)
.flatMap(s -> Optional.ofNullable(s.getPlayer()));
}
private Optional<PlayQueue> getPlayQueue() {
// player play queue might be null e.g. while player is starting
return getPlayer().flatMap(p -> Optional.ofNullable(p.getPlayQueue()));
}
/**
* Returns the current {@link PlayerType} of the {@link PlayerService} service,
@ -61,21 +67,15 @@ public final class PlayerHolder {
*/
@Nullable
public PlayerType getType() {
if (player == null) {
return null;
}
return player.getPlayerType();
return getPlayer().map(Player::getPlayerType).orElse(null);
}
public boolean isPlaying() {
if (player == null) {
return false;
}
return player.isPlaying();
return getPlayer().map(Player::isPlaying).orElse(false);
}
public boolean isPlayerOpen() {
return player != null;
return getPlayer().isPresent();
}
/**
@ -84,70 +84,49 @@ public final class PlayerHolder {
* @return true only if the player is open and its play queue is ready (i.e. it is not null)
*/
public boolean isPlayQueueReady() {
return player != null && player.getPlayQueue() != null;
return getPlayQueue().isPresent();
}
public boolean isNotBoundYet() {
return !bound;
public boolean isBound() {
return bound;
}
public int getQueueSize() {
if (player == null || player.getPlayQueue() == null) {
// player play queue might be null e.g. while player is starting
return 0;
}
return player.getPlayQueue().size();
return getPlayQueue().map(PlayQueue::size).orElse(0);
}
public int getQueuePosition() {
if (player == null || player.getPlayQueue() == null) {
return 0;
}
return player.getPlayQueue().getIndex();
return getPlayQueue().map(PlayQueue::getIndex).orElse(0);
}
public void unsetListeners() {
listener = null;
holderListener = null;
}
public void setListener(@NonNull final PlayerServiceEventListener newListener,
@NonNull final PlayerHolderLifecycleEventListener newHolderListener) {
public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) {
listener = newListener;
holderListener = newHolderListener;
if (listener == null) {
return;
}
// Force reload data from service
if (player != null) {
holderListener.onServiceConnected(playerService, false);
player.setFragmentListener(internalListener);
if (playerService != null) {
listener.onServiceConnected(playerService);
startPlayerListener();
// ^ will call listener.onPlayerConnected() down the line if there is an active player
}
}
/**
* Helper to handle context in common place as using the same
* context to bind/unbind a service is crucial.
*
* @return the common context
* */
// helper to handle context in common place as using the same
// context to bind/unbind a service is crucial
private Context getCommonContext() {
return App.getInstance();
}
/**
* Connect to (and if needed start) the {@link PlayerService}
* and bind {@link PlayerServiceConnection} to it.
* If the service is already started, only set the listener.
* @param playAfterConnect If the service is started, start playing immediately
* @param newListener set this listener
* @param newHolderListener set this listener
* */
public void startService(final boolean playAfterConnect,
final PlayerServiceEventListener newListener,
final PlayerHolderLifecycleEventListener newHolderListener
) {
final PlayerServiceExtendedEventListener newListener) {
if (DEBUG) {
Log.d(TAG, "startService() called with playAfterConnect=" + playAfterConnect);
}
final Context context = getCommonContext();
setListener(newListener, newHolderListener);
setListener(newListener);
if (bound) {
return;
}
@ -155,56 +134,35 @@ public final class PlayerHolder {
// and NullPointerExceptions inside the service because the service will be
// bound twice. Prevent it with unbinding first
unbind(context);
ContextCompat.startForegroundService(context, new Intent(context, PlayerService.class));
serviceConnection.playAfterConnect = playAfterConnect;
if (DEBUG) {
Log.d(TAG, "bind() called");
}
final Intent serviceIntent = new Intent(context, PlayerService.class);
bound = context.bindService(serviceIntent, serviceConnection,
Context.BIND_AUTO_CREATE);
if (!bound) {
context.unbindService(serviceConnection);
}
final Intent intent = new Intent(context, PlayerService.class);
intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true);
ContextCompat.startForegroundService(context, intent);
serviceConnection.doPlayAfterConnect(playAfterConnect);
bind(context);
}
public void stopService() {
if (DEBUG) {
Log.d(TAG, "stopService() called");
}
if (playerService != null) {
playerService.destroyPlayerAndStopService();
}
final Context context = getCommonContext();
unbind(context);
// destroyPlayerAndStopService() already runs the next line of code, but run it again just
// to make sure to stop the service even if playerService is null by any chance.
context.stopService(new Intent(context, PlayerService.class));
}
/**
* Call {@link Context#unbindService(ServiceConnection)} on our service
* (does not necesarily stop the service right away).
* Remove all our listeners and deinitialize them.
* @param context shared context
* */
private void unbind(final Context context) {
if (DEBUG) {
Log.d(TAG, "unbind() called");
}
if (bound) {
context.unbindService(serviceConnection);
bound = false;
if (player != null) {
player.removeFragmentListener(internalListener);
}
playerService = null;
player = null;
if (holderListener != null) {
holderListener.onServiceDisconnected();
}
}
}
class PlayerServiceConnection implements ServiceConnection {
private boolean playAfterConnect = false;
public void doPlayAfterConnect(final boolean playAfterConnection) {
this.playAfterConnect = playAfterConnection;
}
@Override
public void onServiceDisconnected(final ComponentName compName) {
if (DEBUG) {
@ -223,22 +181,81 @@ public final class PlayerHolder {
final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service;
playerService = localBinder.getService();
player = playerService != null ? playerService.getPlayer() : null;
if (holderListener != null) {
holderListener.onServiceConnected(playerService, playAfterConnect);
if (listener != null) {
listener.onServiceConnected(playerService);
getPlayer().ifPresent(p -> listener.onPlayerConnected(p, playAfterConnect));
}
if (player != null) {
player.setFragmentListener(internalListener);
startPlayerListener();
// ^ will call listener.onPlayerConnected() down the line if there is an active player
// notify the main activity that binding the service has completed, so that it can
// open the bottom mini-player
NavigationHelper.sendPlayerStartedEvent(localBinder.getService());
}
}
private void bind(final Context context) {
if (DEBUG) {
Log.d(TAG, "bind() called");
}
// BIND_AUTO_CREATE starts the service if it's not already running
bound = bind(context, Context.BIND_AUTO_CREATE);
if (!bound) {
context.unbindService(serviceConnection);
}
}
public void tryBindIfNeeded(final Context context) {
if (!bound) {
// flags=0 means the service will not be started if it does not already exist. In this
// case the return value is not useful, as a value of "true" does not really indicate
// that the service is going to be bound.
bind(context, 0);
}
}
private boolean bind(final Context context, final int flags) {
final Intent serviceIntent = new Intent(context, PlayerService.class);
serviceIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION);
return context.bindService(serviceIntent, serviceConnection, flags);
}
private void unbind(final Context context) {
if (DEBUG) {
Log.d(TAG, "unbind() called");
}
if (bound) {
context.unbindService(serviceConnection);
bound = false;
stopPlayerListener();
playerService = null;
if (listener != null) {
listener.onPlayerDisconnected();
listener.onServiceDisconnected();
}
}
}
private void startPlayerListener() {
if (playerService != null) {
// setting the player listener will take care of calling relevant callbacks if the
// player in the service is (not) already active, also see playerStateListener below
playerService.setPlayerListener(playerStateListener);
}
getPlayer().ifPresent(p -> p.setFragmentListener(internalListener));
}
private void stopPlayerListener() {
if (playerService != null) {
playerService.setPlayerListener(null);
}
getPlayer().ifPresent(p -> p.removeFragmentListener(internalListener));
}
/**
* Delegate all {@link PlayerServiceEventListener} events to our current `listener` object.
* Only difference is that if {@link PlayerServiceEventListener#onServiceStopped()} is called,
* it also calls {@link PlayerHolder#unbind(Context)}.
* */
* This listener will be held by the players created by {@link PlayerService}.
*/
private final PlayerServiceEventListener internalListener =
new PlayerServiceEventListener() {
@Override
@ -325,4 +342,23 @@ public final class PlayerHolder {
unbind(getCommonContext());
}
};
/**
* This listener will be held by bound {@link PlayerService}s to notify of the player starting
* or stopping. This is necessary since the service outlives the player e.g. to answer Android
* Auto media browser queries.
*/
private final Consumer<Player> playerStateListener = (@Nullable final Player player) -> {
if (listener != null) {
if (player == null) {
// player.fragmentListener=null is already done by player.stopActivityBinding(),
// which is called by player.destroy(), which is in turn called by PlayerService
// before setting its player to null
listener.onPlayerDisconnected();
} else {
listener.onPlayerConnected(player, serviceConnection.playAfterConnect);
player.setFragmentListener(internalListener);
}
}
};
}

View File

@ -0,0 +1,40 @@
package org.schabi.newpipe.player.mediabrowser
import org.schabi.newpipe.BuildConfig
import org.schabi.newpipe.extractor.InfoItem.InfoType
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
internal const val ID_AUTHORITY = BuildConfig.APPLICATION_ID
internal const val ID_ROOT = "//$ID_AUTHORITY"
internal const val ID_BOOKMARKS = "playlists"
internal const val ID_HISTORY = "history"
internal const val ID_INFO_ITEM = "item"
internal const val ID_LOCAL = "local"
internal const val ID_REMOTE = "remote"
internal const val ID_URL = "url"
internal const val ID_STREAM = "stream"
internal const val ID_PLAYLIST = "playlist"
internal const val ID_CHANNEL = "channel"
internal fun infoItemTypeToString(type: InfoType): String {
return when (type) {
InfoType.STREAM -> ID_STREAM
InfoType.PLAYLIST -> ID_PLAYLIST
InfoType.CHANNEL -> ID_CHANNEL
else -> throw IllegalStateException("Unexpected value: $type")
}
}
internal fun infoItemTypeFromString(type: String): InfoType {
return when (type) {
ID_STREAM -> InfoType.STREAM
ID_PLAYLIST -> InfoType.PLAYLIST
ID_CHANNEL -> InfoType.CHANNEL
else -> throw IllegalStateException("Unexpected value: $type")
}
}
internal fun parseError(mediaId: String): ContentNotAvailableException {
return ContentNotAvailableException("Failed to parse media ID $mediaId")
}

View File

@ -0,0 +1,399 @@
package org.schabi.newpipe.player.mediabrowser
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
import android.util.Log
import androidx.annotation.DrawableRes
import androidx.media.MediaBrowserServiceCompat
import androidx.media.MediaBrowserServiceCompat.Result
import androidx.media.utils.MediaConstants
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.MainActivity.DEBUG
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.R
import org.schabi.newpipe.database.history.model.StreamHistoryEntry
import org.schabi.newpipe.database.playlist.PlaylistLocalItem
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity
import org.schabi.newpipe.extractor.InfoItem
import org.schabi.newpipe.extractor.InfoItem.InfoType
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem
import org.schabi.newpipe.extractor.search.SearchInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.bookmark.MergedPlaylistManager
import org.schabi.newpipe.local.playlist.LocalPlaylistManager
import org.schabi.newpipe.local.playlist.RemotePlaylistManager
import org.schabi.newpipe.util.ExtractorHelper
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.image.ImageStrategy
import java.util.function.Consumer
/**
* This class is used to cleanly separate the Service implementation (in
* [org.schabi.newpipe.player.PlayerService]) and the media browser implementation (in this file).
*
* @param notifyChildrenChanged takes the parent id of the children that changed
*/
class MediaBrowserImpl(
private val context: Context,
notifyChildrenChanged: Consumer<String>, // parentId
) {
private val database = NewPipeDatabase.getInstance(context)
private var disposables = CompositeDisposable()
init {
// this will listen to changes in the bookmarks until this MediaBrowserImpl is dispose()d
disposables.add(
getMergedPlaylists().subscribe { notifyChildrenChanged.accept(ID_BOOKMARKS) }
)
}
//region Cleanup
fun dispose() {
disposables.dispose()
}
//endregion
//region onGetRoot
fun onGetRoot(
clientPackageName: String,
clientUid: Int,
rootHints: Bundle?
): MediaBrowserServiceCompat.BrowserRoot {
if (DEBUG) {
Log.d(TAG, "onGetRoot($clientPackageName, $clientUid, $rootHints)")
}
val extras = Bundle()
extras.putBoolean(
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true
)
return MediaBrowserServiceCompat.BrowserRoot(ID_ROOT, extras)
}
//endregion
//region onLoadChildren
fun onLoadChildren(parentId: String, result: Result<List<MediaBrowserCompat.MediaItem>>) {
if (DEBUG) {
Log.d(TAG, "onLoadChildren($parentId)")
}
result.detach() // allows sendResult() to happen later
disposables.add(
onLoadChildren(parentId)
.subscribe(
{ result.sendResult(it) },
{ throwable ->
// null indicates an error, see the docs of MediaSessionCompat.onSearch()
result.sendResult(null)
Log.e(TAG, "onLoadChildren error for parentId=$parentId: $throwable")
}
)
)
}
private fun onLoadChildren(parentId: String): Single<List<MediaBrowserCompat.MediaItem>> {
try {
val parentIdUri = Uri.parse(parentId)
val path = ArrayList(parentIdUri.pathSegments)
if (path.isEmpty()) {
return Single.just(
listOf(
createRootMediaItem(
ID_BOOKMARKS,
context.resources.getString(R.string.tab_bookmarks_short),
R.drawable.ic_bookmark_white
),
createRootMediaItem(
ID_HISTORY,
context.resources.getString(R.string.action_history),
R.drawable.ic_history_white
)
)
)
}
when (/*val uriType = */path.removeAt(0)) {
ID_BOOKMARKS -> {
if (path.isEmpty()) {
return populateBookmarks()
}
if (path.size == 2) {
val localOrRemote = path[0]
val playlistId = path[1].toLong()
if (localOrRemote == ID_LOCAL) {
return populateLocalPlaylist(playlistId)
} else if (localOrRemote == ID_REMOTE) {
return populateRemotePlaylist(playlistId)
}
}
Log.w(TAG, "Unknown playlist URI: $parentId")
throw parseError(parentId)
}
ID_HISTORY -> return populateHistory()
else -> throw parseError(parentId)
}
} catch (e: ContentNotAvailableException) {
return Single.error(e)
}
}
private fun createRootMediaItem(
mediaId: String?,
folderName: String?,
@DrawableRes iconResId: Int
): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(mediaId)
builder.setTitle(folderName)
val resources = context.resources
builder.setIconUri(
Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(resources.getResourcePackageName(iconResId))
.appendPath(resources.getResourceTypeName(iconResId))
.appendPath(resources.getResourceEntryName(iconResId))
.build()
)
val extras = Bundle()
extras.putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
context.getString(R.string.app_name)
)
builder.setExtras(extras)
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
)
}
private fun createPlaylistMediaItem(playlist: PlaylistLocalItem): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder()
builder
.setMediaId(createMediaIdForInfoItem(playlist is PlaylistRemoteEntity, playlist.uid))
.setTitle(playlist.orderingName)
.setIconUri(playlist.thumbnailUrl?.let { Uri.parse(it) })
val extras = Bundle()
extras.putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
context.resources.getString(R.string.tab_bookmarks),
)
builder.setExtras(extras)
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE,
)
}
private fun createInfoItemMediaItem(item: InfoItem): MediaBrowserCompat.MediaItem? {
val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(createMediaIdForInfoItem(item))
.setTitle(item.name)
when (item.infoType) {
InfoType.STREAM -> builder.setSubtitle((item as StreamInfoItem).uploaderName)
InfoType.PLAYLIST -> builder.setSubtitle((item as PlaylistInfoItem).uploaderName)
InfoType.CHANNEL -> builder.setSubtitle((item as ChannelInfoItem).description)
else -> return null
}
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
builder.setIconUri(Uri.parse(it))
}
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
}
private fun buildMediaId(): Uri.Builder {
return Uri.Builder().authority(ID_AUTHORITY)
}
private fun buildPlaylistMediaId(playlistType: String?): Uri.Builder {
return buildMediaId()
.appendPath(ID_BOOKMARKS)
.appendPath(playlistType)
}
private fun buildLocalPlaylistItemMediaId(isRemote: Boolean, playlistId: Long): Uri.Builder {
return buildPlaylistMediaId(if (isRemote) ID_REMOTE else ID_LOCAL)
.appendPath(playlistId.toString())
}
private fun buildInfoItemMediaId(item: InfoItem): Uri.Builder {
return buildMediaId()
.appendPath(ID_INFO_ITEM)
.appendPath(infoItemTypeToString(item.infoType))
.appendPath(item.serviceId.toString())
.appendQueryParameter(ID_URL, item.url)
}
private fun createMediaIdForInfoItem(isRemote: Boolean, playlistId: Long): String {
return buildLocalPlaylistItemMediaId(isRemote, playlistId)
.build().toString()
}
private fun createLocalPlaylistStreamMediaItem(
playlistId: Long,
item: PlaylistStreamEntry,
index: Int,
): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(createMediaIdForPlaylistIndex(false, playlistId, index))
.setTitle(item.streamEntity.title)
.setSubtitle(item.streamEntity.uploader)
.setIconUri(Uri.parse(item.streamEntity.thumbnailUrl))
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
}
private fun createRemotePlaylistStreamMediaItem(
playlistId: Long,
item: StreamInfoItem,
index: Int,
): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(createMediaIdForPlaylistIndex(true, playlistId, index))
.setTitle(item.name)
.setSubtitle(item.uploaderName)
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
builder.setIconUri(Uri.parse(it))
}
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
}
private fun createMediaIdForPlaylistIndex(
isRemote: Boolean,
playlistId: Long,
index: Int,
): String {
return buildLocalPlaylistItemMediaId(isRemote, playlistId)
.appendPath(index.toString())
.build().toString()
}
private fun createMediaIdForInfoItem(item: InfoItem): String {
return buildInfoItemMediaId(item).build().toString()
}
private fun populateHistory(): Single<List<MediaBrowserCompat.MediaItem>> {
val history = database.streamHistoryDAO().getHistory().firstOrError()
return history.map { items ->
items.map { this.createHistoryMediaItem(it) }
}
}
private fun createHistoryMediaItem(streamHistoryEntry: StreamHistoryEntry): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder()
val mediaId = buildMediaId()
.appendPath(ID_HISTORY)
.appendPath(streamHistoryEntry.streamId.toString())
.build().toString()
builder.setMediaId(mediaId)
.setTitle(streamHistoryEntry.streamEntity.title)
.setSubtitle(streamHistoryEntry.streamEntity.uploader)
.setIconUri(Uri.parse(streamHistoryEntry.streamEntity.thumbnailUrl))
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
}
private fun getMergedPlaylists(): Flowable<MutableList<PlaylistLocalItem>> {
return MergedPlaylistManager.getMergedOrderedPlaylists(
LocalPlaylistManager(database),
RemotePlaylistManager(database)
)
}
private fun populateBookmarks(): Single<List<MediaBrowserCompat.MediaItem>> {
val playlists = getMergedPlaylists().firstOrError()
return playlists.map { playlist ->
playlist.map { this.createPlaylistMediaItem(it) }
}
}
private fun populateLocalPlaylist(playlistId: Long): Single<List<MediaBrowserCompat.MediaItem>> {
val playlist = LocalPlaylistManager(database).getPlaylistStreams(playlistId).firstOrError()
return playlist.map { items ->
items.mapIndexed { index, item ->
createLocalPlaylistStreamMediaItem(playlistId, item, index)
}
}
}
private fun populateRemotePlaylist(playlistId: Long): Single<List<MediaBrowserCompat.MediaItem>> {
return RemotePlaylistManager(database).getPlaylist(playlistId).firstOrError()
.flatMap { ExtractorHelper.getPlaylistInfo(it.serviceId, it.url, false) }
.map {
// ignore it.errors, i.e. ignore errors about specific items, since there would
// be no way to show the error properly in Android Auto anyway
it.relatedItems.mapIndexed { index, item ->
createRemotePlaylistStreamMediaItem(playlistId, item, index)
}
}
}
//endregion
//region Search
fun onSearch(
query: String,
result: Result<List<MediaBrowserCompat.MediaItem>>
) {
if (DEBUG) {
Log.d(TAG, "onSearch($query)")
}
result.detach() // allows sendResult() to happen later
disposables.add(
searchMusicBySongTitle(query)
// ignore it.errors, i.e. ignore errors about specific items, since there would
// be no way to show the error properly in Android Auto anyway
.map { it.relatedItems.mapNotNull(this::createInfoItemMediaItem) }
.subscribeOn(Schedulers.io())
.subscribe(
{ result.sendResult(it) },
{ throwable ->
// null indicates an error, see the docs of MediaSessionCompat.onSearch()
result.sendResult(null)
Log.e(TAG, "Search error for query=\"$query\": $throwable")
}
)
)
}
private fun searchMusicBySongTitle(query: String?): Single<SearchInfo> {
val serviceId = ServiceHelper.getSelectedServiceId(context)
return ExtractorHelper.searchFor(serviceId, query, listOf(), "")
}
//endregion
companion object {
private val TAG: String = MediaBrowserImpl::class.java.getSimpleName()
}
}

View File

@ -0,0 +1,259 @@
package org.schabi.newpipe.player.mediabrowser
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.os.ResultReceiver
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector.PlaybackPreparer
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.InfoItem.InfoType
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler
import org.schabi.newpipe.local.playlist.LocalPlaylistManager
import org.schabi.newpipe.local.playlist.RemotePlaylistManager
import org.schabi.newpipe.player.playqueue.ChannelTabPlayQueue
import org.schabi.newpipe.player.playqueue.PlayQueue
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue
import org.schabi.newpipe.player.playqueue.SinglePlayQueue
import org.schabi.newpipe.util.ChannelTabHelper
import org.schabi.newpipe.util.ExtractorHelper
import org.schabi.newpipe.util.NavigationHelper
import java.util.function.BiConsumer
import java.util.function.Consumer
/**
* This class is used to cleanly separate the Service implementation (in
* [org.schabi.newpipe.player.PlayerService]) and the playback preparer implementation (in this
* file). We currently use the playback preparer only in conjunction with the media browser: the
* playback preparer will receive the media URLs generated by [MediaBrowserImpl] and will start
* playback of the corresponding streams or playlists.
*
* @param setMediaSessionError takes an error String and an error code from [PlaybackStateCompat],
* calls `sessionConnector.setCustomErrorMessage(errorString, errorCode)`
* @param clearMediaSessionError calls `sessionConnector.setCustomErrorMessage(null)`
* @param onPrepare takes playWhenReady, calls `player.prepare()`; this is needed because
* `MediaSessionConnector`'s `onPlay()` method calls this class' [onPrepare] instead of
* `player.prepare()` if the playback preparer is not null, but we want the original behavior
*/
class MediaBrowserPlaybackPreparer(
private val context: Context,
private val setMediaSessionError: BiConsumer<String, Int>, // error string, error code
private val clearMediaSessionError: Runnable,
private val onPrepare: Consumer<Boolean>,
) : PlaybackPreparer {
private val database = NewPipeDatabase.getInstance(context)
private var disposable: Disposable? = null
fun dispose() {
disposable?.dispose()
}
//region Overrides
override fun getSupportedPrepareActions(): Long {
return PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
}
override fun onPrepare(playWhenReady: Boolean) {
onPrepare.accept(playWhenReady)
}
override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) {
if (MainActivity.DEBUG) {
Log.d(TAG, "onPrepareFromMediaId($mediaId, $playWhenReady, $extras)")
}
disposable?.dispose()
disposable = extractPlayQueueFromMediaId(mediaId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ playQueue ->
clearMediaSessionError.run()
NavigationHelper.playOnBackgroundPlayer(context, playQueue, playWhenReady)
},
{ throwable ->
Log.e(TAG, "Failed to start playback of media ID [$mediaId]", throwable)
onPrepareError()
}
)
}
override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) {
onUnsupportedError()
}
override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) {
onUnsupportedError()
}
override fun onCommand(
player: Player,
command: String,
extras: Bundle?,
cb: ResultReceiver?
): Boolean {
return false
}
//endregion
//region Errors
private fun onUnsupportedError() {
setMediaSessionError.accept(
context.getString(R.string.content_not_supported),
PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED
)
}
private fun onPrepareError() {
setMediaSessionError.accept(
context.getString(R.string.error_snackbar_message),
PlaybackStateCompat.ERROR_CODE_APP_ERROR
)
}
//endregion
//region Building play queues from playlists and history
private fun extractLocalPlayQueue(playlistId: Long, index: Int): Single<PlayQueue> {
return LocalPlaylistManager(database).getPlaylistStreams(playlistId).firstOrError()
.map { items -> SinglePlayQueue(items.map { it.toStreamInfoItem() }, index) }
}
private fun extractRemotePlayQueue(playlistId: Long, index: Int): Single<PlayQueue> {
return RemotePlaylistManager(database).getPlaylist(playlistId).firstOrError()
.flatMap { ExtractorHelper.getPlaylistInfo(it.serviceId, it.url, false) }
// ignore info.errors, i.e. ignore errors about specific items, since there would
// be no way to show the error properly in Android Auto anyway
.map { info -> PlaylistPlayQueue(info, index) }
}
private fun extractPlayQueueFromMediaId(mediaId: String): Single<PlayQueue> {
try {
val mediaIdUri = Uri.parse(mediaId)
val path = ArrayList(mediaIdUri.pathSegments)
if (path.isEmpty()) {
throw parseError(mediaId)
}
return when (/*val uriType = */path.removeAt(0)) {
ID_BOOKMARKS -> extractPlayQueueFromPlaylistMediaId(
mediaId,
path,
mediaIdUri.getQueryParameter(ID_URL)
)
ID_HISTORY -> extractPlayQueueFromHistoryMediaId(mediaId, path)
ID_INFO_ITEM -> extractPlayQueueFromInfoItemMediaId(
mediaId,
path,
mediaIdUri.getQueryParameter(ID_URL) ?: throw parseError(mediaId)
)
else -> throw parseError(mediaId)
}
} catch (e: ContentNotAvailableException) {
return Single.error(e)
}
}
@Throws(ContentNotAvailableException::class)
private fun extractPlayQueueFromPlaylistMediaId(
mediaId: String,
path: MutableList<String>,
url: String?,
): Single<PlayQueue> {
if (path.isEmpty()) {
throw parseError(mediaId)
}
when (val playlistType = path.removeAt(0)) {
ID_LOCAL, ID_REMOTE -> {
if (path.size != 2) {
throw parseError(mediaId)
}
val playlistId = path[0].toLong()
val index = path[1].toInt()
return if (playlistType == ID_LOCAL)
extractLocalPlayQueue(playlistId, index)
else
extractRemotePlayQueue(playlistId, index)
}
ID_URL -> {
if (path.size != 1 || url == null) {
throw parseError(mediaId)
}
val serviceId = path[0].toInt()
return ExtractorHelper.getPlaylistInfo(serviceId, url, false)
.map { PlaylistPlayQueue(it) }
}
else -> throw parseError(mediaId)
}
}
@Throws(ContentNotAvailableException::class)
private fun extractPlayQueueFromHistoryMediaId(
mediaId: String,
path: List<String>,
): Single<PlayQueue> {
if (path.size != 1) {
throw parseError(mediaId)
}
val streamId = path[0].toLong()
return database.streamHistoryDAO().getHistory()
.firstOrError()
.map { items ->
val infoItems = items
.filter { it.streamId == streamId }
.map { it.toStreamInfoItem() }
SinglePlayQueue(infoItems, 0)
}
}
@Throws(ContentNotAvailableException::class)
private fun extractPlayQueueFromInfoItemMediaId(
mediaId: String,
path: List<String>,
url: String,
): Single<PlayQueue> {
if (path.size != 2) {
throw parseError(mediaId)
}
val serviceId = path[1].toInt()
return when (/*val infoItemType = */infoItemTypeFromString(path[0])) {
InfoType.STREAM -> ExtractorHelper.getStreamInfo(serviceId, url, false)
.map { SinglePlayQueue(it) }
InfoType.PLAYLIST -> ExtractorHelper.getPlaylistInfo(serviceId, url, false)
.map { PlaylistPlayQueue(it) }
InfoType.CHANNEL -> ExtractorHelper.getChannelInfo(serviceId, url, false)
.map { info ->
val playableTab = info.tabs
.firstOrNull { ChannelTabHelper.isStreamsTab(it) }
?: throw ContentNotAvailableException("No streams tab found")
return@map ChannelTabPlayQueue(serviceId, ListLinkHandler(playableTab))
}
else -> throw parseError(mediaId)
}
}
//endregion
companion object {
private val TAG = MediaBrowserPlaybackPreparer::class.simpleName
}
}

View File

@ -38,10 +38,10 @@ public class MediaSessionPlayerUi extends PlayerUi
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "MediaSessUi";
@Nullable
private MediaSessionCompat mediaSession;
@Nullable
private MediaSessionConnector sessionConnector;
@NonNull
private final MediaSessionCompat mediaSession;
@NonNull
private final MediaSessionConnector sessionConnector;
private final String ignoreHardwareMediaButtonsKey;
private boolean shouldIgnoreHardwareMediaButtons = false;
@ -50,9 +50,13 @@ public class MediaSessionPlayerUi extends PlayerUi
private List<NotificationActionData> prevNotificationActions = List.of();
public MediaSessionPlayerUi(@NonNull final Player player) {
public MediaSessionPlayerUi(@NonNull final Player player,
@NonNull final MediaSessionCompat mediaSession,
@NonNull final MediaSessionConnector sessionConnector) {
super(player);
ignoreHardwareMediaButtonsKey =
this.mediaSession = mediaSession;
this.sessionConnector = sessionConnector;
this.ignoreHardwareMediaButtonsKey =
context.getString(R.string.ignore_hardware_media_buttons_key);
}
@ -61,10 +65,8 @@ public class MediaSessionPlayerUi extends PlayerUi
super.initPlayer();
destroyPlayer(); // release previously used resources
mediaSession = new MediaSessionCompat(context, TAG);
mediaSession.setActive(true);
sessionConnector = new MediaSessionConnector(mediaSession);
sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player));
sessionConnector.setPlayer(getForwardingPlayer());
@ -89,27 +91,18 @@ public class MediaSessionPlayerUi extends PlayerUi
public void destroyPlayer() {
super.destroyPlayer();
player.getPrefs().unregisterOnSharedPreferenceChangeListener(this);
if (sessionConnector != null) {
sessionConnector.setMediaButtonEventHandler(null);
sessionConnector.setPlayer(null);
sessionConnector.setQueueNavigator(null);
sessionConnector = null;
}
if (mediaSession != null) {
mediaSession.setActive(false);
mediaSession.release();
mediaSession = null;
}
sessionConnector.setMediaButtonEventHandler(null);
sessionConnector.setPlayer(null);
sessionConnector.setQueueNavigator(null);
mediaSession.setActive(false);
prevNotificationActions = List.of();
}
@Override
public void onThumbnailLoaded(@Nullable final Bitmap bitmap) {
super.onThumbnailLoaded(bitmap);
if (sessionConnector != null) {
// the thumbnail is now loaded: invalidate the metadata to trigger a metadata update
sessionConnector.invalidateMediaSessionMetadata();
}
// the thumbnail is now loaded: invalidate the metadata to trigger a metadata update
sessionConnector.invalidateMediaSessionMetadata();
}
@ -200,8 +193,8 @@ public class MediaSessionPlayerUi extends PlayerUi
return;
}
if (sessionConnector == null) {
// sessionConnector will be null after destroyPlayer is called
if (!mediaSession.isActive()) {
// mediaSession will be inactive after destroyPlayer is called
return;
}

View File

@ -28,13 +28,17 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo<? extends InfoItem>>
private transient Disposable fetchReactor;
protected AbstractInfoPlayQueue(final T info) {
this(info, 0);
}
protected AbstractInfoPlayQueue(final T info, final int index) {
this(info.getServiceId(), info.getUrl(), info.getNextPage(),
info.getRelatedItems()
.stream()
.filter(StreamInfoItem.class::isInstance)
.map(StreamInfoItem.class::cast)
.collect(Collectors.toList()),
0);
index);
}
protected AbstractInfoPlayQueue(final int serviceId,

View File

@ -16,6 +16,10 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo>
super(info);
}
public PlaylistPlayQueue(final PlaylistInfo info, final int index) {
super(info, index);
}
public PlaylistPlayQueue(final int serviceId,
final String url,
final Page nextPage,

View File

@ -382,7 +382,7 @@ public final class PopupPlayerUi extends VideoPlayerUi {
private void end() {
windowManager.removeView(closeOverlayBinding.getRoot());
closeOverlayBinding = null;
player.getService().stopService();
player.getService().destroyPlayerAndStopService();
}
}).start();
}

View File

@ -303,7 +303,7 @@ public final class Localization {
* <ul>
* <li>English (original)</li>
* <li>English (descriptive)</li>
* <li>Spanish (dubbed)</li>
* <li>Spanish (Spain) (dubbed)</li>
* </ul>
*
* @param context the context used to get the app language
@ -313,7 +313,7 @@ public final class Localization {
public static String audioTrackName(@NonNull final Context context, final AudioStream track) {
final String name;
if (track.getAudioLocale() != null) {
name = track.getAudioLocale().getDisplayLanguage(getAppLocale(context));
name = track.getAudioLocale().getDisplayName();
} else if (track.getAudioTrackName() != null) {
name = track.getAudioTrackName();
} else {

View File

@ -98,6 +98,7 @@ public final class NavigationHelper {
}
intent.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN.valueForIntent());
intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true);
return intent;
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/white"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/white"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
</vector>

View File

@ -854,6 +854,5 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">شارِك قائمة التشغيل</string>
<string name="share_playlist_with_titles_message">شارِك قائمة التشغيل بتفاصيليها مثل اسم قائمة التشغيل وعناوين الفيديو أو كقائمة بسيطة من عناوين تشعّبيّة للفيديوهات</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
</resources>

View File

@ -18,7 +18,7 @@
<string name="download_path_dialog_title">اختر مجلد التنزيل لملفات الفيديو</string>
<string name="download_path_summary">يتم تخزين ملفات الفيديو التي تم تنزيلها هنا</string>
<string name="download_path_title">مجلد تحميل الفيديو</string>
<string name="install">ثبت</string>
<string name="install">ثبيت</string>
<string name="kore_not_found">تطبيق Kore غير موجود. هل تريد تثبيته؟</string>
<string name="light_theme_title">فاتح</string>
<string name="network_error">خطأ في الشبكة</string>
@ -83,7 +83,7 @@
<string name="resume_on_audio_focus_gain_title">استئناف التشغيل</string>
<string name="resume_on_audio_focus_gain_summary">متابعة التشغيل بعد المقاطعات (مثل المكالمات الهاتفية)</string>
<string name="show_hold_to_append_title">إظهار تلميح \"اضغط للفتح\"</string>
<string name="show_hold_to_append_summary">إظهار التلميح عند الضغط على الخلفية أو الزر المنبثق في الفيديو \"التفاصيل:\\</string>
<string name="show_hold_to_append_summary">إظهار التلميح عند الضغط على الخلفية أو الزر المنبثق في الفيديو \"التفاصيل:\"</string>
<string name="settings_category_player_title">المشغل</string>
<string name="settings_category_player_behavior_title">السلوك</string>
<string name="popup_playing_toast">تشغيل في وضع منبثق</string>
@ -558,7 +558,7 @@
<string name="remove_watched">إزالة ما تمت مشاهدته</string>
<string name="show_original_time_ago_summary">ستكون النصوص الأصلية من الخدمات مرئية في عناصر البث</string>
<string name="show_original_time_ago_title">عرض الوقت الأصلي على العناصر</string>
<string name="youtube_restricted_mode_enabled_title">قم بتشغيل \"وضع تقييد المحتوى\" في يوتيوب\\</string>
<string name="youtube_restricted_mode_enabled_title">قم بتشغيل \"وضع تقييد المحتوى\" في يوتيوب</string>
<string name="video_detail_by">بواسطة %s</string>
<string name="channel_created_by">أنشأها %s</string>
<string name="detail_sub_channel_thumbnail_view_description">الصورة الرمزية للقناة</string>
@ -854,7 +854,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">مشاركة قائمة التشغيل</string>
<string name="share_playlist_with_titles_message">شارك تفاصيل قائمة التشغيل مثل اسم قائمة التشغيل وعناوين الفيديو أو كقائمة بسيطة من عناوين URL للفيديو</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies">
<item quantity="zero">رد %s</item>

View File

@ -785,7 +785,6 @@
<string name="image_quality_high">Yüksək keyfiyyət</string>
<string name="question_mark">\?</string>
<string name="share_playlist">Oynatma siyahısın paylaş</string>
<string name="share_playlist_with_titles_message">Pleylist adı və video başlıqları kimi təfsilatlar və ya video URL-lərin sadə siyahısı olaraq pleylist paylaş</string>
<string name="share_playlist_with_titles">Başlıqlarla paylaşın</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="share_playlist_content_details">%1$s

View File

@ -26,4 +26,6 @@
<string name="use_external_video_player_summary">Duad bei manchen Auflösungen d\'Tonspur weggad</string>
<string name="open_in_popup_mode">Im Pop-up Modus aufmacha</string>
<string name="main_bg_subtitle">Drug auf\'d Lubn zum ofanga.</string>
</resources>
<string name="ok">Bassd scho</string>
<string name="no">naa</string>
</resources>

View File

@ -19,7 +19,7 @@
<string name="use_external_video_player_summary">Прыбірае гук пры пэўнай раздзяляльнасці</string>
<string name="use_external_audio_player_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="subscription_change_failed">Не ўдалося змяніць падпіску</string>
<string name="subscription_update_failed">Не ўдалося абнавіць падпіску</string>
@ -165,8 +165,9 @@
<string name="no_subscribers">Няма падпісчыкаў</string>
<plurals name="subscribers">
<item quantity="one">%s падпісчык</item>
<item quantity="few">%s падпісчыка</item>
<item quantity="few">%s падпісчыкі</item>
<item quantity="many">%s падпісчыкаў</item>
<item quantity="other">%s падпісчыкаў</item>
</plurals>
<string name="no_views">Няма праглядаў</string>
<plurals name="views">
@ -177,7 +178,7 @@
</plurals>
<string name="no_videos">Няма відэа</string>
<plurals name="videos">
<item quantity="one">%s Відэа</item>
<item quantity="one">%s відэа</item>
<item quantity="few">%s відэа</item>
<item quantity="many">%s відэа</item>
<item quantity="other">%s відэа</item>
@ -207,7 +208,7 @@
<string name="settings_file_replacement_character_summary">Недапушчальныя сімвалы замяняюцца на гэты</string>
<string name="settings_file_replacement_character_title">Сімвал для замены</string>
<string name="charset_letters_and_digits">Літары і лічбы</string>
<string name="charset_most_special_characters">Большасць спецзнакаў</string>
<string name="charset_most_special_characters">Большасць спецсімвалаў</string>
<string name="title_activity_about">Аб NewPipe</string>
<string name="title_licenses">Іншыя ліцэнзіі</string>
<string name="copyright" formatted="true">© %1$s %2$s пад ліцэнзіяй %3$s</string>
@ -221,7 +222,7 @@
<string name="donation_encouragement">NewPipe распрацаваны добраахвотнікамі, якія праводзяць свой вольны час, забяспечваючы лепшы карыстацкі досвед. Дапамажыце распрацоўшчыкам зрабіць NewPipe яшчэ лепшым, пакуль яны атрымліваюць асалоду ад кавы.</string>
<string name="give_back">Ахвяраваць грошы</string>
<string name="website_title">Вэб-сайт</string>
<string name="website_encouragement">Дзеля атрымання больш падрабязнай інфармацыі і апошніх навін аб NewPipe наведайце наш вэб-сайт.</string>
<string name="website_encouragement">Наведайце вэб-сайт, каб атрымаць больш інфармацыі і паглядзець апошнія навіны NewPipe.</string>
<string name="privacy_policy_title">Палітыка прыватнасці NewPipe</string>
<string name="privacy_policy_encouragement">Праект NewPipe вельмі адказна ставіцца да вашай прыватнасці. Таму праграма не збірае ніякіх даных без вашай згоды. \nПалітыка прыватнасці NewPipe падрабязна тлумачыць, якія даныя адпраўляюцца і захоўваюцца пры адпраўцы справаздачы пра збой.</string>
<string name="read_privacy_policy">Прачытаць палітыку прыватнасці</string>
@ -231,9 +232,9 @@
<string name="title_activity_history">Гісторыя</string>
<string name="action_history">Гісторыя</string>
<string name="delete_item_search_history">Выдаліць гэты элемент з гісторыі пошуку?</string>
<string name="title_last_played">Нядаўна прайграныя</string>
<string name="title_most_played">Найбольш прайграваныя</string>
<string name="main_page_content">Кантэнт галоўнай старонкі</string>
<string name="title_last_played">Прайгравалася нядаўна</string>
<string name="title_most_played">Прайгравалася найбольш</string>
<string name="main_page_content">Змесціва галоўнай старонкі</string>
<string name="blank_page_summary">Пустая старонка</string>
<string name="kiosk_page_summary">Старонка кіёска</string>
<string name="channel_page_summary">Старонка канала</string>
@ -254,13 +255,13 @@
<string name="play_queue_audio_settings">Налады аўдыя</string>
<string name="hold_to_append">Зацісніце, каб дадаць у чаргу</string>
<string name="start_here_on_background">Пачаць прайграванне ў фоне</string>
<string name="start_here_on_popup">Пачаць прайграванне у акне</string>
<string name="start_here_on_popup">Пачаць прайграванне ў акне</string>
<string name="drawer_open">Адкрыць бакавую панэль</string>
<string name="drawer_close">Закрыць бакавую панэль</string>
<string name="preferred_open_action_settings_title">Пры адкрыцці кантэнту</string>
<string name="preferred_open_action_settings_summary">Пры адкрыцці спасылкі на кантэнт — %s</string>
<string name="video_player">Відэаплэер</string>
<string name="background_player">Фонавы плэер</string>
<string name="background_player">Фонавы прайгравальнік</string>
<string name="popup_player">Аконны прайгравальнік</string>
<string name="always_ask_open_action">Заўсёды пытаць</string>
<string name="preferred_player_fetcher_notification_title">Атрыманне звестак…</string>
@ -269,7 +270,7 @@
<string name="rename_playlist">Перайменаваць</string>
<string name="name">Імя</string>
<string name="add_to_playlist">Дадаць у плэйліст</string>
<string name="set_as_playlist_thumbnail">Усталяваць як мініяцюру плэйліста</string>
<string name="set_as_playlist_thumbnail">Зрабіць мініяцюрай плэйліста</string>
<string name="bookmark_playlist">Дадаць плэйліст у закладкі</string>
<string name="unbookmark_playlist">Выдаліць закладку</string>
<string name="delete_playlist_prompt">Выдаліць плэйліст\?</string>
@ -308,7 +309,7 @@
<string name="skip_silence_checkbox">Прапускаць цішыню</string>
<string name="playback_step">Крок</string>
<string name="playback_reset">Скід</string>
<string name="start_accept_privacy_policy">У адпаведнасці з Агульным рэгламентам па абароне даных ЕС (GDPR), звяртаем вашу ўвагу на палітыку прыватнасці NewPipe. Уважліва азнаёмцеся з ёй. \nВы павінны прыняць ўмовы, каб адправіць нам справаздачу пра памылку.</string>
<string name="start_accept_privacy_policy">У адпаведнасці з Агульным рэгламентам па абароне даных ЕС (GDPR), звяртаем вашу ўвагу на палітыку прыватнасці NewPipe. Уважліва азнаёмцеся з ёй. \nВы павінны прыняць умовы, каб адправіць нам справаздачу пра памылку.</string>
<string name="accept">Прыняць</string>
<string name="decline">Адмовіцца</string>
<string name="limit_data_usage_none_description">Без абмежаванняў</string>
@ -316,7 +317,7 @@
<string name="minimize_on_exit_title">Згортванне пры пераключэнні праграмы</string>
<string name="minimize_on_exit_summary">Дзеянне пры пераключэнні з асноўнага відэаплэера на іншую праграму — %s</string>
<string name="minimize_on_exit_none_description">Нічога не рабіць</string>
<string name="minimize_on_exit_background_description">Згортванне у фон</string>
<string name="minimize_on_exit_background_description">Згортванне ў фон</string>
<string name="minimize_on_exit_popup_description">Згортванне ў акно</string>
<string name="unsubscribe">Адпісацца</string>
<string name="tab_choose">Выберыце ўкладку</string>
@ -362,7 +363,7 @@
<string name="stop">Спыніць</string>
<string name="max_retry_msg">Максімум спроб</string>
<string name="max_retry_desc">Колькасць спроб спампаваць да адмены</string>
<string name="pause_downloads_on_mobile">Перапыніць у платных сетках</string>
<string name="pause_downloads_on_mobile">Прыпыняць у сетках з тарыфікацыяй</string>
<string name="pause_downloads_on_mobile_desc">Карысна пры пераключэнні на мабільную сетку, хоць некаторыя спампоўванні немагчыма прыпыніць</string>
<string name="events">Падзеі</string>
<string name="conferences">Канферэнцыі</string>
@ -386,7 +387,7 @@
<string name="error_postprocessing_stopped">Праграма NewPipe была закрыта падчас працы з файлам</string>
<string name="error_insufficient_storage_left">На прыладзе скончылася вольнае месца</string>
<string name="error_progress_lost">Прагрэс страчаны, бо файл быў выдалены</string>
<string name="error_timeout">Час злучэння выйшла</string>
<string name="error_timeout">Скончыўся час злучэння</string>
<string name="confirm_prompt">Вы хочаце ачысціць гісторыю спампоўвання ці выдаліць спампаваныя файлы?</string>
<string name="enable_queue_limit">Абмежаваць чаргу спампоўвання</string>
<string name="enable_queue_limit_desc">Толькі адно адначасовае спампоўванне</string>
@ -420,7 +421,7 @@
<string name="peertube_instance_add_fail">Не ўдалося праверыць сервер</string>
<string name="peertube_instance_add_help">Увядзіце URL-адрас сервера</string>
<string name="peertube_instance_url_summary">Выберыце ўлюбёныя серверы PeerTube</string>
<string name="clear_queue_confirmation_description">Актыўны плэер быў зменены</string>
<string name="clear_queue_confirmation_description">Чарга актыўнага прайгравальніка будзе заменена</string>
<string name="clear_queue_confirmation_summary">Пераключэнне з аднаго плэера на другі можа прывесці да замены вашай чаргі</string>
<string name="clear_queue_confirmation_title">Запытваць пацвярджэнне перад ачысткай чаргі</string>
<string name="never">Ніколі</string>
@ -452,7 +453,7 @@
<string name="albums">Альбомы</string>
<string name="songs">Песні</string>
<string name="videos_string">Відэа</string>
<string name="auto_queue_toggle">Аўтаматычная чарга</string>
<string name="auto_queue_toggle">Аўтапрайграванне</string>
<string name="seek_duration_title">Крок перамотвання</string>
<string name="notification_colorize_title">Каляровыя апавяшчэнні</string>
<string name="notification_action_nothing">Нічога</string>
@ -491,8 +492,8 @@
<string name="off">Адключыць</string>
<string name="no_audio_streams_available_for_external_players">Няма аўдыяпатокаў даступных для знешніх плэераў</string>
<string name="get_notified">Апавяшчаць</string>
<string name="no_video_streams_available_for_external_players">Няма даступных відэатрансляцый для знешніх плэераў</string>
<string name="selected_stream_external_player_not_supported">Выбраная трансляцыя не падтрымліваецца знешнімі плэерамі</string>
<string name="no_video_streams_available_for_external_players">Няма відэапатокаў даступных для знешніх плэераў</string>
<string name="selected_stream_external_player_not_supported">Выбраны паток не падтрымліваецца знешнімі плэерамі</string>
<string name="select_quality_external_players">Выберыце якасць для знешніх плэераў</string>
<string name="unknown_quality">Невядомая якасць</string>
<string name="unknown_format">Невядомы фармат</string>
@ -503,7 +504,7 @@
<string name="open_with">Адкрыць праз</string>
<string name="night_theme_title">Начная тэма</string>
<string name="open_website_license">Адкрыць вэб-сайт</string>
<string name="description_select_note">Цяпер Вы можаце вылучаць тэкст у апісанні. Звярніце ўвагу, што ў рэжыме вылучэння старонка можа мігацець, а спасылкі могуць быць недаступныя для націскання.</string>
<string name="description_select_note">Цяпер можна вылучаць тэкст у апісанні. Звярніце ўвагу, што ў рэжыме вылучэння старонка можа мільгаць, а спасылкі не націскацца.</string>
<string name="start_main_player_fullscreen_title">Запускаць галоўны прайгравальнік у поўнаэкранным рэжыме</string>
<string name="show_channel_details">Паказаць дэталі канала</string>
<string name="low_quality_smaller">Нізкая якасць (менш)</string>
@ -562,8 +563,8 @@
<plurals name="minutes">
<item quantity="one">%d хвіліна</item>
<item quantity="few">%d хвіліны</item>
<item quantity="many">%d хвілінаў</item>
<item quantity="other">%d хвілінаў</item>
<item quantity="many">%d хвілін</item>
<item quantity="other">%d хвілін</item>
</plurals>
<string name="progressive_load_interval_summary">Змяніць памер інтэрвалу загрузкі прагрэсіўнага змесціва (у цяперашні час %s). Меншае значэнне можа паскорыць іх першапачатковую загрузку</string>
<string name="show_description_summary">Выключыце, каб схаваць апісанне відэа і дадатковую інфармацыю</string>
@ -603,8 +604,8 @@
<string name="enqueue_next_stream">У чаргу далей</string>
<string name="enqueued_next">У чарзе наступны</string>
<string name="loading_stream_details">Загрузка звестак аб стрыме…</string>
<string name="processing_may_take_a_moment">Апрацоўка... Можа заняць некаторы час</string>
<string name="playlist_add_stream_success_duplicate">Дублікат дададзены %d раз</string>
<string name="processing_may_take_a_moment">Ідзе апрацоўка… Крыху пачакайце</string>
<string name="playlist_add_stream_success_duplicate">Дублікат дададзены %d раз(ы)</string>
<string name="leak_canary_not_available">LeakCanary недаступны</string>
<string name="show_memory_leaks">Паказаць уцечкі памяці</string>
<string name="disable_media_tunneling_summary">Адключыце мультымедыйнае тунэляванне, калі ў вас з\'яўляецца чорны экран або заіканне падчас прайгравання відэа.</string>
@ -613,7 +614,7 @@
<string name="faq_title">Частыя пытанні</string>
<string name="faq">Перайсці на вэб-сайт</string>
<string name="main_page_content_swipe_remove">Правядзіце пальцам па элементах, каб выдаліць іх</string>
<string name="unset_playlist_thumbnail">Адмяніць пастаянную мініяцюру</string>
<string name="unset_playlist_thumbnail">Прыбраць пастаянную мініяцюру</string>
<string name="show_image_indicators_title">Паказваць індыкатары выяў</string>
<string name="show_image_indicators_summary">Паказваць каляровыя стужкі Пікаса на выявах, якія пазначаюць іх крыніцу: чырвоная для сеткі, сіняя для дыска і зялёная для памяці</string>
<string name="feed_processing_message">Апрацоўка стужкі…</string>
@ -624,7 +625,7 @@
<string name="percent">Працэнт</string>
<string name="remove_watched_popup_warning">Відэа, якія прагледжаны перад дадаваннем і пасля дадавання ў спіс прайгравання, будуць выдалены. \nВы ўпэўнены? Гэта дзеянне немагчыма скасаваць!</string>
<string name="show_crash_the_player_summary">Паказвае варыянт збою пры выкарыстанні плэера</string>
<string name="remove_watched">Выдаліць прагледжанае</string>
<string name="remove_watched">Выдаліць прагледжаныя</string>
<string name="show_error_snackbar">Паказаць панэль памылак</string>
<string name="semitone">Паўтон</string>
<string name="any_network">Любая сетка</string>
@ -645,12 +646,12 @@
<string name="feed_group_dialog_delete_message">Выдаліць гэту групу?</string>
<string name="feed_create_new_group_button_title">Новая</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Паказаць толькі разгрупаваныя падпіскі</string>
<string name="feed_show_upcoming">Маючыя адбыцца</string>
<string name="feed_show_upcoming">Запланаваныя</string>
<string name="show_crash_the_player_title">Паказваць «Збой плэера»</string>
<string name="check_new_streams">Запусціце праверку новых патокаў</string>
<string name="crash_the_app">Збой праграмы</string>
<string name="enable_streams_notifications_title">Апавяшчэнні аб новых стрымах</string>
<string name="enable_streams_notifications_summary">Апавяшчаць аб новых стрымах з падпісак</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="check_for_updates">Праверыць наяўнасць абнаўленняў</string>
@ -693,7 +694,7 @@
<string name="you_successfully_subscribed">Вы падпісаліся на канал</string>
<string name="recent">Апошнія</string>
<string name="radio">Радыё</string>
<string name="feed_hide_streams_title">Паказваць запланаваныя трансляцыі</string>
<string name="feed_hide_streams_title">Паказваць наступныя патокі</string>
<string name="feed_show_hide_streams">Паказаць/схаваць трансляцыі</string>
<string name="content_not_supported">Гэты кантэнт яшчэ не падтрымліваецца NewPipe.
\n
@ -710,14 +711,14 @@
<string name="service_provides_reason">%s дае наступную прычыну:</string>
<string name="featured">Вартае ўвагі</string>
<string name="metadata_privacy_internal">Унутраная</string>
<string name="feed_show_watched">Цалкам прагледзеў</string>
<string name="feed_show_watched">Прагледжаныя цалкам</string>
<string name="paid_content">Гэты кантэнт даступны толькі для аплачаных карыстальнікаў, таму NewPipe не можа яго трансляваць або спампоўваць.</string>
<string name="feed_use_dedicated_fetch_method_summary">Даступны ў некаторых службах, звычайна нашмат хутчэй, але можа вяртаць абмежаваную колькасць элементаў і часта няпоўную інфармацыю (напрыклад, без працягласці, тыпу элемента, без актыўнага стану)</string>
<string name="metadata_age_limit">Узроставае абмежаванне</string>
<string name="no_appropriate_file_manager_message_android_10">Для гэтага дзеяння не знойдзены прыдатны файлавы менеджар. \nУсталюйце файлавы менеджар, сумяшчальны з Storage Access Framework</string>
<string name="no_app_to_open_intent">Ніякая праграма на вашай прыладзе не можа адкрыць гэта</string>
<string name="progressive_load_interval_exoplayer_default">Стандартнае значэнне ExoPlayer</string>
<string name="feed_show_partially_watched">Часткова прагледжана</string>
<string name="feed_show_partially_watched">Прагледжаныя часткова</string>
<string name="feed_use_dedicated_fetch_method_help_text">Лічыце, што загрузка каналаў адбываецца занадта павольна? Калі так, паспрабуйце ўключыць хуткую загрузку (можна змяніць у наладах або націснуўшы кнопку ніжэй). \n \nNewPipe прапануе два спосабы загрузкі каналаў: \n• Атрыманне ўсяго канала падпіскі. Павольны, але інфармацыя поўная). \n• Выкарыстанне спецыяльнай канчатковай кропкі абслугоўвання. Хуткі, але звычайна інфармацыя няпоўная). \n \nРозніца паміж імі ў тым, што ў хуткім звычайна адсутнічае частка інфармацыі, напрыклад, працягласць або тып (немагчыма адрозніць трансляцыі ад звычайных відэа), і ён можа вяртаць менш элементаў. \n \nYouTube з\'яўляецца прыкладам сэрвісу, які прапануе гэты хуткі метад праз RSS-канал. \n \nТакім чынам, выбар залежыць ад таго, чаму вы аддаяце перавагу: хуткасці або дакладнасці інфармацыя.</string>
<string name="metadata_privacy">Прыватнасць</string>
<string name="metadata_language">Мова</string>
@ -747,9 +748,7 @@
<string name="audio_track_present_in_video">У гэтым патоку ўжо павінна быць гукавая дарожка</string>
<string name="use_exoplayer_decoder_fallback_summary">Уключыце гэту опцыю, калі ў вас ёсць праблемы з ініцыялізацыяй дэкодэра, якая вяртаецца да дэкодэраў з больш нізкім прыярытэтам, калі ініцыялізацыя асноўных дэкодэраў не ўдаецца. Гэта можа прывесці да нізкай прадукцыйнасці прайгравання, чым пры выкарыстанні асноўных дэкодэраў</string>
<string name="settings_category_exoplayer_summary">Кіраванне некаторымі наладамі ExoPlayer. Каб гэтыя змены ўступілі ў сілу, патрабуецца перазапуск прайгравальніка</string>
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Гэты абыходны шлях вызваляе і паўторна стварае відэакодэкі, калі адбываецца змяненне паверхні, замест таго, каб усталёўваць паверхню непасрэдна для кодэка. ExoPlayer ужо выкарыстоўваецца на некаторых прыладах з гэтай праблемай, гэты параметр мае ўплыў толькі на прыладах з Android 6 і вышэй
\n
\nУключэнне гэтай опцыі можа прадухіліць памылкі прайгравання пры пераключэнні бягучага відэаплэера або пераключэнні ў поўнаэкранны рэжым</string>
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Гэты абыходны шлях вызваляе і паўторна стварае відэакодэкі, калі адбываецца змяненне паверхні, замест таго, каб зажаваць паверхню непасрэдна для кодэка. Ужо выкарыстоўваецца ExoPlayer на некаторых прыладах з такой праблемай, гэты параметр ужываецца толькі на прыладах з Android 6 і вышэй\n\nУключэнне параметра можа прадухіліць памылкі прайгравання пры пераключэнні бягучага відэаплэера або пераключэнні ў поўнаэкранны рэжым</string>
<string name="image_quality_title">Якасць выяў</string>
<string name="channel_tab_videos">Відэа</string>
<string name="question_mark">\?</string>
@ -771,7 +770,7 @@
<string name="next_stream">Наступны паток</string>
<string name="disable_media_tunneling_automatic_info">Прадвызначана на вашай прыладзе адключана медыятунэляванне, бо гэтая мадэль прылады яго не падтрымлівае.</string>
<string name="metadata_subchannel_avatars">Аватары падканалаў</string>
<string name="open_play_queue">Адкрыйце чаргу прайгравання</string>
<string name="open_play_queue">Адкрыць чаргу прайгравання</string>
<string name="image_quality_none">Не загружаць выявы</string>
<string name="image_quality_high">Высокая якасць</string>
<string name="channel_tab_about">Аб канале</string>
@ -781,13 +780,12 @@
<string name="rewind">Пераматаць назад</string>
<string name="replay">Паўтарыць</string>
<string name="feed_fetch_channel_tabs_summary">Атрыманыя ўкладкі пры абнаўленні стужкі. Гэты параметр не прымяняецца, калі канал абнаўляецца ў хуткім рэжыме.</string>
<string name="share_playlist_with_titles_message">Абагуліць плэйліст, перадаецца назва плэйліста і назвы відэа або просты спіс URL-адрасоў відэа</string>
<string name="image_quality_medium">Сярэдняя якасць</string>
<string name="metadata_uploader_avatars">Загрузнік аватараў</string>
<string name="metadata_banners">Банеры</string>
<string name="channel_tab_playlists">Плэйлісты</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="main_tabs_position_summary">Перамясціць панэль укладак ўніз</string>
<string name="main_tabs_position_summary">Перамясціць панэль укладак уніз</string>
<string name="no_live_streams">Няма жывых трансляцый</string>
<string name="image_quality_summary">Выберыце якасць выяў і ці трэба спампоўваць выявы ўвогуле, каб паменшыць выкарыстанне даных і памяці. Змены ачышчаюць кэш выяў як у памяці, так і на дыску - %s</string>
<string name="play">Прайграць</string>
@ -813,7 +811,7 @@
<string name="auto_update_check_description">NewPipe можа аўтаматычна правяраць наяўнасць абнаўленняў і паведаміць вам, калі яны будуць даступны. \nУключыць гэту функцыю?</string>
<string name="import_settings_vulnerable_format">Налады ў імпартаваным экспарце выкарыстоўваюць уразлівы фармат, які састарэў з версіі NewPipe 0.27.0. Пераканайцеся, што імпартаваны экспарт атрыманы з надзейнай крыніцы, і ў будучыні пераважней выкарыстоўваць толькі экспарт, атрыманы з NewPipe 0.27.0 ці навей. Падтрымка імпарту налад у гэтым уразлівым фармаце хутка будзе цалкам выдаленая, і тады старыя версіі NewPipe больш не змогуць імпартаваць наладкі з экспарту з новых версій.</string>
<string name="no">Не</string>
<string name="settings_category_backup_restore_title">Рэзервовае капіраванне і аднаўленне</string>
<string name="settings_category_backup_restore_title">Рэзервовае капіяванне і аднаўленне</string>
<string name="reset_settings_title">Скінуць налады</string>
<string name="reset_settings_summary">Скінуць усе налады на іх прадвызначаныя значэнні</string>
<string name="reset_all_settings">Пры скіданні ўсіх налад будуць адхілены ўсе вашы змены налад і праграма перазапусціцца. \n \nСапраўды хочаце працягнуць?</string>

View File

@ -192,7 +192,7 @@
<string name="copyright" formatted="true">© %1$s от %2$s под лиценза %3$s</string>
<string name="contribution_title">Съдействайте</string>
<string name="contribution_encouragement">За всичко, което се сетите: превод, промени по дизайна, изчистване на кода или много сериозни промени по кода помощта е винаги добре дошла. Колкото повече развитие, толкова по-добре!</string>
<string name="donation_title">Направете дарение</string>
<string name="donation_title">Дарение</string>
<string name="donation_encouragement">NewPipe се разработва от доброволци, които отделят от своето време, за да предоставят най-доброто потребителско изживяване. Включете се в разработката като почерпите разработчиците с една чашка кафе, които да изпият, докато правят NewPipe още по-добро приложение.</string>
<string name="give_back">Дари</string>
<string name="website_title">Уебсайт</string>
@ -203,7 +203,7 @@
<string name="read_privacy_policy">Прочетете нашата политика за поверителност</string>
<string name="app_license_title">Лицензът на NewPipe</string>
<string name="no_player_found_toast">Липсва стрийм плейър (можете да изтеглите VLC, за да пуснете стрийма).</string>
<string name="show_hold_to_append_summary">Покажи съвет при натискане на фона или изскачащия бутон във видеоклипа Подробности:“</string>
<string name="show_hold_to_append_summary">Покажи съвет при натискане на фона или изскачащия бутон във видеоклипа \"Подробности:“</string>
<string name="clear_views_history_summary">Изтрива историята на възпроизвежданите стриймове и позицията на възпроизвеждането</string>
<string name="video_streams_empty">Не са намерени видео стриймове</string>
<string name="audio_streams_empty">Не са намерени аудио стриймове</string>
@ -519,7 +519,7 @@
<string name="pause_downloads_on_mobile_desc">Полезно при превключване към мобилни данни, въпреки че някои изтегляния не поддържат възобновяване и ще започнат отначало</string>
<string name="crash_the_app">Срив на приложението</string>
<string name="notification_colorize_summary">Цветът на известието да се избира според главния цвят в миниатюрата на видеото (може да не работи на всички устройства)</string>
<string name="youtube_restricted_mode_enabled_title">Използване на ограничения режим на YouTube</string>
<string name="youtube_restricted_mode_enabled_title">Включване на \"Ограничен режим“ в YouTube</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube предлага „ограничен режим“, чрез който можете да филтрирате потенциално съдържание за възрастни</string>
<string name="restricted_video">Това видео е с възрастова граница.
\n
@ -557,7 +557,7 @@
<string name="enable_playback_state_lists_summary">Покажи индикатори за позиция на възпроизвеждане в списъци</string>
<string name="notification_actions_summary_android13">Редактирайте всяко действие за известяване по-долу, като щракнете върху него. Първите три действия (възпроизвеждане/пауза, предишно и следващо) се задават от системата и не могат да бъдат конфигурирани.</string>
<string name="right_gesture_control_summary">Изберете жест за дясната половина на екрана на плейъра</string>
<string name="right_gesture_control_title">Действие с жест на дясно</string>
<string name="right_gesture_control_title">Действие с жест надясно</string>
<string name="start_main_player_fullscreen_title">Стартирайте основния плейър на цял екран</string>
<string name="streams_notification_channel_description">Известия за нови видеоклипове в абонаментите</string>
<string name="enable_streams_notifications_summary">Известявайте за нови видеоклипове в абонаментите</string>
@ -713,7 +713,6 @@
<string name="replay">Повторение</string>
<string name="rewind">Превъртане назад</string>
<string name="forward">Напред</string>
<string name="share_playlist_with_titles_message">Споделете плейлист с подробности, като име на плейлист и заглавия на видеоклипове или като обикновен списък с URL адреси на видеоклипове</string>
<string name="share_playlist_with_list">Споделяне на списък с URL</string>
<string name="delete_playback_states_alert">Изтрии всички позиции на възпроизвеждане?</string>
<string name="watch_history_states_deleted">Позициите за възпроизвеждане са изтрити</string>

View File

@ -726,4 +726,11 @@
<string name="audio_track">Pista d\'àudio</string>
<string name="no">No</string>
<string name="no_streams">Cap emissió</string>
<string name="enable_streams_notifications_summary">Notifica sobre les noves retransmissions de les subscripcions</string>
<string name="enable_streams_notifications_title">Noves notificacions de retransmissions</string>
<string name="duplicate_in_playlist">Les llistes de reproducció que estan en gris ja contenen aquest element.</string>
<string name="unset_playlist_thumbnail">Desestableix la miniatura permanent</string>
<string name="playlist_add_stream_success_duplicate">Duplicat afegit/s %d vegada/es</string>
<string name="disable_media_tunneling_automatic_info">El túnel multimèdia s\'ha desactivat de manera predeterminada al dispositiu perquè se sap que el vostre model de dispositiu no ho permet.</string>
<string name="semitone">Semiton</string>
</resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upload_date_text">Publikováno na %1$s</string>
<string name="upload_date_text">Publikováno %1$s</string>
<string name="no_player_found">Nenalezen žádný přehrávač. Nainstalovat VLC?</string>
<string name="install">Instalovat</string>
<string name="cancel">Zrušit</string>
@ -783,7 +783,7 @@
<string name="question_mark">\?</string>
<string name="metadata_subscribers">Odběratelé</string>
<string name="show_channel_tabs_summary">Které karty mají být zobrazeny na stránkách kanálů</string>
<string name="share_playlist_with_list">Sdílet URL seznamu</string>
<string name="share_playlist_with_list">Sdílet seznam adres</string>
<string name="share_playlist_with_titles">Sdílet s názvy</string>
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
@ -804,7 +804,6 @@
<string name="channel_tab_albums">Alba</string>
<string name="rewind">Přetočení zpět</string>
<string name="replay">Znovu přehrát</string>
<string name="share_playlist_with_titles_message">Sdílejte playlist s podrobnostmi jako je jeho název a názvy videí, nebo jako jednoduchý seznam adres videí</string>
<string name="image_quality_medium">Střední kvalita</string>
<string name="metadata_banners">Bannery</string>
<string name="channel_tab_playlists">Playlisty</string>

View File

@ -770,7 +770,6 @@
<string name="image_quality_none">Indlæs ikke billeder</string>
<string name="image_quality_low">Lav kvalitet</string>
<string name="share_playlist">Del Playliste</string>
<string name="share_playlist_with_titles_message">Del playliste med detajler såsom playlistenavn og videotitler eller som en simpel liste over video-URL\'er</string>
<string name="share_playlist_with_titles">Del med Titler</string>
<string name="share_playlist_with_list">Del URL-liste</string>
<plurals name="replies">

View File

@ -63,7 +63,7 @@
<string name="error_snackbar_message">Entschuldigung, etwas ist schiefgelaufen.</string>
<string name="your_comment">Dein Kommentar (auf englisch):</string>
<string name="duration_live">Live</string>
<string name="main_bg_subtitle">Tippe auf die Lupe, um zu beginnen.</string>
<string name="main_bg_subtitle">Tippe auf die Lupe, um zu suchen.</string>
<string name="downloads">Downloads</string>
<string name="downloads_title">Downloads</string>
<string name="error_report_title">Fehlerbericht</string>
@ -425,7 +425,7 @@
<string name="no_one_watching">Niemand schaut zu</string>
<plurals name="watching">
<item quantity="one">%s Zuschauer</item>
<item quantity="other">%s Zuschauende</item>
<item quantity="other">%s Zuschauer</item>
</plurals>
<string name="no_one_listening">Niemand hört zu</string>
<plurals name="listening">
@ -802,7 +802,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">Wiedergabeliste teilen</string>
<string name="share_playlist_with_titles_message">Teile die Wiedergabeliste mit Details wie dem Namen der Wiedergabeliste und den Videotiteln oder als einfache Liste von Video-URLs</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies">
<item quantity="one">%s Antwort</item>

View File

@ -802,7 +802,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">Κοινοποίηση λίστας</string>
<string name="share_playlist_with_titles_message">Μοιραστείτε τη λίστα αναπαραγωγής με λεπτομέρειες όπως το όνομα της λίστας αναπαραγωγής και τους τίτλους βίντεο ή ως μια απλή λίστα διευθύνσεων URL βίντεο</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies">
<item quantity="one">%s απάντηση</item>

View File

@ -616,7 +616,6 @@
<string name="no_video_streams_available_for_external_players">Neniu filmofluo ludeblas por ekstera ludilo</string>
<string name="channel_tab_videos">Filmetoj</string>
<string name="remove_watched_popup_warning">Filmetoj kiuj spektiĝis antaŭ aŭ post sia aldoniĝo al la ludlisto foriĝus.. \nĈu vi certas? Ĉi tio nemalfareblus!</string>
<string name="share_playlist_with_titles_message">Kunhavigus ludliston inkluzivante informojn kiel la nomoj de listeroj, aŭ kiel simpla listo de ligiloj</string>
<string name="reset_settings_summary">Restarigi implicitajn agordojn</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Jes, kaj ankaŭ parte spektitajn filmetojn</string>
</resources>

View File

@ -816,7 +816,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">Compartir la lista de reproducción</string>
<string name="share_playlist_with_titles_message">Compartir las listas de reproducción con los detalles como el nombre de la lista y los títulos de los vídeos o como una simple lista de una dirección URL con los vídeos</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies">
<item quantity="one">%s respuesta</item>

View File

@ -99,8 +99,8 @@
<string name="always">Alati</string>
<string name="just_once">Üks kord</string>
<string name="file">Fail</string>
<string name="notification_channel_name">NewPipe teavitus</string>
<string name="notification_channel_description">Teavitused NewPipe pleierile</string>
<string name="notification_channel_name">NewPipe\'i teavitus</string>
<string name="notification_channel_description">NewPipe\'i meediaesitaja teavitused</string>
<string name="unknown_content">[Tundmatu]</string>
<string name="switch_to_background">Lülita taustale</string>
<string name="switch_to_popup">Lülita hüpikpleierile</string>
@ -183,7 +183,7 @@
<string name="msg_name">Failinimi</string>
<string name="msg_threads">Lõimed</string>
<string name="msg_error">Viga</string>
<string name="msg_running">NewPipe allalaadimine</string>
<string name="msg_running">NewPipe\'i on allalaadimisel</string>
<string name="msg_running_detail">Üksikasjade nägemiseks toksa</string>
<string name="msg_wait">Palun oota…</string>
<string name="msg_copied">Kopeeriti lõikepuhvrisse</string>
@ -197,18 +197,18 @@
<string name="settings_file_replacement_character_title">Asendustähemärk</string>
<string name="charset_letters_and_digits">Tähed ja numbrid</string>
<string name="charset_most_special_characters">Erimärgid</string>
<string name="title_activity_about">NewPipe rakendusest</string>
<string name="title_activity_about">Rakenduse teave: NewPipe</string>
<string name="title_licenses">Kolmanda osapoole litsentsid</string>
<string name="tab_about">Rakenduse teave ja KKK</string>
<string name="tab_licenses">Litsentsid</string>
<string name="contribution_title">Panusta</string>
<string name="view_on_github">Vaata GitHubis</string>
<string name="donation_title">Anneta</string>
<string name="website_title">Veebileht</string>
<string name="website_encouragement">Enama info saamiseks külasta NewPipe veebilehte.</string>
<string name="privacy_policy_title">NewPipe privaatsuspoliitika</string>
<string name="website_title">Veebisait</string>
<string name="website_encouragement">Täiendava info ja uudiste lugemiseks külasta NewPipe\'i veebisaiti.</string>
<string name="privacy_policy_title">NewPipe\'i privaatsuspoliitika</string>
<string name="read_privacy_policy">Loe privaatsuspoliitikat</string>
<string name="app_license_title">NewPipe litsents</string>
<string name="app_license_title">NewPipe\'i litsents</string>
<string name="read_full_license">Loe litsentsi</string>
<string name="title_activity_history">Ajalugu</string>
<string name="action_history">Ajalugu</string>
@ -303,11 +303,10 @@
<string name="copyright" formatted="true">© %1$s %2$s %3$s alla</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="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\'i arendajad on vabatahtlikud, kes kulutavad oma vaba aega, toomaks sulle parimat kasutuskogemust. On aeg anda tagasi aidates arendajaid ja muuta NewPipe veel paremaks, nautides ise tassi kohvi.</string>
<string name="give_back">Anneta</string>
<string name="privacy_policy_encouragement">NewPipe võtab privaatsust väga tõsiselt. Seetõttu ei kogu rakendus ilma nõusolekuta mingeid andmeid.
\nNewPipe privaatsuspoliitika selgitab üksikasjalikult, milliseid andmeid saadetakse ja kogutakse veateate saatmisel.</string>
<string name="app_license">NewPipe vaba avatud koodiga tarkvara. Seada võid kasutada, uurida, jagada ja parandada nii, nagu õigemaks pead. Täpsemalt - seda võid levitada ja/või muuta vastavalt Vaba Tarkvara Sihtasutuse avaldatud GNU Üldise Avaliku Litsentsi v.3 (või sinu valikul hilisema versiooni) tingimustele.</string>
<string name="privacy_policy_encouragement">NewPipe võtab privaatsust väga tõsiselt. Seetõttu ei kogu rakendus ilma nõusolekuta mingeid andmeid. \nNewPipe\'i privaatsuspoliitika selgitab üksikasjalikult, milliseid andmeid saadetakse ja kogutakse veateate saatmisel.</string>
<string name="app_license">NewPipe on vaba ja avatud lähtekoodiga tarkvara. Seada võid kasutada, uurida, jagada ja parandada nii, nagu õigemaks pead. Täpsemalt - seda võid levitada ja/või muuta vastavalt Vaba Tarkvara Sihtasutuse avaldatud GNU Üldise Avaliku Litsentsi v.3 (või sinu valikul hilisema versiooni) tingimustele.</string>
<string name="enable_disposed_exceptions_title">Teavita elutsüklist väljas vigadest</string>
<string name="import_soundcloud_instructions">Impordi SoundCloudi profiil trükkides URL või oma ID:
\n
@ -321,8 +320,7 @@
<string name="skip_silence_checkbox">Keri helitu koht edasi</string>
<string name="playback_step">Samm</string>
<string name="playback_reset">Lähtesta</string>
<string name="start_accept_privacy_policy">Selleks, et täita Euroopa Üldist Andmekaitse Määrust (GDPR), juhime tähelepanu NewPipe\'i privaatsuspoliitikale. Palun lugege seda hoolikalt.
\nMeile veateate saatmiseks pead sellega nõustuma.</string>
<string name="start_accept_privacy_policy">Selleks, et täita Euroopa Üldist Andmekaitse Määrust (GDPR), juhime tähelepanu NewPipe\'i privaatsuspoliitikale. Palun loe seda hoolikalt. \nMeile veateate saatmiseks pead sellega nõustuma.</string>
<string name="minimize_on_exit_title">Minimeeri, kui kasutad teisi rakendusi</string>
<string name="minimize_on_exit_summary">Tegevus lülitusel peamiselt videopleierilt teisele rakendusele — %s</string>
<string name="minimize_on_exit_none_description">Pole</string>
@ -335,7 +333,7 @@
<string name="events">Sündmused</string>
<string name="file_deleted">Fail kustutati</string>
<string name="app_update_notification_channel_name">Rakenduse värskenduse teavitus</string>
<string name="app_update_notification_channel_description">Teavitus NewPipe uuetest versioonidest</string>
<string name="app_update_notification_channel_description">Teavitus NewPipe\'i uuetest versioonidest</string>
<string name="download_to_sdcard_error_title">Väline andmekandja pole saadaval</string>
<string name="download_to_sdcard_error_message">Allalaadimine välisele SD-kaardile ei ole võimalik. Kas lähtestada allalaadimiste kataloogi asukoht\?</string>
<string name="saved_tabs_invalid_json">Tõrge salvestatud vahekaaride lugemisel; kasutatakse vaikeväärtusi</string>
@ -350,7 +348,7 @@
<string name="list">Nimekiri</string>
<string name="grid">Võrgustik</string>
<string name="auto">Auto</string>
<string name="app_update_available_notification_title">NewPipe värskendus on saadaval!</string>
<string name="app_update_available_notification_title">NewPipe\'i värskendus on saadaval!</string>
<string name="missions_header_finished">Lõpetatud</string>
<string name="missions_header_pending">Ootel</string>
<string name="paused">peatatud</string>
@ -453,7 +451,7 @@
<item quantity="other">%s kuulajat</item>
</plurals>
<string name="hash_channel_description">Teavitused video räsimise edenemise kohta</string>
<string name="youtube_restricted_mode_enabled_title">Võta kasutusele YouTube\'i „Piiratud režiim“\\</string>
<string name="youtube_restricted_mode_enabled_title">Võta kasutusele YouTube\'i „Piiratud režiim“</string>
<string name="missing_file">Faili asukoht on muutunud või on ta kustutatud</string>
<string name="watch_history_states_deleted">Taasesituste asukohad on kustutatud</string>
<string name="delete_playback_states_alert">Kas kustutame kõik taasesituste asukohad\?</string>
@ -556,7 +554,7 @@
<string name="enable_queue_limit_desc">Luba korraga vaid üks allalaadimine</string>
<string name="enable_queue_limit">Piira allalaadimiste järjekorda</string>
<string name="error_progress_lost">Faili kustutamisega läks ka tööjärg kautsi</string>
<string name="error_postprocessing_stopped">Faili töötlemisel NewPipe lõpetas töö</string>
<string name="error_postprocessing_stopped">NewPipe lõpetas faili töötlemisel töö</string>
<string name="disable_media_tunneling_summary">Lülita meedia tunneldamine välja juhul, kui esitamisel tekib must ekraan või pildi kuvamine on katkendlik.</string>
<string name="disable_media_tunneling_title">Lülita meedia tunneldamine välja</string>
<string name="drawer_header_description">Vaheta teenust, hetkel on kasutusel:</string>
@ -609,9 +607,7 @@
<string name="feed_use_dedicated_fetch_method_enable_button">Luba kiire režiim</string>
<string name="feed_use_dedicated_fetch_method_title">Hangi võimalusel spetsiaalsest voost</string>
<string name="feed_load_error_fast_unknown">Kiirvoo režiim ei paku selle kohta täiendavat teavet.</string>
<string name="feed_load_error_terminated">Autori konto on lõpetatud.
\nTulevikus ei saa NewPipe seda voogu laadida.
\nKas soovid tühistada selle kanali tellimuse\?</string>
<string name="feed_load_error_terminated">Autori konto on suletud. \nTulevikus ei saa NewPipe seda meediavoogu laadida. \nKas soovid tühistada selle kanali tellimuse?</string>
<string name="feed_load_error_account_info">Voo \'%s\' laadimine nurjus.</string>
<string name="feed_load_error">Via voo laadimisel</string>
<string name="feed_update_threshold_option_always_update">Värskenda alati</string>
@ -622,17 +618,7 @@
<string name="downloads_storage_use_saf_summary_api_29">Android 10st alates on toetatud ainult salvestusjuurdepääsu raamistik \'Storage Access Framework\'</string>
<string name="downloads_storage_ask_summary_no_saf_notice">Sinult küsitakse iga kord, kuhu alla laadimine salvestada</string>
<string name="detail_heart_img_view_description">Südamlik autor</string>
<string name="feed_use_dedicated_fetch_method_help_text">Kas sinu meelest on voo laadimine aeglane\? Sel juhul proovi lubada kiire laadimine (seda saad muuta seadetes või vajutades allolevat nuppu).
\n
\nNewPipe pakub kahte voo laadimise strateegiat:
\n• Tellitud kanali täielik, kuid aeglane hankimine.
\n• Teenuse spetsiaalse lõpp-punkti kasutamine, mis on kiire, kuid tavaliselt mittetäielik.
\n
\nErinevus nende kahe vahel seisneb selles, et kiirel puudub tavaliselt teave, näiteks üksuse pikkus või tüüp (ei saa eristada reaalajas videoid tavalistest) ja see võib tagastada vähem üksusi.
\n
\nYouTube on näide teenusest, mis pakub seda kiirmeetodit oma RSS-vooga.
\n
\nNii et valik taandub sellele, mida eelistad: kiirus või täpne teave.</string>
<string name="feed_use_dedicated_fetch_method_help_text">Kas sinu meelest on voo laadimine aeglane? Sel juhul proovi lubada kiire laadimine (seda saad muuta seadetes või vajutades allolevat nuppu). \n \nNewPipe pakub kahte voo laadimise strateegiat: \n• Tellitud kanali täielik, kuid aeglane hankimine. \n• Teenuse spetsiaalse otspunkti kasutamine, mis on kiire, kuid tavaliselt mittetäielik. \n \nErinevus nende kahe vahel seisneb selles, et kiirel puudub tavaliselt teave, näiteks üksuse pikkus või tüüp (ei saa eristada reaalajas videoid tavalistest) ja see võib tagastada vähem üksusi. \n \nYouTube on näide teenusest, mis pakub seda kiirmeetodit oma RSS-vooga. \n \nNii et valik taandub sellele, mida eelistad: kiirus või täpne teave.</string>
<string name="mark_as_watched">Märgi vaadatuks</string>
<string name="remote_search_suggestions">Kaugotsingu soovitused</string>
<string name="local_search_suggestions">Kohaliku otsingu soovitused</string>
@ -654,9 +640,9 @@
<string name="check_for_updates">Kontrolli uuendusi</string>
<string name="manual_update_description">Kontrolli uuendusi käsitsi</string>
<string name="feed_new_items">Uued andmevoo kirjed</string>
<string name="show_crash_the_player_title">Näita „Jooksuta meediamängija kokku“ nupukest\\</string>
<string name="show_crash_the_player_title">Näita „Jooksuta meediamängija kokku“ nupukest</string>
<string name="show_crash_the_player_summary">Näitab valikut meediamängija kokkujooksutamiseks</string>
<string name="error_report_notification_title">NewPipe töös tekkis viga, sellest teavitamiseks toksa</string>
<string name="error_report_notification_title">NewPipe\'i töös tekkis viga, sellest teavitamiseks toksa</string>
<string name="crash_the_player">Jooksuta meediamängija kokku</string>
<string name="show_error_snackbar">Näita veateate akent</string>
<string name="error_report_channel_name">Teavitus vigadest</string>
@ -802,7 +788,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">Jaga esitusloendit</string>
<string name="share_playlist_with_titles_message">Jaga esitusloendit kas väga detailse teabega palade kohta või lihtsa url\'ide loendina</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="show_more">Näita veel</string>
<plurals name="replies">
@ -812,8 +797,7 @@
<string name="show_less">Näita vähem</string>
<string name="notification_actions_summary_android13">Muuda iga teavituse tegevust sellel toksates. Kolm esimest tegevust (esita/peata esitus, eelmine video, järgmine video) on süsteemsed ja neid ei saa muuta.</string>
<string name="settings_category_backup_restore_title">Varundus ja taastamine</string>
<string name="auto_update_check_description">NewPipe võib aeg-ajalt automaatselt kontrollida uute versioonide olemasolu ning sind vastavalt teavitada.
\nKas sa soovid sellist võimalust kasuutada?</string>
<string name="auto_update_check_description">NewPipe võib aeg-ajalt automaatselt kontrollida uute versioonide olemasolu ning sind vastavalt teavitada. \nKas sa soovid sellist võimalust kasutada?</string>
<string name="reset_settings_title">Lähtesta seadistused</string>
<string name="reset_settings_summary">Lähtesta kõik seadistused nende vaikimisi väärtusteks</string>
<string name="error_insufficient_storage">Seadmes pole enam piisavalt vaba ruumi</string>
@ -822,6 +806,6 @@
\nKas sa soovid jätkata?</string>
<string name="yes">Jah</string>
<string name="no">Ei</string>
<string name="import_settings_vulnerable_format">Imporditavad andmed kasutavad turvaprobleemidega vormingut, mida alates versioonist 0.27.0 NewPipe enam luua ei suuda. Palun kontrolli, et impordifail on loodud usaldusväärse osapoole poolt ning edaspidi loo ekspordifailid NewPipe versiooniga 0.27.0 või uuemaga. Tugi sellise vana vormingu kasutamisele kaob õige pea ja seejärel NewPipe uuemad ja vanemad versioonid ei saa omavahel andmeid enam vahetada.</string>
<string name="import_settings_vulnerable_format">Imporditavad andmed kasutavad turvaprobleemidega vormingut, mida alates versioonist 0.27.0 NewPipe enam kasutada ei suuda. Palun kontrolli, et impordifail on loodud usaldusväärse osapoole poolt ning eelista ekspordifaile, mis on loodud NewPipe\'i versiooniga 0.27.0 või uuemaga. Tugi sellise vana vormingu kasutamisele kaob õige pea ja seejärel NewPipe\'i uuemad ja vanemad versioonid ei saa omavahel andmeid enam vahetada.</string>
<string name="audio_track_type_secondary">täiendav</string>
</resources>

View File

@ -772,7 +772,6 @@
<string name="notification_actions_summary_android13">Editatu beheko jakinarazpen ekintza bakoitza gainean sakatuz. Lehen hiru ekintzak (erreproduzitu/pausatu, aurrekoa eta hurrengoa) sistemarengatik ezarrita daude eta ezin dira pertsonalizatu.</string>
<string name="rewind">Atzera egin</string>
<string name="image_quality_title">Irudiaren kalitatea</string>
<string name="share_playlist_with_titles_message">Partekatu erreprodukzio-zerrenda xehetasunekin, esate baterako, erreprodukzio-zerrendaren izena eta bideo-izenburuak edo bideo-URLen zerrenda soil gisa</string>
<string name="more_options">Aukera gehiago</string>
<string name="duration">Iraupena</string>
<string name="forward">Aurrera egin</string>

View File

@ -762,4 +762,6 @@
<string name="duration">مدّت</string>
<string name="rewind">پسروی</string>
<string name="question_mark">؟</string>
<string name="settings_category_backup_restore_title">پشتیبان‌گیری و بازیابی</string>
<string name="no_live_streams">بدون جریان زنده</string>
</resources>

View File

@ -757,7 +757,6 @@
<string name="rewind">Kelaa taaksepäin</string>
<string name="feed_fetch_channel_tabs_summary">Noudettavat välilehdet syötettä päivitettäessä. Tällä valinnalla ei ole vaikutusta, jos kanava päivitetään käyttämällä nopeaa tilaa.</string>
<string name="delete_downloaded_files_confirm">Poistetaanko kaikki ladatut tiedostot levyltä\?</string>
<string name="share_playlist_with_titles_message">Jaa soittolista, jossa on tietoja, kuten soittolistan nimi ja videon nimi, tai yksinkertainen luettelo videoiden URL-osoitteista</string>
<string name="image_quality_medium">Keskilaatu</string>
<string name="metadata_uploader_avatars">Lataajan avatarit</string>
<string name="percent">Prosentti</string>

View File

@ -178,7 +178,7 @@
<string name="play_queue_stream_detail">Détails</string>
<string name="play_queue_audio_settings">Paramètres audios</string>
<string name="show_hold_to_append_title">Afficher l\'astuce « Maintenir pour ajouter à la file »</string>
<string name="show_hold_to_append_summary">Affiche lastuce lors de lappui des boutons « Arrière-plan » ou « Flottant » sur la page de détails dune vidéo</string>
<string name="show_hold_to_append_summary">Affiche lastuce lors de lappui des boutons « Arrière-plan » ou « Flottant » sur la page de détails dune vidéo</string>
<string name="unknown_content">[Inconnu]</string>
<string name="player_recoverable_failure">Récupération depuis lerreur du lecteur</string>
<string name="kiosk_page_summary">Kiosque</string>
@ -534,7 +534,7 @@
<string name="channel_created_by">Créé par %s</string>
<string name="show_original_time_ago_summary">Les textes originaux des services vont être visibles dans les items</string>
<string name="show_original_time_ago_title">Afficher la date originelle sur les items</string>
<string name="youtube_restricted_mode_enabled_title">Activer le «Mode restreint» de YouTube</string>
<string name="youtube_restricted_mode_enabled_title">Activer le « Mode restreint » de YouTube</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Afficher uniquement les abonnements non groupés</string>
<string name="playlist_page_summary">Page des listes de lecture</string>
<string name="no_playlist_bookmarked_yet">Aucune liste de lecture encore enregistrée</string>
@ -669,7 +669,7 @@
<string name="check_for_updates">Vérifier les mises à jour</string>
<string name="feed_new_items">Nouveaux éléments du flux</string>
<string name="crash_the_player">Faire planter le lecteur</string>
<string name="show_crash_the_player_title">Afficher « Faire planter le lecteur »</string>
<string name="show_crash_the_player_title">Afficher « Faire planter le lecteur »</string>
<string name="show_crash_the_player_summary">Montrer une option de plantage lors de l\'utilisation du lecteur</string>
<string name="error_report_channel_name">Notification de rapport d\'erreur</string>
<string name="error_report_channel_description">Notifications pour signaler les erreurs</string>
@ -815,7 +815,6 @@
<string name="forward">Avancer</string>
<string name="rewind">Rembobiner</string>
<string name="replay">Rejouer</string>
<string name="share_playlist_with_titles_message">Partager la liste de lecture avec des détails tel que son nom et le titre de ses vidéos ou simplement la liste des URLs des vidéos</string>
<string name="metadata_uploader_avatars">Avatars du téléverseur</string>
<string name="image_quality_summary">Sélectionnez la qualité des images et si les images doivent être chargées, pour réduire l\'utilisation de la mémoire et de données. Les modifications vident à la fois le cache des images en mémoire et sur le disque — %s</string>
<string name="play">Lire</string>
@ -840,4 +839,4 @@
<string name="error_insufficient_storage">Pas assez d\'espace disponible sur l\'appareil</string>
<string name="import_settings_vulnerable_format">Les paramètres de l\'export en cours d\'importation utilisent un format vulnérable qui a été déprécié depuis NewPipe 0.27.0. Assurez-vous que l\'export en cours d\'importation provient d\'une source fiable. Privilégiez les exports obtenues à partir de NewPipe 0.27.0 ou des versions plus récentes à l\'avenir. Le support pour l\'importation des paramètres dans ce format vulnérable sera bientôt complètement supprimé et les anciennes versions de NewPipe ne pourront plus importer les paramètres des exports des nouvelles versions.</string>
<string name="audio_track_type_secondary">secondaire</string>
</resources>
</resources>

View File

@ -813,7 +813,6 @@
<string name="metadata_banners">Encabezados</string>
<string name="show_channel_tabs_summary">Lapelas a mostrar nas páxinas das canles</string>
<string name="image_quality_summary">Escolla da calidade das imaxes e se cargar as imaxes na súa totalidade, para reducir o uso de datos e memoria. Os cambios limpan a caché das imaxes na memoria e no disco - %s</string>
<string name="share_playlist_with_titles_message">Compartir a lista de reprodución con detalles como o nome da lista e os títulos dos videos ou como unha lista sinxela cos enlaces URL dos videos</string>
<string name="share_playlist_with_list">Compartir lista de URLs</string>
<string name="import_settings_vulnerable_format">A configuración da exportación a ser importada emprega un formato vulnerable que fica obsoleto dende NewPipe 0.27.0. Comprobe que a exportación que está a importar proveña dunha fonte fiable e preferibelmente empregue exportacións de NewPipe 0.27.0 ou posterior. A compatibilidade coa importación deste formato vulnerable será eliminada por completo próximamente e as versión antigas de NewPipe non poderán importar configuracións de exportacións dende novas versións.</string>
<string name="channel_tab_tracks">Pistas</string>

View File

@ -23,7 +23,7 @@
<string name="notification_action_0_title">પ્રથમ ક્રિયા બટન</string>
<string name="notification_scale_to_square_image_summary">સૂચનામાં બતાવેલ વિડિઓ થંબનેલને ૧૬:૯ થી ૧:૧ સાપેક્ષ ગુણોત્તરમાં કાપો</string>
<string name="notification_scale_to_square_image_title">થંબનેલને ૧:૧ સાપેક્ષ ગુણોત્તરમાં કાપો</string>
<string name="show_play_with_kodi_summary">કોડ મીડિયા સેન્ટર દ્વારા વિડિઓ ચલાવવાનો વિકલ્પ દર્શાવો</string>
<string name="show_play_with_kodi_summary">કોડિ મીડિયા સેન્ટર દ્વારા વિડિઓ ચલાવવાનો વિકલ્પ દર્શાવો</string>
<string name="kore_not_found">અનુપસ્થિત Kore અનુપ્રયોગ સ્થાપિત કરીએ?</string>
<string name="show_higher_resolutions_summary">ફક્ત થોડા ઉપકરણો 2K / 4K વિડિઓઝ ચલાવી શકે છે</string>
<string name="show_higher_resolutions_title">ઉચ્ચ રીઝોલ્યુશન બતાવો</string>
@ -40,7 +40,7 @@
<string name="controls_background_title">પૃષ્ઠભૂમિ</string>
<string name="tab_choose">ટેબ પસંદ કરો</string>
<string name="tab_bookmarks">બુકમાર્ક કરેલ પ્લેલિસ્ટ્સ</string>
<string name="tab_subscriptions">સબ્સ્ક્રિપ્શન્સ</string>
<string name="tab_subscriptions">લવાજમઓ</string>
<string name="show_info">માહિતી બતાવો</string>
<string name="subscription_update_failed">સબ્સ્ક્રિપ્શન અપડેટ કરી શકાયું નથી</string>
<string name="subscription_change_failed">સબ્સ્ક્રિપ્શન બદલી શકાયું નહીં</string>
@ -72,13 +72,15 @@
<string name="ok">ઠીક છે</string>
<string name="yes">હા</string>
<string name="no">ના</string>
<string name="trending">વલણમાં છે</string>
<string name="trending">વલણમાંનાં</string>
<string name="auto_queue_toggle">આપોઆપ કતારબદ્ધતા</string>
<string name="crash_the_player">પ્લેયરને ક્રેશ કરો</string>
<string name="action_history">ઇતિહાસ</string>
<string name="play_with_kodi_title">કોટિથી ચલાવો</string>
<string name="show_play_with_kodi_title">કોટિથી ચલાવવાનો વિકલ્પ દેખાટ</string>
<string name="show_play_with_kodi_title">કોડિથી ચલાવવાનો વિકલ્પ દેખાડ</string>
<string name="download_dialog_title">ડાઉનલોડ કરો</string>
<string name="autoplay_title">આપમેળે ચલાવો</string>
<string name="fragment_feed_title">નવું શું છે</string>
</resources>
<string name="downloads">ડાઉનલોડ્સ</string>
<string name="downloads_title">ડાઉનલોડ્સ</string>
</resources>

View File

@ -828,7 +828,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">שיתוף רשימת נגינה</string>
<string name="share_playlist_with_titles_message">שיתוף רשימת נגינה עם פרטים כגון שם רשימת נגינה וכותרות סרטונים או כרשימה פשוטה של כתובות סרטונים</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="show_more">להציג עוד</string>
<string name="show_less">להציג פחות</string>
@ -850,4 +849,5 @@
\nלהמשיך?</string>
<string name="error_insufficient_storage">אין מספיק מקום פנוי במכשיר</string>
<string name="import_settings_vulnerable_format">ההגדרות בייצוא המיובא משתמשות בתסדיר פגיע שהוצא משימוש מאז NewPipe 0.27.0. יש לוודא שהייצוא המיובא הוא ממקור מהימן, ועדיף להשתמש רק בייצוא שהושג מ־NewPipe 0.27.0 ומעלה בעתיד. תמיכה בייבוא הגדרות בתסדיר פגיע זה תוסר בקרוב לחלוטין, ואז גרסאות ישנות של NewPipe לא יוכלו לייבא עוד הגדרות של ייצוא מגרסאות חדשות.</string>
<string name="audio_track_type_secondary">משני</string>
</resources>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upload_date_text">%1$s पे प्रकाशित हुआ</string>
<string name="no_player_found">स्ट्रीमिंग के लिए प्लेयर नहीं मिला। क्या आप वीएलसी इंस्टॉल करना चाहेंगे\?</string>
<string name="no_player_found">स्ट्रीमिंग के लिए प्लेयर नहीं मिला। क्या आप VLC इंस्टॉल करना चाहेंगे?</string>
<string name="install">इंस्टॉल करें</string>
<string name="open_in_browser">ब्राउज़र में खोलें</string>
<string name="open_in_popup_mode">पॉपअप मोड में खोलें</string>
@ -183,7 +183,7 @@
<string name="hold_to_append">कतार में जोड़ने के लिए दबाकर रखें</string>
<string name="start_here_on_background">बैकग्राउंड में चलाना शुरू करें</string>
<string name="start_here_on_popup">पॉपअप में चलाना शुरू करें</string>
<string name="no_player_found_toast">स्ट्रीमिंग करने के लिए प्लेयर नहीं मिला (आप इसे चलाने के लिए वीएलसी प्लेयर इंस्टॉल कर सकते हैं)।</string>
<string name="no_player_found_toast">स्ट्रीमिंग करने के लिए प्लेयर नहीं मिला (आप इसे चलाने के लिए VLC प्लेयर इंस्टॉल कर सकते हैं)।</string>
<string name="controls_download_desc">स्ट्रीम फाइल डाउनलोड करें</string>
<string name="show_info">जानकारी दिखाएं</string>
<string name="tab_bookmarks">बुकमार्क की गई प्लेलिस्टें</string>
@ -802,7 +802,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">प्लेलिस्ट साझा करें</string>
<string name="share_playlist_with_titles_message">प्लेलिस्ट को प्लेलिस्ट नाम और वीडियो शीर्षक जैसे विवरण के साथ या वीडियो यूआरएल की एक सरल सूची के रूप में साझा करें</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies">
<item quantity="one">%s जवाब</item>

View File

@ -424,8 +424,8 @@
<string name="feed_group_dialog_delete_message">Želiš li izbrisati ovu grupu\?</string>
<string name="feed_create_new_group_button_title">Nova</string>
<string name="feed_update_threshold_option_always_update">Uvijek aktualiziraj</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Uključi brzi način</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Isključi brzi način</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Uključi brzi modus</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Isključi brzi modus</string>
<string name="error_insufficient_storage_left">Memorija uređaja je popunjena</string>
<string name="most_liked">Najomiljeniji</string>
<string name="subtitle_activity_recaptcha">Pritisni „Gotovo” kad je riješeno</string>
@ -639,7 +639,7 @@
<string name="no_dir_yet">Mapa za preuzimanje još nije postavljena, odaberi standardnu mapu za preuzimanje</string>
<string name="comments_are_disabled">Komentari su isključeni</string>
<string name="mark_as_watched">Označi kao pogledano</string>
<string name="feed_load_error_fast_unknown">Način rada brzog feeda ne pruža više informacija o ovome.</string>
<string name="feed_load_error_fast_unknown">Brzi modus feeda ne pruža više informacija o ovome.</string>
<string name="metadata_privacy_internal">Interno</string>
<string name="metadata_privacy">Privatnost</string>
<string name="description_select_note">Sada možeš odabrati tekst u opisu. Napomena: stranica će možda treperiti i možda nećeš moći kliknuti poveznice u načinu rada za odabir teksta.</string>
@ -798,7 +798,6 @@
<string name="image_quality_medium">Srednja kvaliteta</string>
<string name="image_quality_high">Visoka kvaliteta</string>
<string name="question_mark">\?</string>
<string name="share_playlist_with_titles_message">Dijeli playlistu s detaljima kao što su ime playliste i naslovi videa ili kao jednostavan popis URL-ova videa</string>
<string name="share_playlist_with_titles">Dijeli s naslovima</string>
<string name="share_playlist_with_list">Dijeli popis URL-ova</string>
<string name="video_details_list_item"> %1$s: %2$s</string>
@ -831,4 +830,7 @@
<string name="reset_all_settings">Obnavljanje svih postavki odbacit će sve tvoje postavljene postavke i aplikacija će se ponovo pokrenuti.
\n
\nStvarno želiš nastaviti?</string>
<string name="always_use_exoplayer_set_output_surface_workaround_title">Uvijek koristi ExoPlayer postavku zaobilaženja videa za izlaznu površinu</string>
<string name="feed_fetch_channel_tabs_summary">Kartice za dohvaćanje prilikom aktualiziranja feeda. Ova opcija nema učinka ako se kanal aktualizira pomoću brzog modusa.</string>
<string name="audio_track_type_secondary">sekundarno</string>
</resources>

View File

@ -324,7 +324,7 @@
<string name="app_update_notification_channel_name">Alkalmazásfrissítés értesítése</string>
<string name="file_deleted">Fájl törölve</string>
<string name="settings_category_updates_title">Frissítések</string>
<string name="show_hold_to_append_summary">Tipp megjelenítése a háttér vagy a felugró gomb megnyomásakor a videó „Részletek:\\” lehetőségnél</string>
<string name="show_hold_to_append_summary">Tipp megjelenítése a háttér vagy a felugró gomb megnyomásakor a videó „Részletek:” lehetőségnél</string>
<string name="autoplay_title">Automatikus lejátszás</string>
<string name="settings_category_clear_data_title">Adatok törlése</string>
<string name="enable_playback_state_lists_summary">Lejátszási pozíciók megjelenítése a listákban</string>
@ -534,7 +534,7 @@
<string name="hash_channel_description">Értesítések a videók ujjlenyomatkészítési folyamatához</string>
<string name="hash_channel_name">Videó ujjlenyomat-készítési értesítése</string>
<string name="youtube_restricted_mode_enabled_summary">A YouTube biztosít egy „Korlátozott módot”, amely elrejti a lehetséges felnőtteknek szóló tartalmat</string>
<string name="youtube_restricted_mode_enabled_title">A YouTube „Korlátozott mód\\” bekapcsolása</string>
<string name="youtube_restricted_mode_enabled_title">A YouTube „Korlátozott mód” bekapcsolása</string>
<string name="peertube_instance_add_exists">A példány már létezik</string>
<string name="peertube_instance_add_fail">A példány érvényesítése nem sikerült</string>
<string name="peertube_instance_add_help">Adja meg a példány webcímét</string>
@ -657,7 +657,7 @@
<string name="show_original_time_ago_summary">A szolgáltatásokból származó eredeti szövegek láthatók lesznek a közvetítési elemeken</string>
<string name="crash_the_player">Lejátszó összeomlasztása</string>
<string name="show_image_indicators_title">Képjelölők megjelenítése</string>
<string name="show_crash_the_player_title">A „lejátszó összeomlasztása\\” lehetőség megjelenítése</string>
<string name="show_crash_the_player_title">A „Lejátszó összeomlasztása” lehetőség megjelenítése</string>
<string name="show_crash_the_player_summary">Megjeleníti az összeomlasztási lehetőséget a lejátszó használatakor</string>
<string name="unhook_checkbox">Hangmagasság megtartása (torzítást okozhat)</string>
<string name="check_for_updates">Frissítések keresése</string>
@ -795,7 +795,6 @@
<string name="image_quality_high">Magas minőségű</string>
<string name="question_mark">\?</string>
<string name="share_playlist">Lejátszási lista megosztása</string>
<string name="share_playlist_with_titles_message">Lejátszási lista megosztása olyan részletekkel, mint például a lejátszási lista neve és a videó címe, vagy a videó webcímek egyszerű listájaként</string>
<string name="share_playlist_with_titles">Megosztás címekkel</string>
<string name="share_playlist_content_details">%1$s
\n%2$s</string>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="main_bg_subtitle">Սեղմեք որոնման կոճակը որ սկսել</string>
<string name="main_bg_subtitle">Սեղմեք խոշորացույցը որ սկսեք</string>
<string name="search">Որոնել</string>
<string name="downloads">Բեռնված</string>
<string name="downloads_title">Բեռնված</string>
@ -228,7 +228,7 @@
<string name="sort">Դասավորել</string>
<string name="detail_pinned_comment_view_description">Գամված մեկնաբանություն</string>
<string name="account_terminated">Հաշիվը կասեցված է</string>
<string name="channel_tab_about"></string>
<string name="channel_tab_about"/>
<string name="channel_tab_albums">Ալբոմներ</string>
<string name="yes">Այո</string>
<string name="no">Ոչ</string>
@ -241,4 +241,6 @@
<string name="channel_tab_channels">Ալիքներ</string>
<string name="channel_tab_livestreams">Ուղիղ</string>
<string name="unknown_audio_track">Անհայտ</string>
</resources>
<string name="did_you_mean">Նկատի ունե՞ս «%1$s»</string>
<string name="volume">Բարձրություն</string>
</resources>

View File

@ -138,7 +138,7 @@
<string name="auto_queue_summary">Melanjutkan akhir dari antrean pemutaran (tak berulang) dengan menambahkan video terkait</string>
<string name="enable_watch_history_summary">Simpan daftar video yang telah ditonton</string>
<string name="show_hold_to_append_title">Tip \"Tahan untuk menambahkan\"</string>
<string name="show_hold_to_append_summary">Tampilkan tip ketika menekan tombol latar belakang atau popup di dalam video \"Detail:\\</string>
<string name="show_hold_to_append_summary">Tampilkan tip ketika menekan tombol latar belakang atau popup di dalam video \"Detail:\"</string>
<string name="default_content_country_title">Lokasi Konten</string>
<string name="settings_category_player_title">Pemutar</string>
<string name="settings_category_player_behavior_title">Perilaku</string>
@ -508,7 +508,7 @@
\nJadi pilihlah yang sesuai yang Anda inginkan: kecepatan atau kelengkapan informasi.</string>
<string name="show_original_time_ago_summary">Teks asli dari layanan akan ditampilkan di dalam video</string>
<string name="show_original_time_ago_title">Tampilkan waktu yang lalu sebenarnya pada item</string>
<string name="youtube_restricted_mode_enabled_title">Aktifkan \"Mode Terbatas\\</string>
<string name="youtube_restricted_mode_enabled_title">Aktifkan \"Mode Terbatas\"</string>
<string name="video_detail_by">Oleh %s</string>
<string name="channel_created_by">Dibuat oleh %s</string>
<string name="detail_sub_channel_thumbnail_view_description">Thumbnail avatar channel</string>
@ -788,7 +788,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">Bagikan Daftar Putar</string>
<string name="share_playlist_with_titles_message">Bagikan daftar putar dengan detail seperti nama daftar putar dan judul video atau sebagai daftar video URL yang sederhana</string>
<string name="metadata_banners">Panji</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="notification_actions_summary_android13">Sentuh untuk menyunting tindakan notifikasi di bawah. Tiga tindakan pertama (mainkan/jeda, sebelumnya dan selanjutnya) disetel oleh sistem dan tidak bisa dikustomisasi.</string>

View File

@ -607,7 +607,7 @@
<string name="enable_playback_state_lists_title">Spilunarstöður í listum</string>
<string name="enable_playback_state_lists_summary">Sýna spilunarstöður í listum</string>
<string name="show_hold_to_append_title">Sýna ábendinguna „Haltu niðri til að bæta við spilunarröð“</string>
<string name="show_hold_to_append_summary">Sýna ábendingu þegar ýtt er á bakgrunninn eða sprettihnappinn á myndskeiðinu í „Nánar:\\</string>
<string name="show_hold_to_append_summary">Sýna ábendingu þegar ýtt er á bakgrunninn eða sprettihnappinn á myndskeiðinu í „Nánar:\"</string>
<string name="unsupported_url_dialog_message">Óþekkt slóð. Opna með öðru forriti\?</string>
<string name="peertube_instance_url_summary">Veldu uppáhalds PeerTube tilvik þín</string>
<string name="peertube_instance_url_help">Þú mátt finna tilviki á %s</string>
@ -790,7 +790,6 @@
<string name="unset_playlist_thumbnail">Losa varanlega smámynd</string>
<string name="notification_actions_summary_android13">Breyttu hverri tilkynningu hér fyrir neðan með því að ýta á hana. Fyrstu þrjár aðgerðirnar (spila/bíða, fyrra og næsta) eru skilgreindar af kerfinu og er því ekki hægt að sérsníða.</string>
<string name="feed_fetch_channel_tabs_summary">Flipar sem á að sækja við uppfærslu þessa streymis. Þetta hefur engin áhrif ef rás er uppfærð með hraðstreymisham.</string>
<string name="share_playlist_with_titles_message">Deildu spilunarlista með atriðum eins og heiti spilunarlistans og titlum myndskeiða eða sem einföldum lista yfir slóðir á myndskeið</string>
<string name="use_exoplayer_decoder_fallback_title">Nota varaeiginleika ExoPlayer-afkóðarans</string>
<string name="new_seek_duration_toast">Vegna takmarkana í ExoPlayer-spilaranum var tímalengd hoppa sett á %d sekúndur</string>
<string name="disable_media_tunneling_automatic_info">Margmiðlunargöng (media tunneling) voru gerð óvirk á tækinu þínu þar sem þessi gerð tækja er þekkt fyrir að styðja ekki þennan eiginleika.</string>

View File

@ -815,7 +815,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">Condividi playlist</string>
<string name="share_playlist_with_titles_message">Condividi la playlist con dettagli come il suo nome e i titoli video o come un semplice elenco di URL video</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies">
<item quantity="one">%s risposta</item>

View File

@ -789,7 +789,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">プレイリストを共有</string>
<string name="share_playlist_with_titles_message">プレイリスト名やビデオタイトルなどの詳細を含むプレイリスト、またはビデオURLのみのシンプルなリストとしてプレイリストを共有します</string>
<string name="video_details_list_item">- %1$s %2$s</string>
<plurals name="replies">
<item quantity="other">%sの返信</item>

View File

@ -774,7 +774,6 @@
<string name="rewind">되감기</string>
<string name="replay">다시 재생</string>
<string name="feed_fetch_channel_tabs_summary">피드를 업데이트할 때 가져올 탭입니다. 빠른 모드를 사용하여 채널을 업데이트하는 경우 이 옵션은 효과가 없습니다.</string>
<string name="share_playlist_with_titles_message">재생목록 이름, 동영상 제목 등의 세부정보 또는 간단한 동영상 URL 목록으로 재생목록을 공유하세요</string>
<string name="image_quality_medium">중간 품질</string>
<string name="metadata_uploader_avatars">업로더 아바타</string>
<string name="metadata_banners">배너</string>

View File

@ -823,7 +823,6 @@
<string name="channel_tab_videos">Vaizdo įrašai</string>
<string name="channel_tab_tracks">Takeliai</string>
<string name="image_quality_summary">Pasirinkite paveikslėlių kokybę ir ar apskritai įkelti paveikslėlius, kad sumažintumėte duomenų ir atminties naudojimą. Pakeitimai išvalo atmintyje ir diske esančių vaizdų talpyklą - %s</string>
<string name="share_playlist_with_titles_message">Dalintis grojaraščiu su tokia informacija kaip grojaraščio pavadinimas ir vaizdo įrašo pavadinimas arba paprastas vaizdo įrašų nuorodų sąrašas</string>
<string name="share_playlist_with_titles">Dalintis su pavadinimais</string>
<string name="share_playlist">Dalintis grojaraščiu</string>
<string name="share_playlist_with_list">Dalintis nuorodų sąrašu</string>

View File

@ -822,7 +822,6 @@
<string name="audio_track_name">%1$s %2$s</string>
<string name="channel_tab_tracks">Skaņdarbi</string>
<string name="channel_tab_shorts">Īsie video</string>
<string name="share_playlist_with_titles_message">Kopīgot atskaņošanas saraksta nosaukumu un to video nosaukumus vai tikai atskaņošanas sarakstā iekļauto video URL saites</string>
<string name="share_playlist">Kopīgot atskaņošanas sarakstu</string>
<string name="share_playlist_with_titles">Kopīgot nosaukumus</string>
<string name="import_settings_vulnerable_format">Importētā eksporta iestatījumi izmanto ievainojamo formātu, kas tika pārtraukts kopš NewPipe 0.27.0 versijas. Pārliecinieties, ka importētie dati ir no uzticama avota, un turpmāk ir vēlams izmantot tikai datus, kas veikti NewPipe 0.27.0 vai jaunākās versijās. Iestatījumu importēšanas atbalsts šajā neaizsargātajā formātā drīzumā tiks pilnībā aizvākts, un tad vecās NewPipe versijas vairs nevarēs importēt iestatījumus, kas veikti jaunajās versijās.</string>

View File

@ -713,7 +713,6 @@
<string name="peertube_instance_add_fail">Инстанцата не може да биде потврдена</string>
<string name="image_quality_summary">Изберете го квалитетот на сликите и дали воопшто да се вчитуваат слики, за да го намалите користењето на интернет и меморија. Промените го чистат кешот на сликите (анг. image cache), како и во меморијата, така и на дискот — %s</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="share_playlist_with_titles_message">Споделете ја плејлистата со подробности (детали), како името на плејлистата и насловите на видеата или како едноставен список од линковите на видеата</string>
<string name="none">Ништо</string>
<string name="import_settings_vulnerable_format">Поставките во извезениот фајл кој се увезува користат ранлив формат кој повеќе не е поддржан од NewPipe 0.27.0. Уверете се дека извезениот фајл кој се увезува е од доверлив извор и претпочитајте во иднина да користите само износи добиени од NewPipe 0.27.0 или понова верзија. Поддршката за увезување поставки од овој ранлив формат наскоро ќе биде целосно укината и тогаш старите верзии на NewPipe повеќе нема да можат да увезуваат поставки од износи од новите верзии.</string>
<string name="clear_queue_confirmation_title">Побарај потврда пред чистење на редоследот</string>

View File

@ -503,7 +503,6 @@
<string name="image_quality_low">Mutu rendah</string>
<string name="image_quality_medium">Mutu sederhana</string>
<string name="image_quality_high">Mutu tinggi</string>
<string name="share_playlist_with_titles_message">Kongsikan senarai main dengan butiran seperti nama senarai main dan tajuk video atau sebagai senarai ringkas URL video</string>
<string name="share_playlist_with_titles">Kongsi dengan Tajuk</string>
<string name="share_playlist_with_list">Kongsi senarai URL</string>
<string name="show_more">Tunjukkan lagi</string>

View File

@ -802,7 +802,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">Afspeellijst delen</string>
<string name="share_playlist_with_titles_message">Deel afspeellijst met details zoals afspeellijstnaam en videotitels of als een eenvoudige lijst met video-URL\'s</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies">
<item quantity="one">%s reactie</item>

View File

@ -802,7 +802,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">ଖେଳ ତାଲିକା ସହଭାଗ କରନ୍ତୁ</string>
<string name="share_playlist_with_titles_message">ପ୍ଲେ-ଲିଷ୍ଟ ନାମ ଏବଂ ଭିଡିଓ ଶୀର୍ଷକ କିମ୍ବା ଭିଡିଓ URLଗୁଡ଼ିକର ଏକ ସରଳ ତାଲିକା ଭାବରେ ବିବରଣୀ ସହିତ ପ୍ଲେ-ଲିଷ୍ଟ ଅଂଶୀଦାର କରନ୍ତୁ</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="show_more">ଅଧିକ ଦର୍ଶାନ୍ତୁ</string>
<string name="notification_actions_summary_android13">ଏହା ଉପରେ ଟ୍ୟାପ କରି ନିମ୍ନରେ ଦିଆଯାଇଥିବା ପ୍ରତ୍ୟେକ ବିଜ୍ଞପ୍ତି କାର୍ଯ୍ୟକୁ ସମ୍ପାଦନ କରନ୍ତୁ । ପ୍ରଥମ ତିନୋଟି କାର୍ଯ୍ୟ (ଖେଳ/ବିରତି, ପୂର୍ବବର୍ତ୍ତୀ ଏବଂ ପରବର୍ତ୍ତୀ) ତନ୍ତ୍ର ଦ୍ୱାରା ସେଟ କରାଯାଇଥାଏ ଏବଂ ଏହାକୁ ଇଚ୍ଛାରୂପଣ କରାଯାଇପାରିବ ନାହିଁ ।</string>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<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_toast">ਸਟ੍ਰੀਮਿੰਗ ਲਈ ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ (ਤੁਸੀਂ ਇਸਨੂੰ ਚਲਾਉਣ ਲਈ VLC ਪਲੇਅਰ ਇੰਸਟਾਲ ਕਰ ਸਕਦੇ ਹੋ)।</string>
<string name="install">ਇੰਸਟਾਲ ਕਰੋ</string>
@ -15,9 +15,9 @@
<string name="settings">ਸੈਟਿੰਗਾਂ</string>
<string name="did_you_mean">ਕੀ ਤੁਹਾਡਾ ਮਤਲਬ ਸੀ \"%1$s\"\?</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_video_player_summary">ਕੁਝ ਰੈਜ਼ੋਲਿਊਸ਼ਨਾਂ \'ਤੇ ਆਵਾਜ਼ ਹਟ ਸਕਦੀ ਹੈ</string>
<string name="use_external_audio_player_title">ਬਾਹਰੀ ਆਡੀਓ ਪਲੇਅਰ ਵਰਤੋ</string>
<string name="use_external_audio_player_title">ਬਾਹਰੀ ਆਡੀਓ ਪਲੇਅਰ ਦੀ ਵਰਤੋਂ ਕਰ</string>
<string name="subscribe_button_title">ਸਬਸਕ੍ਰਾਈਬ ਕਰੋ</string>
<string name="subscribed_button_title">ਸਬਸਕ੍ਰਾਈਬ ਹੈ</string>
<string name="channel_unsubscribed">ਚੈਨਲ ਅਨ-ਸਬਸਕ੍ਰਾਈਬ ਹੋਇਆ</string>
@ -27,7 +27,7 @@
<string name="tab_subscriptions">ਸਬਸਕ੍ਰਿਪਸ਼ਨਾਂ</string>
<string name="tab_bookmarks">ਬੁੱਕਮਾਰਕ ਕੀਤੀਆਂ ਪਲੇਲਿਸਟਾਂ</string>
<string name="fragment_feed_title">ਨਵਾਂ ਕੀ ਹੈ</string>
<string name="controls_background_title">ਬੈਕਗ੍ਰਾਊਂਡ ਆਡੀਓ</string>
<string name="controls_background_title">ਬੈਕਗ੍ਰਾਊਂਡ</string>
<string name="controls_popup_title">ਪੌਪ-ਅਪ</string>
<string name="controls_add_to_playlist_title">ਵਿੱਚ ਸ਼ਾਮਿਲ ਕਰੋ</string>
<string name="download_path_title">ਵੀਡੀਓ ਲਈ ਡਾਊਨਲੋਡ ਫ਼ੋਲਡਰ</string>
@ -52,7 +52,7 @@
<string name="dark_theme_title">ਗੂੜ੍ਹਾ</string>
<string name="black_theme_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="use_inexact_seek_title">ਤੇਜ਼ ਤੇ ਅਣਸਟੀਕ ਭਾਲ ਦੀ ਵਰਤੋਂ ਕਰੋ</string>
<string name="use_inexact_seek_summary">ਅਣਸਟੀਕ ਭਾਲ ਨਾਲ ਪਲੇਅਰ ਘੱਟ ਸਟੀਕਤਾ ਦੇ ਪਰ ਅਧਿਕ ਤੇਜ਼ੀ ਨਾਲ ਵੀਡੀਓ ਸਥਿੱਤੀਆਂ ਦੀ ਤਲਾਸ਼ ਕਰ ਸਕਦਾ ਹੈ । ਇਸ ਨਾਲ ਅੱਗੇ-ਪਿੱਛੇ 5, 15 ਜਾਂ 25 ਸਕਿੰਟ ਲਿਜਾਣਾ ਕੰਮ ਨਹੀਂ ਕਰਦਾ ਹੈ</string>
<string name="thumbnail_cache_wipe_complete_notice">ਚਿੱਤਰ ਕੈਸ਼ ਮਿਟਾਇਆ ਗਿਆ</string>
@ -101,8 +101,8 @@
<string name="notification_channel_name">ਨਿਊਪਾਈਪ ਨੋਟੀਫਿਕੇਸ਼ਨ</string>
<string name="notification_channel_description">ਨਿਊਪਾਈਪ ਦੇ ਪਲੇਅਰ ਦੇ ਲਈ ਨੋਟੀਫਿਕੇਸ਼ਨ</string>
<string name="unknown_content">[ਅਣਜਾਣ]</string>
<string name="switch_to_background">ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚਲਾਓ</string>
<string name="switch_to_popup">ਪੌਪ-ਅਪ ਵਿੱਚ ਚਲਾਓ</string>
<string name="switch_to_background">ਬੈਕਗ੍ਰਾਊਂਡ ਮੋਡ ਵਿੱਚ ਚਲਾਓ</string>
<string name="switch_to_popup">ਪੌਪ-ਅਪ ਮੋਡ ਵਿੱਚ ਚਲਾਓ</string>
<string name="switch_to_main">ਮੇਨ ਤੇ ਚਲਾਓ</string>
<string name="import_data_title">ਡਾਟਾਬੇਸ ਆਯਾਤ ਕਰੋ</string>
<string name="export_data_title">ਡਾਟਾਬੇਸ ਨਿਰਯਾਤ ਕਰੋ</string>
@ -316,7 +316,7 @@
<string name="limit_data_usage_none_description">ਕੋਈ ਸੀਮਾ ਨਹੀਂ</string>
<string name="limit_mobile_data_usage_title">ਮੋਬਾਈਲ ਡਾਟਾ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਨੂੰ ਸੀਮਿਤ ਕਰੋ</string>
<string name="minimize_on_exit_title">ਐਪ ਬਦਲਦੇ ਸਮੇਂ ਉਸਨੂੰ ਮਿਨੀਮਾਈਜ਼ ਕਰੋ</string>
<string name="minimize_on_exit_summary">ਮੇਨ ਵੀਡੀਓ ਪਲੇਅਰ ਤੋਂ ਦੂਜੇ ਐਪ \'ਤੇ ਜਾਣ ਵੇਲੇ ਕਾਰਵਾਈ — %s</string>
<string name="minimize_on_exit_summary">ਮੇਨ ਵੀਡੀਓ ਪਲੇਅਰ ਤੋਂ ਦੂਜੇ ਐਪ \'ਤੇ ਜਾਣ ਵੇਲੇ ਕਾਰਵਾਈ — %s</string>
<string name="minimize_on_exit_none_description">ਕੋਈ ਨਹੀਂ</string>
<string name="minimize_on_exit_background_description">ਬੈਕਗ੍ਰਾਊਂਡ ਪਲੇਅਰ ਵਿੱਚ ਬਦਲੋ</string>
<string name="minimize_on_exit_popup_description">ਪੌਪ-ਅਪ ਪਲੇਅਰ ਵਿੱਚ ਬਦਲੋ</string>
@ -466,7 +466,7 @@
<string name="no_app_to_open_intent">ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਦੀ ਕੋਈ ਵੀ ਐਪ ਇਸ ਨੂੰ ਖੋਲ੍ਹ ਨਹੀਂ ਸਕਦੀ</string>
<string name="chapters">ਚੈਪਟਰ</string>
<string name="recent">ਹਾਲੀਆ</string>
<string name="show_thumbnail_summary">ਥੰਮਨੇਲ ਨੂੰ ਤਾਲਾਬੱਧ ਸਕਰੀਨ ਦੇ ਪਿਛੋਕੜ ਅਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ ਦੋਵਾਂ ਲਈ ਵਰਤੋ</string>
<string name="show_thumbnail_summary">ਥੰਮਨੇਲ ਨੂੰ ਲਾਕ ਸਕਰੀਨ ਦੇ ਬੈਕਗ੍ਰਾਊਂਡ ਅਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ ਦੋਵਾਂ ਲਈ ਵਰਤੋ</string>
<string name="show_thumbnail_title">ਥੰਮਨੇਲ ਵਿਖਾਓ</string>
<string name="playlist_page_summary">ਪਲੇਲਿਸਟ ਪੰਨਾ</string>
<string name="video_detail_by">%s ਦੁਆਰਾ</string>
@ -802,7 +802,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">ਪਲੇਲਿਸਟ ਸਾਂਝੀ ਕਰੋ</string>
<string name="share_playlist_with_titles_message">ਪਲੇਲਿਸਟ ਨੂੰ ਪਲੇਲਿਸਟ ਨਾਮ ਅਤੇ ਵੀਡੀਓ ਸਿਰਲੇਖ ਜਿਹੇ ਵੇਰਵਿਆਂ ਸਮੇਤ ਜਾਂ ਵੀਡੀਓ URL ਦੀ ਇੱਕ ਸਰਲ ਸੂਚੀ ਦੇ ਰੂਪ ਵਿੱਚ ਸਾਂਝਾ ਕਰੋ</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies">
<item quantity="one">%s ਜਵਾਬ</item>

View File

@ -823,7 +823,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">Udostępnij playlistę</string>
<string name="share_playlist_with_titles_message">Udostępnij playlistę ze szczegółami, takimi jak nazwa playlisty i tytuły wideo, lub jako prostą listę adresów URL wideo.</string>
<string name="video_details_list_item"> %1$s: %2$s</string>
<plurals name="replies">
<item quantity="one">%s odpowiedź</item>

View File

@ -174,7 +174,7 @@
<string name="top_50">Top 50</string>
<string name="new_and_hot">Novos e tendências</string>
<string name="show_hold_to_append_title">Mostrar dica \"Segure para pôr na fila\"</string>
<string name="show_hold_to_append_summary">Mostra dica ao tocar no botão segundo plano ou Popup em \"Detalhes:\\ do vídeo</string>
<string name="show_hold_to_append_summary">Mostra dica ao tocar no botão segundo plano ou popup em \"Detalhes:\" do vídeo</string>
<string name="play_all">Reproduzir tudo</string>
<string name="player_stream_failure">Não é possível reproduzir este vídeo</string>
<string name="player_unrecoverable_failure">Ocorreu um erro irrecuperável na reprodução</string>
@ -528,7 +528,7 @@
<string name="remove_watched">Remover assistidos</string>
<string name="show_original_time_ago_summary">Textos originais dos serviços serão visíveis nos itens de transmissão</string>
<string name="show_original_time_ago_title">Mostrar tempo original nos itens</string>
<string name="youtube_restricted_mode_enabled_title">Ativar o \"Modo Restrito\\ do YouTube</string>
<string name="youtube_restricted_mode_enabled_title">Ativar o \"Modo Restrito\" do YouTube</string>
<string name="video_detail_by">Por %s</string>
<string name="channel_created_by">Criado por %s</string>
<string name="detail_sub_channel_thumbnail_view_description">Foto de perfil do canal</string>
@ -789,8 +789,9 @@
<string name="channel_tab_livestreams">Ao vivo</string>
<string name="image_quality_title">Qualidade da imagem</string>
<string name="question_mark">\?</string>
<string name="share_playlist_with_list">Compartilhar URL</string>
<string name="share_playlist_with_titles">Compartilhar com título</string>
<string name="share_playlist_with_list">Compartilhar URLs</string>
<string name="share_playlist_with_titles">Compartilhar com títulos</string>
<string name="share_playlist_as_youtube_temporary_playlist">Compartilhar como playlist temporária do YouTube</string>
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="toggle_screen_orientation">Alternar orientação da tela</string>
@ -806,7 +807,6 @@
<string name="forward">Avançar</string>
<string name="rewind">Retroceder</string>
<string name="replay">Repetir</string>
<string name="share_playlist_with_titles_message">Compartilhar playlist com detalhes como o nome da playlist e títulos de vídeo ou como uma lista simples dos URL de vídeos</string>
<string name="image_quality_medium">Qualidade média</string>
<string name="metadata_uploader_avatars">Fotos de perfil do autor</string>
<string name="video_details_list_item">- %1$s: %2$s</string>

View File

@ -802,7 +802,6 @@
<string name="rewind">Recuar</string>
<string name="replay">Repetição</string>
<string name="feed_fetch_channel_tabs_summary">Separadores a obter ao atualizar o feed. Esta opção não tem efeito se um canal for atualizado utilizando o modo rápido.</string>
<string name="share_playlist_with_titles_message">Partilhe a lista de reprodução com detalhes como o nome da lista de reprodução e os títulos dos vídeos ou como uma simples lista de URLs de vídeos</string>
<string name="image_quality_medium">Média qualidade</string>
<string name="metadata_uploader_avatars">Avatar dos publicadores</string>
<string name="metadata_banners">Bandeiras</string>

View File

@ -25,7 +25,7 @@
<string name="default_audio_format_title">Formato padrão de áudio</string>
<string name="download_dialog_title">Descarregar</string>
<string name="show_next_and_similar_title">Mostrar vídeos \'Seguintes\' e \'Semelhantes\'</string>
<string name="unsupported_url">URL não suportado</string>
<string name="unsupported_url">URL não suportada</string>
<string name="content_language_title">Idioma padrão para conteúdo</string>
<string name="settings_category_video_audio_title">Vídeo e áudio</string>
<string name="detail_thumbnail_view_description">Reproduzir vídeo, duração:</string>
@ -281,7 +281,7 @@
<string name="clear_views_history_title">Limpar histórico de visualizações</string>
<string name="auto_queue_summary">Continuar (sem repetição) a fila de reprodução anexando um vídeo relacionado</string>
<string name="show_hold_to_append_title">Mostrar dica \"Toque longo para colocar na fila\"</string>
<string name="show_hold_to_append_summary">Mostrar dica ao premir em segundo plano ou no botão \"Detalhes\" da janela popup</string>
<string name="show_hold_to_append_summary">Mostrar dica ao premir em segundo plano ou no botão \"Detalhes\" da janela popup:</string>
<string name="channels">Canais</string>
<string name="playlists">Listas de reprodução</string>
<string name="tracks">Faixas</string>
@ -551,7 +551,7 @@
<string name="never">Nunca</string>
<string name="notification_action_buffering">A carregar</string>
<string name="clear_queue_confirmation_description">A fila de reprodução atual será substituída</string>
<string name="unsupported_url_dialog_message">URL não reconhecido. Abrir com outra aplicação\?</string>
<string name="unsupported_url_dialog_message">URL não reconhecida. Abrir com outra aplicação?</string>
<string name="auto_queue_toggle">Colocar na fila automaticamente</string>
<string name="notification_action_shuffle">Baralhar</string>
<string name="wifi_only">Apenas em Wi-Fi</string>
@ -815,7 +815,6 @@
<string name="channel_tab_channels">Canais</string>
<string name="previous_stream">Vídeo anterior</string>
<string name="channel_tab_livestreams">Direto</string>
<string name="share_playlist_with_titles_message">Partilhe a lista de reprodução com detalhes como o nome da lista de reprodução e os títulos dos vídeos ou como uma simples lista de URLs de vídeos</string>
<string name="image_quality_summary">Escolha a qualidade das imagens e se pretende carregar imagens, para reduzir a utilização de dados e de memória. As alterações limpam a cache de imagens na memória e no disco - %s</string>
<string name="show_more">Mostrar mais</string>
<plurals name="replies">

View File

@ -789,7 +789,6 @@
<string name="feed_fetch_channel_tabs_summary">File ce vor fi preluate când se actualizează fluxul. Această opțiune nu are niciun efect dacă un canal este actualizat folosind modul rapid.</string>
<string name="prefer_descriptive_audio_summary">Selectați o coloană sonoră cu descrieri pentru persoane cu deficiențe vizuale, dacă este disponibilă</string>
<string name="left_gesture_control_title">Acțiunea gestului din stânga</string>
<string name="share_playlist_with_titles_message">Distribuiți playlistul cu detalii precum numele playlistului și titlurile videourilor sau ca o simplă listă de URL-uri a videourilor</string>
<string name="image_quality_medium">Calitate medie</string>
<string name="prefer_descriptive_audio_title">Preferați audioul descriptiv</string>
<string name="progressive_load_interval_summary">Modificați dimensiunea intervalului de încărcare pentru conținuturi progresive (în prezent %s). O valoare mai mică poate accelera încărcarea lor inițială</string>

View File

@ -190,7 +190,7 @@
<string name="subscribed_button_title">Вы подписаны</string>
<string name="channel_unsubscribed">Подписка отменена</string>
<string name="show_hold_to_append_title">Показать подсказку «Зажмите, чтобы добавить»</string>
<string name="show_hold_to_append_summary">Показывать подсказку при нажатии на фон или всплывающую кнопку в видео \"Подробнее:\\</string>
<string name="show_hold_to_append_summary">Показывать подсказку при нажатии \"В фоне\" или на всплывающую кнопку \"Подробнее:\" в видео</string>
<string name="unknown_content">[Неизвестно]</string>
<string name="player_recoverable_failure">Восстановление после ошибки плеера</string>
<string name="hold_to_append">Зажмите, чтобы добавить в очередь</string>
@ -535,7 +535,7 @@
<string name="remove_watched_popup_title">Удалить просмотренные видео\?</string>
<string name="show_original_time_ago_summary">Отображать сообщённое сервисом время с момента публикации</string>
<string name="show_original_time_ago_title">Исходное время публикации</string>
<string name="youtube_restricted_mode_enabled_title">Включите на YouTube \"Ограниченный режим\\</string>
<string name="youtube_restricted_mode_enabled_title">Включить \"Ограниченный режим\" YouTube</string>
<string name="video_detail_by">От %s</string>
<string name="channel_created_by">Создано %s</string>
<string name="detail_sub_channel_thumbnail_view_description">Миниатюра значка канала</string>
@ -688,7 +688,7 @@
<string name="get_notified">Уведомлять</string>
<string name="you_successfully_subscribed">Вы подписались на канал</string>
<string name="toggle_all">Переключить все</string>
<string name="show_crash_the_player_title">Шоу \"Разбей игрока\\</string>
<string name="show_crash_the_player_title">Показывать \"Сбой плеера\"</string>
<string name="show_crash_the_player_summary">Показать функцию вызова сбоя при работе плеера</string>
<string name="crash_the_player">Вызвать сбой плеера</string>
<string name="error_report_channel_name">Уведомление отчёта об ошибке</string>
@ -718,7 +718,7 @@
<string name="no_audio_streams_available_for_external_players">Нет аудиопотоков, доступных внешним плеерам</string>
<string name="select_quality_external_players">Выберите качество для внешних плееров</string>
<string name="unknown_quality">Неизвестное качество</string>
<string name="progressive_load_interval_title">Размер предварительной загрузки</string>
<string name="progressive_load_interval_title">Размер интервала загрузки при воспроизведении</string>
<string name="faq_title">Ответы на частые вопросы</string>
<string name="faq_description">Если у вас возникли проблемы с использованием приложения, обязательно ознакомьтесь с ответами на распространённые вопросы!</string>
<string name="faq">Посмотреть на веб-сайте</string>
@ -806,7 +806,6 @@
<string name="rewind">Перемотать назад</string>
<string name="replay">Повторить</string>
<string name="feed_fetch_channel_tabs_summary">Получаемые вкладки при обновлении ленты. Эта функция не применяется, если канал обновляется с помощью быстрого режима.</string>
<string name="share_playlist_with_titles_message">Поделиться подборкой с подробностями, такими как название подборки и названия видео, или просто списком URL видео</string>
<string name="image_quality_medium">Среднее качество</string>
<string name="metadata_uploader_avatars">Загрузчик аватаров</string>
<string name="metadata_banners">Баннеры</string>

View File

@ -802,7 +802,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">プレイリストちゅーゆーいん</string>
<string name="share_playlist_with_titles_message">プレイリストめいてぃがろービデオタイトルんでーぬしょうさいくくむるプレイリスト、あらんでぃビデオURLぬみぬシンプルやるリストとぅしてぃプレイリストちゅーゆーいんさびーん</string>
<string name="video_details_list_item">- %1$s %2$s</string>
<plurals name="replies">
<item quantity="one">%sぬへんしん</item>

View File

@ -676,7 +676,6 @@
<string name="image_quality_low">ᱞᱟᱯᱷᱟᱝ ᱥᱤᱠᱷᱱᱟ.</string>
<string name="image_quality_medium">ᱛᱟᱞᱢᱟ ᱥᱤᱠᱷᱱᱟᱹᱛ</string>
<string name="image_quality_high">ᱩᱥᱩᱞ ᱥᱤᱠᱷᱱᱟᱹᱛ</string>
<string name="share_playlist_with_titles_message">ᱯᱷᱟᱭᱞᱤᱥᱴ ᱧᱩᱛᱩᱢ ᱟᱨ ᱵᱷᱤᱰᱤᱭᱳ ᱧᱩᱛᱩᱢ ᱞᱮᱠᱟᱛᱮ ᱟᱨᱵᱟᱝ ᱵᱷᱤᱰᱤᱭᱳ URL ᱨᱮᱱᱟᱜ ᱢᱤᱫ ᱞᱮᱠᱟᱱ ᱞᱤᱥᱴᱤ ᱞᱮᱠᱟᱛᱮ ᱴᱷᱟᱶ ᱮᱢ ᱢᱮ</string>
<string name="share_playlist_with_list">URL ᱛᱟᱹᱞᱠᱟᱹ ᱥᱟᱯᱲᱟᱣ</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="notification_scale_to_square_image_summary">ᱱᱚᱴᱤᱯᱷᱤᱠᱮᱥᱚᱱ ᱨᱮ ᱑᱖:᱙ ᱠᱷᱚᱱ ᱑:᱑ ᱟᱥᱯᱮᱠᱴ ᱚᱱᱩᱯᱟᱹᱛ ᱨᱮ ᱵᱷᱤᱰᱤᱭᱳ ᱛᱷᱚᱢᱵᱱᱮᱞ ᱜᱮᱫᱽ ᱢᱮ</string>

View File

@ -786,7 +786,6 @@
<string name="rewind">Torra in segus</string>
<string name="replay">Torra a reprodùere</string>
<string name="feed_fetch_channel_tabs_summary">Ischedas de recuperare cando agiornas sa fonte. Custa optzione non tenet efetu si unu canale benit agiornadu impreende sa modalidade lestra.</string>
<string name="share_playlist_with_titles_message">Cumpartzi s\'iscalita cun detàllios che a su nùmene de s\'iscalita e sos tìtulos de sos vìdeos o che a una lista simpre de URL de vìdeos</string>
<string name="image_quality_medium">Calidade mesana</string>
<string name="metadata_uploader_avatars">Avatars de su carrigadore</string>
<string name="metadata_banners">Insignas</string>

View File

@ -821,7 +821,6 @@
<item quantity="few">%s odpovede</item>
<item quantity="other">%s odpovedí</item>
</plurals>
<string name="share_playlist_with_titles_message">Zdieľajte playlist s podrobnosťami, ako je jeho názov a názvy videí, alebo ako jednoduchý zoznam URL adries videí</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="share_playlist_content_details">%1$s
\n%2$s</string>

View File

@ -815,7 +815,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">Дели плејлисту</string>
<string name="share_playlist_with_titles_message">Делите плејлисту са детаљима, као што су назив плејлисте и наслови видео снимака или као једноставна листа URL адреса видео снимака</string>
<string name="video_details_list_item">-%1$s: %2$s</string>
<plurals name="replies">
<item quantity="one">%s одговор</item>

View File

@ -91,7 +91,7 @@
<string name="resume_on_audio_focus_gain_title">Återuppta uppspelning</string>
<string name="resume_on_audio_focus_gain_summary">Fortsätt uppspelning efter avbrott (t.ex. telefonsamtal)</string>
<string name="show_hold_to_append_title">Visa \"Håll för att köa\"-tips</string>
<string name="show_hold_to_append_summary">Visa tips när bakgrunds- eller popup-knappen trycks på sidan för video \"Detaljer:\\</string>
<string name="show_hold_to_append_summary">Visa tips när bakgrunds eller popup-knappen trycks på sidan för video \"Detaljer:\"</string>
<string name="settings_category_player_title">Spelare</string>
<string name="settings_category_player_behavior_title">Beteende</string>
<string name="settings_category_history_title">Historik och cacheminne</string>
@ -802,7 +802,6 @@
<string name="metadata_uploader_avatars">Uppladdarens visningsbilder</string>
<string name="metadata_banners">Banderoller</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="share_playlist_with_titles_message">Dela spellistan med detaljer så som spellistans namn och video-titlarna eller som en enkel lista med URL till videorna</string>
<string name="image_quality_summary">Välj bildkvalitet och om bilder överhuvudtaget ska laddas för att minska data och minnesanvändningen. Ändringar rensar både i minnet och bildcache på disk %s</string>
<string name="show_more">Visa mer</string>
<plurals name="replies">

View File

@ -470,7 +470,6 @@
<string name="forward">முன்னோக்கி</string>
<string name="image_quality_title">பட தகுதி</string>
<string name="image_quality_high">உயர் தகுதி</string>
<string name="share_playlist_with_titles_message">பிளேலிச்ட் பெயர் மற்றும் வீடியோ தலைப்புகள் போன்ற விவரங்களுடன் அல்லது வீடியோ முகவரி களின் எளிய பட்டியலாக பிளேலிச்ட்டைப் பகிரவும்</string>
<string name="more_options">மேலும் விருப்பங்கள்</string>
<string name="duration">காலம்</string>
<string name="rewind">முன்னாடி</string>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="main_bg_subtitle">แตะที่ปุ่ม \"ค้นหา\" เพื่อเริ่มต้น</string>
<string name="main_bg_subtitle">แตะที่สัญลักษณ์แว่นขยายเพื่อเริ่มต้น</string>
<string name="upload_date_text">เผยแพร่เมื่อ %1$s</string>
<string name="no_player_found">ไม่พบแอปที่สามารถสตรีมสื่อวีดีโอได้ คุณต้องการติดตั้ง VLC หรือไม่\?</string>
<string name="no_player_found_toast">ไม่พบแอปที่สามารถสตรีมสื่อวีดีโอได้ (คุณสามารถติดตั้ง VLC เพื่อดูวีดีโอ)</string>
@ -12,33 +12,33 @@
<string name="download">ดาวน์โหลด</string>
<string name="controls_download_desc">ดาวน์โหลดไฟล์สตรีม</string>
<string name="search">ค้นหา</string>
<string name="settings">ตั้งค่า</string>
<string name="settings">การตั้งค่า</string>
<string name="did_you_mean">หรือคุณหมายถึง \"%1$s\"\?</string>
<string name="share_dialog_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="subscribe_button_title">ติดตาม</string>
<string name="subscribed_button_title">ติดตามแล้ว</string>
<string name="unsubscribe">ยกเลิกการติดตาม</string>
<string name="channel_unsubscribed">ยกเลิกการติดตามช่องแล้ว</string>
<string name="subscription_change_failed">ไม่สามารถเปลี่ยนสถานะการติดตามได้</string>
<string name="subscription_update_failed">ไม่สามารถอัปเดตการติดตาม</string>
<string name="subscription_update_failed">ไม่สามารถอัพเดทการติดตาม</string>
<string name="show_info">แสดงข้อมูล</string>
<string name="tab_subscriptions">การติดตาม</string>
<string name="tab_bookmarks">เพลย์ลิสต์ที่เก็บไว้</string>
<string name="tab_choose">เลือกแท็บ</string>
<string name="fragment_feed_title">มีอะไรใหม่</string>
<string name="controls_background_title">พื้นหลัง</string>
<string name="controls_popup_title">ป๊อปอัพ</string>
<string name="controls_background_title">เล่นในพื้นหลัง</string>
<string name="controls_popup_title">พ็อปอัพ</string>
<string name="controls_add_to_playlist_title">เพิ่มไปยัง</string>
<string name="download_path_title">เส้นทางการดาวน์โหลดวิดีโอ</string>
<string name="download_path_summary">เส้นทางในการจัดเก็บวิดีโอที่ดาวน์โหลดมา</string>
<string name="download_path_dialog_title">เลือกเส้นทางการดาวน์โหลดสำหรับไฟล์วิดีโอ</string>
<string name="download_path_audio_title">โฟลเดอร์ที่ดาวน์โหลดเสียง</string>
<string name="download_path_title">โฟลเดอร์ดาวน์โหลดของวิดีโอ</string>
<string name="download_path_summary">ไฟล์วิดีโอที่ดาวน์โหลดไว้จะถูกเก็บที่นี่</string>
<string name="download_path_dialog_title">เลือกโฟลเดอร์ดาวน์โหลดสำหรับไฟล์วิดีโอ</string>
<string name="download_path_audio_title">โฟลเดอร์ดาวน์โหลดของเสียง</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_popup_resolution_title">ความละเอียดเริ่มต้นในโหมดป๊อปอัพ</string>
<string name="default_popup_resolution_title">ความละเอียดเริ่มต้นในโหมดพ็อปอัพ</string>
<string name="show_higher_resolutions_title">แสดงความละเอียดที่สูงขึ้น</string>
<string name="show_higher_resolutions_summary">เฉพาะบางอุปกรณ์ที่รองรับการเล่นวิดีโอ 2K/4K</string>
<string name="play_with_kodi_title">เปิดด้วย Kodi</string>
@ -349,7 +349,7 @@
<string name="pause_downloads_on_mobile">หยุดชั่วคราวเมื่อเปลี่ยนเป็นข้อมูลมือถือ</string>
<string name="pause_downloads_on_mobile_desc">การดาวน์โหลดที่ไม่สามารถหยุดพักได้จะเริ่มต้นใหม่</string>
<string name="close">ปิด</string>
<string name="use_external_video_player_summary">บางความละเอียดอาจไม่มีเสียง</string>
<string name="use_external_video_player_summary">ลบเสียงสำหรับบางความละเอียดหน้าจอ</string>
<string name="metadata_cache_wipe_complete_notice">แคช metadate ถูกลบแล้ว</string>
<string name="resume_on_audio_focus_gain_title">เล่นต่อหลังจากการขัดจังหวะ</string>
<string name="enable_playback_resume_title">เล่นต่อ</string>
@ -361,13 +361,55 @@
<string name="open_with">เปิดด้วย</string>
<string name="mark_as_watched">ทำเครื่องหมายว่าดูแล้ว</string>
<string name="ok">ตกลง</string>
<string name="notification_action_3_title">ปุ่มการกระทำที่สี่</string>
<string name="notification_action_0_title">ปุ่มการกระทำแรก</string>
<string name="notification_action_2_title">ปุ่มการกระทำที่สาม</string>
<string name="notification_action_4_title">ปุ่มการกระทำที่ห้า</string>
<string name="notification_actions_summary">แก้ไขการกระทำของการแต่การแจ้งเตือนด้วยการแตะไปที่มัน เลือกสามรายการที่จะแสดงในการแจ้งเตือนในการแจ้งเตือนแบบกระทัดรัดโดยใช้ปุ่มกาเครื่องหมายทางขวา</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="crash_the_player">ทำเครื่องเล่นพัง</string>
<string name="notification_action_1_title">ปุ่มการกระทำรอง</string>
<string name="notification_action_3_title">ปุ่มคำสั่งที่สี่</string>
<string name="notification_action_0_title">ปุ่มคำสั่งแรก</string>
<string name="notification_action_2_title">ปุ่มคำสั่งที่สาม</string>
<string name="notification_action_4_title">ปุ่มคำสั่งที่ห้า</string>
<string name="notification_actions_summary">แก้ไขคำสั่งของการแต่การแจ้งเตือนด้วยการแตะไปที่มัน เลือกสามรายการที่จะแสดงในการแจ้งเตือนในการแจ้งเตือนแบบกระทัดรัดโดยใช้ปุ่มกาเครื่องหมายทางขวา</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="crash_the_player">เครื่องเล่นวิดีโอแครช</string>
<string name="notification_action_1_title">ปุ่มคำสั่งที่สอง</string>
<string name="night_theme_title">โหมดกลางคืน</string>
<string name="prefer_original_audio_summary">เลือกเสียงต้นฉบับโดยไม่คำนึงถึงภาษาที่ใช้</string>
<string name="show_description_summary">ปิดเพื่อซ่อนคำอธิบายของวิดีโอและข้อมูลเพิ่มเติมอื่นๆ</string>
<string name="prefer_original_audio_title">ชอบเสียงต้นฉบับมากกว่า</string>
<string name="prefer_descriptive_audio_title">ชอบเสียงแบบบรรยายมากกว่า</string>
<string name="left_gesture_control_title">คำสั่งสัมผัสฝั่งซ้าย</string>
<string name="none">ไม่มี</string>
<string name="peertube_instance_add_help">ใส่ที่อยู่ URL ของอินสแตนซ์</string>
<string name="peertube_instance_add_exists">มีอินสแตนซ์นี้อยู่แล้ว</string>
<string name="notification_action_shuffle">สลับ</string>
<string name="notification_colorize_title">เปลี่ยนสีการแจ้งเตือน</string>
<string name="clear_queue_confirmation_summary">เปลี่ยนจากเครื่องเล่นหนึ่งไปอีกเครื่องเล่นหนึ่งอาจแทนที่คิวของคุณ</string>
<string name="ignore_hardware_media_buttons_summary">มีประโยชน์อย่างมากเมื่อใส่หูฟังที่ปุ่มกดพัง</string>
<string name="prefer_descriptive_audio_summary">เลือกเสียงแบบบรรยายสำหรับผู้มีความบกพร่องทางการมองเห็น ถ้าเกิดมีตัวเลือกนี้</string>
<string name="local_search_suggestions">แนะนำการค้นหาที่อยู่ในท้องที่</string>
<string name="left_gesture_control_summary">เลือกคำสั่งสัมผัสสำหรับฝั่งซ้ายของหน้าจอเครื่องเล่น</string>
<string name="settings_category_player_notification_title">การแจ้งเตือนจากเครื่องเล่น</string>
<string name="notification_action_repeat">วนซ้ำ</string>
<string name="seek_duration_title">เร่งความเร็วไปข้างหน้า/ย้อนกลับหาช่วงเวลา</string>
<string name="start_main_player_fullscreen_title">เริ่มเครื่องเล่นหลักแบบเต็มหน้าจอ</string>
<string name="start_main_player_fullscreen_summary">อย่าเริ่มวิดีโอในเครื่องเล่นเล็ก แต่เปลี่ยนเป็นเต็มหน้าจอโดยตรง ถ้าการหมุนหน้าจออัตโนมัติล็อคไว้ คุณยังสามารถเข้าถึงเครื่องเล่นเล็กโดยการออกจากโหมดเต็มหน้าจอ</string>
<string name="unsupported_url_dialog_message">ไม่สามารถระบุที่อยู่ URL ได้ ลองเปิดด้วยแอปอื่น</string>
<string name="clear_queue_confirmation_title">ถามก่อนการเคลียร์คิว</string>
<string name="clear_queue_confirmation_description">คิวในเครื่องเล่นที่ใช้งานอยู่จะถูกแทนที่</string>
<string name="show_description_title">แสดงคำอธิบาย</string>
<string name="show_meta_info_title">แสดง meta info</string>
<string name="metadata_cache_wipe_title">ล้าง cached metadata</string>
<string name="auto_queue_toggle">เข้าคิวอัตโนมัติ</string>
<string name="right_gesture_control_summary">เลือกคำสั่งสัมผัสฝั่งขวาของหน้าจอเครื่องเล่น</string>
<string name="right_gesture_control_title">คำสั่งสัมผัสฝั่งขวา</string>
<string name="brightness">ความสว่าง</string>
<string name="volume">ระดับเสียง</string>
<string name="remote_search_suggestions">แนะนำการค้นหาที่อยู่ไกลขึ้น</string>
<string name="peertube_instance_url_title">PeerTube อินสแตนซ์</string>
<string name="peertube_instance_url_summary">เลือก PeerTube อินสแตนซ์โปรดของคุณ</string>
<string name="peertube_instance_url_help">หาอินสแตนซ์ที่คุณชอบอยู่ใน %s</string>
<string name="peertube_instance_add_title">เพิ่มอินสแตนซ์</string>
<string name="peertube_instance_add_fail">ไม่สามารถตรวจสอบอินสแตนซ์ได้</string>
<string name="peertube_instance_add_https_only">รับรองเฉพาะที่อยู่ URL แบบ HTTPS</string>
<string name="notification_action_nothing">ไม่มี</string>
<string name="no">ไม่</string>
<string name="yes">ใช่/ตกลง</string>
</resources>

View File

@ -157,7 +157,7 @@
<string name="action_history">Geçmiş</string>
<string name="delete_item_search_history">Bu ögeyi arama geçmişinden silmek istiyor musunuz\?</string>
<string name="show_hold_to_append_title">\"Basılı tutarak kuyruğa ekle\" ipucunu göster</string>
<string name="show_hold_to_append_summary">Video \"Ayrıntılar:\" sayfasında arka plan veya açılır pencere düğmesine basıldığında ipucu göster</string>
<string name="show_hold_to_append_summary">Video \"Ayrıntılar:\" sayfasında arka plan ya da açılır pencere düğmesine basıldığında ipucu göster</string>
<string name="play_all">Tümünü Oynat</string>
<string name="unknown_content">[Bilinmeyen]</string>
<string name="player_stream_failure">Bu akış oynatılamadı</string>
@ -789,7 +789,6 @@
<string name="rewind">Geri sar</string>
<string name="replay">Yeniden oynat</string>
<string name="feed_fetch_channel_tabs_summary">Besleme güncellenirken alınacak sekmeler. Hızlı kip kullanılırken kanal güncelleniyorsa bu seçeneğin etkisi yoktur.</string>
<string name="share_playlist_with_titles_message">Oynatma listesini, oynatma listesi adı ve video başlıkları gibi ayrıntılarla ya da video adreslerinin basit listesi olarak paylaş</string>
<string name="image_quality_medium">Orta nitelik</string>
<string name="metadata_uploader_avatars">Yükleyen avatarları</string>
<string name="metadata_banners">Afişler</string>

View File

@ -170,7 +170,7 @@
<string name="show_info">Показати інформацію</string>
<string name="tab_bookmarks">Закладки відтворення</string>
<string name="controls_add_to_playlist_title">Додати до</string>
<string name="show_hold_to_append_summary">Показувати підказку під час натискання фонової або спливної кнопки у відео \"Деталі:\\</string>
<string name="show_hold_to_append_summary">Показувати підказку під час натискання фонової або спливної кнопки у відео \"Деталі:\"</string>
<string name="player_unrecoverable_failure">Сталася невиправна помилка програвача</string>
<string name="external_player_unsupported_link_type">Зовнішні програвачі не підтримують такі види посилань</string>
<string name="info_labels">Що:\\nЗапит:\\nМова вмісту:\\nКраїна вмісту:\\nМова застосунку:\\nСлужба:\\nЧас GMT:\\nПакунок:\\nВерсія:\\nВерсія ОС:</string>
@ -529,7 +529,7 @@
\nЩоб побачити його ввімкніть «%1$s» в налаштуваннях.</string>
<string name="detail_sub_channel_thumbnail_view_description">Ескіз аватара каналу</string>
<string name="show_original_time_ago_summary">Оригінальні тексти сервісів будуть видимі в потокових елементах</string>
<string name="youtube_restricted_mode_enabled_title">Увімкніть \"Обмежений режим\" YouTube</string>
<string name="youtube_restricted_mode_enabled_title">Увімкнути \"Обмежений режим\" YouTube</string>
<string name="search_showing_result_for">Результати для: %s</string>
<string name="channel_created_by">Створено %s</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Показати лише незгруповані підписки</string>
@ -670,7 +670,7 @@
<string name="manual_update_description">Перевірка нових версій вручну</string>
<string name="checking_updates_toast">Перевірка оновлень…</string>
<string name="feed_new_items">Нові записи стрічки</string>
<string name="show_crash_the_player_title">Показати \"Збій програвача\\</string>
<string name="show_crash_the_player_title">Показати \"Збій програвача\"</string>
<string name="show_crash_the_player_summary">Показує параметр збою під час використання програвача</string>
<string name="crash_the_player">Збій програвача</string>
<string name="error_report_channel_name">Сповіщення про звіт про помилку</string>
@ -819,7 +819,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">Поділитися добіркою</string>
<string name="share_playlist_with_titles_message">Поділитися добіркою з подробицями, такими як назва добірки та назви відео, або просто списком URL-адрес відео</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="show_more">Показати більше</string>
<string name="notification_actions_summary_android13">Відредагуйте кожну дію сповіщення, натиснувши на неї. Перші три дії (відтворення/пауза, попередній і наступний) встановлюються системою і не можуть бути змінені.</string>

View File

@ -785,7 +785,6 @@
<string name="forward">Tua đi</string>
<string name="channel_tab_albums">Album</string>
<string name="rewind">Tua lại</string>
<string name="share_playlist_with_titles_message">Chia sẻ danh sách phát với các thông tin chi tiết như tên danh sách phát và tiêu đề video hoặc dưới dạng danh sách URL video đơn giản</string>
<string name="image_quality_medium">Chất lượng trung bình</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="image_quality_summary">Chọn chất lượng hình ảnh và chọn có tải chất lượng ảnh hay không, để giảm mức sử dụng dữ liệu và bộ nhớ. Thay đổi xoá cache ảnh cho cả trong bộ nhớ lẫn ổ cứng - %s</string>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="open_in_browser">im brüscher öffn</string>
<string name="yes">passd scho</string>
<string name="ok">passd scho</string>
<string name="cancel">stoarnieren</string>
<string name="install">iser</string>
<string name="no">net</string>
<string name="share">tealn</string>
</resources>

View File

@ -789,7 +789,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">分享播放列表</string>
<string name="share_playlist_with_titles_message">分享详细的播放列表(带名称和视频标题等信息)或只分享视频网址列表</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies">
<item quantity="other">%s 条回复</item>

View File

@ -773,7 +773,6 @@
<string name="rewind">跳後</string>
<string name="replay">重播</string>
<string name="feed_fetch_channel_tabs_summary">更新摘要嘅時候要攞邊啲分頁返嚟。若果頻道用快速模式更新,就橫豎都無相干嘞。</string>
<string name="share_playlist_with_titles_message">分享播放清單要詳細包含播放清單個名同埋入面啲片名,定簡單得啲影片嘅 URL</string>
<string name="image_quality_medium">一般畫質</string>
<string name="metadata_uploader_avatars">上載者嘅頭像</string>
<string name="metadata_banners">橫額</string>

View File

@ -789,7 +789,6 @@
<string name="share_playlist_content_details">%1$s
\n%2$s</string>
<string name="share_playlist">分享播放清單</string>
<string name="share_playlist_with_titles_message">分享包含播放清單名稱與影片標題等詳細資訊的播放清單,或是僅作為簡單的影片網址清單</string>
<string name="video_details_list_item">- %1$s%2$s</string>
<plurals name="replies">
<item quantity="other">%s 個回覆</item>

View File

@ -33,6 +33,7 @@
<string name="show_info">Show info</string>
<string name="tab_subscriptions">Subscriptions</string>
<string name="tab_bookmarks">Bookmarked Playlists</string>
<string name="tab_bookmarks_short">Playlists</string>
<string name="tab_choose">Choose Tab</string>
<string name="controls_background_title">Background</string>
<string name="controls_popup_title">Popup</string>
@ -845,9 +846,9 @@
<string name="image_quality_high">High quality</string>
<string name="question_mark">\?</string>
<string name="share_playlist">Share Playlist</string>
<string name="share_playlist_with_titles_message">Share playlist with details such as playlist name and video titles or as a simple list of video URLs</string>
<string name="share_playlist_with_titles">Share with Titles</string>
<string name="share_playlist_with_list">Share URL list</string>
<string name="share_playlist_as_youtube_temporary_playlist">Share as YouTube temporary playlist</string>
<string name="video_details_list_item">- %1$s: %2$s</string>
<string name="share_playlist_content_details">%1$s\n%2$s</string>
<plurals name="replies">

View File

@ -0,0 +1,3 @@
<automotiveApp>
<uses name="media" />
</automotiveApp>

View File

@ -0,0 +1,104 @@
package org.schabi.newpipe.local.playlist
import android.content.Context
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mockito.Mockito.mock
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS
import org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST
import java.util.stream.Stream
class ExportPlaylistTest {
@Test
fun exportAsYouTubeTempPlaylist() {
val playlist = asPlaylist(
"https://www.youtube.com/watch?v=10000000000",
"https://soundcloud.com/cautious-clayofficial/cold-war-2", // non-Youtube URLs should be ignored
"https://www.youtube.com/watch?v=20000000000",
"https://www.youtube.com/watch?v=30000000000"
)
val url = export(YOUTUBE_TEMP_PLAYLIST, playlist, mock(Context::class.java))
assertEquals(
"https://www.youtube.com/watch_videos?video_ids=" +
"10000000000," +
"20000000000," +
"30000000000",
url
)
}
@Test
fun exportMoreThan50Items() {
/*
* Playlist has more than 50 items => take the last 50
* (YouTube limitation)
*/
val playlist = asPlaylist(
(10..70)
.map { id -> "https://www.youtube.com/watch?v=aaaaaaaaa$id" } // YouTube video IDs are 11 characters long
.stream()
)
val url = export(YOUTUBE_TEMP_PLAYLIST, playlist, mock(Context::class.java))
val videoIDs = (21..70).map { id -> "aaaaaaaaa$id" }.joinToString(",")
assertEquals(
"https://www.youtube.com/watch_videos?video_ids=$videoIDs",
url
)
}
@Test
fun exportJustUrls() {
val playlist = asPlaylist(
"https://www.youtube.com/watch?v=10000000000",
"https://www.youtube.com/watch?v=20000000000",
"https://www.youtube.com/watch?v=30000000000"
)
val exported = export(JUST_URLS, playlist, mock(Context::class.java))
assertEquals(
"""
https://www.youtube.com/watch?v=10000000000
https://www.youtube.com/watch?v=20000000000
https://www.youtube.com/watch?v=30000000000
""".trimIndent(),
exported
)
}
}
fun asPlaylist(vararg urls: String): List<PlaylistStreamEntry> {
return asPlaylist(Stream.of(*urls))
}
fun asPlaylist(urls: Stream<String>): List<PlaylistStreamEntry> {
return urls
.map { url: String -> newPlaylistStreamEntry(url) }
.toList()
}
fun newPlaylistStreamEntry(url: String): PlaylistStreamEntry {
return PlaylistStreamEntry(newStreamEntity(url), 0, 0, 0)
}
fun newStreamEntity(url: String): StreamEntity {
return StreamEntity(
0,
1,
url,
"Title",
StreamType.VIDEO_STREAM,
100,
"Uploader"
)
}

View File

@ -1,4 +1,6 @@
تم إصلاح مشكلة عدم تشغيل YouTube لأي بث.
يعالج هذا الإصدار فقط الخطأ الأكثر إلحاحًا الذي يمنع تحميل تفاصيل فيديو YouTube.
نحن ندرك وجود مشاكل أخرى، وسنقوم قريباً بإصدار إصدار منفصل لحلها.
هذا إصدار إصلاح عاجل يعمل على إصلاح أخطاء YouTube:
• [يوتيوب] إصلاح عدم تحميل أي معلومات فيديو ، وإصلاح أخطاء HTTP 403 أثناء تشغيل مقاطع الفيديو واستعادة تشغيل بعض مقاطع الفيديو المقيدة بالفئة العمرية
• إصلاح أحجام التسميات التوضيحية التي لا يتم تغييرها
• إصلاح معلومات التنزيل مرتين عند فتح البث
• [Soundcloud] قم بإزالة التدفقات المحمية بإدارة الحقوق الرقمية غير القابلة للتشغيل
• ترجمات محدثة

View File

@ -1 +1,4 @@
Opraveno nepřehrávání jakéhokoli streamu ve službě YouTube
Opraveno nepřehrávání jakéhokoli streamu ve službě YouTube.
Tato verze řeší pouze nejpalčivější chybu, která brání načtení detailů videa na YouTube.
Jsme si vědomi, že existují i další problémy, a brzy připravíme samostatné vydání, které je vyřeší.

View File

@ -1 +1,6 @@
Opraveno nepřehrávání jakéhokoli streamu ve službě YouTube
Opravná verze, která opravuje chyby YouTube:
- [YouTube] Oprava nenačítání informací o videu, oprava chyb HTTP 403 při přehrávání videí a obnovení přehrávání některých videí s věkovým omezením.
- Oprava nezměněných velikostí titulků
- Oprava dvojího stahování informací při otevření streamu
- [Soundcloud] Odstranění nepřehratelných streamů chráněných DRM
- Aktualizovány překlady

View File

@ -1,4 +1,6 @@
Behoben: YouTube spielt keinen Stream ab.
Diese Version behebt nur den dringendsten Fehler, der das Laden von YouTube-Videodetails verhindert.
Wir sind uns bewusst, dass es andere Probleme gibt, und wir werden bald eine separate Version erstellen, um sie zu lösen.
Dies ist eine Hotfix-Version, die YouTube-Fehler behebt:
• [Youtube] Behebung, dass keine Videoinformationen geladen werden, Behebung von HTTP 403-Fehlern beim Abspielen von Videos und Wiederherstellung der Wiedergabe einiger altersbeschränkter Videos
• Die Größe der Untertitel wird nicht mehr geändert
• Behebung des doppelten Herunterladens von Informationen beim Öffnen eines Streams
• [Soundcloud] Entfernen von nicht abspielbaren DRM-geschützten Streams
• Aktualisierte Übersetzungen

View File

@ -0,0 +1,13 @@
Βελτιώσεις
• Κάντε κλικ στην περιγραφή της λίστας αναπαραγωγής για να εμφανίζεται περισσότερο / λιγότερο περιεχόμενο
• [PeerTube] Χειριστείτε αυτόματα συνδέσμους παρουσίας «subscribeto.me».
• Ξεκινήστε την αναπαραγωγή μόνο ενός στοιχείου στην οθόνη ιστορικού
Διορθώσεις
• Διορθώστε την ορατότητα του κουμπιού RSS
• Διορθώστε τα σφάλματα προεπισκόπησης της γραμμής αναζήτησης
• Διορθώστε την εισχώρηση στη λίστα αναπαραγωγής ενός στοιχείου χωρίς μικρογραφίες
• Διορθώστε την έξοδο από το παράθυρο διαλόγου λήψης προτού εμφανιστεί
• Διορθώστε το αναδυόμενο παράθυρο ουράς λίστας σχετικών στοιχείων
• Διορθώστε τη σειρά στο παράθυρο διαλόγου προσθήκης στη λίστα αναπαραγωγής
• Προσαρμόστε τη διάταξη του σελιδοδείκτη της λίστας αναπαραγωγής

View File

@ -0,0 +1 @@
Αυτή η έκδοση επείγουσας επιδιόρθωσης διορθώνει σφάλματα HTTP 403 στη μέση των βίντεο του YouTube. Νέος • [SoundCloud] Προσθέστε υποστήριξη για διευθύνσεις URL on.soundcloud.com Βελτιωμένο • [Bandcamp] Εμφάνιση πρόσθετων πληροφοριών στο ραδιοφωνικό κιόσκι διορθώσεις • [YouTube] Διόρθωση περιστασιακών σφαλμάτων HTTP 403 στην αρχή ή στη μέση των βίντεο • [YouTube] Εξαγωγή avatar και banner από περισσότερους τύπους κεφαλίδων καναλιού • [Bandcamp] Διορθώστε διάφορα σφάλματα και χρησιμοποιείτε πάντα HTTPS

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