diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 42d24c5b5..54470bdd3 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -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.** { *; } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d9a63fcde..3faa59f7d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -65,6 +65,9 @@ + + + + + + diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 5bca7663c..df38abdc5 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -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); @@ -578,8 +592,8 @@ public class MainActivity extends AppCompatActivity { if (player instanceof BackPressable backPressable && !backPressable.onBackPressed()) { BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder) .setState(BottomSheetBehavior.STATE_COLLAPSED); - return; } + return; } if (fragmentManager.getBackStackEntryCount() == 1) { @@ -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); } } diff --git a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt index a93ba1652..27fc429f1 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt +++ b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt @@ -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) + } } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java index 072c49e2c..91f4622e9 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java @@ -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(); } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java index 03a1e1e30..8fbadb020 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java @@ -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; + } } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java index 8ab8a2afd..ef77d5ade 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java @@ -34,7 +34,7 @@ public interface PlaylistRemoteDAO extends BasicDAO { @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId") - Flowable> getPlaylist(long playlistId); + Flowable getPlaylist(long playlistId); @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " + REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index 60027a057..0b0e3605e 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -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; } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index d9a80b92d..defa533ae 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -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 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 diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 2d5873e3f..a09cd5912 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -121,67 +121,6 @@ public class ChannelFragment extends BaseStateFragment // 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 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 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 } disposables.clear(); binding = null; - activity.removeMenuProvider(menuProvider); menuProvider = null; } diff --git a/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt b/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt index c65b286cf..1c73c5d7c 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt @@ -7,3 +7,16 @@ import java.io.Serializable inline fun 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() +} diff --git a/app/src/main/java/org/schabi/newpipe/ktx/View.kt b/app/src/main/java/org/schabi/newpipe/ktx/View.kt index bf0dcb201..b781335e1 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -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() diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt new file mode 100644 index 000000000..0d4dcbfd0 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt @@ -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, + context: Context +): String { + return when (shareMode) { + WITH_TITLES -> exportWithTitles(playlist, context) + JUST_URLS -> exportJustUrls(playlist) + YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(playlist) + } +} + +fun exportWithTitles( + playlist: List, + 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): String { + + return playlist.asSequence() + .map { it.streamEntity.url } + .joinToString(separator = "\n") +} + +fun exportAsYoutubeTempPlaylist(playlist: List): 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 } +} diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index c87d9cccc..f5562549c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -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 + *
  • {@code JUST_URLS}: shares the URLs only.
  • + *
  • {@code WITH_TITLES}: each entry in the list is accompanied by its title.
  • + *
  • {@code YOUTUBE_TEMP_PLAYLIST}: shares as a YouTube temporary playlist.
  • + * * - * @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 - 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(); } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java b/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java new file mode 100644 index 000000000..f0433aba8 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java @@ -0,0 +1,8 @@ +package org.schabi.newpipe.local.playlist; + +public enum PlayListShareMode { + + JUST_URLS, + WITH_TITLES, + YOUTUBE_TEMP_PLAYLIST +} diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java index 4cc51f752..08b203a7e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java @@ -26,6 +26,10 @@ public class RemotePlaylistManager { return playlistRemoteTable.getPlaylists().subscribeOn(Schedulers.io()); } + public Flowable getPlaylist(final long playlistId) { + return playlistRemoteTable.getPlaylist(playlistId).subscribeOn(Schedulers.io()); + } + public Flowable> getPlaylist(final PlaylistInfo info) { return playlistRemoteTable.getPlaylist(info.getServiceId(), info.getUrl()) .subscribeOn(Schedulers.io()); diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java index e936b9f45..49aff657a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -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()) { diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index c6319c9e8..f32606e0b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -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 diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java index 0e8ff795e..1888bce01 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -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 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; @@ -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 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> result) { + mediaBrowserImpl.onLoadChildren(parentId, result); + } + + @Override + public void onSearch(@NonNull final String query, + final Bundle extras, + @NonNull final Result> result) { + mediaBrowserImpl.onSearch(query, result); + } + //endregion } diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerHolderLifecycleEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerHolderLifecycleEventListener.java deleted file mode 100644 index e5eaa09c7..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerHolderLifecycleEventListener.java +++ /dev/null @@ -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(); -} diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java new file mode 100644 index 000000000..549abc952 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java @@ -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(); +} diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java index 707a44ea5..331ea71c0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java @@ -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 getPlayer() { + return Optional.ofNullable(playerService) + .flatMap(s -> Optional.ofNullable(s.getPlayer())); + } + + private Optional 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 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); + } + } + }; } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserCommon.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserCommon.kt new file mode 100644 index 000000000..12d69a163 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserCommon.kt @@ -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") +} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt new file mode 100644 index 000000000..3108da80f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt @@ -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, // 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>) { + 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> { + 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> { + 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> { + return MergedPlaylistManager.getMergedOrderedPlaylists( + LocalPlaylistManager(database), + RemotePlaylistManager(database) + ) + } + + private fun populateBookmarks(): Single> { + val playlists = getMergedPlaylists().firstOrError() + return playlists.map { playlist -> + playlist.map { this.createPlaylistMediaItem(it) } + } + } + + private fun populateLocalPlaylist(playlistId: Long): Single> { + 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> { + 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> + ) { + 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 { + val serviceId = ServiceHelper.getSelectedServiceId(context) + return ExtractorHelper.searchFor(serviceId, query, listOf(), "") + } + //endregion + + companion object { + private val TAG: String = MediaBrowserImpl::class.java.getSimpleName() + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt new file mode 100644 index 000000000..f34677a29 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt @@ -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, // error string, error code + private val clearMediaSessionError: Runnable, + private val onPrepare: Consumer, +) : 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 { + return LocalPlaylistManager(database).getPlaylistStreams(playlistId).firstOrError() + .map { items -> SinglePlayQueue(items.map { it.toStreamInfoItem() }, index) } + } + + private fun extractRemotePlayQueue(playlistId: Long, index: Int): Single { + 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 { + 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, + url: String?, + ): Single { + 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, + ): Single { + 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, + url: String, + ): Single { + 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 + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java index c673e688c..fe884834b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -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 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; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java index 33ec390a5..dbfac5cca 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java @@ -28,13 +28,17 @@ abstract class AbstractInfoPlayQueue> 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, diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java index 01883d7d9..32316f393 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java @@ -16,6 +16,10 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue super(info); } + public PlaylistPlayQueue(final PlaylistInfo info, final int index) { + super(info, index); + } + public PlaylistPlayQueue(final int serviceId, final String url, final Page nextPage, diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java index 02f7c07b0..6c98ab0fa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -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(); } diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index eac84e229..f8e2df99f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -303,7 +303,7 @@ public final class Localization { *
      *
    • English (original)
    • *
    • English (descriptive)
    • - *
    • Spanish (dubbed)
    • + *
    • Spanish (Spain) (dubbed)
    • *
    * * @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 { diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 516d6c695..aba27c259 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -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; } diff --git a/app/src/main/res/drawable/ic_bookmark_white.xml b/app/src/main/res/drawable/ic_bookmark_white.xml new file mode 100644 index 000000000..a04ed256e --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_white.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_history_white.xml b/app/src/main/res/drawable/ic_history_white.xml new file mode 100644 index 000000000..585285b89 --- /dev/null +++ b/app/src/main/res/drawable/ic_history_white.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values-ar-rLY/strings.xml b/app/src/main/res/values-ar-rLY/strings.xml index 3af148ce5..bbf5b8bdf 100644 --- a/app/src/main/res/values-ar-rLY/strings.xml +++ b/app/src/main/res/values-ar-rLY/strings.xml @@ -854,6 +854,5 @@ %1$s \n%2$s شارِك قائمة التشغيل - شارِك قائمة التشغيل بتفاصيليها مثل اسم قائمة التشغيل وعناوين الفيديو أو كقائمة بسيطة من عناوين تشعّبيّة للفيديوهات - %1$s: %2$s diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 9fcf10f7d..b2c6c4b64 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -18,7 +18,7 @@ اختر مجلد التنزيل لملفات الفيديو يتم تخزين ملفات الفيديو التي تم تنزيلها هنا مجلد تحميل الفيديو - ثبت + ثبيت تطبيق Kore غير موجود. هل تريد تثبيته؟ فاتح خطأ في الشبكة @@ -83,7 +83,7 @@ استئناف التشغيل متابعة التشغيل بعد المقاطعات (مثل المكالمات الهاتفية) إظهار تلميح \"اضغط للفتح\" - إظهار التلميح عند الضغط على الخلفية أو الزر المنبثق في الفيديو \"التفاصيل:\\ + إظهار التلميح عند الضغط على الخلفية أو الزر المنبثق في الفيديو \"التفاصيل:\" المشغل السلوك تشغيل في وضع منبثق @@ -558,7 +558,7 @@ إزالة ما تمت مشاهدته ستكون النصوص الأصلية من الخدمات مرئية في عناصر البث عرض الوقت الأصلي على العناصر - قم بتشغيل \"وضع تقييد المحتوى\" في يوتيوب\\ + قم بتشغيل \"وضع تقييد المحتوى\" في يوتيوب بواسطة %s أنشأها %s الصورة الرمزية للقناة @@ -854,7 +854,6 @@ %1$s \n%2$s مشاركة قائمة التشغيل - شارك تفاصيل قائمة التشغيل مثل اسم قائمة التشغيل وعناوين الفيديو أو كقائمة بسيطة من عناوين URL للفيديو - %1$s: %2$s رد %s diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 928b58f66..3055db9e6 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -785,7 +785,6 @@ Yüksək keyfiyyət \? Oynatma siyahısın paylaş - Pleylist adı və video başlıqları kimi təfsilatlar və ya video URL-lərin sadə siyahısı olaraq pleylist paylaş Başlıqlarla paylaşın - %1$s: %2$s %1$s diff --git a/app/src/main/res/values-bar/strings.xml b/app/src/main/res/values-bar/strings.xml index 1949e1efa..63ae52c9a 100644 --- a/app/src/main/res/values-bar/strings.xml +++ b/app/src/main/res/values-bar/strings.xml @@ -26,4 +26,6 @@ Duad bei manchen Auflösungen d\'Tonspur weggad Im Pop-up Modus aufmacha Drug auf\'d Lubn zum ofanga. - \ No newline at end of file + Bassd scho + naa + diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index b15060e36..19f3c8b26 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -19,7 +19,7 @@ Прыбірае гук пры пэўнай раздзяляльнасці Знешні аўдыяплэер Падпісацца - Вы падпісаныя + Вы падпісаны Падпіска адменена Не ўдалося змяніць падпіску Не ўдалося абнавіць падпіску @@ -165,8 +165,9 @@ Няма падпісчыкаў %s падпісчык - %s падпісчыка + %s падпісчыкі %s падпісчыкаў + %s падпісчыкаў Няма праглядаў @@ -177,7 +178,7 @@ Няма відэа - %s Відэа + %s відэа %s відэа %s відэа %s відэа @@ -207,7 +208,7 @@ Недапушчальныя сімвалы замяняюцца на гэты Сімвал для замены Літары і лічбы - Большасць спецзнакаў + Большасць спецсімвалаў Аб NewPipe Іншыя ліцэнзіі © %1$s %2$s пад ліцэнзіяй %3$s @@ -221,7 +222,7 @@ NewPipe распрацаваны добраахвотнікамі, якія праводзяць свой вольны час, забяспечваючы лепшы карыстацкі досвед. Дапамажыце распрацоўшчыкам зрабіць NewPipe яшчэ лепшым, пакуль яны атрымліваюць асалоду ад кавы. Ахвяраваць грошы Вэб-сайт - Дзеля атрымання больш падрабязнай інфармацыі і апошніх навін аб NewPipe наведайце наш вэб-сайт. + Наведайце вэб-сайт, каб атрымаць больш інфармацыі і паглядзець апошнія навіны NewPipe. Палітыка прыватнасці NewPipe Праект NewPipe вельмі адказна ставіцца да вашай прыватнасці. Таму праграма не збірае ніякіх даных без вашай згоды. \nПалітыка прыватнасці NewPipe падрабязна тлумачыць, якія даныя адпраўляюцца і захоўваюцца пры адпраўцы справаздачы пра збой. Прачытаць палітыку прыватнасці @@ -231,9 +232,9 @@ Гісторыя Гісторыя Выдаліць гэты элемент з гісторыі пошуку? - Нядаўна прайграныя - Найбольш прайграваныя - Кантэнт галоўнай старонкі + Прайгравалася нядаўна + Прайгравалася найбольш + Змесціва галоўнай старонкі Пустая старонка Старонка кіёска Старонка канала @@ -254,13 +255,13 @@ Налады аўдыя Зацісніце, каб дадаць у чаргу Пачаць прайграванне ў фоне - Пачаць прайграванне у акне + Пачаць прайграванне ў акне Адкрыць бакавую панэль Закрыць бакавую панэль Пры адкрыцці кантэнту Пры адкрыцці спасылкі на кантэнт — %s Відэаплэер - Фонавы плэер + Фонавы прайгравальнік Аконны прайгравальнік Заўсёды пытаць Атрыманне звестак… @@ -269,7 +270,7 @@ Перайменаваць Імя Дадаць у плэйліст - Усталяваць як мініяцюру плэйліста + Зрабіць мініяцюрай плэйліста Дадаць плэйліст у закладкі Выдаліць закладку Выдаліць плэйліст\? @@ -308,7 +309,7 @@ Прапускаць цішыню Крок Скід - У адпаведнасці з Агульным рэгламентам па абароне даных ЕС (GDPR), звяртаем вашу ўвагу на палітыку прыватнасці NewPipe. Уважліва азнаёмцеся з ёй. \nВы павінны прыняць ўмовы, каб адправіць нам справаздачу пра памылку. + У адпаведнасці з Агульным рэгламентам па абароне даных ЕС (GDPR), звяртаем вашу ўвагу на палітыку прыватнасці NewPipe. Уважліва азнаёмцеся з ёй. \nВы павінны прыняць умовы, каб адправіць нам справаздачу пра памылку. Прыняць Адмовіцца Без абмежаванняў @@ -316,7 +317,7 @@ Згортванне пры пераключэнні праграмы Дзеянне пры пераключэнні з асноўнага відэаплэера на іншую праграму — %s Нічога не рабіць - Згортванне у фон + Згортванне ў фон Згортванне ў акно Адпісацца Выберыце ўкладку @@ -362,7 +363,7 @@ Спыніць Максімум спроб Колькасць спроб спампаваць да адмены - Перапыніць у платных сетках + Прыпыняць у сетках з тарыфікацыяй Карысна пры пераключэнні на мабільную сетку, хоць некаторыя спампоўванні немагчыма прыпыніць Падзеі Канферэнцыі @@ -386,7 +387,7 @@ Праграма NewPipe была закрыта падчас працы з файлам На прыладзе скончылася вольнае месца Прагрэс страчаны, бо файл быў выдалены - Час злучэння выйшла + Скончыўся час злучэння Вы хочаце ачысціць гісторыю спампоўвання ці выдаліць спампаваныя файлы? Абмежаваць чаргу спампоўвання Толькі адно адначасовае спампоўванне @@ -420,7 +421,7 @@ Не ўдалося праверыць сервер Увядзіце URL-адрас сервера Выберыце ўлюбёныя серверы PeerTube - Актыўны плэер быў зменены + Чарга актыўнага прайгравальніка будзе заменена Пераключэнне з аднаго плэера на другі можа прывесці да замены вашай чаргі Запытваць пацвярджэнне перад ачысткай чаргі Ніколі @@ -452,7 +453,7 @@ Альбомы Песні Відэа - Аўтаматычная чарга + Аўтапрайграванне Крок перамотвання Каляровыя апавяшчэнні Нічога @@ -491,8 +492,8 @@ Адключыць Няма аўдыяпатокаў даступных для знешніх плэераў Апавяшчаць - Няма даступных відэатрансляцый для знешніх плэераў - Выбраная трансляцыя не падтрымліваецца знешнімі плэерамі + Няма відэапатокаў даступных для знешніх плэераў + Выбраны паток не падтрымліваецца знешнімі плэерамі Выберыце якасць для знешніх плэераў Невядомая якасць Невядомы фармат @@ -503,7 +504,7 @@ Адкрыць праз Начная тэма Адкрыць вэб-сайт - Цяпер Вы можаце вылучаць тэкст у апісанні. Звярніце ўвагу, што ў рэжыме вылучэння старонка можа мігацець, а спасылкі могуць быць недаступныя для націскання. + Цяпер можна вылучаць тэкст у апісанні. Звярніце ўвагу, што ў рэжыме вылучэння старонка можа мільгаць, а спасылкі не націскацца. Запускаць галоўны прайгравальнік у поўнаэкранным рэжыме Паказаць дэталі канала Нізкая якасць (менш) @@ -562,8 +563,8 @@ %d хвіліна %d хвіліны - %d хвілінаў - %d хвілінаў + %d хвілін + %d хвілін Змяніць памер інтэрвалу загрузкі прагрэсіўнага змесціва (у цяперашні час %s). Меншае значэнне можа паскорыць іх першапачатковую загрузку Выключыце, каб схаваць апісанне відэа і дадатковую інфармацыю @@ -603,8 +604,8 @@ У чаргу далей У чарзе наступны Загрузка звестак аб стрыме… - Апрацоўка... Можа заняць некаторы час - Дублікат дададзены %d раз + Ідзе апрацоўка… Крыху пачакайце + Дублікат дададзены %d раз(ы) LeakCanary недаступны Паказаць уцечкі памяці Адключыце мультымедыйнае тунэляванне, калі ў вас з\'яўляецца чорны экран або заіканне падчас прайгравання відэа. @@ -613,7 +614,7 @@ Частыя пытанні Перайсці на вэб-сайт Правядзіце пальцам па элементах, каб выдаліць іх - Адмяніць пастаянную мініяцюру + Прыбраць пастаянную мініяцюру Паказваць індыкатары выяў Паказваць каляровыя стужкі Пікаса на выявах, якія пазначаюць іх крыніцу: чырвоная для сеткі, сіняя для дыска і зялёная для памяці Апрацоўка стужкі… @@ -624,7 +625,7 @@ Працэнт Відэа, якія прагледжаны перад дадаваннем і пасля дадавання ў спіс прайгравання, будуць выдалены. \nВы ўпэўнены? Гэта дзеянне немагчыма скасаваць! Паказвае варыянт збою пры выкарыстанні плэера - Выдаліць прагледжанае + Выдаліць прагледжаныя Паказаць панэль памылак Паўтон Любая сетка @@ -645,12 +646,12 @@ Выдаліць гэту групу? Новая Паказаць толькі разгрупаваныя падпіскі - Маючыя адбыцца + Запланаваныя Паказваць «Збой плэера» Запусціце праверку новых патокаў Збой праграмы - Апавяшчэнні аб новых стрымах - Апавяшчаць аб новых стрымах з падпісак + Апавяшчэнні пра новыя відэа + Апавяшчаць пра новыя відэа з падпісак Частата праверкі Неабходны тып злучэння Праверыць наяўнасць абнаўленняў @@ -693,7 +694,7 @@ Вы падпісаліся на канал Апошнія Радыё - Паказваць запланаваныя трансляцыі + Паказваць наступныя патокі Паказаць/схаваць трансляцыі Гэты кантэнт яшчэ не падтрымліваецца NewPipe. \n @@ -710,14 +711,14 @@ %s дае наступную прычыну: Вартае ўвагі Унутраная - Цалкам прагледзеў + Прагледжаныя цалкам Гэты кантэнт даступны толькі для аплачаных карыстальнікаў, таму NewPipe не можа яго трансляваць або спампоўваць. Даступны ў некаторых службах, звычайна нашмат хутчэй, але можа вяртаць абмежаваную колькасць элементаў і часта няпоўную інфармацыю (напрыклад, без працягласці, тыпу элемента, без актыўнага стану) Узроставае абмежаванне Для гэтага дзеяння не знойдзены прыдатны файлавы менеджар. \nУсталюйце файлавы менеджар, сумяшчальны з Storage Access Framework Ніякая праграма на вашай прыладзе не можа адкрыць гэта Стандартнае значэнне ExoPlayer - Часткова прагледжана + Прагледжаныя часткова Лічыце, што загрузка каналаў адбываецца занадта павольна? Калі так, паспрабуйце ўключыць хуткую загрузку (можна змяніць у наладах або націснуўшы кнопку ніжэй). \n \nNewPipe прапануе два спосабы загрузкі каналаў: \n• Атрыманне ўсяго канала падпіскі. Павольны, але інфармацыя поўная). \n• Выкарыстанне спецыяльнай канчатковай кропкі абслугоўвання. Хуткі, але звычайна інфармацыя няпоўная). \n \nРозніца паміж імі ў тым, што ў хуткім звычайна адсутнічае частка інфармацыі, напрыклад, працягласць або тып (немагчыма адрозніць трансляцыі ад звычайных відэа), і ён можа вяртаць менш элементаў. \n \nYouTube з\'яўляецца прыкладам сэрвісу, які прапануе гэты хуткі метад праз RSS-канал. \n \nТакім чынам, выбар залежыць ад таго, чаму вы аддаяце перавагу: хуткасці або дакладнасці інфармацыя. Прыватнасць Мова @@ -747,9 +748,7 @@ У гэтым патоку ўжо павінна быць гукавая дарожка Уключыце гэту опцыю, калі ў вас ёсць праблемы з ініцыялізацыяй дэкодэра, якая вяртаецца да дэкодэраў з больш нізкім прыярытэтам, калі ініцыялізацыя асноўных дэкодэраў не ўдаецца. Гэта можа прывесці да нізкай прадукцыйнасці прайгравання, чым пры выкарыстанні асноўных дэкодэраў Кіраванне некаторымі наладамі ExoPlayer. Каб гэтыя змены ўступілі ў сілу, патрабуецца перазапуск прайгравальніка - Гэты абыходны шлях вызваляе і паўторна стварае відэакодэкі, калі адбываецца змяненне паверхні, замест таго, каб усталёўваць паверхню непасрэдна для кодэка. ExoPlayer ужо выкарыстоўваецца на некаторых прыладах з гэтай праблемай, гэты параметр мае ўплыў толькі на прыладах з Android 6 і вышэй -\n -\nУключэнне гэтай опцыі можа прадухіліць памылкі прайгравання пры пераключэнні бягучага відэаплэера або пераключэнні ў поўнаэкранны рэжым + Гэты абыходны шлях вызваляе і паўторна стварае відэакодэкі, калі адбываецца змяненне паверхні, замест таго, каб зажаваць паверхню непасрэдна для кодэка. Ужо выкарыстоўваецца ExoPlayer на некаторых прыладах з такой праблемай, гэты параметр ужываецца толькі на прыладах з Android 6 і вышэй\n\nУключэнне параметра можа прадухіліць памылкі прайгравання пры пераключэнні бягучага відэаплэера або пераключэнні ў поўнаэкранны рэжым Якасць выяў Відэа \? @@ -771,7 +770,7 @@ Наступны паток Прадвызначана на вашай прыладзе адключана медыятунэляванне, бо гэтая мадэль прылады яго не падтрымлівае. Аватары падканалаў - Адкрыйце чаргу прайгравання + Адкрыць чаргу прайгравання Не загружаць выявы Высокая якасць Аб канале @@ -781,13 +780,12 @@ Пераматаць назад Паўтарыць Атрыманыя ўкладкі пры абнаўленні стужкі. Гэты параметр не прымяняецца, калі канал абнаўляецца ў хуткім рэжыме. - Абагуліць плэйліст, перадаецца назва плэйліста і назвы відэа або просты спіс URL-адрасоў відэа Сярэдняя якасць Загрузнік аватараў Банеры Плэйлісты - %1$s: %2$s - Перамясціць панэль укладак ўніз + Перамясціць панэль укладак уніз Няма жывых трансляцый Выберыце якасць выяў і ці трэба спампоўваць выявы ўвогуле, каб паменшыць выкарыстанне даных і памяці. Змены ачышчаюць кэш выяў як у памяці, так і на дыску - %s Прайграць @@ -813,7 +811,7 @@ NewPipe можа аўтаматычна правяраць наяўнасць абнаўленняў і паведаміць вам, калі яны будуць даступны. \nУключыць гэту функцыю? Налады ў імпартаваным экспарце выкарыстоўваюць уразлівы фармат, які састарэў з версіі NewPipe 0.27.0. Пераканайцеся, што імпартаваны экспарт атрыманы з надзейнай крыніцы, і ў будучыні пераважней выкарыстоўваць толькі экспарт, атрыманы з NewPipe 0.27.0 ці навей. Падтрымка імпарту налад у гэтым уразлівым фармаце хутка будзе цалкам выдаленая, і тады старыя версіі NewPipe больш не змогуць імпартаваць наладкі з экспарту з новых версій. Не - Рэзервовае капіраванне і аднаўленне + Рэзервовае капіяванне і аднаўленне Скінуць налады Скінуць усе налады на іх прадвызначаныя значэнні Пры скіданні ўсіх налад будуць адхілены ўсе вашы змены налад і праграма перазапусціцца. \n \nСапраўды хочаце працягнуць? diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 3d746afce..926498649 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -192,7 +192,7 @@ © %1$s от %2$s под лиценза %3$s Съдействайте За всичко, което се сетите: превод, промени по дизайна, изчистване на кода или много сериозни промени по кода – помощта е винаги добре дошла. Колкото повече развитие, толкова по-добре! - Направете дарение + Дарение NewPipe се разработва от доброволци, които отделят от своето време, за да предоставят най-доброто потребителско изживяване. Включете се в разработката като почерпите разработчиците с една чашка кафе, които да изпият, докато правят NewPipe още по-добро приложение. Дари Уебсайт @@ -203,7 +203,7 @@ Прочетете нашата политика за поверителност Лицензът на NewPipe Липсва стрийм плейър (можете да изтеглите VLC, за да пуснете стрийма). - Покажи съвет при натискане на фона или изскачащия бутон във видеоклипа „Подробности:“ + Покажи съвет при натискане на фона или изскачащия бутон във видеоклипа \"Подробности:“ Изтрива историята на възпроизвежданите стриймове и позицията на възпроизвеждането Не са намерени видео стриймове Не са намерени аудио стриймове @@ -519,7 +519,7 @@ Полезно при превключване към мобилни данни, въпреки че някои изтегляния не поддържат възобновяване и ще започнат отначало Срив на приложението Цветът на известието да се избира според главния цвят в миниатюрата на видеото (може да не работи на всички устройства) - Използване на ограничения режим на YouTube + Включване на \"Ограничен режим“ в YouTube YouTube предлага „ограничен режим“, чрез който можете да филтрирате потенциално съдържание за възрастни Това видео е с възрастова граница. \n @@ -557,7 +557,7 @@ Покажи индикатори за позиция на възпроизвеждане в списъци Редактирайте всяко действие за известяване по-долу, като щракнете върху него. Първите три действия (възпроизвеждане/пауза, предишно и следващо) се задават от системата и не могат да бъдат конфигурирани. Изберете жест за дясната половина на екрана на плейъра - Действие с жест на дясно + Действие с жест надясно Стартирайте основния плейър на цял екран Известия за нови видеоклипове в абонаментите Известявайте за нови видеоклипове в абонаментите @@ -713,7 +713,6 @@ Повторение Превъртане назад Напред - Споделете плейлист с подробности, като име на плейлист и заглавия на видеоклипове или като обикновен списък с URL адреси на видеоклипове Споделяне на списък с URL Изтрии всички позиции на възпроизвеждане? Позициите за възпроизвеждане са изтрити diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 0fd86b8e5..1a9463d05 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -726,4 +726,11 @@ Pista d\'àudio No Cap emissió + Notifica sobre les noves retransmissions de les subscripcions + Noves notificacions de retransmissions + Les llistes de reproducció que estan en gris ja contenen aquest element. + Desestableix la miniatura permanent + Duplicat afegit/s %d vegada/es + 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. + Semiton diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index d7b93667e..bb0a1ec7b 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1,6 +1,6 @@ - Publikováno na %1$s + Publikováno %1$s Nenalezen žádný přehrávač. Nainstalovat VLC? Instalovat Zrušit @@ -783,7 +783,7 @@ \? Odběratelé Které karty mají být zobrazeny na stránkách kanálů - Sdílet URL seznamu + Sdílet seznam adres Sdílet s názvy %1$s \n%2$s @@ -804,7 +804,6 @@ Alba Přetočení zpět Znovu přehrát - Sdílejte playlist s podrobnostmi jako je jeho název a názvy videí, nebo jako jednoduchý seznam adres videí Střední kvalita Bannery Playlisty diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 3ce9f3310..0312b3c74 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -770,7 +770,6 @@ Indlæs ikke billeder Lav kvalitet Del Playliste - Del playliste med detajler såsom playlistenavn og videotitler eller som en simpel liste over video-URL\'er Del med Titler Del URL-liste diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 48a160c9d..e782e700a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -63,7 +63,7 @@ Entschuldigung, etwas ist schiefgelaufen. Dein Kommentar (auf englisch): Live - Tippe auf die Lupe, um zu beginnen. + Tippe auf die Lupe, um zu suchen. Downloads Downloads Fehlerbericht @@ -425,7 +425,7 @@ Niemand schaut zu %s Zuschauer - %s Zuschauende + %s Zuschauer Niemand hört zu @@ -802,7 +802,6 @@ %1$s \n%2$s Wiedergabeliste teilen - Teile die Wiedergabeliste mit Details wie dem Namen der Wiedergabeliste und den Videotiteln oder als einfache Liste von Video-URLs - %1$s: %2$s %s Antwort diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 0dbbcc516..c0b09ad13 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -802,7 +802,6 @@ %1$s \n%2$s Κοινοποίηση λίστας - Μοιραστείτε τη λίστα αναπαραγωγής με λεπτομέρειες όπως το όνομα της λίστας αναπαραγωγής και τους τίτλους βίντεο ή ως μια απλή λίστα διευθύνσεων URL βίντεο - %1$s: %2$s %s απάντηση diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 28bc57358..66e2c4d10 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -616,7 +616,6 @@ Neniu filmofluo ludeblas por ekstera ludilo Filmetoj Filmetoj kiuj spektiĝis antaŭ aŭ post sia aldoniĝo al la ludlisto foriĝus.. \nĈu vi certas? Ĉi tio nemalfareblus! - Kunhavigus ludliston inkluzivante informojn kiel la nomoj de listeroj, aŭ kiel simpla listo de ligiloj Restarigi implicitajn agordojn Jes, kaj ankaŭ parte spektitajn filmetojn diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 60aa0556d..c1efd2ee7 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -816,7 +816,6 @@ %1$s \n%2$s Compartir la lista de reproducción - 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 - %1$s: %2$s %s respuesta diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 36fe778f2..a89d2846b 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -99,8 +99,8 @@ Alati Üks kord Fail - NewPipe teavitus - Teavitused NewPipe pleierile + NewPipe\'i teavitus + NewPipe\'i meediaesitaja teavitused [Tundmatu] Lülita taustale Lülita hüpikpleierile @@ -183,7 +183,7 @@ Failinimi Lõimed Viga - NewPipe allalaadimine + NewPipe\'i on allalaadimisel Üksikasjade nägemiseks toksa Palun oota… Kopeeriti lõikepuhvrisse @@ -197,18 +197,18 @@ Asendustähemärk Tähed ja numbrid Erimärgid - NewPipe rakendusest + Rakenduse teave: NewPipe Kolmanda osapoole litsentsid Rakenduse teave ja KKK Litsentsid Panusta Vaata GitHubis Anneta - Veebileht - Enama info saamiseks külasta NewPipe veebilehte. - NewPipe privaatsuspoliitika + Veebisait + Täiendava info ja uudiste lugemiseks külasta NewPipe\'i veebisaiti. + NewPipe\'i privaatsuspoliitika Loe privaatsuspoliitikat - NewPipe litsents + NewPipe\'i litsents Loe litsentsi Ajalugu Ajalugu @@ -303,11 +303,10 @@ © %1$s %2$s %3$s alla Vaba ja lihtne voogesitus Androidis. Kui sul on ideid kujunduse muutmisest, koodi puhastamisest või suurtest koodi muudatustest - abi on alati teretulnud. Mida rohkem tehtud, seda paremaks läheb! - 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. + 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. Anneta - 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. - 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. + 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. + 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. Teavita elutsüklist väljas vigadest Impordi SoundCloudi profiil trükkides URL või oma ID: \n @@ -321,8 +320,7 @@ Keri helitu koht edasi Samm Lähtesta - 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. + 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. Minimeeri, kui kasutad teisi rakendusi Tegevus lülitusel peamiselt videopleierilt teisele rakendusele — %s Pole @@ -335,7 +333,7 @@ Sündmused Fail kustutati Rakenduse värskenduse teavitus - Teavitus NewPipe uuetest versioonidest + Teavitus NewPipe\'i uuetest versioonidest Väline andmekandja pole saadaval Allalaadimine välisele SD-kaardile ei ole võimalik. Kas lähtestada allalaadimiste kataloogi asukoht\? Tõrge salvestatud vahekaaride lugemisel; kasutatakse vaikeväärtusi @@ -350,7 +348,7 @@ Nimekiri Võrgustik Auto - NewPipe värskendus on saadaval! + NewPipe\'i värskendus on saadaval! Lõpetatud Ootel peatatud @@ -453,7 +451,7 @@ %s kuulajat Teavitused video räsimise edenemise kohta - Võta kasutusele YouTube\'i „Piiratud režiim“\\ + Võta kasutusele YouTube\'i „Piiratud režiim“ Faili asukoht on muutunud või on ta kustutatud Taasesituste asukohad on kustutatud Kas kustutame kõik taasesituste asukohad\? @@ -556,7 +554,7 @@ Luba korraga vaid üks allalaadimine Piira allalaadimiste järjekorda Faili kustutamisega läks ka tööjärg kautsi - Faili töötlemisel NewPipe lõpetas töö + NewPipe lõpetas faili töötlemisel töö Lülita meedia tunneldamine välja juhul, kui esitamisel tekib must ekraan või pildi kuvamine on katkendlik. Lülita meedia tunneldamine välja Vaheta teenust, hetkel on kasutusel: @@ -609,9 +607,7 @@ Luba kiire režiim Hangi võimalusel spetsiaalsest voost Kiirvoo režiim ei paku selle kohta täiendavat teavet. - Autori konto on lõpetatud. -\nTulevikus ei saa NewPipe seda voogu laadida. -\nKas soovid tühistada selle kanali tellimuse\? + Autori konto on suletud. \nTulevikus ei saa NewPipe seda meediavoogu laadida. \nKas soovid tühistada selle kanali tellimuse? Voo \'%s\' laadimine nurjus. Via voo laadimisel Värskenda alati @@ -622,17 +618,7 @@ Android 10st alates on toetatud ainult salvestusjuurdepääsu raamistik \'Storage Access Framework\' Sinult küsitakse iga kord, kuhu alla laadimine salvestada Südamlik autor - 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. + 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. Märgi vaadatuks Kaugotsingu soovitused Kohaliku otsingu soovitused @@ -654,9 +640,9 @@ Kontrolli uuendusi Kontrolli uuendusi käsitsi Uued andmevoo kirjed - Näita „Jooksuta meediamängija kokku“ nupukest\\ + Näita „Jooksuta meediamängija kokku“ nupukest Näitab valikut meediamängija kokkujooksutamiseks - NewPipe töös tekkis viga, sellest teavitamiseks toksa + NewPipe\'i töös tekkis viga, sellest teavitamiseks toksa Jooksuta meediamängija kokku Näita veateate akent Teavitus vigadest @@ -802,7 +788,6 @@ %1$s \n%2$s Jaga esitusloendit - Jaga esitusloendit kas väga detailse teabega palade kohta või lihtsa url\'ide loendina - %1$s: %2$s Näita veel @@ -812,8 +797,7 @@ Näita vähem 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. Varundus ja taastamine - NewPipe võib aeg-ajalt automaatselt kontrollida uute versioonide olemasolu ning sind vastavalt teavitada. -\nKas sa soovid sellist võimalust kasuutada? + NewPipe võib aeg-ajalt automaatselt kontrollida uute versioonide olemasolu ning sind vastavalt teavitada. \nKas sa soovid sellist võimalust kasutada? Lähtesta seadistused Lähtesta kõik seadistused nende vaikimisi väärtusteks Seadmes pole enam piisavalt vaba ruumi @@ -822,6 +806,6 @@ \nKas sa soovid jätkata? Jah Ei - 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. + 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. täiendav diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 22ca848c0..415d2a8d3 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -772,7 +772,6 @@ Editatu beheko jakinarazpen ekintza bakoitza gainean sakatuz. Lehen hiru ekintzak (erreproduzitu/pausatu, aurrekoa eta hurrengoa) sistemarengatik ezarrita daude eta ezin dira pertsonalizatu. Atzera egin Irudiaren kalitatea - Partekatu erreprodukzio-zerrenda xehetasunekin, esate baterako, erreprodukzio-zerrendaren izena eta bideo-izenburuak edo bideo-URLen zerrenda soil gisa Aukera gehiago Iraupena Aurrera egin diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index d6a39acd6..6a252e22f 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -762,4 +762,6 @@ مدّت پسروی ؟ + پشتیبان‌گیری و بازیابی + بدون جریان زنده diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 60da52ae1..300493b52 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -757,7 +757,6 @@ Kelaa taaksepäin 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. Poistetaanko kaikki ladatut tiedostot levyltä\? - Jaa soittolista, jossa on tietoja, kuten soittolistan nimi ja videon nimi, tai yksinkertainen luettelo videoiden URL-osoitteista Keskilaatu Lataajan avatarit Prosentti diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b8af45539..48710619b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -178,7 +178,7 @@ Détails Paramètres audios Afficher l\'astuce « Maintenir pour ajouter à la file » - Affiche l’astuce lors de l’appui des boutons « Arrière-plan » ou « Flottant » sur la page de détails d’une vidéo + Affiche l’astuce lors de l’appui des boutons « Arrière-plan » ou « Flottant » sur la page de détails d’une vidéo [Inconnu] Récupération depuis l’erreur du lecteur Kiosque @@ -534,7 +534,7 @@ Créé par %s Les textes originaux des services vont être visibles dans les items Afficher la date originelle sur les items - Activer le « Mode restreint » de YouTube + Activer le « Mode restreint » de YouTube Afficher uniquement les abonnements non groupés Page des listes de lecture Aucune liste de lecture encore enregistrée @@ -669,7 +669,7 @@ Vérifier les mises à jour Nouveaux éléments du flux Faire planter le lecteur - Afficher « Faire planter le lecteur » + Afficher « Faire planter le lecteur » Montrer une option de plantage lors de l\'utilisation du lecteur Notification de rapport d\'erreur Notifications pour signaler les erreurs @@ -815,7 +815,6 @@ Avancer Rembobiner Rejouer - 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 Avatars du téléverseur 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 Lire @@ -840,4 +839,4 @@ Pas assez d\'espace disponible sur l\'appareil 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. secondaire - \ No newline at end of file + diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 4536b2f42..586e351e2 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -813,7 +813,6 @@ Encabezados Lapelas a mostrar nas páxinas das canles 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 - 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 Compartir lista de URLs 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. Pistas diff --git a/app/src/main/res/values-gu/strings.xml b/app/src/main/res/values-gu/strings.xml index cdeac0b82..637fc9bf8 100644 --- a/app/src/main/res/values-gu/strings.xml +++ b/app/src/main/res/values-gu/strings.xml @@ -23,7 +23,7 @@ પ્રથમ ક્રિયા બટન સૂચનામાં બતાવેલ વિડિઓ થંબનેલને ૧૬:૯ થી ૧:૧ સાપેક્ષ ગુણોત્તરમાં કાપો થંબનેલને ૧:૧ સાપેક્ષ ગુણોત્તરમાં કાપો - કોડી મીડિયા સેન્ટર દ્વારા વિડિઓ ચલાવવાનો વિકલ્પ દર્શાવો + કોડિ મીડિયા સેન્ટર દ્વારા વિડિઓ ચલાવવાનો વિકલ્પ દર્શાવો અનુપસ્થિત Kore અનુપ્રયોગ સ્થાપિત કરીએ? ફક્ત થોડા ઉપકરણો 2K / 4K વિડિઓઝ ચલાવી શકે છે ઉચ્ચ રીઝોલ્યુશન બતાવો @@ -40,7 +40,7 @@ પૃષ્ઠભૂમિ ટેબ પસંદ કરો બુકમાર્ક કરેલ પ્લેલિસ્ટ્સ - સબ્સ્ક્રિપ્શન્સ + લવાજમઓ માહિતી બતાવો સબ્સ્ક્રિપ્શન અપડેટ કરી શકાયું નથી સબ્સ્ક્રિપ્શન બદલી શકાયું નહીં @@ -72,13 +72,15 @@ ઠીક છે હા ના - વલણમાં છે + વલણમાંનાં આપોઆપ કતારબદ્ધતા પ્લેયરને ક્રેશ કરો ઇતિહાસ કોટિથી ચલાવો - કોટિથી ચલાવવાનો વિકલ્પ દેખાટો + કોડિથી ચલાવવાનો વિકલ્પ દેખાડો ડાઉનલોડ કરો આપમેળે ચલાવો નવું શું છે - \ No newline at end of file + ડાઉનલોડ્સ + ડાઉનલોડ્સ + diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 7f68bcbfd..b4c16a473 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -828,7 +828,6 @@ %1$s \n%2$s שיתוף רשימת נגינה - שיתוף רשימת נגינה עם פרטים כגון שם רשימת נגינה וכותרות סרטונים או כרשימה פשוטה של כתובות סרטונים - %1$s: %2$s להציג עוד להציג פחות @@ -850,4 +849,5 @@ \nלהמשיך? אין מספיק מקום פנוי במכשיר ההגדרות בייצוא המיובא משתמשות בתסדיר פגיע שהוצא משימוש מאז NewPipe 0.27.0. יש לוודא שהייצוא המיובא הוא ממקור מהימן, ועדיף להשתמש רק בייצוא שהושג מ־NewPipe 0.27.0 ומעלה בעתיד. תמיכה בייבוא הגדרות בתסדיר פגיע זה תוסר בקרוב לחלוטין, ואז גרסאות ישנות של NewPipe לא יוכלו לייבא עוד הגדרות של ייצוא מגרסאות חדשות. + משני diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 40512aa9f..890ef1347 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1,7 +1,7 @@ %1$s पे प्रकाशित हुआ - स्ट्रीमिंग के लिए प्लेयर नहीं मिला। क्या आप वीएलसी इंस्टॉल करना चाहेंगे\? + स्ट्रीमिंग के लिए प्लेयर नहीं मिला। क्या आप VLC इंस्टॉल करना चाहेंगे? इंस्टॉल करें ब्राउज़र में खोलें पॉपअप मोड में खोलें @@ -183,7 +183,7 @@ कतार में जोड़ने के लिए दबाकर रखें बैकग्राउंड में चलाना शुरू करें पॉपअप में चलाना शुरू करें - स्ट्रीमिंग करने के लिए प्लेयर नहीं मिला (आप इसे चलाने के लिए वीएलसी प्लेयर इंस्टॉल कर सकते हैं)। + स्ट्रीमिंग करने के लिए प्लेयर नहीं मिला (आप इसे चलाने के लिए VLC प्लेयर इंस्टॉल कर सकते हैं)। स्ट्रीम फाइल डाउनलोड करें जानकारी दिखाएं बुकमार्क की गई प्लेलिस्टें @@ -802,7 +802,6 @@ %1$s \n%2$s प्लेलिस्ट साझा करें - प्लेलिस्ट को प्लेलिस्ट नाम और वीडियो शीर्षक जैसे विवरण के साथ या वीडियो यूआरएल की एक सरल सूची के रूप में साझा करें - %1$s: %2$s %s जवाब diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 5e00a9cc7..960106e0d 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -424,8 +424,8 @@ Želiš li izbrisati ovu grupu\? Nova Uvijek aktualiziraj - Uključi brzi način - Isključi brzi način + Uključi brzi modus + Isključi brzi modus Memorija uređaja je popunjena Najomiljeniji Pritisni „Gotovo” kad je riješeno @@ -639,7 +639,7 @@ Mapa za preuzimanje još nije postavljena, odaberi standardnu mapu za preuzimanje Komentari su isključeni Označi kao pogledano - Način rada brzog feeda ne pruža više informacija o ovome. + Brzi modus feeda ne pruža više informacija o ovome. Interno Privatnost 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. @@ -798,7 +798,6 @@ Srednja kvaliteta Visoka kvaliteta \? - Dijeli playlistu s detaljima kao što su ime playliste i naslovi videa ili kao jednostavan popis URL-ova videa Dijeli s naslovima Dijeli popis URL-ova – %1$s: %2$s @@ -831,4 +830,7 @@ Obnavljanje svih postavki odbacit će sve tvoje postavljene postavke i aplikacija će se ponovo pokrenuti. \n \nStvarno želiš nastaviti? + Uvijek koristi ExoPlayer postavku zaobilaženja videa za izlaznu površinu + Kartice za dohvaćanje prilikom aktualiziranja feeda. Ova opcija nema učinka ako se kanal aktualizira pomoću brzog modusa. + sekundarno diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 68dce5bf1..fb04aa724 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -324,7 +324,7 @@ Alkalmazásfrissítés értesítése Fájl törölve Frissítések - Tipp megjelenítése a háttér vagy a felugró gomb megnyomásakor a videó „Részletek:\\” lehetőségnél + Tipp megjelenítése a háttér vagy a felugró gomb megnyomásakor a videó „Részletek:” lehetőségnél Automatikus lejátszás Adatok törlése Lejátszási pozíciók megjelenítése a listákban @@ -534,7 +534,7 @@ Értesítések a videók ujjlenyomatkészítési folyamatához Videó ujjlenyomat-készítési értesítése A YouTube biztosít egy „Korlátozott módot”, amely elrejti a lehetséges felnőtteknek szóló tartalmat - A YouTube „Korlátozott mód\\” bekapcsolása + A YouTube „Korlátozott mód” bekapcsolása A példány már létezik A példány érvényesítése nem sikerült Adja meg a példány webcímét @@ -657,7 +657,7 @@ A szolgáltatásokból származó eredeti szövegek láthatók lesznek a közvetítési elemeken Lejátszó összeomlasztása Képjelölők megjelenítése - A „lejátszó összeomlasztása\\” lehetőség megjelenítése + A „Lejátszó összeomlasztása” lehetőség megjelenítése Megjeleníti az összeomlasztási lehetőséget a lejátszó használatakor Hangmagasság megtartása (torzítást okozhat) Frissítések keresése @@ -795,7 +795,6 @@ Magas minőségű \? Lejátszási lista megosztása - 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 Megosztás címekkel %1$s \n%2$s diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml index 365bfe9ea..3be44ba73 100644 --- a/app/src/main/res/values-hy/strings.xml +++ b/app/src/main/res/values-hy/strings.xml @@ -1,6 +1,6 @@ - Սեղմեք որոնման կոճակը որ սկսել + Սեղմեք խոշորացույցը որ սկսեք Որոնել Բեռնված Բեռնված @@ -228,7 +228,7 @@ Դասավորել Գամված մեկնաբանություն Հաշիվը կասեցված է - + Ալբոմներ Այո Ոչ @@ -241,4 +241,6 @@ Ալիքներ Ուղիղ Անհայտ - \ No newline at end of file + Նկատի ունե՞ս «%1$s» + Բարձրություն + diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index d7eb334a8..4117e3a18 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -138,7 +138,7 @@ Melanjutkan akhir dari antrean pemutaran (tak berulang) dengan menambahkan video terkait Simpan daftar video yang telah ditonton Tip \"Tahan untuk menambahkan\" - Tampilkan tip ketika menekan tombol latar belakang atau popup di dalam video \"Detail:\\ + Tampilkan tip ketika menekan tombol latar belakang atau popup di dalam video \"Detail:\" Lokasi Konten Pemutar Perilaku @@ -508,7 +508,7 @@ \nJadi pilihlah yang sesuai yang Anda inginkan: kecepatan atau kelengkapan informasi. Teks asli dari layanan akan ditampilkan di dalam video Tampilkan waktu yang lalu sebenarnya pada item - Aktifkan \"Mode Terbatas\\ + Aktifkan \"Mode Terbatas\" Oleh %s Dibuat oleh %s Thumbnail avatar channel @@ -788,7 +788,6 @@ %1$s \n%2$s Bagikan Daftar Putar - Bagikan daftar putar dengan detail seperti nama daftar putar dan judul video atau sebagai daftar video URL yang sederhana Panji - %1$s: %2$s Sentuh untuk menyunting tindakan notifikasi di bawah. Tiga tindakan pertama (mainkan/jeda, sebelumnya dan selanjutnya) disetel oleh sistem dan tidak bisa dikustomisasi. diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index 6cdb831b4..c5d5d02c6 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -607,7 +607,7 @@ Spilunarstöður í listum Sýna spilunarstöður í listum Sýna ábendinguna „Haltu niðri til að bæta við spilunarröð“ - Sýna ábendingu þegar ýtt er á bakgrunninn eða sprettihnappinn á myndskeiðinu í „Nánar:\\ + Sýna ábendingu þegar ýtt er á bakgrunninn eða sprettihnappinn á myndskeiðinu í „Nánar:\" Óþekkt slóð. Opna með öðru forriti\? Veldu uppáhalds PeerTube tilvik þín Þú mátt finna tilviki á %s @@ -790,7 +790,6 @@ Losa varanlega smámynd 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. Flipar sem á að sækja við uppfærslu þessa streymis. Þetta hefur engin áhrif ef rás er uppfærð með hraðstreymisham. - Deildu spilunarlista með atriðum eins og heiti spilunarlistans og titlum myndskeiða eða sem einföldum lista yfir slóðir á myndskeið Nota varaeiginleika ExoPlayer-afkóðarans Vegna takmarkana í ExoPlayer-spilaranum var tímalengd hoppa sett á %d sekúndur 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. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index ca77b363d..6767f8572 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -815,7 +815,6 @@ %1$s \n%2$s Condividi playlist - Condividi la playlist con dettagli come il suo nome e i titoli video o come un semplice elenco di URL video - %1$s: %2$s %s risposta diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c7260752b..64e415559 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -789,7 +789,6 @@ %1$s \n%2$s プレイリストを共有 - プレイリスト名やビデオタイトルなどの詳細を含むプレイリスト、またはビデオURLのみのシンプルなリストとしてプレイリストを共有します - %1$s: %2$s %sの返信 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 49ae4854f..0bd08a5e4 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -774,7 +774,6 @@ 되감기 다시 재생 피드를 업데이트할 때 가져올 탭입니다. 빠른 모드를 사용하여 채널을 업데이트하는 경우 이 옵션은 효과가 없습니다. - 재생목록 이름, 동영상 제목 등의 세부정보 또는 간단한 동영상 URL 목록으로 재생목록을 공유하세요 중간 품질 업로더 아바타 배너 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 6eb51bfde..40fe8e8ad 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -823,7 +823,6 @@ Vaizdo įrašai Takeliai 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 - Dalintis grojaraščiu su tokia informacija kaip grojaraščio pavadinimas ir vaizdo įrašo pavadinimas arba paprastas vaizdo įrašų nuorodų sąrašas Dalintis su pavadinimais Dalintis grojaraščiu Dalintis nuorodų sąrašu diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 9415ef9f2..9d99c14d6 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -822,7 +822,6 @@ %1$s %2$s Skaņdarbi Īsie video - Kopīgot atskaņošanas saraksta nosaukumu un to video nosaukumus vai tikai atskaņošanas sarakstā iekļauto video URL saites Kopīgot atskaņošanas sarakstu Kopīgot nosaukumus 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. diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 92d265758..ba41b730b 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -713,7 +713,6 @@ Инстанцата не може да биде потврдена Изберете го квалитетот на сликите и дали воопшто да се вчитуваат слики, за да го намалите користењето на интернет и меморија. Промените го чистат кешот на сликите (анг. image cache), како и во меморијата, така и на дискот — %s - %1$s: %2$s - Споделете ја плејлистата со подробности (детали), како името на плејлистата и насловите на видеата или како едноставен список од линковите на видеата Ништо Поставките во извезениот фајл кој се увезува користат ранлив формат кој повеќе не е поддржан од NewPipe 0.27.0. Уверете се дека извезениот фајл кој се увезува е од доверлив извор и претпочитајте во иднина да користите само износи добиени од NewPipe 0.27.0 или понова верзија. Поддршката за увезување поставки од овој ранлив формат наскоро ќе биде целосно укината и тогаш старите верзии на NewPipe повеќе нема да можат да увезуваат поставки од износи од новите верзии. Побарај потврда пред чистење на редоследот diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 9864051d8..bb0527655 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -503,7 +503,6 @@ Mutu rendah Mutu sederhana Mutu tinggi - Kongsikan senarai main dengan butiran seperti nama senarai main dan tajuk video atau sebagai senarai ringkas URL video Kongsi dengan Tajuk Kongsi senarai URL Tunjukkan lagi diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index ed4bcb84d..00f095e39 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -802,7 +802,6 @@ %1$s \n%2$s Afspeellijst delen - Deel afspeellijst met details zoals afspeellijstnaam en videotitels of als een eenvoudige lijst met video-URL\'s - %1$s: %2$s %s reactie diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index 7855b8f22..92e6cff37 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -802,7 +802,6 @@ %1$s \n%2$s ଖେଳ ତାଲିକା ସହଭାଗ କରନ୍ତୁ - ପ୍ଲେ-ଲିଷ୍ଟ ନାମ ଏବଂ ଭିଡିଓ ଶୀର୍ଷକ କିମ୍ବା ଭିଡିଓ URLଗୁଡ଼ିକର ଏକ ସରଳ ତାଲିକା ଭାବରେ ବିବରଣୀ ସହିତ ପ୍ଲେ-ଲିଷ୍ଟ ଅଂଶୀଦାର କରନ୍ତୁ - %1$s: %2$s ଅଧିକ ଦର୍ଶାନ୍ତୁ ଏହା ଉପରେ ଟ୍ୟାପ କରି ନିମ୍ନରେ ଦିଆଯାଇଥିବା ପ୍ରତ୍ୟେକ ବିଜ୍ଞପ୍ତି କାର୍ଯ୍ୟକୁ ସମ୍ପାଦନ କରନ୍ତୁ । ପ୍ରଥମ ତିନୋଟି କାର୍ଯ୍ୟ (ଖେଳ/ବିରତି, ପୂର୍ବବର୍ତ୍ତୀ ଏବଂ ପରବର୍ତ୍ତୀ) ତନ୍ତ୍ର ଦ୍ୱାରା ସେଟ କରାଯାଇଥାଏ ଏବଂ ଏହାକୁ ଇଚ୍ଛାରୂପଣ କରାଯାଇପାରିବ ନାହିଁ । diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index b40727c2a..3d3975251 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -1,7 +1,7 @@ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਵੱਡਦਰਸ਼ੀ ਸ਼ੀਸ਼ੇ \'ਤੇ ਟੈਪ ਕਰੋ। - %1$s ਨੂੰ ਪ੍ਰਕਾਸ਼ਿਤ ਕੀਤੀ ਗਈ + %1$s ਨੂੰ ਪ੍ਰਕਾਸ਼ਿਤ ਹੋਇਆ ਸਟ੍ਰੀਮਿੰਗ ਲਈ ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ। ਕੀ ਤੁਸੀਂ VLC ਸਥਾਪਤ ਕਰਨਾ ਚਾਹੋਗੇ? ਸਟ੍ਰੀਮਿੰਗ ਲਈ ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ (ਤੁਸੀਂ ਇਸਨੂੰ ਚਲਾਉਣ ਲਈ VLC ਪਲੇਅਰ ਇੰਸਟਾਲ ਕਰ ਸਕਦੇ ਹੋ)। ਇੰਸਟਾਲ ਕਰੋ @@ -15,9 +15,9 @@ ਸੈਟਿੰਗਾਂ ਕੀ ਤੁਹਾਡਾ ਮਤਲਬ ਸੀ \"%1$s\"\? ਦੇ ਨਾਲ ਸਾਂਝਾ ਕਰੋ - ਬਾਹਰੀ ਵੀਡੀਓ ਪਲੇਅਰ ਵਰਤੋ + ਬਾਹਰੀ ਵੀਡੀਓ ਪਲੇਅਰ ਦੀ ਵਰਤੋਂ ਕਰੋ ਕੁਝ ਰੈਜ਼ੋਲਿਊਸ਼ਨਾਂ \'ਤੇ ਆਵਾਜ਼ ਹਟ ਸਕਦੀ ਹੈ - ਬਾਹਰੀ ਆਡੀਓ ਪਲੇਅਰ ਵਰਤੋ + ਬਾਹਰੀ ਆਡੀਓ ਪਲੇਅਰ ਦੀ ਵਰਤੋਂ ਕਰੋ ਸਬਸਕ੍ਰਾਈਬ ਕਰੋ ਸਬਸਕ੍ਰਾਈਬ ਹੈ ਚੈਨਲ ਅਨ-ਸਬਸਕ੍ਰਾਈਬ ਹੋਇਆ @@ -27,7 +27,7 @@ ਸਬਸਕ੍ਰਿਪਸ਼ਨਾਂ ਬੁੱਕਮਾਰਕ ਕੀਤੀਆਂ ਪਲੇਲਿਸਟਾਂ ਨਵਾਂ ਕੀ ਹੈ - ਬੈਕਗ੍ਰਾਊਂਡ ਆਡੀਓ + ਬੈਕਗ੍ਰਾਊਂਡ ਪੌਪ-ਅਪ ਵਿੱਚ ਸ਼ਾਮਿਲ ਕਰੋ ਵੀਡੀਓ ਲਈ ਡਾਊਨਲੋਡ ਫ਼ੋਲਡਰ @@ -52,7 +52,7 @@ ਗੂੜ੍ਹਾ ਕਾਲ਼ਾ ਪੌਪ-ਅਪ ਦਾ ਆਕਾਰ ਅਤੇ ਸਥਿਤੀ ਯਾਦ ਰੱਖੋ - ਪੌਪ-ਅਪ ਦਾ ਆਖਰੀ ਅਕਾਰ ਅਤੇ ਸਥਿਤੀ ਯਾਦ ਰੱਖੋ + ਪੌਪ-ਅਪ ਦਾ ਆਖਰੀ ਆਕਾਰ ਅਤੇ ਸਥਿਤੀ ਯਾਦ ਰੱਖੋ ਤੇਜ਼ ਤੇ ਅਣਸਟੀਕ ਭਾਲ ਦੀ ਵਰਤੋਂ ਕਰੋ ਅਣਸਟੀਕ ਭਾਲ ਨਾਲ ਪਲੇਅਰ ਘੱਟ ਸਟੀਕਤਾ ਦੇ ਪਰ ਅਧਿਕ ਤੇਜ਼ੀ ਨਾਲ ਵੀਡੀਓ ਸਥਿੱਤੀਆਂ ਦੀ ਤਲਾਸ਼ ਕਰ ਸਕਦਾ ਹੈ । ਇਸ ਨਾਲ ਅੱਗੇ-ਪਿੱਛੇ 5, 15 ਜਾਂ 25 ਸਕਿੰਟ ਲਿਜਾਣਾ ਕੰਮ ਨਹੀਂ ਕਰਦਾ ਹੈ ਚਿੱਤਰ ਕੈਸ਼ ਮਿਟਾਇਆ ਗਿਆ @@ -101,8 +101,8 @@ ਨਿਊਪਾਈਪ ਨੋਟੀਫਿਕੇਸ਼ਨ ਨਿਊਪਾਈਪ ਦੇ ਪਲੇਅਰ ਦੇ ਲਈ ਨੋਟੀਫਿਕੇਸ਼ਨ [ਅਣਜਾਣ] - ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚਲਾਓ - ਪੌਪ-ਅਪ ਵਿੱਚ ਚਲਾਓ + ਬੈਕਗ੍ਰਾਊਂਡ ਮੋਡ ਵਿੱਚ ਚਲਾਓ + ਪੌਪ-ਅਪ ਮੋਡ ਵਿੱਚ ਚਲਾਓ ਮੇਨ ਤੇ ਚਲਾਓ ਡਾਟਾਬੇਸ ਆਯਾਤ ਕਰੋ ਡਾਟਾਬੇਸ ਨਿਰਯਾਤ ਕਰੋ @@ -316,7 +316,7 @@ ਕੋਈ ਸੀਮਾ ਨਹੀਂ ਮੋਬਾਈਲ ਡਾਟਾ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਨੂੰ ਸੀਮਿਤ ਕਰੋ ਐਪ ਬਦਲਦੇ ਸਮੇਂ ਉਸਨੂੰ ਮਿਨੀਮਾਈਜ਼ ਕਰੋ - ਮੇਨ ਵੀਡੀਓ ਪਲੇਅਰ ਤੋਂ ਦੂਜੇ ਐਪ \'ਤੇ ਜਾਣ ਵੇਲ਼ੇ ਕਾਰਵਾਈ — %s + ਮੇਨ ਵੀਡੀਓ ਪਲੇਅਰ ਤੋਂ ਦੂਜੇ ਐਪ \'ਤੇ ਜਾਣ ਵੇਲੇ ਕਾਰਵਾਈ — %s ਕੋਈ ਨਹੀਂ ਬੈਕਗ੍ਰਾਊਂਡ ਪਲੇਅਰ ਵਿੱਚ ਬਦਲੋ ਪੌਪ-ਅਪ ਪਲੇਅਰ ਵਿੱਚ ਬਦਲੋ @@ -466,7 +466,7 @@ ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਦੀ ਕੋਈ ਵੀ ਐਪ ਇਸ ਨੂੰ ਖੋਲ੍ਹ ਨਹੀਂ ਸਕਦੀ ਚੈਪਟਰ ਹਾਲੀਆ - ਥੰਮਨੇਲ ਨੂੰ ਤਾਲਾਬੱਧ ਸਕਰੀਨ ਦੇ ਪਿਛੋਕੜ ਅਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ ਦੋਵਾਂ ਲਈ ਵਰਤੋ + ਥੰਮਨੇਲ ਨੂੰ ਲਾਕ ਸਕਰੀਨ ਦੇ ਬੈਕਗ੍ਰਾਊਂਡ ਅਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ ਦੋਵਾਂ ਲਈ ਵਰਤੋ ਥੰਮਨੇਲ ਵਿਖਾਓ ਪਲੇਲਿਸਟ ਪੰਨਾ %s ਦੁਆਰਾ @@ -802,7 +802,6 @@ %1$s \n%2$s ਪਲੇਲਿਸਟ ਸਾਂਝੀ ਕਰੋ - ਪਲੇਲਿਸਟ ਨੂੰ ਪਲੇਲਿਸਟ ਨਾਮ ਅਤੇ ਵੀਡੀਓ ਸਿਰਲੇਖ ਜਿਹੇ ਵੇਰਵਿਆਂ ਸਮੇਤ ਜਾਂ ਵੀਡੀਓ URL ਦੀ ਇੱਕ ਸਰਲ ਸੂਚੀ ਦੇ ਰੂਪ ਵਿੱਚ ਸਾਂਝਾ ਕਰੋ - %1$s: %2$s %s ਜਵਾਬ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 21cd2609c..e260cc887 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -823,7 +823,6 @@ %1$s \n%2$s Udostępnij playlistę - Udostępnij playlistę ze szczegółami, takimi jak nazwa playlisty i tytuły wideo, lub jako prostą listę adresów URL wideo. – %1$s: %2$s %s odpowiedź diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 7e5761a63..e88e27a18 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -174,7 +174,7 @@ Top 50 Novos e tendências Mostrar dica \"Segure para pôr na fila\" - Mostra dica ao tocar no botão segundo plano ou Popup em \"Detalhes:\\ do vídeo + Mostra dica ao tocar no botão segundo plano ou popup em \"Detalhes:\" do vídeo Reproduzir tudo Não é possível reproduzir este vídeo Ocorreu um erro irrecuperável na reprodução @@ -528,7 +528,7 @@ Remover assistidos Textos originais dos serviços serão visíveis nos itens de transmissão Mostrar tempo original nos itens - Ativar o \"Modo Restrito\\ do YouTube + Ativar o \"Modo Restrito\" do YouTube Por %s Criado por %s Foto de perfil do canal @@ -789,8 +789,9 @@ Ao vivo Qualidade da imagem \? - Compartilhar URL - Compartilhar com título + Compartilhar URLs + Compartilhar com títulos + Compartilhar como playlist temporária do YouTube %1$s \n%2$s Alternar orientação da tela @@ -806,7 +807,6 @@ Avançar Retroceder Repetir - 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 Qualidade média Fotos de perfil do autor - %1$s: %2$s diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 02a1c4f21..15c174cd8 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -802,7 +802,6 @@ Recuar Repetição Separadores a obter ao atualizar o feed. Esta opção não tem efeito se um canal for atualizado utilizando o modo rápido. - 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 Média qualidade Avatar dos publicadores Bandeiras diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 1321123db..6d53e9fd9 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -25,7 +25,7 @@ Formato padrão de áudio Descarregar Mostrar vídeos \'Seguintes\' e \'Semelhantes\' - URL não suportado + URL não suportada Idioma padrão para conteúdo Vídeo e áudio Reproduzir vídeo, duração: @@ -281,7 +281,7 @@ Limpar histórico de visualizações Continuar (sem repetição) a fila de reprodução anexando um vídeo relacionado Mostrar dica \"Toque longo para colocar na fila\" - Mostrar dica ao premir em segundo plano ou no botão \"Detalhes\" da janela popup + Mostrar dica ao premir em segundo plano ou no botão \"Detalhes\" da janela popup: Canais Listas de reprodução Faixas @@ -551,7 +551,7 @@ Nunca A carregar A fila de reprodução atual será substituída - URL não reconhecido. Abrir com outra aplicação\? + URL não reconhecida. Abrir com outra aplicação? Colocar na fila automaticamente Baralhar Apenas em Wi-Fi @@ -815,7 +815,6 @@ Canais Vídeo anterior Direto - 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 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 Mostrar mais diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 8d8861397..5b1238b0c 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -789,7 +789,6 @@ 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. Selectați o coloană sonoră cu descrieri pentru persoane cu deficiențe vizuale, dacă este disponibilă Acțiunea gestului din stânga - Distribuiți playlistul cu detalii precum numele playlistului și titlurile videourilor sau ca o simplă listă de URL-uri a videourilor Calitate medie Preferați audioul descriptiv 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ă diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4b504936a..a212d9888 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -190,7 +190,7 @@ Вы подписаны Подписка отменена Показать подсказку «Зажмите, чтобы добавить» - Показывать подсказку при нажатии на фон или всплывающую кнопку в видео \"Подробнее:\\ + Показывать подсказку при нажатии \"В фоне\" или на всплывающую кнопку \"Подробнее:\" в видео [Неизвестно] Восстановление после ошибки плеера Зажмите, чтобы добавить в очередь @@ -535,7 +535,7 @@ Удалить просмотренные видео\? Отображать сообщённое сервисом время с момента публикации Исходное время публикации - Включите на YouTube \"Ограниченный режим\\ + Включить \"Ограниченный режим\" YouTube От %s Создано %s Миниатюра значка канала @@ -688,7 +688,7 @@ Уведомлять Вы подписались на канал Переключить все - Шоу \"Разбей игрока\\ + Показывать \"Сбой плеера\" Показать функцию вызова сбоя при работе плеера Вызвать сбой плеера Уведомление отчёта об ошибке @@ -718,7 +718,7 @@ Нет аудиопотоков, доступных внешним плеерам Выберите качество для внешних плееров Неизвестное качество - Размер предварительной загрузки + Размер интервала загрузки при воспроизведении Ответы на частые вопросы Если у вас возникли проблемы с использованием приложения, обязательно ознакомьтесь с ответами на распространённые вопросы! Посмотреть на веб-сайте @@ -806,7 +806,6 @@ Перемотать назад Повторить Получаемые вкладки при обновлении ленты. Эта функция не применяется, если канал обновляется с помощью быстрого режима. - Поделиться подборкой с подробностями, такими как название подборки и названия видео, или просто списком URL видео Среднее качество Загрузчик аватаров Баннеры diff --git a/app/src/main/res/values-ryu/strings.xml b/app/src/main/res/values-ryu/strings.xml index 141ea2f35..1a1383015 100644 --- a/app/src/main/res/values-ryu/strings.xml +++ b/app/src/main/res/values-ryu/strings.xml @@ -802,7 +802,6 @@ %1$s \n%2$s プレイリストちゅーゆーいん - プレイリストめいてぃがろービデオタイトルんでーぬしょうさいくくむるプレイリスト、あらんでぃビデオURLぬみぬシンプルやるリストとぅしてぃプレイリストちゅーゆーいんさびーん - %1$s: %2$s %sぬへんしん diff --git a/app/src/main/res/values-sat/strings.xml b/app/src/main/res/values-sat/strings.xml index f7ff02d59..c1a5aacca 100644 --- a/app/src/main/res/values-sat/strings.xml +++ b/app/src/main/res/values-sat/strings.xml @@ -676,7 +676,6 @@ ᱞᱟᱯᱷᱟᱝ ᱥᱤᱠᱷᱱᱟ. ᱛᱟᱞᱢᱟ ᱥᱤᱠᱷᱱᱟᱹᱛ ᱩᱥᱩᱞ ᱥᱤᱠᱷᱱᱟᱹᱛ - ᱯᱷᱟᱭᱞᱤᱥᱴ ᱧᱩᱛᱩᱢ ᱟᱨ ᱵᱷᱤᱰᱤᱭᱳ ᱧᱩᱛᱩᱢ ᱞᱮᱠᱟᱛᱮ ᱟᱨᱵᱟᱝ ᱵᱷᱤᱰᱤᱭᱳ URL ᱨᱮᱱᱟᱜ ᱢᱤᱫ ᱞᱮᱠᱟᱱ ᱞᱤᱥᱴᱤ ᱞᱮᱠᱟᱛᱮ ᱴᱷᱟᱶ ᱮᱢ ᱢᱮ URL ᱛᱟᱹᱞᱠᱟᱹ ᱥᱟᱯᱲᱟᱣ - %1$s: %2$s ᱱᱚᱴᱤᱯᱷᱤᱠᱮᱥᱚᱱ ᱨᱮ ᱑᱖:᱙ ᱠᱷᱚᱱ ᱑:᱑ ᱟᱥᱯᱮᱠᱴ ᱚᱱᱩᱯᱟᱹᱛ ᱨᱮ ᱵᱷᱤᱰᱤᱭᱳ ᱛᱷᱚᱢᱵᱱᱮᱞ ᱜᱮᱫᱽ ᱢᱮ diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 4d6f8c80a..fc3558ea3 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -786,7 +786,6 @@ Torra in segus Torra a reprodùere Ischedas de recuperare cando agiornas sa fonte. Custa optzione non tenet efetu si unu canale benit agiornadu impreende sa modalidade lestra. - 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 Calidade mesana Avatars de su carrigadore Insignas diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index d1498cdd8..88f1b13f5 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -821,7 +821,6 @@ %s odpovede %s odpovedí - Zdieľajte playlist s podrobnosťami, ako je jeho názov a názvy videí, alebo ako jednoduchý zoznam URL adries videí - %1$s: %2$s %1$s \n%2$s diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 3e53afea7..e212a480b 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -815,7 +815,6 @@ %1$s \n%2$s Дели плејлисту - Делите плејлисту са детаљима, као што су назив плејлисте и наслови видео снимака или као једноставна листа URL адреса видео снимака -%1$s: %2$s %s одговор diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 533bc6df8..de74c27f7 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -91,7 +91,7 @@ Återuppta uppspelning Fortsätt uppspelning efter avbrott (t.ex. telefonsamtal) Visa \"Håll för att köa\"-tips - Visa tips när bakgrunds- eller popup-knappen trycks på sidan för video \"Detaljer:\\ + Visa tips när bakgrunds eller popup-knappen trycks på sidan för video \"Detaljer:\" Spelare Beteende Historik och cacheminne @@ -802,7 +802,6 @@ Uppladdarens visningsbilder Banderoller - %1$s: %2$s - Dela spellistan med detaljer så som spellistans namn och video-titlarna eller som en enkel lista med URL till videorna 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 Visa mer diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 958df593e..0056f7416 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -470,7 +470,6 @@ முன்னோக்கி பட தகுதி உயர் தகுதி - பிளேலிச்ட் பெயர் மற்றும் வீடியோ தலைப்புகள் போன்ற விவரங்களுடன் அல்லது வீடியோ முகவரி களின் எளிய பட்டியலாக பிளேலிச்ட்டைப் பகிரவும் மேலும் விருப்பங்கள் காலம் முன்னாடி diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 1816fa212..bcbbca0a4 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -1,6 +1,6 @@ - แตะที่ปุ่ม \"ค้นหา\" เพื่อเริ่มต้น + แตะที่สัญลักษณ์แว่นขยายเพื่อเริ่มต้น เผยแพร่เมื่อ %1$s ไม่พบแอปที่สามารถสตรีมสื่อวีดีโอได้ คุณต้องการติดตั้ง VLC หรือไม่\? ไม่พบแอปที่สามารถสตรีมสื่อวีดีโอได้ (คุณสามารถติดตั้ง VLC เพื่อดูวีดีโอ) @@ -12,33 +12,33 @@ ดาวน์โหลด ดาวน์โหลดไฟล์สตรีม ค้นหา - ตั้งค่า + การตั้งค่า หรือคุณหมายถึง \"%1$s\"\? แชร์ด้วย ใช้เครื่องเล่นวีดิโอภายนอก - ใช้แอปเล่นเสียงภายนอก + ใช้เครื่องเล่นเสียงภายนอก ติดตาม ติดตามแล้ว ยกเลิกการติดตาม ยกเลิกการติดตามช่องแล้ว ไม่สามารถเปลี่ยนสถานะการติดตามได้ - ไม่สามารถอัปเดตการติดตาม + ไม่สามารถอัพเดทการติดตาม แสดงข้อมูล การติดตาม เพลย์ลิสต์ที่เก็บไว้ เลือกแท็บ มีอะไรใหม่ - พื้นหลัง - ป๊อปอัพ + เล่นในพื้นหลัง + พ็อปอัพ เพิ่มไปยัง - เส้นทางการดาวน์โหลดวิดีโอ - เส้นทางในการจัดเก็บวิดีโอที่ดาวน์โหลดมา - เลือกเส้นทางการดาวน์โหลดสำหรับไฟล์วิดีโอ - โฟลเดอร์ที่ดาวน์โหลดเสียง + โฟลเดอร์ดาวน์โหลดของวิดีโอ + ไฟล์วิดีโอที่ดาวน์โหลดไว้จะถูกเก็บที่นี่ + เลือกโฟลเดอร์ดาวน์โหลดสำหรับไฟล์วิดีโอ + โฟลเดอร์ดาวน์โหลดของเสียง ไฟล์เสียงที่ดาวน์โหลดไว้จะถูกเก็บไว้ที่นี่ - เลือกเส้นทางการดาวน์โหลดสำหรับไฟล์เสียง + เลือกโฟลเดอร์ดาวน์โหลดสำหรับไฟล์เสียง ความละเอียดเริ่มต้น - ความละเอียดเริ่มต้นในโหมดป๊อปอัพ + ความละเอียดเริ่มต้นในโหมดพ็อปอัพ แสดงความละเอียดที่สูงขึ้น เฉพาะบางอุปกรณ์ที่รองรับการเล่นวิดีโอ 2K/4K เปิดด้วย Kodi @@ -349,7 +349,7 @@ หยุดชั่วคราวเมื่อเปลี่ยนเป็นข้อมูลมือถือ การดาวน์โหลดที่ไม่สามารถหยุดพักได้จะเริ่มต้นใหม่ ปิด - บางความละเอียดอาจไม่มีเสียง + ลบเสียงสำหรับบางความละเอียดหน้าจอ แคช metadate ถูกลบแล้ว เล่นต่อหลังจากการขัดจังหวะ เล่นต่อ @@ -361,13 +361,55 @@ เปิดด้วย ทำเครื่องหมายว่าดูแล้ว ตกลง - ปุ่มการกระทำที่สี่ - ปุ่มการกระทำแรก - ปุ่มการกระทำที่สาม - ปุ่มการกระทำที่ห้า - แก้ไขการกระทำของการแต่การแจ้งเตือนด้วยการแตะไปที่มัน เลือกสามรายการที่จะแสดงในการแจ้งเตือนในการแจ้งเตือนแบบกระทัดรัดโดยใช้ปุ่มกาเครื่องหมายทางขวา - ครอบตัดตัวอย่างภาพเป็นอัตราส่วน 1:1 - ครอบตัดตัวอย่างภาพที่แสดงในการแจ้งเตือนจากอัตราส่วน 16:9 เป็น 1:1 - ทำเครื่องเล่นพัง - ปุ่มการกระทำรอง + ปุ่มคำสั่งที่สี่ + ปุ่มคำสั่งแรก + ปุ่มคำสั่งที่สาม + ปุ่มคำสั่งที่ห้า + แก้ไขคำสั่งของการแต่การแจ้งเตือนด้วยการแตะไปที่มัน เลือกสามรายการที่จะแสดงในการแจ้งเตือนในการแจ้งเตือนแบบกระทัดรัดโดยใช้ปุ่มกาเครื่องหมายทางขวา + ตัดหน้าปกวิดีโอเป็นอัตราส่วน 1:1 + ตัดหน้าปกวิดีโอที่แสดงในการแจ้งเตือนจากอัตราส่วน 16:9 เป็น 1:1 + เครื่องเล่นวิดีโอแครช + ปุ่มคำสั่งที่สอง + โหมดกลางคืน + เลือกเสียงต้นฉบับโดยไม่คำนึงถึงภาษาที่ใช้ + ปิดเพื่อซ่อนคำอธิบายของวิดีโอและข้อมูลเพิ่มเติมอื่นๆ + ชอบเสียงต้นฉบับมากกว่า + ชอบเสียงแบบบรรยายมากกว่า + คำสั่งสัมผัสฝั่งซ้าย + ไม่มี + ใส่ที่อยู่ URL ของอินสแตนซ์ + มีอินสแตนซ์นี้อยู่แล้ว + สลับ + เปลี่ยนสีการแจ้งเตือน + เปลี่ยนจากเครื่องเล่นหนึ่งไปอีกเครื่องเล่นหนึ่งอาจแทนที่คิวของคุณ + มีประโยชน์อย่างมากเมื่อใส่หูฟังที่ปุ่มกดพัง + เลือกเสียงแบบบรรยายสำหรับผู้มีความบกพร่องทางการมองเห็น ถ้าเกิดมีตัวเลือกนี้ + แนะนำการค้นหาที่อยู่ในท้องที่ + เลือกคำสั่งสัมผัสสำหรับฝั่งซ้ายของหน้าจอเครื่องเล่น + การแจ้งเตือนจากเครื่องเล่น + วนซ้ำ + เร่งความเร็วไปข้างหน้า/ย้อนกลับหาช่วงเวลา + เริ่มเครื่องเล่นหลักแบบเต็มหน้าจอ + อย่าเริ่มวิดีโอในเครื่องเล่นเล็ก แต่เปลี่ยนเป็นเต็มหน้าจอโดยตรง ถ้าการหมุนหน้าจออัตโนมัติล็อคไว้ คุณยังสามารถเข้าถึงเครื่องเล่นเล็กโดยการออกจากโหมดเต็มหน้าจอ + ไม่สามารถระบุที่อยู่ URL ได้ ลองเปิดด้วยแอปอื่น + ถามก่อนการเคลียร์คิว + คิวในเครื่องเล่นที่ใช้งานอยู่จะถูกแทนที่ + แสดงคำอธิบาย + แสดง meta info + ล้าง cached metadata + เข้าคิวอัตโนมัติ + เลือกคำสั่งสัมผัสฝั่งขวาของหน้าจอเครื่องเล่น + คำสั่งสัมผัสฝั่งขวา + ความสว่าง + ระดับเสียง + แนะนำการค้นหาที่อยู่ไกลขึ้น + PeerTube อินสแตนซ์ + เลือก PeerTube อินสแตนซ์โปรดของคุณ + หาอินสแตนซ์ที่คุณชอบอยู่ใน %s + เพิ่มอินสแตนซ์ + ไม่สามารถตรวจสอบอินสแตนซ์ได้ + รับรองเฉพาะที่อยู่ URL แบบ HTTPS + ไม่มี + ไม่ + ใช่/ตกลง diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 762e8c42f..1c736bb99 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -157,7 +157,7 @@ Geçmiş Bu ögeyi arama geçmişinden silmek istiyor musunuz\? \"Basılı tutarak kuyruğa ekle\" ipucunu göster - Video \"Ayrıntılar:\" sayfasında arka plan veya açılır pencere düğmesine basıldığında ipucu göster + Video \"Ayrıntılar:\" sayfasında arka plan ya da açılır pencere düğmesine basıldığında ipucu göster Tümünü Oynat [Bilinmeyen] Bu akış oynatılamadı @@ -789,7 +789,6 @@ Geri sar Yeniden oynat Besleme güncellenirken alınacak sekmeler. Hızlı kip kullanılırken kanal güncelleniyorsa bu seçeneğin etkisi yoktur. - Oynatma listesini, oynatma listesi adı ve video başlıkları gibi ayrıntılarla ya da video adreslerinin basit listesi olarak paylaş Orta nitelik Yükleyen avatarları Afişler diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 7d9814144..c9fa9f25c 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -170,7 +170,7 @@ Показати інформацію Закладки відтворення Додати до - Показувати підказку під час натискання фонової або спливної кнопки у відео \"Деталі:\\ + Показувати підказку під час натискання фонової або спливної кнопки у відео \"Деталі:\" Сталася невиправна помилка програвача Зовнішні програвачі не підтримують такі види посилань Що:\\nЗапит:\\nМова вмісту:\\nКраїна вмісту:\\nМова застосунку:\\nСлужба:\\nЧас GMT:\\nПакунок:\\nВерсія:\\nВерсія ОС: @@ -529,7 +529,7 @@ \nЩоб побачити його ввімкніть «%1$s» в налаштуваннях.
    Ескіз аватара каналу Оригінальні тексти сервісів будуть видимі в потокових елементах - Увімкніть \"Обмежений режим\" YouTube + Увімкнути \"Обмежений режим\" YouTube Результати для: %s Створено %s Показати лише незгруповані підписки @@ -670,7 +670,7 @@ Перевірка нових версій вручну Перевірка оновлень… Нові записи стрічки - Показати \"Збій програвача\\ + Показати \"Збій програвача\" Показує параметр збою під час використання програвача Збій програвача Сповіщення про звіт про помилку @@ -819,7 +819,6 @@ %1$s \n%2$s Поділитися добіркою - Поділитися добіркою з подробицями, такими як назва добірки та назви відео, або просто списком URL-адрес відео - %1$s: %2$s Показати більше Відредагуйте кожну дію сповіщення, натиснувши на неї. Перші три дії (відтворення/пауза, попередній і наступний) встановлюються системою і не можуть бути змінені. diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 90397541d..179f566e8 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -785,7 +785,6 @@ Tua đi Album Tua lại - 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 Chất lượng trung bình - %1$s: %2$s 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 diff --git a/app/src/main/res/values-vmf/strings.xml b/app/src/main/res/values-vmf/strings.xml new file mode 100644 index 000000000..b1cb4e774 --- /dev/null +++ b/app/src/main/res/values-vmf/strings.xml @@ -0,0 +1,10 @@ + + + im brüscher öffn + passd scho + passd scho + stoarnieren + iser + net + tealn + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 582d69cdd..7f9576d7f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -789,7 +789,6 @@ %1$s \n%2$s 分享播放列表 - 分享详细的播放列表(带名称和视频标题等信息)或只分享视频网址列表 - %1$s: %2$s %s 条回复 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index b8085480d..de343d5e7 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -773,7 +773,6 @@ 跳後 重播 更新摘要嘅時候要攞邊啲分頁返嚟。若果頻道用快速模式更新,就橫豎都無相干嘞。 - 分享播放清單要詳細包含播放清單個名同埋入面啲片名,定簡單得啲影片嘅 URL 一般畫質 上載者嘅頭像 橫額 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index d07ffb624..5dcc6b0da 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -789,7 +789,6 @@ %1$s \n%2$s 分享播放清單 - 分享包含播放清單名稱與影片標題等詳細資訊的播放清單,或是僅作為簡單的影片網址清單 - %1$s:%2$s %s 個回覆 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 215c0b6f0..011c69fcb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,6 +33,7 @@ Show info Subscriptions Bookmarked Playlists + Playlists Choose Tab Background Popup @@ -845,9 +846,9 @@ High quality \? Share Playlist - Share playlist with details such as playlist name and video titles or as a simple list of video URLs Share with Titles Share URL list + Share as YouTube temporary playlist - %1$s: %2$s %1$s\n%2$s diff --git a/app/src/main/res/xml/automotive_app_desc.xml b/app/src/main/res/xml/automotive_app_desc.xml new file mode 100644 index 000000000..90e6f30ef --- /dev/null +++ b/app/src/main/res/xml/automotive_app_desc.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt new file mode 100644 index 000000000..d9be2271e --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/ExportPlaylistTest.kt @@ -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 { + return asPlaylist(Stream.of(*urls)) +} + +fun asPlaylist(urls: Stream): List { + 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" + ) +} diff --git a/fastlane/metadata/android/ar/changelogs/1003.txt b/fastlane/metadata/android/ar/changelogs/1003.txt index fef3a6cc3..84395a0b2 100644 --- a/fastlane/metadata/android/ar/changelogs/1003.txt +++ b/fastlane/metadata/android/ar/changelogs/1003.txt @@ -1,4 +1,6 @@ -تم إصلاح مشكلة عدم تشغيل YouTube لأي بث. - -يعالج هذا الإصدار فقط الخطأ الأكثر إلحاحًا الذي يمنع تحميل تفاصيل فيديو YouTube. -نحن ندرك وجود مشاكل أخرى، وسنقوم قريباً بإصدار إصدار منفصل لحلها. +هذا إصدار إصلاح عاجل يعمل على إصلاح أخطاء YouTube: +• [يوتيوب] إصلاح عدم تحميل أي معلومات فيديو ، وإصلاح أخطاء HTTP 403 أثناء تشغيل مقاطع الفيديو واستعادة تشغيل بعض مقاطع الفيديو المقيدة بالفئة العمرية +• إصلاح أحجام التسميات التوضيحية التي لا يتم تغييرها +• إصلاح معلومات التنزيل مرتين عند فتح البث +• [Soundcloud] قم بإزالة التدفقات المحمية بإدارة الحقوق الرقمية غير القابلة للتشغيل +• ترجمات محدثة diff --git a/fastlane/metadata/android/cs/changelogs/1002.txt b/fastlane/metadata/android/cs/changelogs/1002.txt index 7035a1112..e77bb552f 100644 --- a/fastlane/metadata/android/cs/changelogs/1002.txt +++ b/fastlane/metadata/android/cs/changelogs/1002.txt @@ -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ší. diff --git a/fastlane/metadata/android/cs/changelogs/1003.txt b/fastlane/metadata/android/cs/changelogs/1003.txt index 7035a1112..f9c301d33 100644 --- a/fastlane/metadata/android/cs/changelogs/1003.txt +++ b/fastlane/metadata/android/cs/changelogs/1003.txt @@ -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 diff --git a/fastlane/metadata/android/de/changelogs/1003.txt b/fastlane/metadata/android/de/changelogs/1003.txt index 6c6dc761a..76f798a72 100644 --- a/fastlane/metadata/android/de/changelogs/1003.txt +++ b/fastlane/metadata/android/de/changelogs/1003.txt @@ -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 diff --git a/fastlane/metadata/android/el/changelogs/1000.txt b/fastlane/metadata/android/el/changelogs/1000.txt new file mode 100644 index 000000000..858bae60b --- /dev/null +++ b/fastlane/metadata/android/el/changelogs/1000.txt @@ -0,0 +1,13 @@ +Βελτιώσεις +• Κάντε κλικ στην περιγραφή της λίστας αναπαραγωγής για να εμφανίζεται περισσότερο / λιγότερο περιεχόμενο +• [PeerTube] Χειριστείτε αυτόματα συνδέσμους παρουσίας «subscribeto.me». +• Ξεκινήστε την αναπαραγωγή μόνο ενός στοιχείου στην οθόνη ιστορικού + +Διορθώσεις +• Διορθώστε την ορατότητα του κουμπιού RSS +• Διορθώστε τα σφάλματα προεπισκόπησης της γραμμής αναζήτησης +• Διορθώστε την εισχώρηση στη λίστα αναπαραγωγής ενός στοιχείου χωρίς μικρογραφίες +• Διορθώστε την έξοδο από το παράθυρο διαλόγου λήψης προτού εμφανιστεί +• Διορθώστε το αναδυόμενο παράθυρο ουράς λίστας σχετικών στοιχείων +• Διορθώστε τη σειρά στο παράθυρο διαλόγου προσθήκης στη λίστα αναπαραγωγής +• Προσαρμόστε τη διάταξη του σελιδοδείκτη της λίστας αναπαραγωγής diff --git a/fastlane/metadata/android/el/changelogs/999.txt b/fastlane/metadata/android/el/changelogs/999.txt new file mode 100644 index 000000000..c0782663d --- /dev/null +++ b/fastlane/metadata/android/el/changelogs/999.txt @@ -0,0 +1 @@ +Αυτή η έκδοση επείγουσας επιδιόρθωσης διορθώνει σφάλματα HTTP 403 στη μέση των βίντεο του YouTube. Νέος • [SoundCloud] Προσθέστε υποστήριξη για διευθύνσεις URL on.soundcloud.com Βελτιωμένο • [Bandcamp] Εμφάνιση πρόσθετων πληροφοριών στο ραδιοφωνικό κιόσκι διορθώσεις • [YouTube] Διόρθωση περιστασιακών σφαλμάτων HTTP 403 στην αρχή ή στη μέση των βίντεο • [YouTube] Εξαγωγή avatar και banner από περισσότερους τύπους κεφαλίδων καναλιού • [Bandcamp] Διορθώστε διάφορα σφάλματα και χρησιμοποιείτε πάντα HTTPS diff --git a/fastlane/metadata/android/et/changelogs/1003.txt b/fastlane/metadata/android/et/changelogs/1003.txt index c4c747f5b..53c967eea 100644 --- a/fastlane/metadata/android/et/changelogs/1003.txt +++ b/fastlane/metadata/android/et/changelogs/1003.txt @@ -1,4 +1,6 @@ -Parandasime vea, kus ühtegi YouTube'i meediavoogu ei õnnestunud esitada. - -See versioon parandab vaid hetkel kõige olulisema vea, kus YouTube'i video andmeid ei õnnestunud laadida. -Me oleme teadlikud ka muudest vigadest ning nendega tegeleme hiljem. +See on kiirparandus, mis teeb korda need YouTube'i vead: +• [YouTube] Parandasime vea, kus ühtegi meediavoogu ei õnnestunud esitada, parandasime HTTP 403-tüüpi vead videote esitamisel ja taastasime mõnede vanusepiirangutega videote esitamise +• Parandasime vea, kus subtiitrite suurus ei muutunud +• Parandasime vea, kus meediavoo avamisel laadisime tema teabe kaks korda alla +• [Soundcloud] eemaldasime mitteesitatavad DRM-kaitsega meediavood +• Uuendasime tõlkeid diff --git a/fastlane/metadata/android/et/changelogs/998.txt b/fastlane/metadata/android/et/changelogs/998.txt new file mode 100644 index 000000000..a1bb7399c --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/998.txt @@ -0,0 +1,4 @@ +Parandasime vea, kus ühtegi YouTube'i meediavoogu ei õnnestunud esitada ja HTTP olekuteade oli 403. + +Juhuslikud HTTP 403 veas esituse keskel pole veel parandatud. +Me oleme teadlikud ka sellest veast ja kiirparandus lisandub niipea, kui võimalik. diff --git a/fastlane/metadata/android/et/changelogs/999.txt b/fastlane/metadata/android/et/changelogs/999.txt new file mode 100644 index 000000000..3f3aa24e5 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/999.txt @@ -0,0 +1,12 @@ +See kiirparandus teeb korda vea, kus YouTube'i video esituse keskel esitus katkeb HTTP olekuteatega 403. + +Uus +• [SoundCloud] Lisandus on.soundcloud.com võrguaadresside tugi + +Täiendused +• [Bandcamp] Näitame lisateavet raadiokioski vaates + +Parandused +• [YouTube] Parandasime juhuslikud HTTP 403 vead videoesituse keskel +• [YouTube] Tuvastame tunnuspildi ja päisepildi enamatest kanalipäisete tüüpidest +• [Bandcamp] Mitmed veaparandused ja nüüdsest alati kasutame HTTPSi diff --git a/fastlane/metadata/android/et/full_description.txt b/fastlane/metadata/android/et/full_description.txt index 1a8cb9cac..e5192d070 100644 --- a/fastlane/metadata/android/et/full_description.txt +++ b/fastlane/metadata/android/et/full_description.txt @@ -1 +1 @@ -NewPipe ei kasuta Google-i raamistiku teeke ega YouTube APIt. See ainult sõelub veebilehelt vajamineva info. Seega saab rakendust kasutada ka seadmes, millesse ei ole paigaldatud Google teenuseid (Google Services). Lisaks ei ole NewPipe kasutamisel vaja YouTube'i kontot ja ta on FLOSS. +NewPipe ei kasuta Google'i raamistiku teeke ega YouTube'i APIt. Ta ainult sõelub veebilehelt vajamineva info. Seega saab rakendust kasutada ka seadmes, millesse ei ole paigaldatud Google'i teenuseid (Google Services). Lisaks ei ole NewPipe'i kasutamisel vaja YouTube'i kontot ja tegemist on avatud lähtekoodil põhineva tasuta ja vaba tarkvaraga. diff --git a/fastlane/metadata/android/et/short_description.txt b/fastlane/metadata/android/et/short_description.txt index 5d0d820e2..ee606a574 100644 --- a/fastlane/metadata/android/et/short_description.txt +++ b/fastlane/metadata/android/et/short_description.txt @@ -1 +1 @@ -Tasuta ja lihtne rakendus YouTube vaatamiseks. +Tasuta ja lihtne rakendus YouTube'i vaatamiseks. diff --git a/fastlane/metadata/android/fr/changelogs/1002.txt b/fastlane/metadata/android/fr/changelogs/1002.txt index 3ad3bf279..1b6846169 100644 --- a/fastlane/metadata/android/fr/changelogs/1002.txt +++ b/fastlane/metadata/android/fr/changelogs/1002.txt @@ -1 +1,4 @@ -Correction de YouTube qui ne lisait aucun média +Correction d'un problème empêchant YouTube de lire les vidéos en streaming. + +Cette version résout uniquement l'erreur la plus urgente qui empêche le chargement des détails des vidéos YouTube. +Nous sommes conscients qu'il existe d'autres problèmes et nous publierons bientôt une version séparée pour les résoudre. diff --git a/fastlane/metadata/android/fr/changelogs/1003.txt b/fastlane/metadata/android/fr/changelogs/1003.txt index 3ad3bf279..13c76e921 100644 --- a/fastlane/metadata/android/fr/changelogs/1003.txt +++ b/fastlane/metadata/android/fr/changelogs/1003.txt @@ -1 +1,6 @@ -Correction de YouTube qui ne lisait aucun média +Il s'agit d'une version de correction qui résout les erreurs de YouTube : +• [YouTube] Correction du non-chargement des informations des vidéos, correction des erreurs HTTP 403 lors de la lecture des vidéos et restauration de la lecture de certaines vidéos restreintes par l'âge +• Correction des tailles de sous-titres qui ne changent pas +• Correction du téléchargement des informations deux fois lors de l'ouverture d'un flux +• [Soundcloud] Suppression des flux protégés par DRM non lisibles +• Traductions mises à jour diff --git a/fastlane/metadata/android/hi/changelogs/1001.txt b/fastlane/metadata/android/hi/changelogs/1001.txt new file mode 100644 index 000000000..bc92ea2f1 --- /dev/null +++ b/fastlane/metadata/android/hi/changelogs/1001.txt @@ -0,0 +1,6 @@ +सुधार +• Android 13+ पर हमेशा प्लेयर नोटिफिकेशन प्राथमिकताएँ बदलने की अनुमति दें + +ठीक किया गया +• डेटाबेस/सदस्यता निर्यात करने से पहले से मौजूद फ़ाइल को छोटा नहीं किया जा सकता था, जिससे संभवतः दूषित निर्यात हो सकता था +• टाइमस्टैम्प पर क्लिक करने पर प्लेयर फिर से शुरू होने की समस्या को ठीक किया गया diff --git a/fastlane/metadata/android/hi/changelogs/1002.txt b/fastlane/metadata/android/hi/changelogs/1002.txt index 071ab64e3..d780f47a6 100644 --- a/fastlane/metadata/android/hi/changelogs/1002.txt +++ b/fastlane/metadata/android/hi/changelogs/1002.txt @@ -1 +1,5 @@ -फिक्स्ड YouTube कोई स्ट्रीम नहीं चला रहा है +YouTube द्वारा कोई भी स्ट्रीम न चलाए जाने की समस्या को ठीक किया गया। + +यह रिलीज़ केवल सबसे ज़्यादा दबाव वाली त्रुटि को संबोधित करती है जो YouTube वीडियो विवरण को लोड होने से रोकती है। + +हम जानते हैं कि अन्य समस्याएँ भी हैं, और हम जल्द ही उन्हें हल करने के लिए एक अलग रिलीज़ जारी करेंगे। diff --git a/fastlane/metadata/android/hi/changelogs/1003.txt b/fastlane/metadata/android/hi/changelogs/1003.txt index 071ab64e3..210a2566b 100644 --- a/fastlane/metadata/android/hi/changelogs/1003.txt +++ b/fastlane/metadata/android/hi/changelogs/1003.txt @@ -1 +1,6 @@ -फिक्स्ड YouTube कोई स्ट्रीम नहीं चला रहा है +यह एक हॉटफ़िक्स रिलीज़ है जो YouTube त्रुटियों को ठीक करता है: +• [YouTube] कोई भी वीडियो जानकारी लोड न होने की समस्या को ठीक करें, वीडियो चलाते समय HTTP 403 त्रुटियाँ ठीक करें और कुछ आयु-प्रतिबंधित वीडियो के प्लेबैक को पुनर्स्थापित करें +• कैप्शन का आकार न बदलने की समस्या को ठीक करें +• स्ट्रीम खोलते समय जानकारी दो बार डाउनलोड होने की समस्या को ठीक करें +• [साउंडक्लाउड] न चलाए जा सकने वाले DRM-संरक्षित स्ट्रीम हटाएँ +• अपडेट किए गए अनुवाद diff --git a/fastlane/metadata/android/hi/changelogs/65.txt b/fastlane/metadata/android/hi/changelogs/65.txt index 8570a056a..d2c2b8c71 100644 --- a/fastlane/metadata/android/hi/changelogs/65.txt +++ b/fastlane/metadata/android/hi/changelogs/65.txt @@ -1,26 +1,26 @@ -### Improvements +### सुधार -- Disable burgermenu icon animation #1486 -- undo delete of downloads #1472 -- Download option in share menu #1498 -- Added share option to long tap menu #1454 -- Minimize main player on exit #1354 -- Library version update and database backup fix #1510 -- ExoPlayer 2.8.2 Update #1392 - - Reworked the playback speed control dialog to support different step sizes for faster speed change. - - Added a toggle to fast-forward during silences in playback speed control. This should be helpful for audiobooks and certain music genres, and can bring a true seamless experience (and can break a song with lots of silences =\\). - - Refactored media source resolution to allow passing metadata alongside media internally in the player, rather than doing so manually. Now we have a single source of metadata and is directly available when playback starts. - - Fixed remote playlist metadata not updating when new metadata is available when playlist fragment is opened. - - Various UI fixes: #1383, background player notification controls now always white, easier to shutdown popup player through flinging -- Use new extractor with refactored architecture for multiservice +- बर्गरमेनू आइकन एनीमेशन को अक्षम करें #1486 +- डाउनलोड को पूर्ववत करें #1472 +- शेयर मेनू में डाउनलोड विकल्प #1498 +- लॉन्ग टैप मेनू में शेयर विकल्प जोड़ा गया #1454 +- बाहर निकलने पर मुख्य प्लेयर को छोटा करें #1354 +- लाइब्रेरी संस्करण अपडेट और डेटाबेस बैकअप फिक्स #1510 +- एक्सोप्लेयर 2.8.2 अपडेट #1392 +- तेज गति परिवर्तन के लिए विभिन्न चरण आकारों का समर्थन करने के लिए प्लेबैक गति नियंत्रण संवाद को फिर से तैयार किया गया। +- प्लेबैक गति नियंत्रण में मौन के दौरान तेजी से आगे बढ़ने के लिए एक टॉगल जोड़ा गया। यह ऑडियोबुक और कुछ संगीत शैलियों के लिए मददगार होना चाहिए, और एक सच्चा सहज अनुभव ला सकता है (और बहुत सारे मौन वाले गीत को तोड़ सकता है =\\)। +- मीडिया स्रोत रिज़ॉल्यूशन को फिर से तैयार किया गया ताकि प्लेयर में आंतरिक रूप से मीडिया के साथ मेटाडेटा को पास किया जा सके, बजाय मैन्युअल रूप से ऐसा करने के। अब हमारे पास मेटाडेटा का एक ही स्रोत है और प्लेबैक शुरू होने पर सीधे उपलब्ध है। +- जब प्लेलिस्ट का टुकड़ा खोला जाता है तो नया मेटाडेटा उपलब्ध होने पर रिमोट प्लेलिस्ट मेटाडेटा अपडेट नहीं होता है। +- विभिन्न UI फ़िक्सेस: #1383, बैकग्राउंड प्लेयर नोटिफिकेशन कंट्रोल अब हमेशा सफ़ेद रहता है, फ़्लिंगिंग के ज़रिए पॉपअप प्लेयर को बंद करना आसान है +- मल्टीसर्विस के लिए रीफ़ैक्टर्ड आर्किटेक्चर के साथ नए एक्सट्रैक्टर का उपयोग करें -### Fixes +### फ़िक्सेस -- Fix #1440 Broken Video Info Layout #1491 -- View history fix #1497 - - #1495, by updating the metadata (thumbnail, title and video count) as soon as the user access the playlist. - - #1475, by registering a view in the database when the user starts a video on external player on detail fragment. -- Fix creen timeout in case of popup mode. #1463 (Fixed #640) -- Main video player fix #1509 - - [#1412] Fixed repeat mode causing player NPE when new intent is received while player activity is in background. - - Fixed minimizing player to popup does not destroy player when popup permission is not granted. +- फ़िक्स #1440 टूटी हुई वीडियो जानकारी लेआउट #1491 +- व्यू हिस्ट्री फ़िक्स #1497 +- #1495, जैसे ही उपयोगकर्ता प्लेलिस्ट एक्सेस करता है मेटाडेटा (थंबनेल, शीर्षक और वीडियो काउंट) को अपडेट करके। +- #1475, जब उपयोगकर्ता डिटेल फ़्रैगमेंट पर बाहरी प्लेयर पर वीडियो शुरू करता है तो डेटाबेस में व्यू रजिस्टर करके। +- पॉपअप मोड के मामले में स्क्रीन टाइमआउट को ठीक करें। #1463 (फ़िक्स #640) +- मुख्य वीडियो प्लेयर फ़िक्स #1509 +- [#1412] प्लेयर गतिविधि के बैकग्राउंड में होने पर नया इंटेंट प्राप्त होने पर प्लेयर NPE का कारण बनने वाले रिपीट मोड को ठीक किया गया। +- पॉपअप के लिए प्लेयर को छोटा करने की सुविधा को ठीक किया गया, जब पॉपअप की अनुमति नहीं दी जाती है तो प्लेयर नष्ट नहीं होता है। diff --git a/fastlane/metadata/android/hu/changelogs/1002.txt b/fastlane/metadata/android/hu/changelogs/1002.txt index 5eebb2174..87454df24 100644 --- a/fastlane/metadata/android/hu/changelogs/1002.txt +++ b/fastlane/metadata/android/hu/changelogs/1002.txt @@ -1,4 +1,4 @@ -Javítva: a YouTube-ról nem játszik le semmilyen streamet. +Javítva a YouTube-ról nem játszik le semmilyen streamet. -Ez a kiadás csak a legsürgősebb hibát kezeli, ami megakadályozza a YouTube-videó részleteinek betöltését. +Ez a kiadás csak a legsürgősebb hibát kezeli, ami megakadályozza a YouTube videó részleteinek betöltését. Tisztában vagyunk azzal, hogy vannak más problémák is, és hamarosan külön kiadást készítünk ezek megoldására. diff --git a/fastlane/metadata/android/hu/changelogs/1003.txt b/fastlane/metadata/android/hu/changelogs/1003.txt index 5eebb2174..b33f5725b 100644 --- a/fastlane/metadata/android/hu/changelogs/1003.txt +++ b/fastlane/metadata/android/hu/changelogs/1003.txt @@ -1,4 +1,6 @@ -Javítva: a YouTube-ról nem játszik le semmilyen streamet. - -Ez a kiadás csak a legsürgősebb hibát kezeli, ami megakadályozza a YouTube-videó részleteinek betöltését. -Tisztában vagyunk azzal, hogy vannak más problémák is, és hamarosan külön kiadást készítünk ezek megoldására. +Ez egy azonnali kiadás, amely a YouTube hibáit javítja: +• [YouTube] A videóinformációk betöltésének elmaradása, a videók lejátszása közben fellépő HTTP 403 hibák javítása és néhány korhatáros videó lejátszásának visszaállítása +• A feliratméret váltásának javítása +• Az információ kétszeri letöltésének javítása a stream megnyitásakor +• [Soundcloud] A lejátszhatatlan DRM-védett streamek eltávolítása +• Frissített fordítások diff --git a/fastlane/metadata/android/it/changelogs/1002.txt b/fastlane/metadata/android/it/changelogs/1002.txt index 951ffee5e..7028421cb 100644 --- a/fastlane/metadata/android/it/changelogs/1002.txt +++ b/fastlane/metadata/android/it/changelogs/1002.txt @@ -1 +1,4 @@ -Corretto problema di riproduzione di YouTube +Risolto il problema per cui YouTube non riproduceva alcun flusso + +Questa versione risolve solo l'errore più urgente che impedisce il caricamento dei dettagli dei video di YouTube. +Siamo consapevoli che ci sono altri problemi e presto pubblicheremo una versione separata per risolverli. diff --git a/fastlane/metadata/android/it/changelogs/1003.txt b/fastlane/metadata/android/it/changelogs/1003.txt index 951ffee5e..c8fa124b9 100644 --- a/fastlane/metadata/android/it/changelogs/1003.txt +++ b/fastlane/metadata/android/it/changelogs/1003.txt @@ -1 +1,6 @@ -Corretto problema di riproduzione di YouTube +Questa versione corregge gli errori di YouTube: +• [YouTube] Corretto il problema del mancato caricamento delle informazioni video, degli errori HTTP 403 durante la riproduzione dei video e ripristinata la riproduzione di alcuni video con limiti di età +• Corrette le dimensioni delle didascalie che non vengono modificate +• Corretto il download delle informazioni due volte durante l'apertura di un flusso +• [Soundcloud] Rimossi i flussi protetti da DRM non riproducibili +• Traduzioni aggiornate diff --git a/fastlane/metadata/android/ka/changelogs/1002.txt b/fastlane/metadata/android/ka/changelogs/1002.txt deleted file mode 100644 index d20512f17..000000000 --- a/fastlane/metadata/android/ka/changelogs/1002.txt +++ /dev/null @@ -1 +0,0 @@ -გაასწორა YouTube არ უკრავს არცერთ ნაკადს diff --git a/fastlane/metadata/android/ka/changelogs/1003.txt b/fastlane/metadata/android/ka/changelogs/1003.txt deleted file mode 100644 index d20512f17..000000000 --- a/fastlane/metadata/android/ka/changelogs/1003.txt +++ /dev/null @@ -1 +0,0 @@ -გაასწორა YouTube არ უკრავს არცერთ ნაკადს diff --git a/fastlane/metadata/android/ka/changelogs/65.txt b/fastlane/metadata/android/ka/changelogs/65.txt deleted file mode 100644 index e2ca8059d..000000000 --- a/fastlane/metadata/android/ka/changelogs/65.txt +++ /dev/null @@ -1,26 +0,0 @@ -### გაუმჯობესებები - - - გამორთეთ ბურგერმენუს ხატის ანიმაცია #1486 - - ჩამოტვირთვების წაშლის გაუქმება #1472 - - ჩამოტვირთვის ვარიანტი გაზიარების მენიუში #1498 - - დამატებულია გაზიარების ვარიანტი გრძელი შეხების მენიუში #1454 - - მთავარი მოთამაშის მინიმიზაცია #1354 გასასვლელზე - - ბიბლიოთეკის ვერსიის განახლება და მონაცემთა ბაზის სარეზერვო დაფიქსირება #1510 - - ExoPlayer 2.8.2 განახლება #1392 - - გადამუშავდა დაკვრის სიჩქარის კონტროლის დიალოგი, რათა მხარი დაუჭიროს სხვადასხვა ნაბიჯების ზომას უფრო სწრაფი სიჩქარის ცვლილებისთვის. - - დამატებულია გადართვა სწრაფი წინსვლისთვის დუმილის დროს დაკვრის სიჩქარის კონტროლში. ეს გამოსადეგი უნდა იყოს აუდიო წიგნებისთვის და გარკვეული მუსიკის ჟანრებისთვის და შეუძლია ნამდვილი უწყვეტი გამოცდილების მოტანა (და შეიძლება დაარღვიოს სიმღერა მრავალი დუმილით =\\). - - რეფაქტორირებული მედია წყაროს გარჩევადობა, რათა მეტამონაცემების გადაცემა მედიასთან ერთად შიგადაშიგ პლეერში, ვიდრე ხელით. ახლა ჩვენ გვაქვს მეტამონაცემების ერთი წყარო და პირდაპირ ხელმისაწვდომია დაკვრის დაწყებისას. - - დაფიქსირდა დისტანციური დასაკრავი სიის მეტამონაცემები არ განახლდება, როდესაც ახალი მეტამონაცემები ხელმისაწვდომია დასაკრავი სიის ფრაგმენტის გახსნისას. - - სხვადასხვა ინტერფეისის შესწორებები: #1383, ფონური მოთამაშის შეტყობინებების კონტროლი ახლა ყოველთვის თეთრია, უფრო ადვილია ამომხტარი მოთამაშის გამორთვა ფლანგით - - გამოიყენეთ ახალი ექსტრაქტორი რეფაქტორირებული არქიტექტურით მულტისერვისისთვის - - ### ასწორებს - - - დააფიქსირეთ #1440 გატეხილი ვიდეო ინფორმაციის განლაგება #1491 - - ნახეთ ისტორიის შესწორება #1497 - - #1495, მეტამონაცემების (მინიატურების, სათაურის და ვიდეოების რაოდენობა) განახლებით, როგორც კი მომხმარებელი წვდება დასაკრავ სიას. - - #1475, მონაცემთა ბაზაში ხედის დარეგისტრირებით, როდესაც მომხმარებელი იწყებს ვიდეოს გარე პლეერზე დეტალურ ფრაგმენტზე. - - დააფიქსირეთ ეკრანის დროის ამოწურვა ამომხტარი რეჟიმის შემთხვევაში. #1463 (დასწორებულია #640) - - მთავარი ვიდეო პლეერის დაფიქსირება #1509 - - [#1412] დაფიქსირდა გამეორების რეჟიმი, რომელიც იწვევს მოთამაშის NPE-ს, როდესაც მიიღება ახალი განზრახვა, როდესაც მოთამაშის აქტივობა ფონზეა. - - დაფიქსირებული მინიმიზაცია მოთამაშის ამომხტარ ფანჯარაში არ ანადგურებს მოთამაშეს, როდესაც ამომხტარი ნებართვა არ არის მინიჭებული. diff --git a/fastlane/metadata/android/ka/changelogs/68.txt b/fastlane/metadata/android/ka/changelogs/68.txt deleted file mode 100644 index 1c6967bcb..000000000 --- a/fastlane/metadata/android/ka/changelogs/68.txt +++ /dev/null @@ -1,31 +0,0 @@ -v0.14.1-ის ცვლილებები - - ### გამოსწორდა - - დაფიქსირდა ვიდეო url #1659-ის გაშიფვრა ვერ მოხერხდა - - დაფიქსირდა აღწერილობის ბმული კარგად არ არის ამონაწერი #1657 - - v0.14.0-ის # ცვლილებები - - ### ახალი - - ახალი უჯრის დიზაინი #1461 - - ახალი კონფიგურირებადი წინა გვერდი #1461 - - ### გაუმჯობესებები - - გადამუშავებული ჟესტების კონტროლი #1604 - - ახალი გზა ამომხტარი პლეერის დახურვის #1597 - - ### გამოსწორდა - - შეცდომის გამოსწორება, როდესაც ხელმოწერების რაოდენობა მიუწვდომელია. იხურება #1649. - - აჩვენეთ "აბონენტთა რაოდენობა მიუწვდომელია" ამ შემთხვევებში - - შეასწორეთ NPE, როდესაც YouTube დასაკრავი სია ცარიელია - - სწრაფი შესწორება კიოსკებისთვის SoundCloud-ში - - Refactor და bugfix #1623 - - დააფიქსირეთ ციკლური ძიების შედეგი #1562 - - შეასწორეთ ძიების ზოლი, რომელიც არ არის სტატიკურად განლაგებული - - გაასწორეთ YT Premium ვიდეო არ არის სწორად დაბლოკილი - - დააფიქსირეთ ვიდეოები, რომლებიც ზოგჯერ არ იტვირთება (DASH ანალიზების გამო) - - დააფიქსირეთ ბმულები ვიდეოს აღწერაში - - გაფრთხილების ჩვენება, როდესაც ვინმე ცდილობს გარე sdcard-ზე ჩამოტვირთვას - - დააფიქსირეთ არაფერი ნაჩვენები გამონაკლისის გამომწვევი ანგარიში - - ესკიზი არ არის ნაჩვენები ანდროიდ 8.1-ის ფონურ პლეერში [იხილეთ აქ](https://github.com/TeamNewPipe/NewPipe/issues/943) - - სამაუწყებლო მიმღების რეგისტრაციის დაფიქსირება. იხურება #1641. diff --git a/fastlane/metadata/android/ka/changelogs/69.txt b/fastlane/metadata/android/ka/changelogs/69.txt deleted file mode 100644 index 4bc9f3c03..000000000 --- a/fastlane/metadata/android/ka/changelogs/69.txt +++ /dev/null @@ -1,19 +0,0 @@ -### ახალი - - დიდხანს შეეხეთ წაშლას და გააზიარეთ გამოწერებში #1516 - - ტაბლეტის ინტერფეისი და ბადის სიის განლაგება #1617 - - ### გაუმჯობესებები - - შეინახეთ და გადატვირთეთ ბოლო გამოყენებული ასპექტის თანაფარდობა #1748 - - ჩართეთ ხაზოვანი განლაგება ჩამოტვირთვების აქტივობაში ვიდეოს სრული სახელებით #1771 - - წაშალეთ და გააზიარეთ ხელმოწერები პირდაპირ გამოწერების ჩანართიდან #1516 - - ახლა რიგში დაყენება იწვევს ვიდეოს დაკვრას, თუ დაკვრის რიგი უკვე დასრულდა #1783 - - ცალკე პარამეტრები მოცულობისა და სიკაშკაშის ჟესტებისთვის #1644 - - დაამატეთ მხარდაჭერა ლოკალიზაციის #1792-ისთვის - - ### ასწორებს - - დააფიქსირეთ დროის ანალიზი . ფორმატში, ამიტომ NewPipe შეიძლება გამოყენებულ იქნას ფინეთში - - შეასწორეთ გამოწერების რაოდენობა - - დაამატეთ წინა პლანზე სერვისის ნებართვა API 28+ მოწყობილობებისთვის #1830 - - ### ცნობილი შეცდომები - - დაკვრის მდგომარეობის შენახვა შეუძლებელია Android P-ზე diff --git a/fastlane/metadata/android/ne/short_description.txt b/fastlane/metadata/android/ne/short_description.txt new file mode 100644 index 000000000..383f78457 --- /dev/null +++ b/fastlane/metadata/android/ne/short_description.txt @@ -0,0 +1 @@ +एन्ड्रोइडका लागि निशुल्क, हलुका युट्युब फ्रन्टइन्ड । diff --git a/fastlane/metadata/android/pa/changelogs/1001.txt b/fastlane/metadata/android/pa/changelogs/1001.txt new file mode 100644 index 000000000..83f91c82b --- /dev/null +++ b/fastlane/metadata/android/pa/changelogs/1001.txt @@ -0,0 +1,6 @@ +ਸੁਧਾਰਿਆ ਗਿਆ +• ਐਂਡਰਾਇਡ 13+ 'ਤੇ ਪਲੇਅਰ ਸੂਚਨਾ ਤਰਜੀਹਾਂ ਨੂੰ ਹਮੇਸ਼ਾ ਬਦਲਣ ਦੀ ਆਗਿਆ ਦਿਓ + +ਠੀਕ ਕੀਤਾ ਗਿਆ +• ਡੇਟਾਬੇਸ/ਸਬਸਕ੍ਰਿਪਸ਼ਨ ਨਿਰਯਾਤ ਕਰਨ ਨਾਲ ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਫਾਈਲ ਨੂੰ ਕੱਟਿਆ ਨਹੀਂ ਜਾਵੇਗਾ, ਜਿਸ ਨਾਲ ਸੰਭਾਵਤ ਤੌਰ 'ਤੇ ਖਰਾਬ ਨਿਰਯਾਤ ਹੋ ਸਕਦਾ ਹੈ +• ਟਾਈਮਸਟੈਂਪ 'ਤੇ ਕਲਿੱਕ ਕਰਨ 'ਤੇ ਪਲੇਅਰ ਨੂੰ ਸ਼ੁਰੂ ਤੋਂ ਮੁੜ ਸ਼ੁਰੂ ਕਰਨ ਨੂੰ ਠੀਕ ਕਰੋ diff --git a/fastlane/metadata/android/pa/changelogs/1002.txt b/fastlane/metadata/android/pa/changelogs/1002.txt index fe62a1330..3cfce7b21 100644 --- a/fastlane/metadata/android/pa/changelogs/1002.txt +++ b/fastlane/metadata/android/pa/changelogs/1002.txt @@ -1 +1,4 @@ -ਸਥਿਰ YouTube ਕੋਈ ਸਟ੍ਰੀਮ ਨਹੀਂ ਚਲਾ ਰਿਹਾ +YouTube ਵੱਲੋਂ ਕੋਈ ਵੀ ਸਟ੍ਰੀਮ ਨਾ ਚਲਾਉਣ ਨੂੰ ਠੀਕ ਕੀਤਾ ਗਿਆ ਹੈ। + +ਇਹ ਰੀਲੀਜ਼ ਸਿਰਫ਼ ਉਸ ਸਭ ਤੋਂ ਵੱਡੀ ਗਲਤੀ ਨੂੰ ਹੱਲ ਕਰਦੀ ਹੈ ਜੋ YouTube ਵੀਡੀਓ ਵੇਰਵਿਆਂ ਨੂੰ ਲੋਡ ਹੋਣ ਤੋਂ ਰੋਕਦੀ ਹੈ। +ਅਸੀਂ ਜਾਣਦੇ ਹਾਂ ਕਿ ਹੋਰ ਸਮੱਸਿਆਵਾਂ ਵੀ ਹਨ, ਅਤੇ ਅਸੀਂ ਜਲਦੀ ਹੀ ਉਨ੍ਹਾਂ ਨੂੰ ਹੱਲ ਕਰਨ ਲਈ ਇੱਕ ਵੱਖਰੀ ਰੀਲੀਜ਼ ਕਰਾਂਗੇ। diff --git a/fastlane/metadata/android/pa/changelogs/1003.txt b/fastlane/metadata/android/pa/changelogs/1003.txt index fe62a1330..0c8a5a1a0 100644 --- a/fastlane/metadata/android/pa/changelogs/1003.txt +++ b/fastlane/metadata/android/pa/changelogs/1003.txt @@ -1 +1,6 @@ -ਸਥਿਰ YouTube ਕੋਈ ਸਟ੍ਰੀਮ ਨਹੀਂ ਚਲਾ ਰਿਹਾ +ਇਹ ਇੱਕ ਹੌਟਫਿਕਸ ਰੀਲੀਜ਼ ਹੈ ਜੋ YouTube ਗਲਤੀਆਂ ਨੂੰ ਠੀਕ ਕਰਦੀ ਹੈ: +• [YouTube] ਵੀਡੀਓ ਚਲਾਉਣ ਦੌਰਾਨ ਕਿਸੇ ਵੀ ਵੀਡੀਓ ਜਾਣਕਾਰੀ ਨੂੰ ਲੋਡ ਨਾ ਹੋਣ ਨੂੰ ਠੀਕ ਕਰੋ, ਵੀਡੀਓ ਚਲਾਉਂਦੇ ਸਮੇਂ HTTP 403 ਗਲਤੀਆਂ ਨੂੰ ਠੀਕ ਕਰੋ ਅਤੇ ਕੁਝ ਉਮਰ-ਪ੍ਰਤੀਬੰਧਿਤ ਵੀਡੀਓਜ਼ ਦੇ ਪਲੇਬੈਕ ਨੂੰ ਬਹਾਲ ਕਰੋ +• ਕੈਪਸ਼ਨ ਆਕਾਰਾਂ ਨੂੰ ਨਾ ਬਦਲਣ ਨੂੰ ਠੀਕ ਕਰੋ +• ਸਟ੍ਰੀਮ ਖੋਲ੍ਹਣ ਵੇਲੇ ਦੋ ਵਾਰ ਡਾਊਨਲੋਡਿੰਗ ਜਾਣਕਾਰੀ ਨੂੰ ਠੀਕ ਕਰੋ +• [Soundcloud] ਨਾ ਚਲਾਏ ਜਾ ਸਕਣ ਵਾਲੇ DRM-ਸੁਰੱਖਿਅਤ ਸਟ੍ਰੀਮਾਂ ਨੂੰ ਹਟਾਓ +• ਅੱਪਡੇਟ ਕੀਤੇ ਅਨੁਵਾਦ diff --git a/fastlane/metadata/android/sk/changelogs/1000.txt b/fastlane/metadata/android/sk/changelogs/1000.txt index 36b9aeae1..61cabd89d 100644 --- a/fastlane/metadata/android/sk/changelogs/1000.txt +++ b/fastlane/metadata/android/sk/changelogs/1000.txt @@ -1,4 +1,4 @@ -Vylepšenie +Vylepšené - Umožnené kliknutie na popis playlistu, aby sa zobrazilo viac/menej obsahu - [PeerTube] Automatické spracovanie odkazov inštancie `subscribeto.me` - Spustenie prehrávania iba jednej položky v histórii diff --git a/fastlane/metadata/android/sk/changelogs/1002.txt b/fastlane/metadata/android/sk/changelogs/1002.txt index 2f96b8dc5..789b1caef 100644 --- a/fastlane/metadata/android/sk/changelogs/1002.txt +++ b/fastlane/metadata/android/sk/changelogs/1002.txt @@ -1 +1,4 @@ -Fixed YouTube not playing any stream +Opravené prehrávanie videí. + +Toto vydanie rieši len najpálčivejšiu chybu, ktorá zabraňuje načítať detaily s videom. +Sme si vedomí aj ďalších chýb a čoskoro vydáme ďalšie vydanie, ktoré ich bude riešiť. diff --git a/fastlane/metadata/android/sk/full_description.txt b/fastlane/metadata/android/sk/full_description.txt index abda185e2..9de86c677 100644 --- a/fastlane/metadata/android/sk/full_description.txt +++ b/fastlane/metadata/android/sk/full_description.txt @@ -1 +1 @@ -NewPipe nepoužíva žiadne Google knižnice ani YouTube rozhranie. Analyzuje iba webovú stránku aby získala potrebné informácie. Preto je možné túto aplikáciu používať na zariadeniach bez nainštalovaných služieb Google. Na použitie aplikácie NewPipe tiež nepotrebujete účet na YouTube, je to bezproblémové. +NewPipe nepoužíva žiadne Google framework knižnice, ani YouTube API rozhranie. Len analyzuje web, aby získal potrebné informácie. Preto je možné túto aplikáciu používať na zariadeniach bez nainštalovaných Google služieb. Taktiež nepotrebujete účet na YouTube. Appka je FLOSS. diff --git a/fastlane/metadata/android/sk/short_description.txt b/fastlane/metadata/android/sk/short_description.txt index 1fe84348f..b2351c660 100644 --- a/fastlane/metadata/android/sk/short_description.txt +++ b/fastlane/metadata/android/sk/short_description.txt @@ -1 +1 @@ -Jednoduchý a bezplatný YouTube prehrávač pre Android. +Slobodný a nenáročný YouTube prehrávač pre Android. diff --git a/fastlane/metadata/android/sv/changelogs/1003.txt b/fastlane/metadata/android/sv/changelogs/1003.txt index b25a7e652..3b4532ca0 100644 --- a/fastlane/metadata/android/sv/changelogs/1003.txt +++ b/fastlane/metadata/android/sv/changelogs/1003.txt @@ -1,4 +1,6 @@ -Åtgärdat att YouTube inte spelar någon stream. - -Den här versionen fixar enbart det mest brådskande felet som förhindrar att YouTube-videoinformation laddas. -Vi är medvetna om att det finns andra problem, och vi kommer snart att göra en separat version för att lösa dem. +Detta är en snabbkorrigeringsversion som fixar YouTube-fel: +• [YouTube] Fixat att ingen videoinformation laddades, fixat HTTP 403 när videor spelas och återställde uppspelningen av några åldersbegränsade videor +• Fixade att undertexts storleken inte ändrades +• Fixade att information laddades ner två gånger vid öppning av en ström +• [Soundcloud] Tog bort ospelbara DRM-skyddade strömmar +• Uppdaterade översättningar diff --git a/fastlane/metadata/android/zh-Hans/changelogs/1000.txt b/fastlane/metadata/android/zh-Hans/changelogs/1000.txt new file mode 100644 index 000000000..69ceb255e --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/1000.txt @@ -0,0 +1,13 @@ +改进 +• 使播放列表简介可点击以显示更多或更少的内容 +• [PeerTube] 自动接管 `subscribeto.me` 链接 +• 在历史记录页面中仅开始播放单一项目 + +修复 +• 修复 RSS 按钮的可见度 +• 修复进度预览可能引起的崩溃 +• 修复播放列表中缺少缩略图的项目 +• 修复在下载弹窗出现之前退出的问题 +• 修复相关项目列表排序弹出 +• 修复新增至播放列表的菜单项顺序 +• 调整播放列表书签项目的布局 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 45724203b..104d69d81 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,7 +54,7 @@ swiperefreshlayout = "1.1.0" # This works thanks to JitPack: https://jitpack.io/ teamnewpipe-filepicker = "5.0.0" teamnewpipe-nanojson = "1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751" -teamnewpipe-newpipe-extractor = "9f83b385a" +teamnewpipe-newpipe-extractor = "0b99100db" webkit = "1.9.0" work = "2.10.0"