From 24cf19710fae23a1a1594c2dc7ec2fadb2796ff2 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 9 Jun 2022 11:34:57 -0400 Subject: [PATCH 001/152] Clean up proguard file --- app/proguard-rules.pro | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 4a54d8992..5e10d3916 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,7 +18,6 @@ -dontobfuscate -keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; } --keep class org.ocpsoft.prettytime.i18n.** { *; } -keep class org.mozilla.javascript.** { *; } @@ -26,9 +25,6 @@ -keep class com.google.android.exoplayer2.** { *; } -dontwarn org.mozilla.javascript.tools.** --dontwarn android.arch.util.paging.CountedDataSource --dontwarn android.arch.persistence.room.paging.LimitOffsetDataSource - # Rules for icepick. Copy paste from https://github.com/frankiesardo/icepick -dontwarn icepick.** @@ -39,12 +35,11 @@ } -keepnames class * { @icepick.State *;} -# Rules for OkHttp. Copy paste from https://github.com/square/okhttp +## Rules for OkHttp. Copy paste from https://github.com/square/okhttp -dontwarn okhttp3.** -dontwarn okio.** --dontwarn javax.annotation.** -# A resource is loaded with a relative path so the package of this class must be preserved. --keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase +## + -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; !static !transient ; From bd19013771d40986eb93f3342623aced9dd8b0ac Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 21 Jun 2022 21:44:00 +0200 Subject: [PATCH 002/152] Updated extractor to latest revision --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 995dae6ed..a56dc1611 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:1b51eab664ec7cbd2295c96d8b43000379cd1b7b' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:a26bcc55c47f169e21c994f28b22f8edcb6f19bf' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" From 04e8e03d8f9de40116029b4514501075c0cfe520 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 21 Jun 2022 21:45:10 +0200 Subject: [PATCH 003/152] Fix compile errors --- .../org/schabi/newpipe/error/ErrorInfo.kt | 8 ++-- .../schabi/newpipe/error/ErrorPanelHelper.kt | 3 +- .../holder/LocalPlaylistStreamItemHolder.java | 6 +-- .../LocalStatisticStreamItemHolder.java | 15 +++---- .../holder/RemotePlaylistItemHolder.java | 8 ++-- .../SubscriptionsImportFragment.java | 14 +++---- .../playqueue/PlayQueueItemBuilder.java | 4 +- .../settings/tabs/ChooseTabsFragment.java | 39 ++++++++----------- .../schabi/newpipe/util/ServiceHelper.java | 38 +++++++++++------- 9 files changed, 69 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index b2ba912ec..f9f9f003a 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -7,13 +7,13 @@ import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.schabi.newpipe.R import org.schabi.newpipe.extractor.Info -import org.schabi.newpipe.extractor.NewPipe import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException import org.schabi.newpipe.extractor.exceptions.ExtractionException import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException import org.schabi.newpipe.ktx.isNetworkRelated +import org.schabi.newpipe.util.ServiceHelper import java.io.PrintWriter import java.io.StringWriter @@ -65,7 +65,7 @@ class ErrorInfo( constructor(throwable: Throwable, userAction: UserAction, request: String) : this(throwable, userAction, SERVICE_NONE, request) constructor(throwable: Throwable, userAction: UserAction, request: String, serviceId: Int) : - this(throwable, userAction, NewPipe.getNameOfService(serviceId), request) + this(throwable, userAction, ServiceHelper.getNameOfServiceById(serviceId), request) constructor(throwable: Throwable, userAction: UserAction, request: String, info: Info?) : this(throwable, userAction, getInfoServiceName(info), request) @@ -73,7 +73,7 @@ class ErrorInfo( constructor(throwable: List, userAction: UserAction, request: String) : this(throwable, userAction, SERVICE_NONE, request) constructor(throwable: List, userAction: UserAction, request: String, serviceId: Int) : - this(throwable, userAction, NewPipe.getNameOfService(serviceId), request) + this(throwable, userAction, ServiceHelper.getNameOfServiceById(serviceId), request) constructor(throwable: List, userAction: UserAction, request: String, info: Info?) : this(throwable, userAction, getInfoServiceName(info), request) @@ -95,7 +95,7 @@ class ErrorInfo( Array(throwable.size) { i -> getStackTrace(throwable[i]) } private fun getInfoServiceName(info: Info?) = - if (info == null) SERVICE_NONE else NewPipe.getNameOfService(info.serviceId) + if (info == null) SERVICE_NONE else ServiceHelper.getNameOfServiceById(info.serviceId) @StringRes private fun getMessageStringId( diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt index 692cb427a..15343f53d 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt @@ -15,7 +15,6 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.Disposable import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R -import org.schabi.newpipe.extractor.NewPipe import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException @@ -106,7 +105,7 @@ class ErrorPanelHelper( if (!isNullOrEmpty((errorInfo.throwable as AccountTerminatedException).message)) { errorServiceInfoTextView.text = context.resources.getString( R.string.service_provides_reason, - NewPipe.getNameOfService(ServiceHelper.getSelectedServiceId(context)) + ServiceHelper.getSelectedService(context)?.serviceInfo?.name ?: "" ) errorServiceInfoTextView.isVisible = true diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java index 561cde560..d39758326 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java @@ -11,12 +11,12 @@ import androidx.core.content.ContextCompat; import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.PicassoHelper; +import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.views.AnimatedProgressBar; import java.time.format.DateTimeFormatter; @@ -59,7 +59,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { itemVideoTitleView.setText(item.getStreamEntity().getTitle()); itemAdditionalDetailsView.setText(Localization .concatenateStrings(item.getStreamEntity().getUploader(), - NewPipe.getNameOfService(item.getStreamEntity().getServiceId()))); + ServiceHelper.getNameOfServiceById(item.getStreamEntity().getServiceId()))); if (item.getStreamEntity().getDuration() > 0) { itemDurationView.setText(Localization diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java index d2fe8b40f..0d88eecba 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java @@ -11,12 +11,12 @@ import androidx.core.content.ContextCompat; import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.stream.StreamStatisticsEntry; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.PicassoHelper; +import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.views.AnimatedProgressBar; import java.time.format.DateTimeFormatter; @@ -70,11 +70,12 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder { private String getStreamInfoDetailLine(final StreamStatisticsEntry entry, final DateTimeFormatter dateTimeFormatter) { - final String watchCount = Localization - .shortViewCount(itemBuilder.getContext(), entry.getWatchCount()); - final String uploadDate = dateTimeFormatter.format(entry.getLatestAccessDate()); - final String serviceName = NewPipe.getNameOfService(entry.getStreamEntity().getServiceId()); - return Localization.concatenateStrings(watchCount, uploadDate, serviceName); + return Localization.concatenateStrings( + // watchCount + Localization.shortViewCount(itemBuilder.getContext(), entry.getWatchCount()), + dateTimeFormatter.format(entry.getLatestAccessDate()), + // serviceName + ServiceHelper.getNameOfServiceById(entry.getStreamEntity().getServiceId())); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index 440353ac7..70987a6fc 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -5,11 +5,11 @@ import android.view.ViewGroup; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.PicassoHelper; +import org.schabi.newpipe.util.ServiceHelper; import java.time.format.DateTimeFormatter; @@ -39,9 +39,9 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { // Here is where the uploader name is set in the bookmarked playlists library if (!TextUtils.isEmpty(item.getUploader())) { itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), - NewPipe.getNameOfService(item.getServiceId()))); + ServiceHelper.getNameOfServiceById(item.getServiceId()))); } else { - itemUploaderView.setText(NewPipe.getNameOfService(item.getServiceId())); + itemUploaderView.setText(ServiceHelper.getNameOfServiceById(item.getServiceId())); } PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java index 4737fa14f..56972b60d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java @@ -1,5 +1,11 @@ package org.schabi.newpipe.local.subscription; +import static org.schabi.newpipe.extractor.subscription.SubscriptionExtractor.ContentSource.CHANNEL_URL; +import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.CHANNEL_URL_MODE; +import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.INPUT_STREAM_MODE; +import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE; +import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE; + import android.app.Activity; import android.content.Intent; import android.os.Bundle; @@ -40,12 +46,6 @@ import java.util.List; import icepick.State; -import static org.schabi.newpipe.extractor.subscription.SubscriptionExtractor.ContentSource.CHANNEL_URL; -import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.CHANNEL_URL_MODE; -import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.INPUT_STREAM_MODE; -import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE; -import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE; - public class SubscriptionsImportFragment extends BaseFragment { @State int currentServiceId = Constants.NO_SERVICE_ID; @@ -89,7 +89,7 @@ public class SubscriptionsImportFragment extends BaseFragment { if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) { ErrorUtil.showSnackbar(activity, new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT, - NewPipe.getNameOfService(currentServiceId), + ServiceHelper.getNameOfServiceById(currentServiceId), "Service does not support importing subscriptions", R.string.general_error)); activity.finish(); diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java index f2e98d866..e7aeb9638 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java @@ -5,9 +5,9 @@ import android.text.TextUtils; import android.view.MotionEvent; import android.view.View; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.PicassoHelper; +import org.schabi.newpipe.util.ServiceHelper; public class PlayQueueItemBuilder { private static final String TAG = PlayQueueItemBuilder.class.toString(); @@ -25,7 +25,7 @@ public class PlayQueueItemBuilder { holder.itemVideoTitleView.setText(item.getTitle()); } holder.itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getUploader(), - NewPipe.getNameOfService(item.getServiceId()))); + ServiceHelper.getNameOfServiceById(item.getServiceId()))); if (item.getDuration() > 0) { holder.itemDurationView.setText(Localization.getDurationString(item.getDuration())); diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index 73aec4a7b..289c824ba 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -1,5 +1,8 @@ package org.schabi.newpipe.settings.tabs; +import static org.schabi.newpipe.settings.tabs.Tab.typeFrom; +import static org.schabi.newpipe.util.ServiceHelper.getNameOfServiceById; + import android.annotation.SuppressLint; import android.app.Dialog; import android.content.Context; @@ -28,7 +31,6 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.settings.SelectChannelFragment; import org.schabi.newpipe.settings.SelectKioskFragment; import org.schabi.newpipe.settings.SelectPlaylistFragment; @@ -39,8 +41,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static org.schabi.newpipe.settings.tabs.Tab.typeFrom; - public class ChooseTabsFragment extends Fragment { private TabsManager tabsManager; @@ -374,36 +374,31 @@ public class ChooseTabsFragment extends Fragment { return; } - final String tabName; + tabNameView.setText(getTabName(type, tab)); + tabIconView.setImageResource(tab.getTabIconRes(requireContext())); + } + + private String getTabName(@NonNull final Tab.Type type, @NonNull final Tab tab) { switch (type) { case BLANK: - tabName = getString(R.string.blank_page_summary); - break; + return getString(R.string.blank_page_summary); case DEFAULT_KIOSK: - tabName = getString(R.string.default_kiosk_page_summary); - break; + return getString(R.string.default_kiosk_page_summary); case KIOSK: - tabName = NewPipe.getNameOfService(((Tab.KioskTab) tab) - .getKioskServiceId()) + "/" + tab.getTabName(requireContext()); - break; + return getNameOfServiceById(((Tab.KioskTab) tab).getKioskServiceId()) + + "/" + tab.getTabName(requireContext()); case CHANNEL: - tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab) - .getChannelServiceId()) + "/" + tab.getTabName(requireContext()); - break; + return getNameOfServiceById(((Tab.ChannelTab) tab).getChannelServiceId()) + + "/" + tab.getTabName(requireContext()); case PLAYLIST: final int serviceId = ((Tab.PlaylistTab) tab).getPlaylistServiceId(); final String serviceName = serviceId == -1 ? getString(R.string.local) - : NewPipe.getNameOfService(serviceId); - tabName = serviceName + "/" + tab.getTabName(requireContext()); - break; + : getNameOfServiceById(serviceId); + return serviceName + "/" + tab.getTabName(requireContext()); default: - tabName = tab.getTabName(requireContext()); - break; + return tab.getTabName(requireContext()); } - - tabNameView.setText(tabName); - tabIconView.setImageResource(tab.getTabIconRes(requireContext())); } @SuppressLint("ClickableViewAccessibility") diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index bf173e08d..386c1c912 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -1,9 +1,12 @@ package org.schabi.newpipe.util; +import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; + import android.content.Context; import android.content.SharedPreferences; import androidx.annotation.DrawableRes; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.preference.PreferenceManager; @@ -18,9 +21,10 @@ import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; +import java.util.Optional; import java.util.concurrent.TimeUnit; -import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; +import javax.annotation.Nonnull; public final class ServiceHelper { private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube; @@ -113,18 +117,32 @@ public final class ServiceHelper { } public static int getSelectedServiceId(final Context context) { + return Optional.ofNullable(getSelectedService(context)) + .orElse(DEFAULT_FALLBACK_SERVICE) + .getServiceId(); + } + + @Nullable + public static StreamingService getSelectedService(final Context context) { final String serviceName = PreferenceManager.getDefaultSharedPreferences(context) .getString(context.getString(R.string.current_service_key), context.getString(R.string.default_service_value)); - int serviceId; try { - serviceId = NewPipe.getService(serviceName).getServiceId(); + return NewPipe.getService(serviceName); } catch (final ExtractionException e) { - serviceId = DEFAULT_FALLBACK_SERVICE.getServiceId(); + return null; } + } - return serviceId; + @Nonnull + public static String getNameOfServiceById(final int serviceId) { + return ServiceList.all().stream() + .filter(s -> s.getServiceId() == serviceId) + .findFirst() + .map(StreamingService::getServiceInfo) + .map(StreamingService.ServiceInfo::getName) + .orElse(""); } public static void setSelectedServiceId(final Context context, final int serviceId) { @@ -138,16 +156,6 @@ public final class ServiceHelper { setSelectedServicePreferences(context, serviceName); } - public static void setSelectedServiceId(final Context context, final String serviceName) { - final int serviceId = NewPipe.getIdOfService(serviceName); - if (serviceId == -1) { - setSelectedServicePreferences(context, - DEFAULT_FALLBACK_SERVICE.getServiceInfo().getName()); - } else { - setSelectedServicePreferences(context, serviceName); - } - } - private static void setSelectedServicePreferences(final Context context, final String serviceName) { PreferenceManager.getDefaultSharedPreferences(context).edit(). From ad97b3d995058302de2d76a0fe5b6a4c3d98a844 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 22 Jun 2022 19:54:46 +0200 Subject: [PATCH 004/152] Use correct ``NonNull`` --- app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index 386c1c912..b13ae4a97 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -6,6 +6,7 @@ import android.content.Context; import android.content.SharedPreferences; import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.preference.PreferenceManager; @@ -24,8 +25,6 @@ import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import java.util.Optional; import java.util.concurrent.TimeUnit; -import javax.annotation.Nonnull; - public final class ServiceHelper { private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube; @@ -135,7 +134,7 @@ public final class ServiceHelper { } } - @Nonnull + @NonNull public static String getNameOfServiceById(final int serviceId) { return ServiceList.all().stream() .filter(s -> s.getServiceId() == serviceId) From f22417e7e7b01bd04c37eb2c3e57c4cfa273a6fe Mon Sep 17 00:00:00 2001 From: Carlos Melero <10256660+carmebar@users.noreply.github.com> Date: Fri, 24 Jun 2022 18:03:48 +0200 Subject: [PATCH 005/152] Add option to hide future videos in feed --- .../schabi/newpipe/local/feed/FeedFragment.kt | 16 +++++++ .../newpipe/local/feed/FeedViewModel.kt | 43 +++++++++++++++---- .../main/res/drawable/ic_history_future.xml | 15 +++++++ app/src/main/res/menu/menu_feed_fragment.xml | 11 ++++- app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 1 + 6 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 app/src/main/res/drawable/ic_history_future.xml diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index b291aa035..f0ebabd85 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -98,6 +98,7 @@ class FeedFragment : BaseStateFragment() { private lateinit var groupAdapter: GroupieAdapter @State @JvmField var showPlayedItems: Boolean = true + @State @JvmField var showFutureItems: Boolean = true private var onSettingsChangeListener: SharedPreferences.OnSharedPreferenceChangeListener? = null private var updateListViewModeOnResume = false @@ -137,6 +138,7 @@ class FeedFragment : BaseStateFragment() { val factory = FeedViewModel.Factory(requireContext(), groupId) viewModel = ViewModelProvider(this, factory).get(FeedViewModel::class.java) showPlayedItems = viewModel.getShowPlayedItemsFromPreferences() + showFutureItems = viewModel.getShowFutureItemsFromPreferences() viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(::handleResult) } groupAdapter = GroupieAdapter().apply { @@ -212,6 +214,7 @@ class FeedFragment : BaseStateFragment() { inflater.inflate(R.menu.menu_feed_fragment, menu) updateTogglePlayedItemsButton(menu.findItem(R.id.menu_item_feed_toggle_played_items)) + updateToggleFutureItemsButton(menu.findItem(R.id.menu_item_feed_toggle_future_items)) } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -241,6 +244,11 @@ class FeedFragment : BaseStateFragment() { updateTogglePlayedItemsButton(item) viewModel.togglePlayedItems(showPlayedItems) viewModel.saveShowPlayedItemsToPreferences(showPlayedItems) + } else if (item.itemId == R.id.menu_item_feed_toggle_future_items) { + showFutureItems = !item.isChecked + updateToggleFutureItemsButton(item) + viewModel.toggleFutureItems(showFutureItems) + viewModel.saveShowFutureItemsToPreferences(showFutureItems) } return super.onOptionsItemSelected(item) @@ -280,6 +288,14 @@ class FeedFragment : BaseStateFragment() { ) } + private fun updateToggleFutureItemsButton(menuItem: MenuItem) { + menuItem.isChecked = showFutureItems + menuItem.icon = AppCompatResources.getDrawable( + requireContext(), + if (showFutureItems) R.drawable.ic_history_future else R.drawable.ic_history + ) + } + // ////////////////////////////////////////////////////////////////////////// // Handling // ////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index e21963c16..87409ddae 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -9,7 +9,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.functions.Function4 +import io.reactivex.rxjava3.functions.Function5 import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.schedulers.Schedulers import org.schabi.newpipe.R @@ -28,7 +28,8 @@ import java.util.concurrent.TimeUnit class FeedViewModel( private val applicationContext: Context, groupId: Long = FeedGroupEntity.GROUP_ALL_ID, - initialShowPlayedItems: Boolean = true + initialShowPlayedItems: Boolean = true, + initialShowFutureItems: Boolean = true ) : ViewModel() { private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext) @@ -37,6 +38,11 @@ class FeedViewModel( .startWithItem(initialShowPlayedItems) .distinctUntilChanged() + private val toggleShowFutureItems = BehaviorProcessor.create() + private val toggleShowFutureItemsFlowable = toggleShowFutureItems + .startWithItem(initialShowFutureItems) + .distinctUntilChanged() + private val mutableStateLiveData = MutableLiveData() val stateLiveData: LiveData = mutableStateLiveData @@ -44,22 +50,24 @@ class FeedViewModel( .combineLatest( FeedEventManager.events(), toggleShowPlayedItemsFlowable, + toggleShowFutureItemsFlowable, feedDatabaseManager.notLoadedCount(groupId), feedDatabaseManager.oldestSubscriptionUpdate(groupId), - Function4 { t1: FeedEventManager.Event, t2: Boolean, - t3: Long, t4: List -> - return@Function4 CombineResultEventHolder(t1, t2, t3, t4.firstOrNull()) + Function5 { t1: FeedEventManager.Event, t2: Boolean, t3: Boolean, + t4: Long, t5: List -> + return@Function5 CombineResultEventHolder(t1, t2, t3, t4, t5.firstOrNull()) } ) .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) - .map { (event, showPlayedItems, notLoadedCount, oldestUpdate) -> + .map { (event, showPlayedItems, showFutureItems, notLoadedCount, oldestUpdate) -> val streamItems = if (event is SuccessResultEvent || event is IdleEvent) feedDatabaseManager .getStreams(groupId, showPlayedItems) .blockingGet(arrayListOf()) + .filter { s -> showFutureItems || s.stream.uploadDate?.isBefore(OffsetDateTime.now()) ?: true } else arrayListOf() @@ -89,8 +97,9 @@ class FeedViewModel( private data class CombineResultEventHolder( val t1: FeedEventManager.Event, val t2: Boolean, - val t3: Long, - val t4: OffsetDateTime? + val t3: Boolean, + val t4: Long, + val t5: OffsetDateTime? ) private data class CombineResultDataHolder( @@ -112,10 +121,25 @@ class FeedViewModel( fun getShowPlayedItemsFromPreferences() = getShowPlayedItemsFromPreferences(applicationContext) + fun toggleFutureItems(showFutureItems: Boolean) { + toggleShowFutureItems.onNext(showFutureItems) + } + + fun saveShowFutureItemsToPreferences(showFutureItems: Boolean) = + PreferenceManager.getDefaultSharedPreferences(applicationContext).edit { + this.putBoolean(applicationContext.getString(R.string.feed_show_future_items_key), showFutureItems) + this.apply() + } + + fun getShowFutureItemsFromPreferences() = getShowFutureItemsFromPreferences(applicationContext) + companion object { private fun getShowPlayedItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(context.getString(R.string.feed_show_played_items_key), true) + private fun getShowFutureItemsFromPreferences(context: Context) = + PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.feed_show_future_items_key), true) } class Factory( @@ -128,7 +152,8 @@ class FeedViewModel( context.applicationContext, groupId, // Read initial value from preferences - getShowPlayedItemsFromPreferences(context.applicationContext) + getShowPlayedItemsFromPreferences(context.applicationContext), + getShowFutureItemsFromPreferences(context.applicationContext) ) as T } } diff --git a/app/src/main/res/drawable/ic_history_future.xml b/app/src/main/res/drawable/ic_history_future.xml new file mode 100644 index 000000000..db6f2acbf --- /dev/null +++ b/app/src/main/res/drawable/ic_history_future.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/menu/menu_feed_fragment.xml b/app/src/main/res/menu/menu_feed_fragment.xml index 7a948ea8a..f6929a914 100644 --- a/app/src/main/res/menu/menu_feed_fragment.xml +++ b/app/src/main/res/menu/menu_feed_fragment.xml @@ -4,13 +4,22 @@ + + feed_update_threshold_key 300 feed_show_played_items + feed_show_future_items show_thumbnail_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a1c220f0..1d65b6fba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -747,4 +747,5 @@ Select quality for external players Unknown format Unknown quality + Show future videos \ No newline at end of file From d5985be94a7c631e21aa7e0c160013508d138c16 Mon Sep 17 00:00:00 2001 From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Date: Sat, 25 Jun 2022 22:13:54 +0000 Subject: [PATCH 006/152] Made some much needed changes to the ReadMe (#8372) Co-authored-by: Mohammed Anas Co-authored-by: Poolitzer --- README.md | 122 +++++++++++++++++++++++++++--------------------------- 1 file changed, 60 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index c47f8c2f4..d3a34816d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

NewPipe

-

A libre lightweight streaming frontend for Android.

+

A libre lightweight streaming front-end for Android.

Get it on F-Droid

@@ -13,15 +13,15 @@


-

ScreenshotsDescriptionFeaturesInstallation and updatesContributionDonateLicense

+

ScreenshotsSupported ServicesDescriptionFeaturesInstallation and updatesContributionDonateLicense

WebsiteBlogFAQPress


*Read this in other languages: [English](README.md), [Español](doc/README.es.md), [हिन्दी](doc/README.hi.md), [한국어](doc/README.ko.md), [Soomaali](doc/README.so.md), [Português Brasil](doc/README.pt_BR.md), [Polski](doc/README.pl.md), [日本語](doc/README.ja.md), [Română](doc/README.ro.md), [Türkçe](doc/README.tr.md), [正體中文](doc/README.zh_TW.md).* -WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY. +WARNING: THIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN OUR GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE. -PUTTING NEWPIPE OR ANY FORK OF IT INTO THE GOOGLE PLAY STORE VIOLATES THEIR TERMS AND CONDITIONS. +PUTTING NEWPIPE, OR ANY FORK OF IT, INTO THE GOOGLE PLAY STORE VIOLATES THEIR TERMS AND CONDITIONS. ## Screenshots @@ -38,50 +38,53 @@ [](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png) [](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png) +### Supported Services + +NewPipe currently supports these services: + + +* YouTube ([website](https://www.youtube.com/)) and YouTube Music ([website](https://music.youtube.com/)) ([wiki](https://en.wikipedia.org/wiki/YouTube)) +* PeerTube ([website](https://joinpeertube.org/)) and all its instances (open the website to know what that means!) ([wiki](https://en.wikipedia.org/wiki/PeerTube)) +* Bandcamp ([website](https://bandcamp.com/)) ([wiki](https://en.wikipedia.org/wiki/Bandcamp)) +* SoundCloud ([website](https://soundcloud.com/)) ([wiki](https://en.wikipedia.org/wiki/SoundCloud)) +* media.ccc.de ([website](https://media.ccc.de/)) ([wiki](https://en.wikipedia.org/wiki/Chaos_Computer_Club)) + +As you can see, NewPipe supports multiple video and audio services. Though it started off with YouTube, other people have added more services over the years, making NewPipe more and more versatile! + +Partially due to circumstance, and partially due to its popularity, YouTube is the best supported out of these services. If you use or are familiar with any of these other services, please help us improve support for them! We're looking for maintainers for SoundCloud and PeerTube. + +If you intend to add a new service, please get in touch with us first! Our [docs](https://teamnewpipe.github.io/documentation/) provide more information on how a new service can be added to the app and to the [NewPipe Extractor](https://github.com/TeamNewPipe/NewPipeExtractor). + ## Description -NewPipe does not use any Google framework libraries, nor the YouTube API. Websites are only parsed to fetch required info, so this app can be used on devices without Google services installed. Also, you don't need a YouTube account to use NewPipe, which is copylefted libre software. +NewPipe works by fetching the required data from the official API (e.g. PeerTube) of the service you're using. If the official API is restricted (e.g. YouTube) for our purposes, or is proprietary, the app parses the website or uses an internal API instead. This means that you don't need an account on any service to use NewPipe. + +Also, since they are free and open source software, neither the app nor the Extractor use any proprietary libraries or frameworks, such as Google Play Services. This means you can use NewPipe on devices or custom ROMs that do not have Google apps installed. ### Features -* Search videos -* No Login Required -* Display general info about videos -* Watch YouTube videos -* Listen to YouTube videos -* Popup mode (floating player) -* Select streaming player to watch video with -* Download videos -* Download audio only -* Open a video in Kodi -* Show next/related videos -* Search YouTube in a specific language -* Watch/Block age restricted material -* Display general info about channels -* Search channels -* Watch videos from a channel -* Orbot/Tor support (not yet directly) -* 1080p/2K/4K support -* View history -* Subscribe to channels -* Search history -* Search/watch playlists -* Watch as enqueued playlists -* Enqueue videos -* Local playlists -* Subtitles -* Livestream support -* Show comments - -### Supported Services - -NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/documentation/) provide more info on how a new service can be added to the app and the extractor. Please get in touch with us if you intend to add a new one. Currently supported services are: - -* YouTube -* SoundCloud \[beta\] -* media.ccc.de \[beta\] -* PeerTube instances \[beta\] -* Bandcamp \[beta\] +* Watch videos at resolutions up to 4K +* Listen to audio in the background, only loading the audio stream to save data +* Popup mode (floating player, aka Picture-in-Picture) +* Watch live streams +* Show/hide subtitles/closed captions +* Search videos and audios (on YouTube, you can specify the content language as well) +* Enqueue videos (and optionally save them as local playlists) +* Show/hide general information about videos (such as description and tags) +* Show/hide next/related videos +* Show/hide comments +* Search videos, audios, channels, playlists and albums +* Browse videos and audios within a channel +* Subscribe to channels (yes, without logging into any account!) +* Get notifications about new videos from channels you're subscribed to +* Create and edit channel groups (for easier browsing and management) +* Browse video feeds generated from your channel groups +* View and search your watch history +* Search and watch playlists (these are remote playlists, which means they're fetched from the service you're browsing) +* Create and edit local playlists (these are created and saved within the app, and have nothing to do with any service) +* Download videos/audios/subtitles (closed captions) +* Open in Kodi +* Watch/Block age-restricted material @@ -90,10 +93,11 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc You can install NewPipe using one of the following methods: 1. Add our custom repo to F-Droid and install it from there. The instructions are here: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/ 2. Download the APK from [GitHub Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it. - 3. Update via F-Droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, then push the update to users. + 3. Update via F-Droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, and then push the update to users. 4. Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods. + 5. If you're interested in a specific feature or bugfix provided in a Pull Request in this repo, you can also download its APK from within the PR. Read the PR description for instructions. The great thing about PR-specific APKs is that they're installed side-by-side the official app, so you don't have to worry about losing your data or messing anything up. -We recommend method 1 for most users. APKs installed using method 1 or 2 are compatible with each other, but not with those installed using method 3. This is due to the same signing key (ours) being used for 1 and 2, but a different signing key (F-Droid's) being used for 3. Building a debug APK using method 4 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app. +We recommend method 1 for most users. APKs installed using method 1 or 2 are compatible with each other (meaning that if you installed NewPipe using either method 1 or 2, you can also update NewPipe using the other), but not with those installed using method 3. This is due to the same signing key (ours) being used for 1 and 2, but a different signing key (F-Droid's) being used for 3. Building a debug APK using method 4 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app. When using method 5, each APK is signed with a different random key supplied by GitHub Actions, so you cannot even update it. You will have to backup and restore the app data each time you wish to use a new APK. In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality breaks and F-Droid doesn't have the latest update yet), we recommend following this procedure: 1. Back up your data via Settings > Content > Export Database so you keep your history, subscriptions, and playlists @@ -101,30 +105,29 @@ In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's 3. Download the APK from the new source and install it 4. Import the data from step 1 via Settings > Content > Import Database -## Contribution -Whether you have ideas, translations, design changes, code cleaning, or real heavy code changes, help is always welcome. -The more is done the better it gets! +Note: when you're importing a database into the official app, always make sure that it is the one you exported _from_ the official app. If you import a database exported from an APK other than the official app, it may break things. Such an action is unsupported, and you should only do so when you're absolutely certain you know what you're doing. -If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md). +## Contribution +Whether you have ideas, translations, design changes, code cleaning, or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small! If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md). Translation status ## Donate -If you like NewPipe we'd be happy about a donation. You can either send bitcoin or donate via Bountysource or Liberapay. For further info on donating to NewPipe, please visit our [website](https://newpipe.net/donate). +If you like NewPipe, you're welcome to send a donation. We prefer Liberapay, as it is both open-source and non-profit. For further info on donating to NewPipe, please visit our [website](https://newpipe.net/donate). - - - - - + + + + + @@ -134,14 +137,9 @@ If you like NewPipe we'd be happy about a donation. You can either send bitcoin ## Privacy Policy -The NewPipe project aims to provide a private, anonymous experience for using media web services. -Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report, or comment in our blog. You can find the document [here](https://newpipe.net/legal/privacy/). +The NewPipe project aims to provide a private, anonymous experience for using web-based media services. Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report, or leave a comment in our blog. You can find the document [here](https://newpipe.net/legal/privacy/). ## License [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html) -NewPipe is Free Software: You can use, study, share, and improve it at -will. Specifically you can redistribute and/or modify it under the terms of the -[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as -published by the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +NewPipe is Free Software: You can use, study, share, and improve it at will. Specifically you can redistribute and/or modify it under the terms of the [GNU General Public License](https://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. From 1404581e9b94ca85e50371dc700ffd22e204e19d Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sat, 25 Jun 2022 21:14:42 -0400 Subject: [PATCH 007/152] Update ExoPlayer to 2.18.0 --- app/build.gradle | 2 +- .../org/schabi/newpipe/player/Player.java | 52 ++++++++----------- .../datasource/YoutubeHttpDataSource.java | 2 +- .../player/mediaitem/MediaItemTag.java | 7 ++- .../playback/SurfaceHolderCallback.java | 16 +++--- .../player/resolver/PlaybackResolver.java | 15 +++--- 6 files changed, 46 insertions(+), 48 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 995dae6ed..3c12f8fde 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -105,7 +105,7 @@ ext { androidxWorkVersion = '2.7.1' icepickVersion = '3.2.0' - exoPlayerVersion = '2.17.1' + exoPlayerVersion = '2.18.0' googleAutoServiceVersion = '1.0.1' groupieVersion = '2.10.1' markwonVersion = '4.6.2' 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 b2c8836e5..332e12744 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -116,7 +116,6 @@ import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.view.ContextThemeWrapper; import androidx.appcompat.widget.AppCompatImageButton; import androidx.appcompat.widget.PopupMenu; -import androidx.collection.ArraySet; import androidx.core.content.ContextCompat; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; @@ -135,9 +134,9 @@ import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player.PositionInfo; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.TracksInfo; +import com.google.android.exoplayer2.Tracks; import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; @@ -2506,12 +2505,12 @@ public final class Player implements } @Override - public void onTracksInfoChanged(@NonNull final TracksInfo tracksInfo) { + public void onTracksChanged(@NonNull final Tracks tracks) { if (DEBUG) { Log.d(TAG, "ExoPlayer - onTracksChanged(), " - + "track group size = " + tracksInfo.getTrackGroupInfos().size()); + + "track group size = " + tracks.getGroups().size()); } - onTextTracksChanged(tracksInfo); + onTextTracksChanged(tracks); } @Override @@ -2577,8 +2576,8 @@ public final class Player implements } @Override - public void onCues(@NonNull final List cues) { - binding.subtitleView.onCues(cues); + public void onCues(@NonNull final CueGroup cueGroup) { + binding.subtitleView.setCues(cueGroup.cues); } //endregion @@ -3665,34 +3664,35 @@ public final class Player implements binding.subtitleView.setStyle(captionStyle); } - private void onTextTracksChanged(@NonNull final TracksInfo currentTrackInfo) { + private void onTextTracksChanged(@NonNull final Tracks currentTrack) { if (binding == null) { return; } - if (trackSelector.getCurrentMappedTrackInfo() == null - || !currentTrackInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_TEXT)) { + final boolean trackTypeTextSupported = !currentTrack.containsType(C.TRACK_TYPE_TEXT) + || currentTrack.isTypeSupported(C.TRACK_TYPE_TEXT, false); + if (trackSelector.getCurrentMappedTrackInfo() == null || !trackTypeTextSupported) { binding.captionTextView.setVisibility(View.GONE); return; } // Extract all loaded languages - final List textTracks = currentTrackInfo - .getTrackGroupInfos() + final List textTracks = currentTrack + .getGroups() .stream() - .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getTrackType()) + .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) .collect(Collectors.toList()); final List availableLanguages = textTracks.stream() - .map(TracksInfo.TrackGroupInfo::getTrackGroup) + .map(Tracks.Group::getMediaTrackGroup) .filter(textTrack -> textTrack.length > 0) .map(textTrack -> textTrack.getFormat(0).language) .collect(Collectors.toList()); // Find selected text track final Optional selectedTracks = textTracks.stream() - .filter(TracksInfo.TrackGroupInfo::isSelected) - .filter(info -> info.getTrackGroup().length >= 1) - .map(info -> info.getTrackGroup().getFormat(0)) + .filter(Tracks.Group::isSelected) + .filter(info -> info.getMediaTrackGroup().length >= 1) + .map(info -> info.getMediaTrackGroup().getFormat(0)) .findFirst(); // Build UI @@ -4240,20 +4240,12 @@ public final class Player implements return; } - final DefaultTrackSelector.ParametersBuilder parametersBuilder = + final DefaultTrackSelector.Parameters.Builder parametersBuilder = trackSelector.buildUponParameters(); - if (videoEnabled) { - // Enable again the video track and the subtitles, if there is one selected - parametersBuilder.setDisabledTrackTypes(Collections.emptySet()); - } else { - // Disable the video track and the ability to select subtitles - // Use an ArraySet because we can't use Set.of() on all supported APIs by the app - final ArraySet disabledTracks = new ArraySet<>(); - disabledTracks.add(C.TRACK_TYPE_TEXT); - disabledTracks.add(C.TRACK_TYPE_VIDEO); - parametersBuilder.setDisabledTrackTypes(disabledTracks); - } + // Enable/disable the video track and the ability to select subtitles + parametersBuilder.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, !videoEnabled); + parametersBuilder.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, !videoEnabled); trackSelector.setParameters(parametersBuilder); } diff --git a/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java b/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java index c9abe65f6..2eecddf36 100644 --- a/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java @@ -1,5 +1,5 @@ /* - * Based on ExoPlayer's DefaultHttpDataSource, version 2.17.1. + * Based on ExoPlayer's DefaultHttpDataSource, version 2.18.0. * * Original source code copyright (C) 2016 The Android Open Source Project, licensed under the * Apache License, Version 2.0. diff --git a/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java b/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java index f84b0383a..d23dd4574 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java @@ -3,6 +3,7 @@ package org.schabi.newpipe.player.mediaitem; import android.net.Uri; import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.MediaItem.RequestMetadata; import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.Player; @@ -76,7 +77,6 @@ public interface MediaItemTag { @NonNull default MediaItem asMediaItem() { final MediaMetadata mediaMetadata = new MediaMetadata.Builder() - .setMediaUri(Uri.parse(getStreamUrl())) .setArtworkUri(Uri.parse(getThumbnailUrl())) .setArtist(getUploaderName()) .setDescription(getTitle()) @@ -84,10 +84,15 @@ public interface MediaItemTag { .setTitle(getTitle()) .build(); + final RequestMetadata requestMetaData = new RequestMetadata.Builder() + .setMediaUri(Uri.parse(getStreamUrl())) + .build(); + return MediaItem.fromUri(getStreamUrl()) .buildUpon() .setMediaId(makeMediaId()) .setMediaMetadata(mediaMetadata) + .setRequestMetadata(requestMetaData) .setTag(this) .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java b/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java index 5d67e6967..da6cb36d4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java @@ -4,7 +4,7 @@ import android.content.Context; import android.view.SurfaceHolder; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.video.DummySurface; +import com.google.android.exoplayer2.video.PlaceholderSurface; /** * Prevent error message: 'Unrecoverable player error occurred' @@ -26,7 +26,7 @@ public final class SurfaceHolderCallback implements SurfaceHolder.Callback { private final Context context; private final Player player; - private DummySurface dummySurface; + private PlaceholderSurface placeholderSurface; public SurfaceHolderCallback(final Context context, final Player player) { this.context = context; @@ -47,16 +47,16 @@ public final class SurfaceHolderCallback implements SurfaceHolder.Callback { @Override public void surfaceDestroyed(final SurfaceHolder holder) { - if (dummySurface == null) { - dummySurface = DummySurface.newInstanceV17(context, false); + if (placeholderSurface == null) { + placeholderSurface = PlaceholderSurface.newInstanceV17(context, false); } - player.setVideoSurface(dummySurface); + player.setVideoSurface(placeholderSurface); } public void release() { - if (dummySurface != null) { - dummySurface.release(); - dummySurface = null; + if (placeholderSurface != null) { + placeholderSurface.release(); + placeholderSurface = null; } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java index 34e7e9bd1..ead127250 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java @@ -172,9 +172,10 @@ public interface PlaybackResolver extends Resolver { try { final StreamInfoTag tag = StreamInfoTag.of(info); if (!info.getHlsUrl().isEmpty()) { - return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.TYPE_HLS, tag); + return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.CONTENT_TYPE_HLS, tag); } else if (!info.getDashMpdUrl().isEmpty()) { - return buildLiveMediaSource(dataSource, info.getDashMpdUrl(), C.TYPE_DASH, tag); + return buildLiveMediaSource( + dataSource, info.getDashMpdUrl(), C.CONTENT_TYPE_DASH, tag); } } catch (final Exception e) { Log.w(TAG, "Error when generating live media source, falling back to standard sources", @@ -190,17 +191,17 @@ public interface PlaybackResolver extends Resolver { final MediaItemTag metadata) throws ResolverException { final MediaSource.Factory factory; switch (type) { - case C.TYPE_SS: + case C.CONTENT_TYPE_SS: factory = dataSource.getLiveSsMediaSourceFactory(); break; - case C.TYPE_DASH: + case C.CONTENT_TYPE_DASH: factory = dataSource.getLiveDashMediaSourceFactory(); break; - case C.TYPE_HLS: + case C.CONTENT_TYPE_HLS: factory = dataSource.getLiveHlsMediaSourceFactory(); break; - case C.TYPE_OTHER: - case C.TYPE_RTSP: + case C.CONTENT_TYPE_OTHER: + case C.CONTENT_TYPE_RTSP: default: throw new ResolverException("Unsupported type: " + type); } From 45d2492bcb16eb36069f181985c5520d235284fd Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 1 Jul 2022 23:08:44 +0200 Subject: [PATCH 008/152] Run CI on all release branches (#8565) We don't seem to agree on which character to use as a separator between `release` and `X.X.X` in release branch names (e.g. `release-0.23.1` or `release/0.23.0`). All those names start with `release`, though, so let's use that to identify releases. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 306b8c2c8..2448e0add 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: branches: - dev - master - - release/** + - release** paths-ignore: - 'README.md' - 'doc/**' From 00b9c082a38b991eeabbaf9fe80ec3de3d41e637 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sat, 2 Jul 2022 15:01:39 -0400 Subject: [PATCH 009/152] Set setUsePlatformDiagnostics to false --- app/src/main/java/org/schabi/newpipe/player/Player.java | 1 + 1 file changed, 1 insertion(+) 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 332e12744..fc2eb2917 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -512,6 +512,7 @@ public final class Player implements simpleExoPlayer = new ExoPlayer.Builder(context, renderFactory) .setTrackSelector(trackSelector) .setLoadControl(loadController) + .setUsePlatformDiagnostics(false) .build(); simpleExoPlayer.addListener(this); simpleExoPlayer.setPlayWhenReady(playOnReady); From a1f1acfbf9425d24d946cabe0af3ee718444ba0e Mon Sep 17 00:00:00 2001 From: mhmdanas Date: Sun, 3 Jul 2022 20:38:51 +0300 Subject: [PATCH 010/152] Use minimum required permissions for GitHub workflows This reduces the attack surface if the workflows are ever compromised. --- .github/workflows/ci.yml | 14 +++++++++++++- .github/workflows/image-minimizer.yml | 4 ++++ .github/workflows/no-response.yml | 4 ++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2448e0add..d9342e72a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,10 @@ on: jobs: build-and-test-jvm: runs-on: ubuntu-latest + + permissions: + contents: read + steps: - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 @@ -64,6 +68,10 @@ jobs: matrix: # api-level 19 is min sdk, but throws errors related to desugaring api-level: [ 21, 29 ] + + permissions: + contents: read + steps: - uses: actions/checkout@v3 @@ -81,7 +89,7 @@ jobs: # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160 emulator-build: 7425822 script: ./gradlew connectedCheck --stacktrace - + - name: Upload test report when tests fail # because the printed out stacktrace (console) is too short, see also #7553 uses: actions/upload-artifact@v3 if: failure() @@ -91,6 +99,10 @@ jobs: sonar: runs-on: ubuntu-latest + + permissions: + contents: read + steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/image-minimizer.yml b/.github/workflows/image-minimizer.yml index c6ab6d5b3..b8bf9e1d2 100644 --- a/.github/workflows/image-minimizer.yml +++ b/.github/workflows/image-minimizer.yml @@ -6,6 +6,10 @@ on: issues: types: [opened, edited] +permissions: + issues: write + pull-requests: write + jobs: try-minimize: runs-on: ubuntu-latest diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index 54e749dc0..6a4a8a61a 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -9,6 +9,10 @@ on: # Run daily at midnight. - cron: '0 0 * * *' +permissions: + issues: write + pull-requests: write + jobs: noResponse: runs-on: ubuntu-latest From 47a2adca960705e7901388e082afa8032f778e08 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 4 Jul 2022 21:43:31 +0200 Subject: [PATCH 011/152] Make thumbnails' scaleType fitCenter Otherwise e.g. shorts thumbnails would be cropped too much --- app/src/main/res/layout/channel_header.xml | 2 +- app/src/main/res/layout/item_stream_segment.xml | 2 +- app/src/main/res/layout/list_playlist_grid_item.xml | 2 +- app/src/main/res/layout/list_playlist_item.xml | 2 +- app/src/main/res/layout/list_playlist_mini_item.xml | 2 +- app/src/main/res/layout/list_stream_grid_item.xml | 2 +- app/src/main/res/layout/list_stream_item.xml | 2 +- app/src/main/res/layout/list_stream_mini_item.xml | 2 +- app/src/main/res/layout/list_stream_playlist_grid_item.xml | 2 +- app/src/main/res/layout/list_stream_playlist_item.xml | 2 +- app/src/main/res/layout/play_queue_item.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/layout/channel_header.xml b/app/src/main/res/layout/channel_header.xml index 86a308b9f..3365e66e2 100644 --- a/app/src/main/res/layout/channel_header.xml +++ b/app/src/main/res/layout/channel_header.xml @@ -17,7 +17,7 @@ android:layout_height="70dp" android:background="@android:color/black" android:fitsSystemWindows="true" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/channel_banner" tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/item_stream_segment.xml b/app/src/main/res/layout/item_stream_segment.xml index 3c7631788..2dc414ac9 100644 --- a/app/src/main/res/layout/item_stream_segment.xml +++ b/app/src/main/res/layout/item_stream_segment.xml @@ -17,7 +17,7 @@ android:id="@+id/previewImage" android:layout_width="0dp" android:layout_height="@dimen/play_queue_thumbnail_width" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/dummy_thumbnail" app:layout_constraintDimensionRatio="16:9" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/list_playlist_grid_item.xml b/app/src/main/res/layout/list_playlist_grid_item.xml index c68e780f9..11ececad0 100644 --- a/app/src/main/res/layout/list_playlist_grid_item.xml +++ b/app/src/main/res/layout/list_playlist_grid_item.xml @@ -17,7 +17,7 @@ android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/dummy_thumbnail_playlist" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_playlist_item.xml b/app/src/main/res/layout/list_playlist_item.xml index 6643207a9..79c0d45a1 100644 --- a/app/src/main/res/layout/list_playlist_item.xml +++ b/app/src/main/res/layout/list_playlist_item.xml @@ -18,7 +18,7 @@ android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/dummy_thumbnail_playlist" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_playlist_mini_item.xml b/app/src/main/res/layout/list_playlist_mini_item.xml index a6bab9f2c..422bc357f 100644 --- a/app/src/main/res/layout/list_playlist_mini_item.xml +++ b/app/src/main/res/layout/list_playlist_mini_item.xml @@ -18,7 +18,7 @@ android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/dummy_thumbnail_playlist" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_stream_grid_item.xml b/app/src/main/res/layout/list_stream_grid_item.xml index e770e6614..b8192bbc1 100644 --- a/app/src/main/res/layout/list_stream_grid_item.xml +++ b/app/src/main/res/layout/list_stream_grid_item.xml @@ -14,7 +14,7 @@ android:id="@+id/itemThumbnailView" android:layout_width="@dimen/video_item_grid_thumbnail_image_width" android:layout_height="@dimen/video_item_grid_thumbnail_image_height" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/dummy_thumbnail" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/list_stream_item.xml b/app/src/main/res/layout/list_stream_item.xml index 5806ed96e..1117a028e 100644 --- a/app/src/main/res/layout/list_stream_item.xml +++ b/app/src/main/res/layout/list_stream_item.xml @@ -14,7 +14,7 @@ android:id="@+id/itemThumbnailView" android:layout_width="@dimen/video_item_search_thumbnail_image_width" android:layout_height="@dimen/video_item_search_thumbnail_image_height" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/dummy_thumbnail" app:layout_constraintBottom_toTopOf="@+id/itemProgressView" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/list_stream_mini_item.xml b/app/src/main/res/layout/list_stream_mini_item.xml index 5de3eac46..d33c9d4ba 100644 --- a/app/src/main/res/layout/list_stream_mini_item.xml +++ b/app/src/main/res/layout/list_stream_mini_item.xml @@ -17,7 +17,7 @@ android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/dummy_thumbnail" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_stream_playlist_grid_item.xml b/app/src/main/res/layout/list_stream_playlist_grid_item.xml index ac57ae378..e21a7238c 100644 --- a/app/src/main/res/layout/list_stream_playlist_grid_item.xml +++ b/app/src/main/res/layout/list_stream_playlist_grid_item.xml @@ -17,7 +17,7 @@ android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/dummy_thumbnail" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_stream_playlist_item.xml b/app/src/main/res/layout/list_stream_playlist_item.xml index 9ac1045e2..428c762aa 100644 --- a/app/src/main/res/layout/list_stream_playlist_item.xml +++ b/app/src/main/res/layout/list_stream_playlist_item.xml @@ -18,7 +18,7 @@ android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/dummy_thumbnail" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/play_queue_item.xml b/app/src/main/res/layout/play_queue_item.xml index f266af091..5fb9419f6 100644 --- a/app/src/main/res/layout/play_queue_item.xml +++ b/app/src/main/res/layout/play_queue_item.xml @@ -17,7 +17,7 @@ android:layout_marginStart="@dimen/video_item_search_image_right_margin" android:layout_marginTop="@dimen/video_item_search_image_right_margin" android:layout_marginBottom="@dimen/video_item_search_image_right_margin" - android:scaleType="centerCrop" + android:scaleType="fitCenter" android:src="@drawable/dummy_thumbnail" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" From 70b20f90cd80e1749c7a74efee5d47a04efa1004 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 4 Jul 2022 22:37:27 +0200 Subject: [PATCH 012/152] Align playlist thumbnail to left for more visibility --- app/src/main/res/layout/list_playlist_grid_item.xml | 5 ++--- app/src/main/res/layout/list_playlist_item.xml | 5 ++--- app/src/main/res/layout/list_playlist_mini_item.xml | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/layout/list_playlist_grid_item.xml b/app/src/main/res/layout/list_playlist_grid_item.xml index 11ececad0..2357458ff 100644 --- a/app/src/main/res/layout/list_playlist_grid_item.xml +++ b/app/src/main/res/layout/list_playlist_grid_item.xml @@ -17,7 +17,7 @@ android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" - android:scaleType="fitCenter" + android:scaleType="fitStart" android:src="@drawable/dummy_thumbnail_playlist" tools:ignore="RtlHardcoded" /> @@ -26,7 +26,7 @@ android:layout_width="@dimen/playlist_item_thumbnail_stream_count_width" android:layout_height="match_parent" android:layout_alignTop="@id/itemThumbnailView" - android:layout_alignRight="@id/itemThumbnailView" + android:layout_alignEnd="@id/itemThumbnailView" android:layout_alignBottom="@id/itemThumbnailView" android:background="@color/playlist_stream_count_background_color" android:gravity="center" @@ -38,7 +38,6 @@ android:textStyle="bold" app:drawableTint="@color/duration_text_color" app:drawableTopCompat="@drawable/ic_playlist_play" - tools:ignore="RtlHardcoded" tools:text="314159" /> @@ -27,7 +27,7 @@ android:layout_width="@dimen/playlist_item_thumbnail_stream_count_width" android:layout_height="match_parent" android:layout_alignTop="@id/itemThumbnailView" - android:layout_alignRight="@id/itemThumbnailView" + android:layout_alignEnd="@id/itemThumbnailView" android:layout_alignBottom="@id/itemThumbnailView" android:background="@color/playlist_stream_count_background_color" android:gravity="center" @@ -39,7 +39,6 @@ android:textStyle="bold" app:drawableTint="@color/duration_text_color" app:drawableTopCompat="@drawable/ic_playlist_play" - tools:ignore="RtlHardcoded" tools:text="314159" /> @@ -27,7 +27,7 @@ android:layout_width="45dp" android:layout_height="match_parent" android:layout_alignTop="@id/itemThumbnailView" - android:layout_alignRight="@id/itemThumbnailView" + android:layout_alignEnd="@id/itemThumbnailView" android:layout_alignBottom="@id/itemThumbnailView" android:background="@color/playlist_stream_count_background_color" android:gravity="center" @@ -39,7 +39,6 @@ android:textStyle="bold" app:drawableTint="@color/duration_text_color" app:drawableTopCompat="@drawable/ic_playlist_play" - tools:ignore="RtlHardcoded" tools:text="3141" /> Date: Tue, 5 Jul 2022 22:46:11 +0200 Subject: [PATCH 013/152] Translated using Weblate (Greek) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (640 of 640 strings) Co-authored-by: Agnieszka C Co-authored-by: Ajeje Brazorf Co-authored-by: Andrij Mizyk Co-authored-by: D āvis Co-authored-by: Danial Behzadi Co-authored-by: David Braz Co-authored-by: Davit Mayilyan Co-authored-by: Eric Co-authored-by: Erik J Co-authored-by: Giovanni Donisi Co-authored-by: Hosted Weblate Co-authored-by: Ihor Hordiichuk Co-authored-by: Jeff Huang Co-authored-by: Julie WF Co-authored-by: Linerly Co-authored-by: Nizami Co-authored-by: Priit Jõerüüt Co-authored-by: Ray Co-authored-by: ReVanced Co-authored-by: Retrial Co-authored-by: Rex_sa Co-authored-by: S3aBreeze Co-authored-by: SC Co-authored-by: SameenAhnaf Co-authored-by: Stypox Co-authored-by: Translator Co-authored-by: Vasilis K Co-authored-by: VfBFan Co-authored-by: Weblate Co-authored-by: Yaron Shahrabani Co-authored-by: Yeldar Kudaibergenov Co-authored-by: chr56 Co-authored-by: nautilusx Co-authored-by: patrik Co-authored-by: pjammo Co-authored-by: riveravaldez Co-authored-by: ssantos Co-authored-by: zica <9918800@gmail.com> Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/az/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/kk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/ Translation: NewPipe/Metadata --- app/src/main/res/values-el/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 06c7e4ae4..401b2b06b 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -717,4 +717,5 @@ Επιλογή ποιότητας εξωτερικών αναπαραγωγών Άγνωστος τύπος αρχείου Άγνωστη ποιότητα - \ No newline at end of file + Μέγεθος διαστήματος φόρτωσης αναπαραγωγής + From 4fd8294b092c2653154484cb2182b796c4894d6b Mon Sep 17 00:00:00 2001 From: chr_56 Date: Sat, 25 Jun 2022 12:28:47 +0800 Subject: [PATCH 014/152] Fix lang code 1/2: remove localization zh-rCN --- app/src/main/res/values-zh-rCN/strings.xml | 445 --------------------- 1 file changed, 445 deletions(-) delete mode 100644 app/src/main/res/values-zh-rCN/strings.xml diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml deleted file mode 100644 index 6794d6bbd..000000000 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,445 +0,0 @@ - - - 点击搜索按钮即可开始使用 - 发布于 %1$s - 在浏览器中打开 - 在悬浮窗模式下打开 - 您是不是要找:%1$s? - 找不到串流播放器 (您可以安装 VLC 进行播放)。 - 下载串流文件 - 安装 - 取消 - 分享 - 下载 - 搜索 - 设置 - 分享给… - 视频下载路径 - 已下载的视频存储在这里 - 请选择下载视频的保存位置 - 已下载的音频存储在这里 - 选择下载音频的储存位置 - 使用Kodi播放 - 主题 - 浅色 - 暗黑 - 黑色 - 下载 - 不支持的 URL - 外观 - 全部 - 网络错误 - - %s 个视频 - - 禁用 - 后台播放 - 搜索建议 - 订阅 - 已订阅 - 观看历史 - 播放器 - 历史记录与缓存 - 撤销 - 全部播放 - 总是 - 仅一次 - 添加至 - 文件 - 加载缩略图 - 清除观看记录 - - 最小化后台播放器 - 最小化悬浮窗播放器 - 频道 - 播放列表 - 取消订阅 - 更新 - 文件已删除 - 无法得知订阅人数 - 每推出新版本时,弹出应用升级通知 - 网格 - 新版 NewPipe 已可升级! - 服务器不接受 接收 multi-threaded 下载, 以 @string/msg_threads = 1 重试 - 自动播放 - 清除数据 - 观看记录已删除 - 喜欢 - 不喜欢 - 视频 - 音频 - 重试 - - %s 次观看 - - - 百万 - 开始 - 暂停 - 删除 - 校验 - OK - 文件名 - 线程数 - 错误 - 点击了解详情 - 请稍候… - 复制至剪贴板 - 悬浮窗播放 - 关于NewPipe - 第三方许可 - © %1$s :作者 %2$s (使用 %3$s ) - 关于 - 许可证 - 下载 - 文件名中允许的字符 - 无效字符将会被替换为此 - 字母和数字 - 最特殊字符 - 没有结果 - 没有订阅者 - - %s 位订阅者 - - 没有视频 - 拖动以重新排序 - 创建 - 解除 - 重 命名 - 未安装用于播放此文件的应用程序 - 已删除1个项目。 - 哪些标签需要在主页上展示 - 列表视图模式 - 已完成 - 等待中… - 已暂停 - 排队中 - 加入队列 - 操作已被系统拒绝 - 下载失败 - 没有评论 - 切换服务,当前选择: - 找不到串流播放器。您想安装 VLC 吗? - 使用外部视频播放器 - 使用外部音频播放器 - 音频下载文件夹 - 默认分辨率 - 找不到Kore。是否安装? - 显示“用Kodi播放”选项 - 显示“通过Kodi media center播放视频的选项” - 音频 - 默认音频格式 - 显示“下一个”和“类似的”视频 - 视频和音频 - 在后台播放 - 内容 - 受年龄限制的内容 - 直播 - 下载 - 下载 - 错误报告 - 错误 - 无法加载所有缩略图 - 无法解密视频的 URL 签名 - 无法解析网址 - 内容不可用 - 无法设置下载菜单 - App UI 崩溃 - 抱歉,这不应该发生的。 - 通过电子邮件报告错误 - 抱歉,发生了一些错误。 - 报告 - 信息: - 发生了什么: - 详情:\\n请求:\\n内容语言:\\n服务:\\nGMT时间:\\n包:\\n版本:\\n操作系统版本: - 您的注释(请用英文): - 详细信息: - 播放视频,时长: - 视频上传者的头像缩略图 - 字节 - NewPipe下载中 - 请稍后在设置中设定下载目录 - 用悬浮窗模式 -\n需要此权限 - reCAPTCHA验证 - 请求的新的CAPTCHA验证 - 在悬浮窗中播放 - 默认悬浮窗分辨率 - 使用更高的分辨率 - 仅某些设备支持播放2K / 4K视频 - 清除 - 记住悬浮窗的尺寸与位置 - 记住最后一次使用悬浮窗的大小和位置 - 隐藏部分没有音频的分辨率 - 显示搜索建议 - 最佳分辨率 - 开源小巧的Android媒体播放器。 - 在GitHub上查看 - NewPipe开源许可证 - 你是否有想:翻译、设计、清理或重型代码更改 ——我们始终欢迎你来贡献! - 阅读许可证 - 贡献 - 替换字符 - 取消订阅频道 - 无法修改订阅 - 无法更新订阅 - 订阅 - 最新 - 恢复前台焦点 - 中断后继续播放(例如突然来电后) - 搜索历史记录 - 在本地存储搜索查询记录 - 记录已观看视频 - 历史 - 历史 - NewPipe 通知 - NewPipe 后台播放和悬浮窗播放的通知 - 默认视频格式 - 行为 - 空空如也… - 0次观看 - 是否要从搜索历史记录中删除此项目? - 显示在主页面内容 - 空白页 - 『时下流行』页-自定义 - 频道页 - 选择一个频道 - 尚未订阅频道 - 选择一个时下流行页 - 趋势 - 前50 - 最新与热门 - 显示 \"长按添加\" 说明 - 在视频详情页中,按下背景播放或悬浮窗播放按钮时显示提示 - 无法播放此串流 - 发生无法恢复播放器错误 - 恢复播放器错误 - 移除 - 详情 - 音频设置 - 长按队列 - [未知] - 开始后台播放 - 开始在新悬浮窗中播放 - 捐赠 - NewPipe 是由志愿者花费时间为您带来最佳体验开发的。回馈帮助开发人员在享用一杯咖啡的同时,让 NewPipe 变得更好。 - 回馈 - 网站 - 请访问 NewPipe 网站了解更多信息和讯息。 - 默认国家/地区 - 切换到背景播放 - 切换到悬浮窗播放 - 切换到主页面 - 打开抽屉 - 关闭抽屉 - 第三方播放器不支持此类型链接 - 未找到视频串流 - 找不到音频串流 - 视频播放器 - 后台播放器 - 悬浮窗播放器 - 正在获取信息… - 正在加载请求的内容 - 导入数据库 - 导出数据库 - 覆盖当前历史记录和订阅 - 导出历史记录、订阅和播放列表 - 导出成功 - 导入成功 - 没有有效的ZIP文件 - 警告:无法导入所有文件。 - 这将覆盖当前设置。 - 显示信息 - 书签 - 最后播放 - 播放最多 - 总是寻问 - 新建播放列表 - 重 命名 - 名称 - 添加到播放列表 - 设为播放列表缩略图 - 收藏播放列表 - 删除收藏 - 删除此播放列表? - 新建播放列表成功 - 加入播放列表成功 - 播放列表缩略图更改成功。 - 无字幕 - 适应屏幕 - 填充屏幕 - 缩放 - 调试 - 自动生成 - 『内存泄漏监视』可能导致应用在『核心转储』时无响应 - 报告『提前结束Android生命周期』错误 - 强制报告处理后的未送达的Activity或Fragment生命周期之外的Rx异常 - 使用快速不精确搜索 - 粗略定位播放:允许播放器以略低的精确度为代价换取更快的定位速度 - 自动播放下一个 - 当播放完非循环列表中的最后一个视频时,自动加入一个相关视频到播放列表 - 没有此文件夹 - 无相似文件/内容源 - 该文件不存在 或 缺少读写该文件的权限 - 文件名不能为空 - 发生错误: %1$s - 导入 - 从…导入 - 导出到… - 正在导入… - 正在导出… - 导入文件 - 以前的导出 - 无法导入订阅 - 无法导出订阅 - 通过下载导出文件来导入 YouTube 订阅: -\n -\n1. 转到此网站: %1$s -\n2. 登录(如果需要) -\n3. 应该立即开始下载(即导出文件) - 通过键入网址或你的 ID 导入 SoundCloud 配置文件: -\n -\n1. 在浏览器中启用\"电脑模式\"(该网站不适用于移动设备) -\n2. 转到此 URL: %1$s -\n3. 登录(如果需要) -\n4. 复制重定向的配置文件下载地址。 - 你的 ID:soundcloud.com/[你的ID] - 该操作消耗大量流量, -\n你想继续吗? - 关闭可防止加载缩略图,节已省数据和内存使用。(若现在更改会清除内存和储存中缓存) - 清空图像缓存成功 - 清空已缓存元数据 - 清空已缓存的网页数据 - 清空元数据缓存成功 - 播放速度控制 - 节奏 - 音调 - 解除关联(可能导致失真) - 首选“打开”操作 - 打开内容时默认操作: = %s - 无可下载的串流内容 - 字幕 - 修改播放器字幕比例和背景样式。需要重新启动应用程序才能生效。 - 删除串流的播放历史和播放位置 - 删除全部观看记录? - 清除搜索历史记录 - 清除搜索关键词的历史记录 - 是否删除全部搜索历史记录? - 搜索历史记录已删除。 - NewPipe 是版权自由软件:您可以随时使用、研究共享和改进它。您可以根据自由软件基金会发布的 GNU 通用公共许可证GPLv3或(由您选择的)任何更高版本的许可证重新分发或修改该许可证。 - 是否要同时导入设置? - NewPipe的隐私政策 - NewPipe 项目非常重视您的隐私。因此,未经您的同意,应用程序不会收集任何数据。 -\nNewPipe 的隐私政策详细解释了在发送崩溃报告时发送和存储的数据。 - 阅读隐私政策 - 为了遵守欧盟的《通用数据保护条例》(GDPR),我们特此提醒您注意 NewPipe 的隐私政策。请您仔细阅读。 -\n您必须在同意以后才能向我们发送错误报告。 - 接受 - 拒绝 - 无限制 - 使用移动数据时限制分辨率 - 退出应用时最小化 - 从主播放器切换到其他应用时的操作 - %s - 静音时快进 - 滑块[比例尺] - 重 置 - 曲目 - 用户 - 选择标签 - 音量手势控制 - 使用手势控制播放器的音量 - 亮度手势控制 - 使用手势控制播放器的亮度 - 视频默认语言 - 应用更新通知 - NewPipe有新版本的通知 - 外置存储不可用 - 无法下载到外部 SD 卡。重置下载文件夹位置? - 读取已保存标签时发生错误,因此使用者默认标签 - 恢复默认 - 是否恢复默认值? - 更新 - 列表 - 自动 - 点击下载 - 后期处理 - 生成唯一名称 - 覆盖 - 正在使用此名称进行下载 - 显示错误 - 无法创建目标文件夹 - 无法创建文件 - 安全连接失败 - 找不到服务器 - 无法连接到服务器 - 服务器未发送数据 - 找不到 NOT FOUND - 后期处理失败 - 停止 - 最大重试次数 - 取消下载前的最多尝试次数 - 在切换到移动流量网络时中断播放 - 切换至移动数据时可能有用,尽管一些下载无法被暂停 - 事件 - 近期大会 - 显示评论 - 禁用,以停止显示评论 - 无法加载评论 - 关闭 - 恢复播放 - 恢复上次播放位置 - 列表中的位置 - 在列表中,显示视频最后一次播放时的播放位置 - 已删除播放位置记录。 - 文件被已移动或删除 - 该名称的文件已经存在 - 命名冲突,已存在具有此名称文件 - 无法覆盖文件 - 有此名称的已暂停下载 - NewPipe 在处理文件时被关闭 - 设备上没有剩余储存空间 - 进度丢失,文件已被删除 - 连接超时 - 是否要清除下载历史记录或删除所有下载的文件? - 最大下载队列 - 同时只允许一个下载进行 - 开始下载 - 暂停下载 - 询问下载位置 - 系统将询问您将每次下载的保存位置 - 使用 SAF - 存储访问框架(SAF)允许下载文件到外部SD卡。 -\n注:一些设备不兼容SAF - 删除播放位置记录 - 删除所有播放位置记录 - 删除所有播放位置记录? - 『时下流行』页-默认 - 没有人在观看 - - %s 人在观看 - - 没有人在听 - - %s 人在听 - - 重新启动应用后,语言将更改。 - PeerTube 服务器 - 设置自己喜欢的PeerTube服务器 - 查找最适合你的服务器%s - 添加服务器 - 输入服务器网址(URL) - 无法验证服务器 - 仅支持 HTTPS和URL - 该服务器已存在 - 本地 - 最近添加 - 最喜欢的 - 自动生成的(未找到上传者) - 正在恢复 - 无法恢复此下载 - 选择一个服务器 - 快进 / 快退的单位时间 - 清除下载历史记录 - 删除下载了的文件 - - \ No newline at end of file From d033a6e40d7f070ee6bf7cf019831a1a31b3b6d8 Mon Sep 17 00:00:00 2001 From: chr_56 Date: Sat, 25 Jun 2022 12:30:40 +0800 Subject: [PATCH 015/152] Fix lang code 2/2: rename b+zh+HANS+CN to zh-rCN --- .../main/res/{values-b+zh+HANS+CN => values-zh-rCN}/strings.xml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/res/{values-b+zh+HANS+CN => values-zh-rCN}/strings.xml (100%) diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml similarity index 100% rename from app/src/main/res/values-b+zh+HANS+CN/strings.xml rename to app/src/main/res/values-zh-rCN/strings.xml From a4724fec4aee3b67c47a38b87458c906dba89096 Mon Sep 17 00:00:00 2001 From: "nikita.artikhovich" Date: Fri, 13 May 2022 23:42:18 +0300 Subject: [PATCH 016/152] Add download option to long-press menu --- .../newpipe/info_list/dialog/InfoItemDialog.java | 1 + .../info_list/dialog/StreamDialogDefaultEntry.java | 11 +++++++++++ .../java/org/schabi/newpipe/util/SparseItemUtil.java | 8 ++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java index 5afaea038..61a88bb8f 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java @@ -321,6 +321,7 @@ public final class InfoItemDialog { */ public Builder addDefaultEndEntries() { addAllEntries( + StreamDialogDefaultEntry.DOWNLOAD, StreamDialogDefaultEntry.APPEND_PLAYLIST, StreamDialogDefaultEntry.SHARE, StreamDialogDefaultEntry.OPEN_IN_BROWSER diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java index 7e87318ee..d03546593 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java @@ -2,6 +2,7 @@ package org.schabi.newpipe.info_list.dialog; import static org.schabi.newpipe.util.NavigationHelper.openChannelFragment; import static org.schabi.newpipe.util.SparseItemUtil.fetchItemInfoIfSparse; +import static org.schabi.newpipe.util.SparseItemUtil.fetchStreamInfoAndSaveToDatabase; import static org.schabi.newpipe.util.SparseItemUtil.fetchUploaderUrlIfSparse; import android.net.Uri; @@ -11,6 +12,7 @@ import androidx.annotation.StringRes; import org.schabi.newpipe.R; import org.schabi.newpipe.database.stream.model.StreamEntity; +import org.schabi.newpipe.download.DownloadDialog; import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; @@ -110,6 +112,15 @@ public enum StreamDialogDefaultEntry { ShareUtils.shareText(fragment.requireContext(), item.getName(), item.getUrl(), item.getThumbnailUrl())), + DOWNLOAD(R.string.download, (fragment, item) -> + fetchStreamInfoAndSaveToDatabase(fragment.requireContext(), item.getServiceId(), + item.getUrl(), info -> { + final DownloadDialog downloadDialog + = new DownloadDialog(fragment.requireContext(), info); + downloadDialog.show(fragment.getChildFragmentManager(), "downloadDialog"); + }) + ), + OPEN_IN_BROWSER(R.string.open_in_browser, (fragment, item) -> ShareUtils.openUrlInBrowser(fragment.requireContext(), item.getUrl())), diff --git a/app/src/main/java/org/schabi/newpipe/util/SparseItemUtil.java b/app/src/main/java/org/schabi/newpipe/util/SparseItemUtil.java index 0c5f418b2..6e9ea7a47 100644 --- a/app/src/main/java/org/schabi/newpipe/util/SparseItemUtil.java +++ b/app/src/main/java/org/schabi/newpipe/util/SparseItemUtil.java @@ -97,10 +97,10 @@ public final class SparseItemUtil { * @param url url of the stream to load * @param callback callback to be called with the result */ - private static void fetchStreamInfoAndSaveToDatabase(@NonNull final Context context, - final int serviceId, - @NonNull final String url, - final Consumer callback) { + public static void fetchStreamInfoAndSaveToDatabase(@NonNull final Context context, + final int serviceId, + @NonNull final String url, + final Consumer callback) { Toast.makeText(context, R.string.loading_stream_details, Toast.LENGTH_SHORT).show(); ExtractorHelper.getStreamInfo(serviceId, url, false) .subscribeOn(Schedulers.io()) From a392a06cc05f01e7012e6d6f660105d400087264 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 13 Jul 2022 11:04:40 +0200 Subject: [PATCH 017/152] Fix feed menu items order in category to leave space for search The search menu item gets added in first place when the feed fragment is added as a tab to the main fragment. So the main fragment's menu items' orderInCategory should start from 2. --- app/src/main/res/menu/menu_feed_fragment.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/menu/menu_feed_fragment.xml b/app/src/main/res/menu/menu_feed_fragment.xml index f6929a914..775a2d979 100644 --- a/app/src/main/res/menu/menu_feed_fragment.xml +++ b/app/src/main/res/menu/menu_feed_fragment.xml @@ -4,7 +4,7 @@ From d0b6d95f1b684d912cbbdb4a3d9bd012c9e614a2 Mon Sep 17 00:00:00 2001 From: Carlos Melero <10256660+carmebar@users.noreply.github.com> Date: Fri, 24 Jun 2022 16:09:08 +0200 Subject: [PATCH 018/152] Add Share option to local playlists A newline-separated text is shared --- .../local/playlist/LocalPlaylistFragment.java | 26 ++++++++++++++++--- app/src/main/res/menu/menu_local_playlist.xml | 6 +++++ 2 files changed, 29 insertions(+), 3 deletions(-) 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 6023d4b10..4bffc0983 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 @@ -1,5 +1,6 @@ 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.util.ThemeHelper.shouldUseGridLayout; @@ -50,6 +51,7 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; +import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.ArrayList; import java.util.Collections; @@ -57,10 +59,12 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import icepick.State; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; @@ -345,7 +349,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment Single.just(playlist.stream() + .map(PlaylistStreamEntry::getStreamEntity) + .map(StreamEntity::getUrl) + .collect(Collectors.joining("\n")))) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(urlsText -> ShareUtils.shareText(requireContext(), name, urlsText), + throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable))); + } + public void removeWatchedStreams(final boolean removePartiallyWatched) { if (isRemovingWatched) { return; diff --git a/app/src/main/res/menu/menu_local_playlist.xml b/app/src/main/res/menu/menu_local_playlist.xml index 41791b791..0ff182b48 100644 --- a/app/src/main/res/menu/menu_local_playlist.xml +++ b/app/src/main/res/menu/menu_local_playlist.xml @@ -2,6 +2,12 @@ + + Date: Wed, 13 Jul 2022 15:19:44 +0000 Subject: [PATCH 019/152] Crop the notification thumbnail in 1:1 mode instead of stretching it (#8533) Change square bitmap transformation strategy: change the bitmap transformation strategy when a 1:1 aspect ratio is enabled to not stretch the bitmap but rather crop it. On Android 11/12, the way the whole thumbnail was used for the notification icon was not ideal, however the setting to toggle a 1:1 (as it states in settings) resulted in distortions. Fix this by simply cropping the bitmap. Also update the 1:1 mode strings to remove mentions of scaling or distortions, as those no longer apply. --- .../newpipe/player/NotificationUtil.java | 20 ++++++++----------- app/src/main/res/values/strings.xml | 4 ++-- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java index 948343be2..2060d67c4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java @@ -6,7 +6,6 @@ import android.app.Service; import android.content.Intent; import android.content.pm.ServiceInfo; import android.graphics.Bitmap; -import android.graphics.Matrix; import android.os.Build; import android.util.Log; @@ -366,16 +365,13 @@ public final class NotificationUtil { } private Bitmap getBitmapWithSquareAspectRatio(final Bitmap bitmap) { - return getResizedBitmap(bitmap, bitmap.getWidth(), bitmap.getWidth()); - } - - private Bitmap getResizedBitmap(final Bitmap bitmap, final int newWidth, final int newHeight) { - final int width = bitmap.getWidth(); - final int height = bitmap.getHeight(); - final float scaleWidth = ((float) newWidth) / width; - final float scaleHeight = ((float) newHeight) / height; - final Matrix matrix = new Matrix(); - matrix.postScale(scaleWidth, scaleHeight); - return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false); + // Find the smaller dimension and then take a center portion of the image that + // has that size. + final int w = bitmap.getWidth(); + final int h = bitmap.getHeight(); + final int dstSize = Math.min(w, h); + final int x = (w - dstSize) / 2; + final int y = (h - dstSize) / 2; + return Bitmap.createBitmap(bitmap, x, y, dstSize, dstSize); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1d65b6fba..cedb01b20 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,8 +50,8 @@ Show \"Play with Kodi\" option Display an option to play a video via Kodi media center Crash the player - Scale thumbnail to 1:1 aspect ratio - Scale the video thumbnail shown in the notification from 16:9 to 1:1 aspect ratio (may introduce distortions) + Crop thumbnail to 1:1 aspect ratio + Crop the video thumbnail shown in the notification from 16:9 to 1:1 aspect ratio First action button Second action button Third action button From f7d8781bac7adb3a21aca0009176c04f56780dee Mon Sep 17 00:00:00 2001 From: Taco Date: Wed, 13 Jul 2022 11:57:14 -0400 Subject: [PATCH 020/152] Specify used ExoPlayer libraries (#8469) --- app/build.gradle | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5fc38b85a..4050e116c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -238,7 +238,13 @@ dependencies { implementation "com.squareup.okhttp3:okhttp:3.12.13" // Media player - implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}" + implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}" + implementation "com.google.android.exoplayer:exoplayer-dash:${exoPlayerVersion}" + implementation "com.google.android.exoplayer:exoplayer-database:${exoPlayerVersion}" + implementation "com.google.android.exoplayer:exoplayer-datasource:${exoPlayerVersion}" + implementation "com.google.android.exoplayer:exoplayer-hls:${exoPlayerVersion}" + implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:${exoPlayerVersion}" + implementation "com.google.android.exoplayer:exoplayer-ui:${exoPlayerVersion}" implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}" // Metadata generator for service descriptors From e55e79bcca44d831dfd951d0c96f222540c00525 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:30:30 -0400 Subject: [PATCH 021/152] Bump minSdk to 21 (Android 5 / Lollipop) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 4050e116c..e41695887 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,7 +14,7 @@ android { defaultConfig { applicationId "org.schabi.newpipe" resValue "string", "app_name", "NewPipe" - minSdk 19 + minSdk 21 targetSdk 29 versionCode 987 versionName "0.23.1" From 7cb137ae8d4e3372d453048170d4a6bf6b9771df Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:30:56 -0400 Subject: [PATCH 022/152] Remove MultiDex --- app/build.gradle | 3 --- app/src/main/java/org/schabi/newpipe/App.java | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e41695887..06eb822cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,8 +19,6 @@ android { versionCode 987 versionName "0.23.1" - multiDexEnabled true - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true @@ -210,7 +208,6 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' implementation 'androidx.media:media:1.5.0' - implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "androidx.room:room-runtime:${androidxRoomVersion}" diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 70c947478..c63f07891 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -1,5 +1,6 @@ package org.schabi.newpipe; +import android.app.Application; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; @@ -7,7 +8,6 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.core.app.NotificationChannelCompat; import androidx.core.app.NotificationManagerCompat; -import androidx.multidex.MultiDexApplication; import androidx.preference.PreferenceManager; import com.jakewharton.processphoenix.ProcessPhoenix; @@ -56,7 +56,7 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins; * along with NewPipe. If not, see . */ -public class App extends MultiDexApplication { +public class App extends Application { public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID; private static final String TAG = App.class.toString(); private static App app; From 99104fc11d1bc4696f67877a9bf04ac580fa9315 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:46:20 -0400 Subject: [PATCH 023/152] Clean up pre-Lollipop checks --- .../org/schabi/newpipe/DownloaderImpl.java | 68 ------------ .../java/org/schabi/newpipe/ExitActivity.java | 7 +- .../java/org/schabi/newpipe/MainActivity.java | 7 -- .../newpipe/PanicResponderActivity.java | 7 +- .../newpipe/error/ReCaptchaActivity.java | 8 +- .../fragments/detail/VideoDetailFragment.java | 17 +-- .../fragments/list/search/SearchFragment.java | 3 - .../subscription/dialog/FeedGroupDialog.kt | 10 -- .../org/schabi/newpipe/player/Player.java | 7 +- .../settings/AppearanceSettingsFragment.java | 2 +- .../settings/DownloadSettingsFragment.java | 13 +-- .../newpipe/settings/NewPipeSettings.java | 2 +- .../settings/NotificationSettingsFragment.kt | 10 -- .../newpipe/settings/SettingMigrations.java | 11 +- .../PreferenceSearchResultHighlighter.java | 4 +- .../streams/io/StoredDirectoryHelper.java | 9 +- .../newpipe/streams/io/StoredFileHelper.java | 3 - .../org/schabi/newpipe/util/DeviceUtils.java | 11 +- .../schabi/newpipe/util/PermissionHelper.java | 1 - .../newpipe/util/TLSSocketFactoryCompat.java | 104 ------------------ .../external_communication/ShareUtils.java | 15 +-- .../schabi/newpipe/views/CollapsibleView.java | 3 - .../newpipe/views/ExpandableSurfaceView.java | 6 +- .../newpipe/views/FocusAwareCoordinator.java | 3 - .../us/shandian/giga/get/DownloadMission.java | 19 ++-- .../giga/service/DownloadManagerService.java | 84 +++----------- .../giga/ui/adapter/MissionAdapter.java | 4 +- app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-az/strings.xml | 1 - app/src/main/res/values-ca/strings.xml | 1 - app/src/main/res/values-ckb/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 1 - app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-el/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 1 - app/src/main/res/values-et/strings.xml | 1 - app/src/main/res/values-eu/strings.xml | 1 - app/src/main/res/values-fa/strings.xml | 1 - app/src/main/res/values-fi/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-gl/strings.xml | 1 - app/src/main/res/values-he/strings.xml | 1 - app/src/main/res/values-hr/strings.xml | 1 - app/src/main/res/values-hu/strings.xml | 1 - app/src/main/res/values-in/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-lt/strings.xml | 1 - app/src/main/res/values-lv/strings.xml | 1 - app/src/main/res/values-ml/strings.xml | 1 - app/src/main/res/values-nb-rNO/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-pa/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt-rPT/strings.xml | 1 - app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ro/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-sc/strings.xml | 1 - app/src/main/res/values-sk/strings.xml | 1 - app/src/main/res/values-so/strings.xml | 1 - app/src/main/res/values-sq/strings.xml | 1 - app/src/main/res/values-sr/strings.xml | 1 - app/src/main/res/values-sv/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-uk/strings.xml | 1 - app/src/main/res/values-vi/strings.xml | 1 - app/src/main/res/values-zh-rCN/strings.xml | 1 - app/src/main/res/values-zh-rHK/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 1 - app/src/main/res/xml/download_settings.xml | 1 + 73 files changed, 59 insertions(+), 425 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/TLSSocketFactoryCompat.java diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 1a3a8adee..f2803dc2f 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -1,7 +1,6 @@ package org.schabi.newpipe; import android.content.Context; -import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -14,33 +13,18 @@ import org.schabi.newpipe.extractor.downloader.Response; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.util.CookieUtils; import org.schabi.newpipe.util.InfoCache; -import org.schabi.newpipe.util.TLSSocketFactoryCompat; import java.io.IOException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import okhttp3.CipherSuite; -import okhttp3.ConnectionSpec; import okhttp3.OkHttpClient; import okhttp3.RequestBody; import okhttp3.ResponseBody; -import static org.schabi.newpipe.MainActivity.DEBUG; - public final class DownloaderImpl extends Downloader { public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; @@ -54,9 +38,6 @@ public final class DownloaderImpl extends Downloader { private final OkHttpClient client; private DownloaderImpl(final OkHttpClient.Builder builder) { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { - enableModernTLS(builder); - } this.client = builder .readTimeout(30, TimeUnit.SECONDS) // .cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"), @@ -81,55 +62,6 @@ public final class DownloaderImpl extends Downloader { return instance; } - /** - * Enable TLS 1.2 and 1.1 on Android Kitkat. This function is mostly taken - * from the documentation of OkHttpClient.Builder.sslSocketFactory(_,_). - *

- * If there is an error, the function will safely fall back to doing nothing - * and printing the error to the console. - *

- * - * @param builder The HTTPClient Builder on which TLS is enabled on (will be modified in-place) - */ - private static void enableModernTLS(final OkHttpClient.Builder builder) { - try { - // get the default TrustManager - final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new IllegalStateException("Unexpected default trust managers:" - + Arrays.toString(trustManagers)); - } - final X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; - - // insert our own TLSSocketFactory - final SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance(); - - builder.sslSocketFactory(sslSocketFactory, trustManager); - - // This will try to enable all modern CipherSuites(+2 more) - // that are supported on the device. - // Necessary because some servers (e.g. Framatube.org) - // don't support the old cipher suites. - // https://github.com/square/okhttp/issues/4053#issuecomment-402579554 - final List cipherSuites = - new ArrayList<>(ConnectionSpec.MODERN_TLS.cipherSuites()); - cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA); - cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA); - final ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) - .cipherSuites(cipherSuites.toArray(new CipherSuite[0])) - .build(); - - builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT)); - } catch (final KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { - if (DEBUG) { - e.printStackTrace(); - } - } - } - public String getCookies(final String url) { final List resultCookies = new ArrayList<>(); if (url.contains(YOUTUBE_DOMAIN)) { diff --git a/app/src/main/java/org/schabi/newpipe/ExitActivity.java b/app/src/main/java/org/schabi/newpipe/ExitActivity.java index 8da22db2d..bd1351f0c 100644 --- a/app/src/main/java/org/schabi/newpipe/ExitActivity.java +++ b/app/src/main/java/org/schabi/newpipe/ExitActivity.java @@ -3,7 +3,6 @@ package org.schabi.newpipe; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import org.schabi.newpipe.util.NavigationHelper; @@ -44,11 +43,7 @@ public class ExitActivity extends Activity { protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - finishAndRemoveTask(); - } else { - finish(); - } + finishAndRemoveTask(); NavigationHelper.restartApp(this); } diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index fcb9d9725..dd59eeb25 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -28,7 +28,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -86,7 +85,6 @@ import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.StateSaver; -import org.schabi.newpipe.util.TLSSocketFactoryCompat; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; @@ -131,11 +129,6 @@ public class MainActivity extends AppCompatActivity { + "savedInstanceState = [" + savedInstanceState + "]"); } - // enable TLS1.1/1.2 for kitkat devices, to fix download and play for media.ccc.de sources - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { - TLSSocketFactoryCompat.setAsDefault(); - } - ThemeHelper.setDayNightMode(this); ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); diff --git a/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java b/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java index b4fbdfb28..f0d1af81a 100644 --- a/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java +++ b/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java @@ -3,7 +3,6 @@ package org.schabi.newpipe; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; -import android.os.Build; import android.os.Bundle; /* @@ -40,10 +39,6 @@ public class PanicResponderActivity extends Activity { ExitActivity.exitAndRemoveFromRecentApps(this); } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - finishAndRemoveTask(); - } else { - finish(); - } + finishAndRemoveTask(); } } diff --git a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java index 555dd709b..c9f3a82d9 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.error; import android.annotation.SuppressLint; import android.content.Intent; import android.content.SharedPreferences; -import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.Menu; @@ -107,12 +106,7 @@ public class ReCaptchaActivity extends AppCompatActivity { // cleaning cache, history and cookies from webView recaptchaBinding.reCaptchaWebView.clearCache(true); recaptchaBinding.reCaptchaWebView.clearHistory(); - final CookieManager cookieManager = CookieManager.getInstance(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - cookieManager.removeAllCookies(value -> { }); - } else { - cookieManager.removeAllCookie(); - } + CookieManager.getInstance().removeAllCookies(null); recaptchaBinding.reCaptchaWebView.loadUrl(url); } 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 5e19f558d..278d472d4 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 @@ -1922,13 +1922,7 @@ public final class VideoDetailFragment } scrollToTop(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - addVideoPlayerView(); - } else { - // KitKat needs a delay before addVideoPlayerView call or it reports wrong height in - // activity.getWindow().getDecorView().getHeight() - new Handler().post(this::addVideoPlayerView); - } + addVideoPlayerView(); } @Override @@ -1991,10 +1985,8 @@ public final class VideoDetailFragment } activity.getWindow().getDecorView().setSystemUiVisibility(0); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - activity.getWindow().setStatusBarColor(ThemeHelper.resolveColorFromAttr( - requireContext(), android.R.attr.colorPrimary)); - } + activity.getWindow().setStatusBarColor(ThemeHelper.resolveColorFromAttr( + requireContext(), android.R.attr.colorPrimary)); } private void hideSystemUi() { @@ -2025,8 +2017,7 @@ public final class VideoDetailFragment } activity.getWindow().getDecorView().setSystemUiVisibility(visibility); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - && (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen()))) { + if (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen())) { activity.getWindow().setStatusBarColor(Color.TRANSPARENT); activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 055c27733..44f8328a5 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -497,9 +497,6 @@ public class SearchFragment extends BaseListFragment content - val contrastColor = AppCompatResources.getColorStateList(requireContext(), R.color.contrastColor) - searchLayoutBinding.toolbarSearchEditText.setTextColor(contrastColor) - searchLayoutBinding.toolbarSearchEditText.setHintTextColor(contrastColor.withAlpha(128)) - ImageViewCompat.setImageTintList(searchLayoutBinding.toolbarSearchClearIcon, contrastColor) - } - viewModel = ViewModelProvider( this, FeedGroupDialogViewModel.Factory( 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 bae88d07c..100563765 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -1963,10 +1963,9 @@ public final class Player implements private void showSystemUIPartially() { final AppCompatActivity activity = getParentActivity(); if (isFullscreen && activity != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - activity.getWindow().setStatusBarColor(Color.TRANSPARENT); - activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); - } + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); + final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java index 70ac5cdcc..550d64d06 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java @@ -74,7 +74,7 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment { defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply(); defaultPreferences.edit().putString(themeKey, newValue.toString()).apply(); - ThemeHelper.setDayNightMode(getContext(), newValue.toString()); + ThemeHelper.setDayNightMode(requireContext(), newValue.toString()); if (!newValue.equals(beginningThemeKey) && getActivity() != null) { // if it's not the current theme diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index ec98b865e..74dc9f63e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -66,16 +66,10 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { prefStorageAsk = findPreference(downloadStorageAsk); final SwitchPreferenceCompat prefUseSaf = findPreference(storageUseSafPreference); - prefUseSaf.setDefaultValue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); prefUseSaf.setChecked(NewPipeSettings.useStorageAccessFramework(ctx)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q - || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { prefUseSaf.setEnabled(false); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - prefUseSaf.setSummary(R.string.downloads_storage_use_saf_summary_api_29); - } else { - prefUseSaf.setSummary(R.string.downloads_storage_use_saf_summary_api_19); - } + prefUseSaf.setSummary(R.string.downloads_storage_use_saf_summary_api_29); prefStorageAsk.setSummary(R.string.downloads_storage_ask_summary_no_saf_notice); } @@ -253,8 +247,7 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { forgetSAFTree(context, defaultPreferences.getString(key, "")); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - && !FilePickerActivityHelper.isOwnFileUri(context, uri)) { + if (!FilePickerActivityHelper.isOwnFileUri(context, uri)) { // steps to acquire the selected path: // 1. acquire permissions on the new save path // 2. save the new path, if step(2) was successful diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java index 1e1d08856..16df646f9 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -116,7 +116,7 @@ public final class NewPipeSettings { public static boolean useStorageAccessFramework(final Context context) { // There's a FireOS bug which prevents SAF open/close dialogs from being confirmed with a // remote (see #6455). - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || DeviceUtils.isFireTv()) { + if (DeviceUtils.isFireTv()) { return false; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return true; diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt index 6bea8b69e..11eb4fa33 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt @@ -1,19 +1,9 @@ package org.schabi.newpipe.settings -import android.os.Build import android.os.Bundle -import androidx.preference.Preference -import org.schabi.newpipe.R class NotificationSettingsFragment : BasePreferenceFragment() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResourceRegistry() - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - val colorizePref: Preference? = findPreference(getString(R.string.notification_colorize_key)) - colorizePref?.let { - preferenceScreen.removePreference(it) - } - } } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java index 8924ecbe1..b1e2c04eb 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -2,7 +2,6 @@ package org.schabi.newpipe.settings; import android.content.Context; import android.content.SharedPreferences; -import android.os.Build; import android.util.Log; import androidx.preference.PreferenceManager; @@ -71,12 +70,12 @@ public final class SettingMigrations { // and standard way to access folders and files to be used consistently everywhere. // We reset the setting to its default value, i.e. "use SAF", since now there are no // more issues with SAF and users should use that one instead of the old - // NoNonsenseFilePicker. SAF does not work on KitKat and below, though, so the setting - // is set to false in that case. Also, there's a bug on FireOS in which SAF open/close + // NoNonsenseFilePicker. Also, there's a bug on FireOS in which SAF open/close // dialogs cannot be confirmed with a remote (see #6455). - sp.edit().putBoolean(context.getString(R.string.storage_use_saf), - Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - && !DeviceUtils.isFireTv()).apply(); + sp.edit().putBoolean( + context.getString(R.string.storage_use_saf), + !DeviceUtils.isFireTv() + ).apply(); } }; diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchResultHighlighter.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchResultHighlighter.java index 418a3ea46..7eae5c128 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchResultHighlighter.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchResultHighlighter.java @@ -6,7 +6,6 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; -import android.os.Build; import android.os.Handler; import android.os.Looper; import android.util.Log; @@ -65,8 +64,7 @@ public final class PreferenceSearchResultHighlighter { recyclerView.findViewHolderForAdapterPosition(position); if (holder != null) { final Drawable background = holder.itemView.getBackground(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - && background instanceof RippleDrawable) { + if (background instanceof RippleDrawable) { showRippleAnimation((RippleDrawable) background); return; } diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java index feca89f02..48ae54284 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java +++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java @@ -5,7 +5,6 @@ import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; -import android.os.Build; import android.provider.DocumentsContract; import androidx.annotation.NonNull; @@ -53,10 +52,6 @@ public class StoredDirectoryHelper { throw new IOException(e); } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - throw new IOException("Storage Access Framework with Directory API is not available"); - } - this.docTree = DocumentFile.fromTreeUri(context, path); if (this.docTree == null) { @@ -73,7 +68,7 @@ public class StoredDirectoryHelper { final String[] filename = splitFilename(name); final String lcFilename = filename[0].toLowerCase(); - if (docTree == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + if (docTree == null) { for (final File file : ioTree.listFiles()) { addIfStartWith(matches, lcFilename, file.getName()); } @@ -277,7 +272,7 @@ public class StoredDirectoryHelper { */ static DocumentFile findFileSAFHelper(@Nullable final Context context, final DocumentFile tree, final String filename) { - if (context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + if (context == null) { return tree.findFile(filename); // warning: this is very slow } diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java index 9fe4a9340..1f0c91456 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java +++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.streams.io; -import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -74,7 +73,6 @@ public class StoredFileHelper implements Serializable { this.tag = tag; } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) StoredFileHelper(@Nullable final Context context, final DocumentFile tree, final String filename, final String mime, final boolean safe) throws IOException { @@ -124,7 +122,6 @@ public class StoredFileHelper implements Serializable { this.srcType = mime; } - @TargetApi(Build.VERSION_CODES.KITKAT) public StoredFileHelper(final Context context, @Nullable final Uri parent, @NonNull final Uri path, final String tag) throws IOException { this.tag = tag; diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index a4ff5ff19..7f4b33f44 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -65,7 +65,7 @@ public final class DeviceUtils { boolean isTv = ContextCompat.getSystemService(context, UiModeManager.class) .getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION || isFireTv() - || pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION); + || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); // from https://stackoverflow.com/a/58932366 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { @@ -77,10 +77,6 @@ public final class DeviceUtils { && pm.hasSystemFeature(PackageManager.FEATURE_ETHERNET)); } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - isTv = isTv || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); - } - DeviceUtils.isTV = isTv; return DeviceUtils.isTV; } @@ -131,11 +127,10 @@ public final class DeviceUtils { /** * Some devices have broken tunneled video playback but claim to support it. * See https://github.com/TeamNewPipe/NewPipe/issues/5911 - * @return false if Kitkat (does not support tunneling) or affected device + * @return false if affected device */ public static boolean shouldSupportMediaTunneling() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - && !HI3798MV200 + return !HI3798MV200 && !CVT_MT5886_EU_1G && !REALTEKATV && !QM16XE_U; diff --git a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java index 160eb59cd..1e3061374 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java @@ -37,7 +37,6 @@ public final class PermissionHelper { return checkWriteStoragePermissions(activity, requestCode); } - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) public static boolean checkReadStoragePermissions(final Activity activity, final int requestCode) { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) diff --git a/app/src/main/java/org/schabi/newpipe/util/TLSSocketFactoryCompat.java b/app/src/main/java/org/schabi/newpipe/util/TLSSocketFactoryCompat.java deleted file mode 100644 index 05e69408a..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/TLSSocketFactoryCompat.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.schabi.newpipe.util; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; - -import android.util.Log; - - -/** - * This is an extension of the SSLSocketFactory which enables TLS 1.2 and 1.1. - * Created for usage on Android 4.1-4.4 devices, which haven't enabled those by default. - */ -public class TLSSocketFactoryCompat extends SSLSocketFactory { - - private static final String TAG = "TLSSocketFactoryCom"; - - private static TLSSocketFactoryCompat instance = null; - - private final SSLSocketFactory internalSSLSocketFactory; - - public TLSSocketFactoryCompat() throws KeyManagementException, NoSuchAlgorithmException { - final SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, null, null); - internalSSLSocketFactory = context.getSocketFactory(); - } - - public static TLSSocketFactoryCompat getInstance() - throws NoSuchAlgorithmException, KeyManagementException { - if (instance != null) { - return instance; - } - instance = new TLSSocketFactoryCompat(); - return instance; - } - - public static void setAsDefault() { - try { - HttpsURLConnection.setDefaultSSLSocketFactory(getInstance()); - } catch (NoSuchAlgorithmException | KeyManagementException e) { - Log.e(TAG, "Unable to setAsDefault", e); - } - } - - @Override - public String[] getDefaultCipherSuites() { - return internalSSLSocketFactory.getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return internalSSLSocketFactory.getSupportedCipherSuites(); - } - - @Override - public Socket createSocket() throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket()); - } - - @Override - public Socket createSocket(final Socket s, final String host, final int port, - final boolean autoClose) throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)); - } - - @Override - public Socket createSocket(final String host, final int port) throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); - } - - @Override - public Socket createSocket(final String host, final int port, final InetAddress localHost, - final int localPort) throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket( - host, port, localHost, localPort)); - } - - @Override - public Socket createSocket(final InetAddress host, final int port) throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); - } - - @Override - public Socket createSocket(final InetAddress address, final int port, - final InetAddress localAddress, final int localPort) - throws IOException { - return enableTLSOnSocket(internalSSLSocketFactory.createSocket( - address, port, localAddress, localPort)); - } - - private Socket enableTLSOnSocket(final Socket socket) { - if (socket instanceof SSLSocket) { - ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.1", "TLSv1.2"}); - } - return socket; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java index 8324146fe..debeb902c 100644 --- a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java @@ -185,17 +185,10 @@ public final class ShareUtils { } // Migrate any clip data and flags from the original intent. - final int permFlags; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - permFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); - } else { - permFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - } + final int permFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); if (permFlags != 0) { ClipData targetClipData = intent.getClipData(); if (targetClipData == null && intent.getData() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java b/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java index e1ada4f9b..f79e1e3a3 100644 --- a/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java +++ b/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java @@ -21,7 +21,6 @@ package org.schabi.newpipe.views; import android.animation.ValueAnimator; import android.content.Context; -import android.os.Build; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; @@ -29,7 +28,6 @@ import android.widget.LinearLayout; import androidx.annotation.IntDef; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import org.schabi.newpipe.ktx.ViewUtils; @@ -76,7 +74,6 @@ public class CollapsibleView extends LinearLayout { super(context, attrs, defStyleAttr); } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public CollapsibleView(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); diff --git a/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java index cfa17e20c..175c81e46 100644 --- a/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java +++ b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.views; import android.content.Context; -import android.os.Build; import android.util.AttributeSet; import android.view.SurfaceView; @@ -45,10 +44,7 @@ public class ExpandableSurfaceView extends SurfaceView { scaleX = 1.0f; scaleY = 1.0f; - if (resizeMode == RESIZE_MODE_FIT - // KitKat doesn't work well when a view has a scale like needed for ZOOM - || (resizeMode == RESIZE_MODE_ZOOM - && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)) { + if (resizeMode == RESIZE_MODE_FIT) { if (aspectDeformation > 0) { height = (int) (width / videoAspectRatio); } else { diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java index 798d08c72..747aed025 100644 --- a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java @@ -17,10 +17,8 @@ */ package org.schabi.newpipe.views; -import android.annotation.TargetApi; import android.content.Context; import android.graphics.Rect; -import android.os.Build; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -74,7 +72,6 @@ public final class FocusAwareCoordinator extends CoordinatorLayout { * Makes possible for multiple fragments to co-exist. Without this code * the first ViewGroup who consumes will be the last who receive the insets */ - @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public WindowInsets dispatchApplyWindowInsets(final WindowInsets insets) { boolean consumed = false; diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 9d8eaf9a5..04930b002 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -1,6 +1,5 @@ package us.shandian.giga.get; -import android.os.Build; import android.os.Handler; import android.system.ErrnoException; import android.system.OsConstants; @@ -316,16 +315,14 @@ public class DownloadMission extends Mission { public synchronized void notifyError(int code, Exception err) { Log.e(TAG, "notifyError() code = " + code, err); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (err != null && err.getCause() instanceof ErrnoException) { - int errno = ((ErrnoException) err.getCause()).errno; - if (errno == OsConstants.ENOSPC) { - code = ERROR_INSUFFICIENT_STORAGE; - err = null; - } else if (errno == OsConstants.EACCES) { - code = ERROR_PERMISSION_DENIED; - err = null; - } + if (err != null && err.getCause() instanceof ErrnoException) { + int errno = ((ErrnoException) err.getCause()).errno; + if (errno == OsConstants.ENOSPC) { + code = ERROR_INSUFFICIENT_STORAGE; + err = null; + } else if (errno == OsConstants.EACCES) { + code = ERROR_PERMISSION_DENIED; + err = null; } } diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index d96b4fc5b..8b8a6ff09 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -4,10 +4,8 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.graphics.Bitmap; @@ -18,7 +16,6 @@ import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.Handler; import android.os.Handler.Callback; import android.os.IBinder; @@ -100,7 +97,6 @@ public class DownloadManagerService extends Service { private final ArrayList mEchoObservers = new ArrayList<>(1); private ConnectivityManager mConnectivityManager; - private BroadcastReceiver mNetworkStateListener = null; private ConnectivityManager.NetworkCallback mNetworkStateListenerL = null; private SharedPreferences mPrefs = null; @@ -166,28 +162,18 @@ public class DownloadManagerService extends Service { mConnectivityManager = ContextCompat.getSystemService(this, ConnectivityManager.class); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mNetworkStateListenerL = new ConnectivityManager.NetworkCallback() { - @Override - public void onAvailable(Network network) { - handleConnectivityState(false); - } + mNetworkStateListenerL = new ConnectivityManager.NetworkCallback() { + @Override + public void onAvailable(Network network) { + handleConnectivityState(false); + } - @Override - public void onLost(Network network) { - handleConnectivityState(false); - } - }; - mConnectivityManager.registerNetworkCallback(new NetworkRequest.Builder().build(), mNetworkStateListenerL); - } else { - mNetworkStateListener = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - handleConnectivityState(false); - } - }; - registerReceiver(mNetworkStateListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - } + @Override + public void onLost(Network network) { + handleConnectivityState(false); + } + }; + mConnectivityManager.registerNetworkCallback(new NetworkRequest.Builder().build(), mNetworkStateListenerL); mPrefs.registerOnSharedPreferenceChangeListener(mPrefChangeListener); @@ -246,10 +232,7 @@ public class DownloadManagerService extends Service { manageLock(false); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - mConnectivityManager.unregisterNetworkCallback(mNetworkStateListenerL); - else - unregisterReceiver(mNetworkStateListener); + mConnectivityManager.unregisterNetworkCallback(mNetworkStateListenerL); mPrefs.unregisterOnSharedPreferenceChangeListener(mPrefChangeListener); @@ -263,21 +246,6 @@ public class DownloadManagerService extends Service { @Override public IBinder onBind(Intent intent) { - /* - int permissionCheck; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { - permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); - if (permissionCheck == PermissionChecker.PERMISSION_DENIED) { - Toast.makeText(this, "Permission denied (read)", Toast.LENGTH_SHORT).show(); - } - } - - permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); - if (permissionCheck == PermissionChecker.PERMISSION_DENIED) { - Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show(); - } - */ - return mBinder; } @@ -473,12 +441,7 @@ public class DownloadManagerService extends Service { if (downloadDoneCount == 1) { downloadDoneList.append(name); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - downloadDoneNotification.setContentTitle(getString(R.string.app_name)); - } else { - downloadDoneNotification.setContentTitle(null); - } - + downloadDoneNotification.setContentTitle(null); downloadDoneNotification.setContentText(Localization.downloadCount(this, downloadDoneCount)); downloadDoneNotification.setStyle(new NotificationCompat.BigTextStyle() .setBigContentTitle(Localization.downloadCount(this, downloadDoneCount)) @@ -511,16 +474,10 @@ public class DownloadManagerService extends Service { .setContentIntent(mOpenDownloadList); } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - downloadFailedNotification.setContentTitle(getString(R.string.app_name)); - downloadFailedNotification.setStyle(new NotificationCompat.BigTextStyle() - .bigText(getString(R.string.download_failed).concat(": ").concat(mission.storage.getName()))); - } else { - downloadFailedNotification.setContentTitle(getString(R.string.download_failed)); - downloadFailedNotification.setContentText(mission.storage.getName()); - downloadFailedNotification.setStyle(new NotificationCompat.BigTextStyle() - .bigText(mission.storage.getName())); - } + downloadFailedNotification.setContentTitle(getString(R.string.download_failed)); + downloadFailedNotification.setContentText(mission.storage.getName()); + downloadFailedNotification.setStyle(new NotificationCompat.BigTextStyle() + .bigText(mission.storage.getName())); mNotificationManager.notify(id, downloadFailedNotification.build()); } @@ -556,12 +513,7 @@ public class DownloadManagerService extends Service { if (path.charAt(0) == File.separatorChar) { Log.i(TAG, "Old save path style present: " + path); - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) - path = Uri.fromFile(new File(path)).toString(); - else - path = ""; - + path = ""; mPrefs.edit().putString(getString(prefKey), "").apply(); } diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 961c45bc5..343b13ef8 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -350,10 +350,8 @@ public class MissionAdapter extends Adapter implements Handler.Callb Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(resolveShareableUri(mission), mimeType); intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION); - } if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { intent.addFlags(FLAG_ACTIVITY_NEW_TASK); } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 4e9606f48..71d849a2f 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -673,7 +673,6 @@ تعذر تحميل تغذية لـ\'%s\'. خطأ في تحميل الخلاصة بدءًا من Android 10، يتم دعم \"Storage Access Framework\" فقط - \"Storage Access Framework\" غير مدعوم على Android KitKat والإصدارات الأقدم سيتم سؤالك عن مكان حفظ كل تنزيل لم يتم تعيين مجلد التحميل، الرجاء اختيار مجلد التحميل الافتراضي الآن إيقاف diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 95b7b76f4..2d9257e3c 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -463,7 +463,6 @@ Server məlumat göndərmir Bu endirməni bərpa etmək mümkün deyil Sizdən hər endirmənin harada saxlanacağı soruşulacaq - \'Yaddaş Giriş Çərçivəsi\' Android KitKat və ondan aşağı versiyalarda dəstəklənmir \"Yaddaş Giriş Çərçivəsi\"yalnız Android 10\'dan başlayaraq dəstəklənir Kanalın avatar miniatürü Sevdiyiniz gecə mövzusunu aşağıda seçə bilərsiniz diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index a42e68940..099203790 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -675,7 +675,6 @@ S\'ha suprimit %1$s baixada S\'han suprimit %1$s baixades - El \"Sistema d\'Accés a l\'Emmagatzematge\" no està implementat a Android KitKat i a versions anteriors A partir de l\'Android 10 només s\'admet el \"Sistema d\'Accés a l\'Emmagatzematge\" Elements de feed nous El mode d\'alimentació ràpida no proporciona més informació sobre això. diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index 9796404b4..8b6c08434 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -625,7 +625,6 @@ بۆ دابه‌زاندنی هه‌ر بابه‌تێك پرست پێ ده‌كرێت له‌باره‌ی شوێنی دابه‌زاندنیان ناكاراكردنی تونێلكردنی میدیا ئه‌و بابه‌تانه‌ی نه‌گونجاون بۆ منداڵان پیشان بدرێن كه‌ سنووری ته‌مه‌ن ده‌یانگرێته‌وه‌ (وه‌ك +18) - \'Storage Access Framework\' پشتگیری نه‌كراوه‌ له‌سه‌ر وه‌شانه‌كانی ئه‌ندرۆید كیتكات و نزمتر كڕاشی به‌رنامه‌كه‌ پیشاندانی دزه‌كردنی بیرگه‌ له‌نۆبه‌ت دانرا diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index ea6d292d8..2e840629d 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -643,7 +643,6 @@ Feed pro \'%s\' nemohl být načten. Chyba při načítání feedu Počínaje Android 10 je podporován pouze \"Storage Access Framework\" - \"Storage Access Framework\" není podporován na KitKat a níže Budete dotázáni, kde uložit každý stažený soubor Adresář pro stažené soubory dosud nenastaven, zvolte, prosím, výchozí adresář nyní Vypnuto diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b91fec84a..4c8e9cfe1 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -638,7 +638,6 @@ Noch kein Downloadordner festgelegt, wähle jetzt den Standard-Downloadordner Webseite öffnen Ab Android 10 wird nur noch „Storage Access Framework“ unterstützt - Das „Storage Access Framework“ wird auf Android KitKat und niedriger nicht unterstützt Du wirst jedes Mal gefragt werden, wohin der Download gespeichert werden soll Fehler beim Laden des Feeds Konnte Feed für \'%s\' nicht laden. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 401b2b06b..d9239547b 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -632,7 +632,6 @@ Αδυναμία φόρτωσης τροφοδοσίας για \'%s\'. Σφάλμα φόρτωσης τροφοδοσίας Από το Android 10 και μετά, μόνο το SAF υποστηρίζεται - Το «Πλαίσιο Πρόσβασης Αποθήκευσης» δεν υποστηρίζεται σε Android KitKat και παλαιότερο Θα ερωτηθείτε πού να αποθηκεύσετε κάθε λήψη Δεν έχει ορισθεί φάκελος λήψεων ακόμα, eπιλέξτε τον προεπιλεγμένο φάκελο τώρα Host diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 51d61c92d..56d529b04 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -633,7 +633,6 @@ \n¿Desea desuscribirse de este canal\? Error al cargar el muro Desde Android 10 solo el \'Sistema de Acceso al Almacenamiento\' es soportado - El \'Sistema de Acceso al Almacenamiento\' no es sorportado en Android KitKat o versiones anteriores Se le preguntará dónde guardar cada descarga Deshabilitar el túnel de medios si experimenta una pantalla negra o interrupciones en la reproduccción de videos Deshabilitar el túnel de medios diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 7cb96405a..11b5266b0 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -632,7 +632,6 @@ Voog Vali eksemplar Android 10st alates on toetatud ainult salvestusjuurdepääsu raamistik \'Storage Access Framework\' - Android KitKat ja vanemad versioonid ei toeta salvestusjuurdepääsu raamistikku \'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). diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 80e2d5a04..8098db146 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -626,7 +626,6 @@ Ezin izan da \'%s\' jarioa kargatu. Errorea jarioa kargatzean Android 10etik aurrera \'Biltegiaren Sarrera Framework\'a soilik onartzen da - \'Biltegiaren Sarrera Framework\'a ez da Android KitKat eta aurreko bertsioetan onartzen Non gorde galdetuko zaizu deskarga bakoitzean Ez da deskargatzeko karpetarik ezarri oraindik, aukeratu lehenetsitako deskargatzeko karpeta orain Pribatutasuna diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index a00c64ad0..97fd534f1 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -636,7 +636,6 @@ اکنون می‌توانید متن درون شرخ را برگزینید. به یاد داشته باشید که در حالت گزینش، ممکن است صفحه چشمک زده و پیوندها قابل کلیک نباشند. هنوز شاخهٔ بارگیری‌ای تنظیم نشده. اکنون شاخهٔ بارگیری پیش‌گزیده را برگزینید برای ذخیرهٔ هر بارگیری از شما پرسیده خواهد شد - «چارچوب دسترسی ذخیره» روی اندروید کیت‌کت و پایین‌تر پشتیبانی نمی‌شود از اندروید ۱۰، تنها «چارچوب دسترسی ذخیره» پشتیبانی می‌شود نتوانست خوراک را برای «%s» بار کند. حساب این نگارنده نابود شده است. diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 8eef663b0..af19e4f12 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -638,7 +638,6 @@ \nHaluatko poistaa kanavan tilauksesta\? Ei voitu ladata syötettä hakusanalle \'%s\'. Virhe syötteen lataamisessa - \'Storage Access Framework\' ei ole tuettu Android KitKatissa tai vanhemmissa versioissa Sinulta kysytään joka kerta, minne tiedosto ladataan Älä näytä Matala laatu (pienempi) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 327877651..e09f292b0 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -640,7 +640,6 @@ \nNewPipe ne sera plus en mesure de charger ce flux à l’avenir. \nSouhaitez-vous vous désabonner de cette chaîne \? À partir d’Android 10, seule « l’Infrastructure d’accès au stockage  » est prise en charge - L’« Infrastructure d’accès au stockage » n’est pas prise en charge par Android KitKat et les versions antérieures Le mode flux rapide ne fournit pas plus d’info à ce sujet. Les commentaires sont désactivés Ne pas afficher diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 06b35acd3..5cf8abcf0 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -678,7 +678,6 @@ Procesando... Pode devagar un momento Crear unha notificación de erro Amosar fitas coloridas de Picasso na cima das imaxes que indican a súa fonte: vermello para a rede, azul para o disco e verde para a memoria - O \'Sistema de Acceso ao almacenamento\' non está soportado en Android KitKat e anteriores Novos elementos Predefinido do ExoPlayer Amosar \"Travar o reprodutor\" diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index b0ece06f1..de5128493 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -653,7 +653,6 @@ לא ניתן לטעון את ההזנה עבור ‚%s’. שגיאה בהורדת ההזנה התמיכה ב‚תשתית גישה לאחסון’ נתמכת מ־Android 10 בלבד - ‚תשתית הגישה לאחסון’ אינה נתמכת על ידי Android KitKat ומטה תופיע שאלה לאן לשמור כל הורדה טרם הוגדרה תיקיית הורדה, נא לבחור את תיקיית ההורדה כעת כבוי diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 0ca0c75dc..cb7db9f44 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -643,7 +643,6 @@ Nije moguće učitati feed za \'%s\'. Pogreška pri učitavanju feeda Počevši od Androida 10, podržan je samo \'Storage Access Framework\' - „Storage Access Framework“ nije podržan na Androidu KitKat i starijim Od vas će se tražiti gdje spremiti svako preuzimanje Ne prikazuj Niska kvaliteta (manji) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e5457d3e1..e25183985 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -582,7 +582,6 @@ Tiltsa le a médiacsatornázást, ha fekete képernyőt vagy akadozást tapasztal videólejátszáskor Picasso színes szalagok megjelenítése a képek fölött, megjelölve a forrásukat: piros a hálózathoz, kék a lemezhez, zöld a memóriához Minden letöltésnél meg fogja kérdezni, hogy hova mentse el - A „Storage Access Framework” nem támogatott Android KitKaten vagy régebbin Válasszon egy példányt Lista legutóbbi frissítése: %s Lista betöltése… diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index c1f296849..e84fb393a 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -623,7 +623,6 @@ Tidak bisa memuat langganan untuk \'%s\'. Galat memuat langganan Mulai Android 10, hanya \'Storage Access Framework\' yang didukung - \'Storage Access Framework\' tidak didukung pada Android KitKat dan yang lebih rendah Anda akan ditanya lokasi penyimpanan berkas unduhan Belum ada folder unduhan, pilih folder unduhan sekarang Nonaktif diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index f347eba67..96d541d80 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -633,7 +633,6 @@ Impossibile caricare feed per \"%s\". Errore caricamento feed A partire da Android 10 è supportato solo il Framework di accesso all\'archiviazione - Il Framework di accesso all\'archiviazione non è supportato su Android KitKat e versioni precedenti È necessario specificare la destinazione di ogni dowload Non è impostata alcuna cartella per i file scaricati, scegliere quella predefinita Disattivata diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 955912126..e65897de3 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -614,7 +614,6 @@ ダウンロードのたびに保存する場所を尋ねます ダウンロードフォルダがまだ設定されていません。今すぐデフォルトのフォルダを選択してください Android 10 以降は \'Storage Access Framework\' のみがサポートされます - \'Storage Access Framework\' は Android KitKat 以下ではサポートされていません 高速モードでこの情報の詳細は提供されません。 \'%s\' のフィードを読み込めませんでした。 フィードの読み込みエラー diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index e3ffbb03a..01b2e114b 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -647,7 +647,6 @@ \nAr norite atsisakyti šio kanalo prenumeratos\? Klaida įkeliant srautą Pradedant Android 10 palaikoma tik \'Storage Access Framework\' - \'Storage Access Framework\' nėra palaikomas Android KitKat ir žemesnėse versijose Jūsų bus paklausta, kur išsaugoti kiekvieną atsiuntimą Atsiuntimo aplankas dar nenustatytas, pasirinkite numatytąjį atsiuntimų aplanką dabar Komentarai yra išjungti diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 4db63cf13..ba8d49e57 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -636,7 +636,6 @@ Izslēgt multivides tuneļošanu Izslēdziet multivides tuneļošanu, ja jums video atskaņošanas laikā parādās melns ekrāns vai aizķeršanās Rādīt krāsainas lentes virs attēliem, norādot to avotu: sarkana - tīkls, zila - disks, zaļa - atmiņa - “Krātuves Piekļuves Sistēma” ir neatbalstīta uz Android KitKat un zemākām versijām Ieslēgt teksta atlasīšanu video aprakstā Lejupielādes mape vēl nav iestatīta, izvēlieties noklusējuma lejupielādes mapi Pārvelciet objektus, lai tos noņemtu diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index edbc7785e..0e2cf6962 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -639,7 +639,6 @@ ഫീഡ് ലോഡ് ചെയ്യുന്നതിൽ പിശക് സംഭവിച്ചിരിക്കുന്നു ആൻഡ്രോയ്ഡ് 10 മുതൽ മാത്രമേ \"സ്റ്റോറേജ് അക്സസ് ഫ്രെയിംവർക്ക്\" പിന്തുണക്കു എവിടെ ആണ് ഡൌൺലോഡ് ചെയ്യ്യപെടുന്ന ഓരോ ഫയൽലും സംരക്ഷിക്കപ്പെടേണ്ടത് എന്ന് തങ്കളോട് ചോദിക്കും - ആൻഡ്രോയ്ഡ് കിറ്റ് ക്യാറ്റോ അതിനു താഴെക്കോ ഉള്ളതിൽ \"സ്റ്റോറേജ് ആസസ്സ് ഫ്രെയിംവർക്ക് പിന്തുണക്കുന്നില്ല കാണിക്കരുത് കുറഞ്ഞ നിലവാരം (ചെറുത് ) ഉയർന്ന നിലവാരം (വലിയത് ) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 58b4cb08c..4bd996ede 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -618,7 +618,6 @@ Kunne ikke laste inn informasjonskanal for «%s». Kunne ikke laste inn informasjonskanal Fra Android 10 er kun «lagringstilgangsrammeverk» støttet - «Lagringstilgangsrammeverket» støttes ikke på Android KitKat og tidligere. Du vil bli spurt om hvor du vil lagre hver nedlastning Miniatyrbildeforhåndsvisning Ingen nedlastingsmappe valgt. Velg forvalgt nedlastingsmappe nå. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 7bdc902a5..fd80a7d54 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -637,7 +637,6 @@ Kan geen feed laden voor \'%s\'. Error bij het inladen van de feed Vanaf Android 10 is enkel \'Storage Access Framework\' ondersteund - Het \'Storage Access Framework\' is niet ondersteund op Android KitKat en lager U wordt gevraagd waar elk bestand wordt opgeslagen Nog geen downloadfolder gekozen, kies de standaard downloadfolder Geliefd door de maker diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 2b08ebb32..46b33206f 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -529,7 +529,6 @@ ਐਪ ਭਾਸ਼ਾ ਕੋਈ ਸਥਿਤੀ ਚੁਣੋ \'ਸਟੋਰੇਜ ਐਕਸੈੱਸ ਫ਼ਰੇਮਵਰਕ\' ਐਂਡਰਾਇਡ 10 ਤੋਂ ਕੰਮ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਦਾ ਹੈ - \'ਸਟੋਰੇਜ ਐਕਸੈੱਸ ਫ਼ਰੇਮਵਰਕ\' ਐਂਡਰਾਇਡ ਕਿਟਕੈਟ ਅਤੇ ਇਸਤੋਂ ਹੇਠਾਂ ਦੇ ਵਰਜਨਾਂ \'ਤੇ ਕੰਮ ਨਹੀਂ ਕਰਦਾ ਤੁਹਾਨੂੰ ਹਰ ਵਾਰ ਪੁੱਛਿਆ ਜਾਵੇਗਾ ਕਿ ਡਾਊਨਲੋਡ ਨੂੰ ਕਿੱਥੇ ਸਾਂਭਣਾ ਹੈ ਡਾਊਨਲੋਡ ਕੀਤੀਆਂ ਫ਼ਾਈਲਾਂ ਮਿਟਾਓ ਡਾਊਨਲੋਡ ਇਤਿਹਾਸ ਸਾਫ਼ ਕਰੋ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a09549fcf..283c1f223 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -646,7 +646,6 @@ \nCzy chcesz anulować subskrypcję tego kanału\? Nie udało się załadować kanału dla „%s”. Począwszy od Androida 10 obsługiwany jest tylko systemowy selektor folderów (SAF) - Systemowy selektor folderów (SAF) nie jest obsługiwany przez system Android KitKat i niższy Zostaniesz zapytany(-na), gdzie zapisać każdy pobierany plik Nie ustawiono jeszcze folderu zapisywania, wybierz domyślny teraz Błąd podczas ładowania kanału diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4b0af0718..0a4b2c2ad 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -633,7 +633,6 @@ Não foi possível carregar o feed para \'%s\'. Erro ao carregar o feed O \'Storage Access Framework\' é compatível apenas com versões a partir do Android 10 - O \'Storage Access Framework\' não é compatível com Android KitKat e versões anteriores Você será questionado onde salvar cada download Nenhuma pasta de download definida ainda, escolha a pasta de download padrão agora Desligado diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 9a9adad52..4f3559eeb 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -638,7 +638,6 @@ Não foi possível carregar o feed para \'%s\'. Erro ao carregar o feed A partir do Android 10, apenas o \'Storage Access Framework\' é compatível - \'Storage Access Framework\' não é compatível com Android KitKat e versões anteriores Sempre que descarregar um ficheiro, terá que indicar o local para o guardar Não mostrar Baixa qualidade (menor) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5453b1fa4..b1f579298 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -619,7 +619,6 @@ Não foi possível carregar o feed para \'%s\'. Erro ao carregar o feed A partir do Android 10, apenas o \'Storage Access Framework\' é compatível - A \'Framework de acesso ao armazenamento\' não está disponível no Android KitKat e anteriores Pré-visualização da miniatura da barra de pesquisa Marcar como visto Desligado diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index fe2292ef7..58a38ea6e 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -649,7 +649,6 @@ Nu s-a putut încărca fluxul pentru \"%s\". Eroare la încărcarea fluxului Începând cu Android 10, este acceptat doar \"Storage Access Framework\" - \"Storage Access Framework\" nu este acceptat pe Android KitKat și versiunile ulterioare Veți fi întrebat unde să salvați fiecare descărcare S-a șters %1$s descărcare diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 64351c7d3..c91368c2a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -651,7 +651,6 @@ %s указывает следующую причину: Аккаунт отключён Начиная с Android 10 поддерживается только \"Storage Access Framework\" - \"Storage Access Framework\" не поддерживается на Android KitKat и ниже Спрашивать, куда сохранять каждую загрузку Папка для загрузки ещё не выбрана, укажите папку для загрузки сейчас Отключить diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 2fc18cb6f..81a71402e 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -633,7 +633,6 @@ \nCheres bogare s\'iscritzione a custu canale\? Carrigamentu de su flussu pro \'%s\' fallidu. Errore carrighende su flussu - Su \'Storage Access Framework\' no est suportadu in Android KitKat e versiones prus betzas T\'at a bènnere pedidu in ue sarvare cada documentu Non b\'at galu peruna cartella de iscarrigamentu impostada. Issèbera como sa cartella de iscarrigamentu predefinida Istudadu diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index bfba46a5c..4645fcfff 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -643,7 +643,6 @@ Nemožno načítať informačný kanál \'%s\'. Chyba pri načítaní kanála \'Storage Access Framework\' je podporovaný len od Androidu 10 a vyššie - \'Storage Access Framework\' nie je podporovaný v systéme Android KitKat a ani v starších verziách Pri každom sťahovaní sa zobrazí výzva kam uložiť súbor Nie je nastavený adresár na sťahovanie, nastavte ho teraz Označiť ako videné diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index f8be2c8a5..d12b1e4d4 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -633,7 +633,6 @@ Lama soo kicin karo bandhigga: \'%s\'. Khalad ayaa ka dhacay sookicintii Laga bilaabo Android 10 kaliya waxaa la isticmaali \'SAF\' - \'SAF\' kuma shaqeeyo Android KitKat iyo wixii ka hooseeya Dajin walba meeshii lagu kaydin lahaa ayaa lagu waydiin Iska xidh kala-leexinta muuqaalada/dhagaysiga hadaad lakulanto shaashad madow ama muuqaalka oo isistaaga Xidh kala-leexinta diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index c5b016dd4..74d89908b 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -626,7 +626,6 @@ Nuk u arrit të ngarkohej feed-i për \'%s\'. Gabim gjatë ngarkimit të feed-it Duke nisur nga Android 10 vetëm \'Storage Access Framework\' është i mbështetur - \'Storage Access Framework\' nuk është e mbështetur në Android KitKat dhe më poshtë Ju do të pyeteni se ku doni të ruani çdo shkarkim Rrëzoje aplikacionin manualisht Ç\'aktivizo tunelin e medias nëse po hasni një ekran të zi apo ngecje gjatë luajtjes së një videoje diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 68981868c..26c3e896f 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -642,7 +642,6 @@ Не могу да учитам довод за „%s“. Грешка учитавања довода Од Андроида 10 само „Storage Access Framework“ је подржан - „Storage Access Framework“ није подржан на Андроиду 4.4 и старијим Питаће вас где да сачувате свако преузимање Фасцикла за преузимање није одређена. Изаберите подразумевану фасциклу искљ diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 53cd6909b..edcc13b08 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -633,7 +633,6 @@ Visa sedda objekt Det snabba flödesläget ger inte mer information om detta. Fel vid inläsning av flödet - \"Storage Access Framework\" är inte tillgängligt på Android KitKat och tidigare versioner Markera som sedd Ej listad Aktuellt diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 512790f7c..0986fd74b 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -633,7 +633,6 @@ \'%s\' için besleme yüklenemedi. Besleme yüklenirken hata \'Depolama Erişimi Çerçevesi\' yalnızca Android 10\'dan başlayarak desteklenmektedir - \'Depolama Erişimi Çerçevesi\' Android KitKat ve altında desteklenmez Her indirmede nereye kaydedileceği sorulacak İndirme klasörü belirlenmedi, şimdi öntanımlı indirme klasörünü seçin Kapat diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 50e1b00b7..736a49c8e 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -620,7 +620,6 @@ Вимкнення тунелювання медіаданих за наявності чорного екрана або гальмування під час відтворення відео Вимкнути тунелювання медіа «Фреймворк доступу до сховища» (SAF) підтримується лише починаючи з Android 10 - «Фреймворк доступу до сховища» (SAF) не підтримується в KitKat і нижче Вас питатиме, куди зберігати кожне завантаження Не вказано теки завантаження, оберіть типову теку завантаження зараз Відкрити вебсайт diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 658c96b55..2346a4cf9 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -627,7 +627,6 @@ Không thể tải nguồn dữ liệu cho \'%s\'. Lỗi khi tải nguồn dữ liệu \'Storage Access Framework\' chỉ được hỗ trợ từ Android 10 trở đi - \'Storage Access Framework\' không được hỗ trợ trên Android KitKat và cũ hơn Bạn sẽ được hỏi nơi bạn muốn lưu mỗi mục tải xuống Chưa có thư mục tải xuống nào được đặt, hãy chọn thư mục tải xuống mặc định ngay Không hiện diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b3642829c..3c307f195 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -623,7 +623,6 @@ 无法加载“%s”的 Feed。 加载 Feed 时出错 仅 Android 10 及以上版本支持“存储访问框架” - Android KitKat 及更低版本不支持“存储访问框架” 你会被问到在哪里保存每个下载 尚未设置下载文件夹,现在选择默认下载文件夹 平板模式 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 399282f7c..9b142a747 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -563,7 +563,6 @@ 預設開啟內容嘅時候做咩好 — %s 脫鈎 (聲音可能會失真) 飛前一格 - Android KitKat 以及樓下唔支援「儲存空間存取框架」 未有收起嘅播放清單 照單全播 未有頻道訂閱 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index db6caf56d..47117ee90 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -623,7 +623,6 @@ 無法載入「%s」。 載入 feed 時發生錯誤 從 Android 10 開始僅支援「儲存空間存取框架」 - Android KitKat 或更舊的版本不支援「儲存空間存取框架」 每次下載都會詢問您要下載到哪裡 尚未設定下載資料夾,立刻選擇預設的下載資料夾 關閉 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cedb01b20..b742f2517 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -615,7 +615,6 @@ You will be asked where to save each download Use system folder picker (SAF) The \'Storage Access Framework\' allows downloads to an external SD card - The \'Storage Access Framework\' is not supported on Android KitKat and below Starting from Android 10 only \'Storage Access Framework\' is supported Choose an instance App language diff --git a/app/src/main/res/xml/download_settings.xml b/app/src/main/res/xml/download_settings.xml index fa15af406..48e7bbd2e 100644 --- a/app/src/main/res/xml/download_settings.xml +++ b/app/src/main/res/xml/download_settings.xml @@ -12,6 +12,7 @@ app:iconSpaceReserved="false" /> Date: Wed, 6 Jul 2022 17:46:39 -0400 Subject: [PATCH 024/152] Clean up pre-Lollipop compat attributes --- app/build.gradle | 1 - .../activity_player_queue_control.xml | 10 ++-- .../fragment_video_detail.xml | 58 ++++++++---------- .../layout/activity_player_queue_control.xml | 10 ++-- .../res/layout/dialog_feed_group_create.xml | 2 +- .../res/layout/dialog_playback_parameter.xml | 3 +- app/src/main/res/layout/dialog_playlists.xml | 3 +- app/src/main/res/layout/drawer_header.xml | 6 +- .../res/layout/feed_group_add_new_item.xml | 2 +- .../res/layout/feed_group_reorder_item.xml | 2 +- app/src/main/res/layout/fragment_about.xml | 3 +- .../main/res/layout/fragment_choose_tabs.xml | 4 +- .../main/res/layout/fragment_description.xml | 4 +- app/src/main/res/layout/fragment_feed.xml | 3 +- .../res/layout/fragment_instance_list.xml | 4 +- .../main/res/layout/fragment_video_detail.xml | 59 ++++++++----------- app/src/main/res/layout/item_instance.xml | 2 +- .../res/layout/item_search_suggestion.xml | 3 +- app/src/main/res/layout/list_choose_tabs.xml | 2 +- .../main/res/layout/list_comments_item.xml | 36 ++--------- .../res/layout/list_comments_mini_item.xml | 29 +-------- .../res/layout/list_playlist_grid_item.xml | 5 +- .../main/res/layout/list_playlist_item.xml | 5 +- .../res/layout/list_playlist_mini_item.xml | 5 +- .../layout/list_stream_playlist_grid_item.xml | 3 +- .../res/layout/list_stream_playlist_item.xml | 3 +- app/src/main/res/layout/mission_item.xml | 2 +- .../main/res/layout/mission_item_linear.xml | 2 +- .../res/layout/picker_subscription_item.xml | 2 +- app/src/main/res/layout/play_queue_item.xml | 2 +- app/src/main/res/layout/player.xml | 36 +++++------ .../layout/player_fast_seek_seconds_view.xml | 7 +-- .../res/layout/player_popup_close_overlay.xml | 4 +- app/src/main/res/layout/playlist_control.xml | 11 ++-- .../res/layout/statistic_playlist_control.xml | 3 +- .../main/res/layout/stream_quality_item.xml | 3 +- .../main/res/layout/toolbar_search_layout.xml | 2 +- 37 files changed, 122 insertions(+), 219 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 06eb822cc..67026df7e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,7 +20,6 @@ android { versionName "0.23.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables.useSupportLibrary = true javaCompileOptions { annotationProcessorOptions { diff --git a/app/src/main/res/layout-land/activity_player_queue_control.xml b/app/src/main/res/layout-land/activity_player_queue_control.xml index c2359552e..7d3b43ecc 100644 --- a/app/src/main/res/layout-land/activity_player_queue_control.xml +++ b/app/src/main/res/layout-land/activity_player_queue_control.xml @@ -138,8 +138,8 @@ android:clickable="true" android:focusable="true" android:scaleType="fitCenter" + android:src="@drawable/ic_pause" android:tint="?attr/colorAccent" - app:srcCompat="@drawable/ic_pause" tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 851085b5b..d8e52b354 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -65,8 +65,8 @@ android:layout_height="64dp" android:layout_gravity="center" android:background="@android:color/transparent" + android:src="@drawable/ic_play_arrow_shadow" android:visibility="invisible" - app:srcCompat="@drawable/ic_play_arrow_shadow" tools:ignore="ContentDescription" tools:visibility="visible" /> @@ -199,7 +199,7 @@ android:layout_gravity="top|end" android:layout_marginTop="11dp" android:layout_marginEnd="10dp" - app:srcCompat="@drawable/ic_expand_more" + android:src="@drawable/ic_expand_more" tools:ignore="ContentDescription" /> @@ -326,18 +326,6 @@ tools:text="Uploader" /> - @@ -369,7 +357,7 @@ android:layout_height="@dimen/video_item_detail_like_image_height" android:layout_below="@id/detail_view_count_view" android:contentDescription="@string/detail_likes_img_view_description" - app:srcCompat="@drawable/ic_thumb_up" /> + android:src="@drawable/ic_thumb_up" /> + android:textSize="@dimen/detail_control_text_size" /> + android:textSize="@dimen/detail_control_text_size" /> + android:textSize="@dimen/detail_control_text_size" /> + android:textSize="@dimen/detail_control_text_size" /> @@ -526,12 +514,12 @@ android:background="?attr/selectableItemBackgroundBorderless" android:clickable="true" android:contentDescription="@string/share" + android:drawableTop="@drawable/ic_share" android:focusable="true" android:gravity="center" android:paddingVertical="@dimen/detail_control_padding" android:text="@string/share" - android:textSize="@dimen/detail_control_text_size" - app:drawableTopCompat="@drawable/ic_share" /> + android:textSize="@dimen/detail_control_text_size" /> + android:textSize="@dimen/detail_control_text_size" /> + android:textSize="@dimen/detail_control_text_size" /> - + android:textSize="@dimen/detail_control_text_size" /> @@ -719,7 +707,7 @@ android:scaleType="center" android:focusable="true" android:focusedByDefault="true" - app:srcCompat="@drawable/ic_play_arrow" + android:src="@drawable/ic_play_arrow" tools:ignore="ContentDescription,RtlHardcoded" /> diff --git a/app/src/main/res/layout/activity_player_queue_control.xml b/app/src/main/res/layout/activity_player_queue_control.xml index 24e062932..29efa36f9 100644 --- a/app/src/main/res/layout/activity_player_queue_control.xml +++ b/app/src/main/res/layout/activity_player_queue_control.xml @@ -174,8 +174,8 @@ android:clickable="true" android:focusable="true" android:scaleType="fitXY" + android:src="@drawable/ic_repeat" android:tint="?attr/colorAccent" - app:srcCompat="@drawable/ic_repeat" tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/dialog_feed_group_create.xml b/app/src/main/res/layout/dialog_feed_group_create.xml index a08041e97..464940238 100644 --- a/app/src/main/res/layout/dialog_feed_group_create.xml +++ b/app/src/main/res/layout/dialog_feed_group_create.xml @@ -193,8 +193,8 @@ android:layout_centerVertical="true" android:minWidth="0dp" android:scaleType="centerInside" + android:src="@drawable/ic_delete" android:visibility="gone" - app:srcCompat="@drawable/ic_delete" tools:ignore="ContentDescription" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index e402f4fb1..cc506cc79 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -1,6 +1,5 @@ @@ -20,7 +19,7 @@ android:layout_centerVertical="true" android:layout_marginLeft="12dp" android:layout_marginRight="12dp" - app:srcCompat="@drawable/ic_playlist_add" + android:src="@drawable/ic_playlist_add" tools:ignore="ContentDescription,RtlHardcoded" /> + tools:src="@drawable/ic_smart_display" /> diff --git a/app/src/main/res/layout/feed_group_add_new_item.xml b/app/src/main/res/layout/feed_group_add_new_item.xml index df3fc9b3a..0dfe819a6 100644 --- a/app/src/main/res/layout/feed_group_add_new_item.xml +++ b/app/src/main/res/layout/feed_group_add_new_item.xml @@ -24,7 +24,7 @@ android:layout_height="14dp" android:layout_gravity="center" android:scaleType="centerInside" - app:srcCompat="@drawable/ic_add" + android:src="@drawable/ic_add" tools:ignore="ContentDescription" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index db93f1d8d..1807acb10 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -1,5 +1,4 @@ + android:src="@mipmap/ic_launcher" /> + app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/res/layout/fragment_description.xml b/app/src/main/res/layout/fragment_description.xml index b020d5db3..157b8f394 100644 --- a/app/src/main/res/layout/fragment_description.xml +++ b/app/src/main/res/layout/fragment_description.xml @@ -38,11 +38,11 @@ android:contentDescription="@string/description_select_enable" android:focusable="true" android:padding="5dp" + android:src="@drawable/ic_select_all" app:layout_constraintBottom_toTopOf="@+id/barrier" app:layout_constraintDimensionRatio="1" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:srcCompat="@drawable/ic_select_all" /> + app:layout_constraintTop_toTopOf="parent" /> @@ -57,7 +56,7 @@ android:layout_alignParentEnd="true" android:layout_marginStart="6dp" android:layout_marginEnd="12dp" - app:srcCompat="@drawable/ic_refresh" + android:src="@drawable/ic_refresh" tools:ignore="ContentDescription" /> + app:fabSize="auto" /> diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 08a9bcf0a..18655283e 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -55,8 +55,8 @@ android:layout_height="64dp" android:layout_gravity="center" android:background="@android:color/transparent" + android:src="@drawable/ic_play_arrow_shadow" android:visibility="invisible" - app:srcCompat="@drawable/ic_play_arrow_shadow" tools:ignore="ContentDescription" tools:visibility="visible" /> @@ -187,7 +187,7 @@ android:layout_gravity="top|end" android:layout_marginTop="11dp" android:layout_marginEnd="10dp" - app:srcCompat="@drawable/ic_expand_more" + android:src="@drawable/ic_expand_more" tools:ignore="ContentDescription" /> @@ -313,19 +313,6 @@ tools:text="Uploader" /> - - @@ -357,7 +344,7 @@ android:layout_height="@dimen/video_item_detail_like_image_height" android:layout_below="@id/detail_view_count_view" android:contentDescription="@string/detail_likes_img_view_description" - app:srcCompat="@drawable/ic_thumb_up" /> + android:src="@drawable/ic_thumb_up" /> + android:textSize="@dimen/detail_control_text_size" /> + android:textSize="@dimen/detail_control_text_size" /> + android:textSize="@dimen/detail_control_text_size" /> + android:textSize="@dimen/detail_control_text_size" /> @@ -510,12 +497,12 @@ android:background="?attr/selectableItemBackgroundBorderless" android:clickable="true" android:contentDescription="@string/share" + android:drawableTop="@drawable/ic_share" android:focusable="true" android:gravity="center" android:paddingVertical="@dimen/detail_control_padding" android:text="@string/share" - android:textSize="@dimen/detail_control_text_size" - app:drawableTopCompat="@drawable/ic_share" /> + android:textSize="@dimen/detail_control_text_size" /> + android:textSize="@dimen/detail_control_text_size" /> + android:textSize="@dimen/detail_control_text_size" /> - + android:textSize="@dimen/detail_control_text_size" /> @@ -696,7 +683,7 @@ android:scaleType="center" android:focusable="true" android:focusedByDefault="true" - app:srcCompat="@drawable/ic_play_arrow" + android:src="@drawable/ic_play_arrow" tools:ignore="ContentDescription,RtlHardcoded" /> diff --git a/app/src/main/res/layout/item_instance.xml b/app/src/main/res/layout/item_instance.xml index dd5b4156f..12ecb2ea7 100644 --- a/app/src/main/res/layout/item_instance.xml +++ b/app/src/main/res/layout/item_instance.xml @@ -75,7 +75,7 @@ android:paddingTop="12dp" android:paddingRight="10dp" android:paddingBottom="12dp" - app:srcCompat="@drawable/ic_drag_handle" + android:src="@drawable/ic_drag_handle" tools:ignore="ContentDescription,RtlHardcoded" /> diff --git a/app/src/main/res/layout/item_search_suggestion.xml b/app/src/main/res/layout/item_search_suggestion.xml index 4b1025fea..f7a07bbcc 100644 --- a/app/src/main/res/layout/item_search_suggestion.xml +++ b/app/src/main/res/layout/item_search_suggestion.xml @@ -1,6 +1,5 @@ diff --git a/app/src/main/res/layout/list_choose_tabs.xml b/app/src/main/res/layout/list_choose_tabs.xml index e6cebda11..ed7ad94c4 100644 --- a/app/src/main/res/layout/list_choose_tabs.xml +++ b/app/src/main/res/layout/list_choose_tabs.xml @@ -55,7 +55,7 @@ android:paddingTop="12dp" android:paddingRight="16dp" android:paddingBottom="12dp" - app:srcCompat="@drawable/ic_drag_handle" + android:src="@drawable/ic_drag_handle" tools:ignore="ContentDescription,RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index 3b148de03..4358c438f 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -32,8 +32,8 @@ android:layout_marginRight="@dimen/video_item_detail_pinned_right_margin" android:layout_toEndOf="@+id/itemThumbnailView" android:contentDescription="@string/detail_pinned_comment_view_description" + android:src="@drawable/ic_pin" android:visibility="gone" - app:srcCompat="@drawable/ic_pin" tools:visibility="visible" /> + android:src="@drawable/ic_thumb_up" /> - - - - - + tools:visibility="visible" /> + android:src="@drawable/ic_thumb_up" /> - - - - diff --git a/app/src/main/res/layout/mission_item_linear.xml b/app/src/main/res/layout/mission_item_linear.xml index d7235fcaf..ce2d1af4b 100644 --- a/app/src/main/res/layout/mission_item_linear.xml +++ b/app/src/main/res/layout/mission_item_linear.xml @@ -72,7 +72,7 @@ android:layout_marginRight="4dp" android:contentDescription="TODO" android:scaleType="centerInside" - app:srcCompat="@drawable/ic_more_vert" + android:src="@drawable/ic_more_vert" app:tint="?attr/actionColor" /> diff --git a/app/src/main/res/layout/picker_subscription_item.xml b/app/src/main/res/layout/picker_subscription_item.xml index 1aaa1e7d8..c858ccc4e 100644 --- a/app/src/main/res/layout/picker_subscription_item.xml +++ b/app/src/main/res/layout/picker_subscription_item.xml @@ -52,8 +52,8 @@ android:layout_gravity="top|center_horizontal" android:layout_marginTop="4dp" android:scaleType="centerInside" + android:src="@drawable/ic_done" app:tint="@color/white" - app:srcCompat="@drawable/ic_done" tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/play_queue_item.xml b/app/src/main/res/layout/play_queue_item.xml index 5fb9419f6..7d672dc8b 100644 --- a/app/src/main/res/layout/play_queue_item.xml +++ b/app/src/main/res/layout/play_queue_item.xml @@ -89,10 +89,10 @@ android:layout_gravity="center_vertical" android:paddingHorizontal="@dimen/video_item_search_image_right_margin" android:scaleType="center" + android:src="@drawable/ic_drag_handle" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" - app:srcCompat="@drawable/ic_drag_handle" tools:ignore="ContentDescription,RtlHardcoded" /> diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index d748942e0..b8b60b3f8 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -112,8 +112,8 @@ android:focusable="true" android:padding="@dimen/player_main_buttons_padding" android:scaleType="fitXY" + android:src="@drawable/ic_close" android:visibility="gone" - app:srcCompat="@drawable/ic_close" app:tint="@color/white" tools:ignore="ContentDescription,RtlHardcoded" /> @@ -198,8 +198,8 @@ android:paddingEnd="3dp" android:paddingBottom="3dp" android:scaleType="fitCenter" + android:src="@drawable/ic_list" android:visibility="gone" - app:srcCompat="@drawable/ic_list" app:tint="@color/white" tools:ignore="ContentDescription,RtlHardcoded" /> @@ -216,8 +216,8 @@ android:paddingEnd="6dp" android:paddingBottom="3dp" android:scaleType="fitCenter" + android:src="@drawable/ic_format_list_numbered" android:visibility="gone" - app:srcCompat="@drawable/ic_format_list_numbered" app:tint="@color/white" tools:ignore="ContentDescription,RtlHardcoded" /> @@ -230,7 +230,7 @@ android:focusable="true" android:padding="@dimen/player_main_buttons_padding" android:scaleType="fitXY" - app:srcCompat="@drawable/ic_expand_more" + android:src="@drawable/ic_expand_more" app:tint="@color/white" tools:ignore="ContentDescription,RtlHardcoded" /> @@ -294,7 +294,7 @@ android:focusable="true" android:padding="@dimen/player_main_buttons_padding" android:scaleType="fitXY" - app:srcCompat="@drawable/ic_cast" + android:src="@drawable/ic_cast" app:tint="@color/white" tools:ignore="RtlHardcoded" /> @@ -309,7 +309,7 @@ android:focusable="true" android:padding="@dimen/player_main_buttons_padding" android:scaleType="fitXY" - app:srcCompat="@drawable/ic_language" + android:src="@drawable/ic_language" app:tint="@color/white" tools:ignore="RtlHardcoded" /> @@ -324,7 +324,7 @@ android:focusable="true" android:padding="@dimen/player_main_buttons_padding" android:scaleType="fitXY" - app:srcCompat="@drawable/ic_share" + android:src="@drawable/ic_share" app:tint="@color/white" tools:ignore="RtlHardcoded" /> @@ -338,7 +338,7 @@ android:focusable="true" android:padding="@dimen/player_main_buttons_padding" android:scaleType="fitXY" - app:srcCompat="@drawable/ic_volume_off" + android:src="@drawable/ic_volume_off" app:tint="@color/white" tools:ignore="RtlHardcoded" /> @@ -351,8 +351,8 @@ android:focusable="true" android:padding="@dimen/player_main_buttons_padding" android:scaleType="fitCenter" + android:src="@drawable/ic_fullscreen" android:visibility="gone" - app:srcCompat="@drawable/ic_fullscreen" app:tint="@color/white" tools:ignore="ContentDescription,RtlHardcoded" tools:visibility="visible" /> @@ -397,8 +397,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="2dp" + android:src="@drawable/dummy_thumbnail" android:visibility="gone" - app:srcCompat="@drawable/dummy_thumbnail" tools:visibility="visible" /> @@ -475,8 +475,8 @@ android:nextFocusUp="@id/playbackSeekBar" android:padding="@dimen/player_main_buttons_padding" android:scaleType="fitCenter" + android:src="@drawable/ic_fullscreen" android:visibility="gone" - app:srcCompat="@drawable/ic_fullscreen" app:tint="@color/white" tools:ignore="ContentDescription,RtlHardcoded" tools:visibility="visible" /> @@ -500,7 +500,7 @@ android:clickable="true" android:focusable="true" android:scaleType="fitCenter" - app:srcCompat="@drawable/ic_previous" + android:src="@drawable/ic_previous" app:tint="@color/white" tools:ignore="ContentDescription" /> @@ -512,7 +512,7 @@ android:layout_weight="1" android:background="?attr/selectableItemBackgroundBorderless" android:scaleType="fitCenter" - app:srcCompat="@drawable/ic_pause" + android:src="@drawable/ic_pause" app:tint="@color/white" tools:ignore="ContentDescription" /> @@ -526,7 +526,7 @@ android:clickable="true" android:focusable="true" android:scaleType="fitCenter" - app:srcCompat="@drawable/ic_next" + android:src="@drawable/ic_next" app:tint="@color/white" tools:ignore="ContentDescription" /> @@ -577,8 +577,8 @@ android:focusable="true" android:padding="10dp" android:scaleType="fitXY" + android:src="@drawable/exo_controls_repeat_off" android:tint="?attr/colorAccent" - app:srcCompat="@drawable/exo_controls_repeat_off" tools:ignore="ContentDescription,RtlHardcoded" /> diff --git a/app/src/main/res/layout/player_fast_seek_seconds_view.xml b/app/src/main/res/layout/player_fast_seek_seconds_view.xml index 57f5aa787..2946f8449 100644 --- a/app/src/main/res/layout/player_fast_seek_seconds_view.xml +++ b/app/src/main/res/layout/player_fast_seek_seconds_view.xml @@ -1,6 +1,5 @@ diff --git a/app/src/main/res/layout/player_popup_close_overlay.xml b/app/src/main/res/layout/player_popup_close_overlay.xml index b2403583d..10d81d77e 100644 --- a/app/src/main/res/layout/player_popup_close_overlay.xml +++ b/app/src/main/res/layout/player_popup_close_overlay.xml @@ -10,8 +10,8 @@ android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" android:layout_marginBottom="24dp" + android:src="@drawable/ic_close" app:backgroundTint="@color/light_youtube_primary_color" app:borderWidth="0dp" - app:fabSize="normal" - app:srcCompat="@drawable/ic_close" /> + app:fabSize="normal" /> diff --git a/app/src/main/res/layout/playlist_control.xml b/app/src/main/res/layout/playlist_control.xml index a5f258f50..5a8856128 100644 --- a/app/src/main/res/layout/playlist_control.xml +++ b/app/src/main/res/layout/playlist_control.xml @@ -1,6 +1,5 @@ + android:textSize="@dimen/channel_rss_title_size" /> + android:textSize="@dimen/channel_rss_title_size" /> diff --git a/app/src/main/res/layout/statistic_playlist_control.xml b/app/src/main/res/layout/statistic_playlist_control.xml index 577e94e4b..36540d32e 100644 --- a/app/src/main/res/layout/statistic_playlist_control.xml +++ b/app/src/main/res/layout/statistic_playlist_control.xml @@ -1,6 +1,5 @@ @@ -13,7 +12,7 @@ android:layout_centerVertical="true" android:layout_marginLeft="6dp" android:scaleType="fitCenter" - app:srcCompat="@drawable/ic_volume_off" + android:src="@drawable/ic_volume_off" tools:ignore="ContentDescription,RtlHardcoded" /> From 9648525ac19207643c18d85c5b1afef8dfd21b08 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:46:57 -0400 Subject: [PATCH 025/152] Clean up pre-Lollipop theming --- .../styles.xml | 2 +- app/src/main/res/values-v21/styles.xml | 42 --------- .../main/res/values-v21/styles_services.xml | 92 ------------------- app/src/main/res/values/colors_services.xml | 16 ---- app/src/main/res/values/styles.xml | 26 +++--- app/src/main/res/values/styles_misc.xml | 8 +- app/src/main/res/values/styles_services.xml | 36 +++++--- 7 files changed, 41 insertions(+), 181 deletions(-) rename app/src/main/res/{values-night-v21 => values-night}/styles.xml (73%) delete mode 100644 app/src/main/res/values-v21/styles.xml delete mode 100644 app/src/main/res/values-v21/styles_services.xml diff --git a/app/src/main/res/values-night-v21/styles.xml b/app/src/main/res/values-night/styles.xml similarity index 73% rename from app/src/main/res/values-night-v21/styles.xml rename to app/src/main/res/values-night/styles.xml index eb39dee38..7327ac145 100644 --- a/app/src/main/res/values-night-v21/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -2,7 +2,7 @@ - diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml deleted file mode 100644 index 8fa00d0d8..000000000 --- a/app/src/main/res/values-v21/styles.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/colors_services.xml b/app/src/main/res/values/colors_services.xml index d6cd73d52..f3487810a 100644 --- a/app/src/main/res/values/colors_services.xml +++ b/app/src/main/res/values/colors_services.xml @@ -2,53 +2,37 @@ #e53935 - #992722 #000000 - #e53935 #992722 - #7a1717 #FFFFFF - #992722 #f57c00 - #995700 #000000 - #f57c00 #a35300 - #7d4000 #FFFFFF - #a35300 #ff6f00 - #c43e00 #000000 - #ff6f00 #a34700 - #942f00 #FFFFFF - #a34700 #9e9e9e #000000 - #9e9e9e #878787 #FFFFFF - #878787 #17a0c4 #000000 - #17a0c4 #1383a1 #FFFFFF - #1383a1 \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e711b35ab..164f10672 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,27 +1,29 @@ - + + - - - - diff --git a/app/src/main/res/values/styles_services.xml b/app/src/main/res/values/styles_services.xml index ce67f468b..db1fba397 100644 --- a/app/src/main/res/values/styles_services.xml +++ b/app/src/main/res/values/styles_services.xml @@ -1,16 +1,22 @@ - - - From 4ec9cbe379a55579648ec2ac28e14bf5e232db76 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 6 Jul 2022 17:47:12 -0400 Subject: [PATCH 026/152] Remove AndroidX Webkit --- app/build.gradle | 1 - .../org/schabi/newpipe/error/ReCaptchaActivity.java | 12 +++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 67026df7e..9867037e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -216,7 +216,6 @@ dependencies { // Newer version specified to prevent accessibility regressions with RecyclerView, see: // https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01 implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' - implementation 'androidx.webkit:webkit:1.4.0' implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" implementation 'com.google.android.material:material:1.5.0' diff --git a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java index c9f3a82d9..e2780d215 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java @@ -8,8 +8,10 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.webkit.CookieManager; +import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; +import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -17,7 +19,6 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.NavUtils; import androidx.preference.PreferenceManager; -import androidx.webkit.WebViewClientCompat; import org.schabi.newpipe.databinding.ActivityRecaptchaBinding; import org.schabi.newpipe.DownloaderImpl; @@ -85,14 +86,15 @@ public class ReCaptchaActivity extends AppCompatActivity { webSettings.setJavaScriptEnabled(true); webSettings.setUserAgentString(DownloaderImpl.USER_AGENT); - recaptchaBinding.reCaptchaWebView.setWebViewClient(new WebViewClientCompat() { + recaptchaBinding.reCaptchaWebView.setWebViewClient(new WebViewClient() { @Override - public boolean shouldOverrideUrlLoading(final WebView view, final String url) { + public boolean shouldOverrideUrlLoading(final WebView view, + final WebResourceRequest request) { if (MainActivity.DEBUG) { - Log.d(TAG, "shouldOverrideUrlLoading: url=" + url); + Log.d(TAG, "shouldOverrideUrlLoading: url=" + request.getUrl().toString()); } - handleCookiesFromUrl(url); + handleCookiesFromUrl(request.getUrl().toString()); return false; } From 189c92affadf40f3bedd7f52eec1b1813fce98a3 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 6 Jul 2022 17:48:38 -0400 Subject: [PATCH 027/152] More minSdk 21 cleanup --- .../main/java/org/schabi/newpipe/error/ErrorUtil.kt | 8 +------- app/src/main/java/org/schabi/newpipe/ktx/View.kt | 11 +++-------- .../settings/PlayerNotificationSettingsFragment.kt | 10 ---------- .../custom/NotificationActionsPreference.java | 2 +- 4 files changed, 5 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt index e4dd2e16d..86e2e1028 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt @@ -114,13 +114,7 @@ class ErrorUtil { context, context.getString(R.string.error_report_channel_id) ) - .setSmallIcon( - // the vector drawable icon causes crashes on KitKat devices - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - R.drawable.ic_bug_report - else - android.R.drawable.stat_notify_error - ) + .setSmallIcon(R.drawable.ic_bug_report) .setContentTitle(context.getString(R.string.error_report_notification_title)) .setContentText(context.getString(errorInfo.messageStringId)) .setAutoCancel(true) 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 ace1dbf7e..ea680dd60 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -12,7 +12,6 @@ import android.view.View import androidx.annotation.ColorInt import androidx.annotation.FloatRange import androidx.core.animation.addListener -import androidx.core.view.ViewCompat import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -21,10 +20,6 @@ import org.schabi.newpipe.MainActivity private const val TAG = "ViewUtils" -inline var View.backgroundTintListCompat: ColorStateList? - get() = ViewCompat.getBackgroundTintList(this) - set(value) = ViewCompat.setBackgroundTintList(this, value) - /** * Animate the view. * @@ -106,11 +101,11 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo viewPropertyAnimator.interpolator = FastOutSlowInInterpolator() viewPropertyAnimator.duration = duration viewPropertyAnimator.addUpdateListener { animation: ValueAnimator -> - backgroundTintListCompat = ColorStateList(empty, intArrayOf(animation.animatedValue as Int)) + backgroundTintList = ColorStateList(empty, intArrayOf(animation.animatedValue as Int)) } viewPropertyAnimator.addListener( - onCancel = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) }, - onEnd = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) } + onCancel = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) }, + onEnd = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) } ) viewPropertyAnimator.start() } diff --git a/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt index 3549bff42..7d95433a4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt @@ -1,19 +1,9 @@ package org.schabi.newpipe.settings -import android.os.Build import android.os.Bundle -import androidx.preference.Preference -import org.schabi.newpipe.R class PlayerNotificationSettingsFragment : BasePreferenceFragment() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResourceRegistry() - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - val colorizePref: Preference? = findPreference(getString(R.string.notification_colorize_key)) - colorizePref?.let { - preferenceScreen.removePreference(it) - } - } } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 798d299c0..849574171 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -218,7 +218,7 @@ public class NotificationActionsPreference extends Preference { final int color = ThemeHelper.resolveColorFromAttr(getContext(), android.R.attr.textColorPrimary); drawable = DrawableCompat.wrap(drawable).mutate(); - DrawableCompat.setTint(drawable, color); + drawable.setTint(color); radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, drawable, null); } From 76ced59b62322537eb818cd3fea7ddd1b9c2d711 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 8 Apr 2022 09:35:14 +0200 Subject: [PATCH 028/152] Refactor player: separate UIs and more --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 2 +- .../org/schabi/newpipe/RouterActivity.java | 6 +- .../fragments/detail/VideoDetailFragment.java | 155 +- .../list/channel/ChannelFragment.java | 2 +- .../list/playlist/PlaylistFragment.java | 2 +- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../newpipe/player/NotificationUtil.java | 18 +- .../newpipe/player/PlayQueueActivity.java | 50 +- .../org/schabi/newpipe/player/Player.java | 2654 ++--------------- .../{MainPlayer.java => PlayerService.java} | 102 +- .../player/event/BasePlayerGestureListener.kt | 520 ---- .../player/event/PlayerEventListener.java | 1 - .../player/event/PlayerGestureListener.java | 256 -- .../event/PlayerServiceEventListener.java | 2 + .../PlayerServiceExtendedEventListener.java | 4 +- .../gesture/BasePlayerGestureListener.kt | 182 ++ .../CustomBottomSheetBehavior.java | 2 +- .../{event => gesture}/DisplayPortion.kt | 2 +- .../{event => gesture}/DoubleTapListener.kt | 2 +- .../gesture/MainPlayerGestureListener.kt | 232 ++ .../gesture/PopupPlayerGestureListener.kt | 287 ++ .../helper/PlaybackParameterDialog.java | 4 +- .../newpipe/player/helper/PlayerHelper.java | 61 +- .../newpipe/player/helper/PlayerHolder.java | 23 +- .../view/PlaybackSpeedClickListener.kt | 47 - .../listeners/view/QualityClickListener.kt | 41 - .../player/playback/PlayerMediaSession.java | 5 +- .../newpipe/player/ui/MainPlayerUi.java | 937 ++++++ .../player/ui/NotificationPlayerUi.java | 26 + .../schabi/newpipe/player/ui/PlayerUi.java | 120 + .../newpipe/player/ui/PlayerUiList.java | 36 + .../newpipe/player/ui/PopupPlayerUi.java | 460 +++ .../newpipe/player/ui/VideoPlayerUi.java | 1523 ++++++++++ .../custom/NotificationActionsPreference.java | 4 +- .../schabi/newpipe/util/NavigationHelper.java | 28 +- .../views/player/PlayerFastSeekOverlay.kt | 4 +- app/src/main/res/layout/activity_main.xml | 2 +- 38 files changed, 4242 insertions(+), 3564 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{MainPlayer.java => PlayerService.java} (63%) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt rename app/src/main/java/org/schabi/newpipe/player/{event => gesture}/CustomBottomSheetBehavior.java (98%) rename app/src/main/java/org/schabi/newpipe/player/{event => gesture}/DisplayPortion.kt (65%) rename app/src/main/java/org/schabi/newpipe/player/{event => gesture}/DoubleTapListener.kt (81%) create mode 100644 app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt create mode 100644 app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java diff --git a/app/build.gradle b/app/build.gradle index 9867037e6..46eee8d00 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -166,7 +166,7 @@ afterEvaluate { if (!System.properties.containsKey('skipFormatKtlint')) { preDebugBuild.dependsOn formatKtlint } - preDebugBuild.dependsOn runCheckstyle, runKtlint + //preDebugBuild.dependsOn runCheckstyle, runKtlint } sonarqube { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f9c99819c..04e28c1ea 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,7 +44,7 @@ diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 1fe6ce7ec..1194b4068 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -60,7 +60,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.dialog.PlaylistDialog; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; @@ -630,8 +630,8 @@ public class RouterActivity extends AppCompatActivity { } // ...the player is not running or in normal Video-mode/type - final MainPlayer.PlayerType playerType = PlayerHolder.getInstance().getType(); - return playerType == null || playerType == MainPlayer.PlayerType.VIDEO; + final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); + return playerType == null || playerType == PlayerService.PlayerType.MAIN; } private void openAddToPlaylistDialog() { 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 278d472d4..5ecc35034 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 @@ -43,6 +43,7 @@ import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; +import androidx.viewbinding.ViewBinding; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; @@ -77,8 +78,8 @@ import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.MainPlayer; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; @@ -87,6 +88,8 @@ import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.player.ui.MainPlayerUi; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; @@ -106,6 +109,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.TimeUnit; import icepick.State; @@ -202,7 +206,7 @@ public final class VideoDetailFragment private ContentObserver settingsContentObserver; @Nullable - private MainPlayer playerService; + private PlayerService playerService; private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); @@ -211,7 +215,7 @@ public final class VideoDetailFragment //////////////////////////////////////////////////////////////////////////*/ @Override public void onServiceConnected(final Player connectedPlayer, - final MainPlayer connectedPlayerService, + final PlayerService connectedPlayerService, final boolean playAfterConnect) { player = connectedPlayer; playerService = connectedPlayerService; @@ -219,6 +223,7 @@ public final class VideoDetailFragment // It will do nothing if the player is not in fullscreen mode hideSystemUiIfNeeded(); + final Optional playerUi = player.UIs().get(MainPlayerUi.class); if (!player.videoPlayerSelected() && !playAfterConnect) { return; } @@ -227,22 +232,23 @@ public final class VideoDetailFragment // If the video is playing but orientation changed // let's make the video in fullscreen again checkLandscape(); - } else if (player.isFullscreen() && !player.isVerticalVideo() + } else if (playerUi.map(ui -> ui.isFullscreen() && !ui.isVerticalVideo()).orElse(false) // Tablet UI has orientation-independent fullscreen && !DeviceUtils.isTablet(activity)) { // Device is in portrait orientation after rotation but UI is in fullscreen. // Return back to non-fullscreen state - player.toggleFullscreen(); + playerUi.ifPresent(MainPlayerUi::toggleFullscreen); } if (playerIsNotStopped() && player.videoPlayerSelected()) { addVideoPlayerView(); } + //noinspection SimplifyOptionalCallChains if (playAfterConnect || (currentInfo != null && isAutoplayEnabled() - && player.getParentActivity() == null)) { + && !playerUi.isPresent())) { autoPlayEnabled = true; // forcefully start playing openVideoPlayerAutoFullscreen(); } @@ -518,7 +524,7 @@ public final class VideoDetailFragment case R.id.overlay_play_pause_button: if (playerIsNotStopped()) { player.playPause(); - player.hideControls(0, 0); + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); showSystemUi(); } else { autoPlayEnabled = true; // forcefully start playing @@ -583,12 +589,12 @@ public final class VideoDetailFragment if (binding.detailSecondaryControlPanel.getVisibility() == View.GONE) { binding.detailVideoTitleView.setMaxLines(10); animateRotation(binding.detailToggleSecondaryControlsView, - Player.DEFAULT_CONTROLS_DURATION, 180); + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 180); binding.detailSecondaryControlPanel.setVisibility(View.VISIBLE); } else { binding.detailVideoTitleView.setMaxLines(1); animateRotation(binding.detailToggleSecondaryControlsView, - Player.DEFAULT_CONTROLS_DURATION, 0); + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 0); binding.detailSecondaryControlPanel.setVisibility(View.GONE); } // view pager height has changed, update the tab layout @@ -746,7 +752,9 @@ public final class VideoDetailFragment @Override public boolean onKeyDown(final int keyCode) { - return isPlayerAvailable() && player.onKeyDown(keyCode); + return isPlayerAvailable() + && player.UIs().get(VideoPlayerUi.class) + .map(playerUi -> playerUi.onKeyDown(keyCode)).orElse(false); } @Override @@ -756,7 +764,7 @@ public final class VideoDetailFragment } // If we are in fullscreen mode just exit from it via first back press - if (isPlayerAvailable() && player.isFullscreen()) { + if (isFullscreen()) { if (!DeviceUtils.isTablet(activity)) { player.pause(); } @@ -1006,8 +1014,7 @@ public final class VideoDetailFragment getChildFragmentManager().beginTransaction() .replace(R.id.relatedItemsLayout, RelatedItemsFragment.getInstance(info)) .commitAllowingStateLoss(); - binding.relatedItemsLayout.setVisibility( - isPlayerAvailable() && player.isFullscreen() ? View.GONE : View.VISIBLE); + binding.relatedItemsLayout.setVisibility(isFullscreen() ? View.GONE : View.VISIBLE); } } @@ -1087,8 +1094,12 @@ public final class VideoDetailFragment private void toggleFullscreenIfInFullscreenMode() { // If a user watched video inside fullscreen mode and than chose another player // return to non-fullscreen mode - if (isPlayerAvailable() && player.isFullscreen()) { - player.toggleFullscreen(); + if (isPlayerAvailable()) { + player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + if (playerUi.isFullscreen()) { + playerUi.toggleFullscreen(); + } + }); } } @@ -1214,16 +1225,10 @@ public final class VideoDetailFragment } final PlayQueue queue = setupPlayQueueForIntent(false); - - // Video view can have elements visible from popup, - // We hide it here but once it ready the view will be shown in handleIntent() - if (playerService.getView() != null) { - playerService.getView().setVisibility(View.GONE); - } addVideoPlayerView(); final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(), - MainPlayer.class, queue, true, autoPlayEnabled); + PlayerService.class, queue, true, autoPlayEnabled); ContextCompat.startForegroundService(activity, playerIntent); } @@ -1235,8 +1240,8 @@ public final class VideoDetailFragment * be reused in a few milliseconds and the flickering would be annoying. */ private void hideMainPlayerOnLoadingNewStream() { - if (!isPlayerServiceAvailable() - || playerService.getView() == null + //noinspection SimplifyOptionalCallChains + if (!isPlayerServiceAvailable() || !getRoot().isPresent() || !player.videoPlayerSelected()) { return; } @@ -1244,7 +1249,7 @@ public final class VideoDetailFragment removeVideoPlayerView(); if (isAutoplayEnabled()) { playerService.stopForImmediateReusing(); - playerService.getView().setVisibility(View.GONE); + getRoot().ifPresent(view -> view.setVisibility(View.GONE)); } else { playerHolder.stopService(); } @@ -1302,26 +1307,33 @@ public final class VideoDetailFragment } private void addVideoPlayerView() { - if (!isPlayerAvailable() || getView() == null) { + if (!isPlayerAvailable()) { return; } - // Check if viewHolder already contains a child - if (player.getRootView().getParent() != binding.playerPlaceholder) { + final Optional root = player.UIs().get(VideoPlayerUi.class) + .map(VideoPlayerUi::getBinding) + .map(ViewBinding::getRoot); + + // Check if viewHolder already contains a child TODO TODO whaat + /*if (playerService != null + && root.map(View::getParent).orElse(null) != binding.playerPlaceholder) { playerService.removeViewFromParent(); - } + }*/ setHeightThumbnail(); // Prevent from re-adding a view multiple times - if (player.getRootView().getParent() == null) { - binding.playerPlaceholder.addView(player.getRootView()); + if (root.isPresent() && root.get().getParent() == null) { + binding.playerPlaceholder.addView(root.get()); } } private void removeVideoPlayerView() { makeDefaultHeightForVideoPlaceholder(); - playerService.removeViewFromParent(); + if (player != null) { + player.UIs().get(VideoPlayerUi.class).ifPresent(VideoPlayerUi::removeViewFromParent); + } } private void makeDefaultHeightForVideoPlaceholder() { @@ -1362,7 +1374,7 @@ public final class VideoDetailFragment final boolean isPortrait = metrics.heightPixels > metrics.widthPixels; requireView().getViewTreeObserver().removeOnPreDrawListener(preDrawListener); - if (isPlayerAvailable() && player.isFullscreen()) { + if (isFullscreen()) { final int height = (DeviceUtils.isInMultiWindow(activity) ? requireView() : activity.getWindow().getDecorView()).getHeight(); @@ -1387,8 +1399,9 @@ public final class VideoDetailFragment binding.detailThumbnailImageView.setMinimumHeight(newHeight); if (isPlayerAvailable()) { final int maxHeight = (int) (metrics.heightPixels * MAX_PLAYER_HEIGHT); - player.getSurfaceView() - .setHeights(newHeight, player.isFullscreen() ? newHeight : maxHeight); + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> + ui.getBinding().surfaceView.setHeights(newHeight, + ui.isFullscreen() ? newHeight : maxHeight)); } } @@ -1517,7 +1530,7 @@ public final class VideoDetailFragment if (binding.relatedItemsLayout != null) { if (showRelatedItems) { binding.relatedItemsLayout.setVisibility( - isPlayerAvailable() && player.isFullscreen() ? View.GONE : View.INVISIBLE); + isFullscreen() ? View.GONE : View.INVISIBLE); } else { binding.relatedItemsLayout.setVisibility(View.GONE); } @@ -1778,6 +1791,14 @@ public final class VideoDetailFragment // Player event listener //////////////////////////////////////////////////////////////////////////*/ + @Override + public void onViewCreated() { + // Video view can have elements visible from popup, + // We hide it here but once it ready the view will be shown in handleIntent() + getRoot().ifPresent(view -> view.setVisibility(View.GONE)); + addVideoPlayerView(); + } + @Override public void onQueueUpdate(final PlayQueue queue) { playQueue = queue; @@ -1898,15 +1919,10 @@ public final class VideoDetailFragment @Override public void onFullscreenStateChanged(final boolean fullscreen) { setupBrightness(); + //noinspection SimplifyOptionalCallChains if (!isPlayerAndPlayerServiceAvailable() - || playerService.getView() == null - || player.getParentActivity() == null) { - return; - } - - final View view = playerService.getView(); - final ViewGroup parent = (ViewGroup) view.getParent(); - if (parent == null) { + || !player.UIs().get(MainPlayerUi.class).isPresent() + || getRoot().map(View::getParent).orElse(null) == null) { return; } @@ -1934,7 +1950,7 @@ public final class VideoDetailFragment final boolean isLandscape = DeviceUtils.isLandscape(requireContext()); if (DeviceUtils.isTablet(activity) && (!globalScreenOrientationLocked(activity) || isLandscape)) { - player.toggleFullscreen(); + player.UIs().get(MainPlayerUi.class).ifPresent(MainPlayerUi::toggleFullscreen); return; } @@ -2017,7 +2033,7 @@ public final class VideoDetailFragment } activity.getWindow().getDecorView().setSystemUiVisibility(visibility); - if (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen())) { + if (isInMultiWindow || isFullscreen()) { activity.getWindow().setStatusBarColor(Color.TRANSPARENT); activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); } @@ -2026,13 +2042,17 @@ public final class VideoDetailFragment // Listener implementation public void hideSystemUiIfNeeded() { - if (isPlayerAvailable() - && player.isFullscreen() + if (isFullscreen() && bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { hideSystemUi(); } } + private boolean isFullscreen() { + return isPlayerAvailable() && player.UIs().get(VideoPlayerUi.class) + .map(VideoPlayerUi::isFullscreen).orElse(false); + } + private boolean playerIsNotStopped() { return isPlayerAvailable() && !player.isStopped(); } @@ -2055,10 +2075,7 @@ public final class VideoDetailFragment } final WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); - if (!isPlayerAvailable() - || !player.videoPlayerSelected() - || !player.isFullscreen() - || bottomSheetState != BottomSheetBehavior.STATE_EXPANDED) { + if (!isFullscreen() || bottomSheetState != BottomSheetBehavior.STATE_EXPANDED) { // Apply system brightness when the player is not in fullscreen restoreDefaultBrightness(); } else { @@ -2082,7 +2099,7 @@ public final class VideoDetailFragment setAutoPlay(true); } - player.checkLandscape(); + player.UIs().get(MainPlayerUi.class).ifPresent(MainPlayerUi::checkLandscape); // Let's give a user time to look at video information page if video is not playing if (globalScreenOrientationLocked(activity) && !player.isPlaying()) { player.play(); @@ -2309,10 +2326,10 @@ public final class VideoDetailFragment if (DeviceUtils.isLandscape(requireContext()) && isPlayerAvailable() && player.isPlaying() - && !player.isFullscreen() - && !DeviceUtils.isTablet(activity) - && player.videoPlayerSelected()) { - player.toggleFullscreen(); + && !isFullscreen() + && !DeviceUtils.isTablet(activity)) { + player.UIs().get(MainPlayerUi.class) + .ifPresent(MainPlayerUi::toggleFullscreen); } setOverlayLook(binding.appBarLayout, behavior, 1); break; @@ -2325,17 +2342,22 @@ public final class VideoDetailFragment // Re-enable clicks setOverlayElementsClickable(true); if (isPlayerAvailable()) { - player.closeItemsList(); + player.UIs().get(MainPlayerUi.class) + .ifPresent(MainPlayerUi::closeItemsList); } setOverlayLook(binding.appBarLayout, behavior, 0); break; case BottomSheetBehavior.STATE_DRAGGING: case BottomSheetBehavior.STATE_SETTLING: - if (isPlayerAvailable() && player.isFullscreen()) { + if (isFullscreen()) { showSystemUi(); } - if (isPlayerAvailable() && player.isControlsVisible()) { - player.hideControls(0, 0); + if (isPlayerAvailable()) { + player.UIs().get(MainPlayerUi.class).ifPresent(ui -> { + if (ui.isControlsVisible()) { + ui.hideControls(0, 0); + } + }); } break; } @@ -2409,4 +2431,13 @@ public final class VideoDetailFragment boolean isPlayerAndPlayerServiceAvailable() { return (player != null && playerService != null); } + + public Optional getRoot() { + if (player == null) { + return Optional.empty(); + } + + return player.UIs().get(VideoPlayerUi.class) + .map(playerUi -> playerUi.getBinding().getRoot()); + } } 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 fa8f5fdbd..aabd64744 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 @@ -43,7 +43,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.local.feed.notifications.NotificationHelper; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ExtractorHelper; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index ed63c6fd7..65fd8ada1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -43,7 +43,7 @@ import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.util.ExtractorHelper; 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 6023d4b10..3bec07dcc 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 @@ -43,7 +43,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.util.Localization; diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java index 2060d67c4..f5caf2c79 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java @@ -26,14 +26,14 @@ import java.util.List; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE; +import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; +import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; /** * This is a utility class for player notifications. @@ -173,7 +173,7 @@ public final class NotificationUtil { } - void createNotificationAndStartForeground(final Player player, final Service service) { + public void createNotificationAndStartForeground(final Player player, final Service service) { if (notificationBuilder == null) { notificationBuilder = createNotification(player); } 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 676d63458..d00e6265e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -51,7 +51,9 @@ public final class PlayQueueActivity extends AppCompatActivity private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; - protected Player player; + private Player player; + + private PlayQueueAdapter adapter = null; private boolean serviceBound; private ServiceConnection serviceConnection; @@ -132,7 +134,7 @@ public final class PlayQueueActivity extends AppCompatActivity openPlaybackParameterDialog(); return true; case R.id.action_mute: - player.onMuteUnmuteButtonClicked(); + player.toggleMute(); return true; case R.id.action_system_audio: startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS)); @@ -168,7 +170,7 @@ public final class PlayQueueActivity extends AppCompatActivity //////////////////////////////////////////////////////////////////////////// private void bind() { - final Intent bindIntent = new Intent(this, MainPlayer.class); + final Intent bindIntent = new Intent(this, PlayerService.class); final boolean success = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE); if (!success) { unbindService(serviceConnection); @@ -184,10 +186,7 @@ public final class PlayQueueActivity extends AppCompatActivity player.removeActivityListener(this); } - if (player != null && player.getPlayQueueAdapter() != null) { - player.getPlayQueueAdapter().unsetSelectedListener(); - } - queueControlBinding.playQueue.setAdapter(null); + onQueueUpdate(null); if (itemTouchHelper != null) { itemTouchHelper.attachToRecyclerView(null); } @@ -210,15 +209,15 @@ public final class PlayQueueActivity extends AppCompatActivity if (service instanceof PlayerServiceBinder) { player = ((PlayerServiceBinder) service).getPlayerInstance(); - } else if (service instanceof MainPlayer.LocalBinder) { - player = ((MainPlayer.LocalBinder) service).getPlayer(); + } else if (service instanceof PlayerService.LocalBinder) { + player = ((PlayerService.LocalBinder) service).getPlayer(); } - if (player == null || player.getPlayQueue() == null - || player.getPlayQueueAdapter() == null || player.exoPlayerIsNull()) { + if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) { unbind(); finish(); } else { + onQueueUpdate(player.getPlayQueue()); buildComponents(); if (player != null) { player.setActivityListener(PlayQueueActivity.this); @@ -241,7 +240,6 @@ public final class PlayQueueActivity extends AppCompatActivity private void buildQueue() { queueControlBinding.playQueue.setLayoutManager(new LinearLayoutManager(this)); - queueControlBinding.playQueue.setAdapter(player.getPlayQueueAdapter()); queueControlBinding.playQueue.setClickable(true); queueControlBinding.playQueue.setLongClickable(true); queueControlBinding.playQueue.clearOnScrollListeners(); @@ -249,8 +247,6 @@ public final class PlayQueueActivity extends AppCompatActivity itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper.attachToRecyclerView(queueControlBinding.playQueue); - - player.getPlayQueueAdapter().setSelectedListener(getOnSelectedListener()); } private void buildMetadata() { @@ -370,7 +366,7 @@ public final class PlayQueueActivity extends AppCompatActivity } if (view.getId() == queueControlBinding.controlRepeat.getId()) { - player.onRepeatClicked(); + player.cycleNextRepeatMode(); } else if (view.getId() == queueControlBinding.controlBackward.getId()) { player.playPrevious(); } else if (view.getId() == queueControlBinding.controlFastRewind.getId()) { @@ -382,7 +378,7 @@ public final class PlayQueueActivity extends AppCompatActivity } else if (view.getId() == queueControlBinding.controlForward.getId()) { player.playNext(); } else if (view.getId() == queueControlBinding.controlShuffle.getId()) { - player.onShuffleClicked(); + player.toggleShuffleModeEnabled(); } else if (view.getId() == queueControlBinding.metadata.getId()) { scrollToSelected(); } else if (view.getId() == queueControlBinding.liveSync.getId()) { @@ -445,7 +441,15 @@ public final class PlayQueueActivity extends AppCompatActivity //////////////////////////////////////////////////////////////////////////// @Override - public void onQueueUpdate(final PlayQueue queue) { + public void onQueueUpdate(@Nullable final PlayQueue queue) { + if (queue == null) { + adapter = null; + queueControlBinding.playQueue.setAdapter(null); + } else { + adapter = new PlayQueueAdapter(this, queue); + adapter.setSelectedListener(getOnSelectedListener()); + queueControlBinding.playQueue.setAdapter(adapter); + } } @Override @@ -454,7 +458,6 @@ public final class PlayQueueActivity extends AppCompatActivity onStateChanged(state); onPlayModeChanged(repeatMode, shuffled); onPlaybackParameterChanged(parameters); - onMaybePlaybackAdapterChanged(); onMaybeMuteChanged(); } @@ -582,17 +585,6 @@ public final class PlayQueueActivity extends AppCompatActivity } } - private void onMaybePlaybackAdapterChanged() { - if (player == null) { - return; - } - final PlayQueueAdapter maybeNewAdapter = player.getPlayQueueAdapter(); - if (maybeNewAdapter != null - && queueControlBinding.playQueue.getAdapter() != maybeNewAdapter) { - queueControlBinding.playQueue.setAdapter(maybeNewAdapter); - } - } - private void onMaybeMuteChanged() { if (menu != null && player != null) { final MenuItem item = menu.findItem(R.id.action_mute); 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 100563765..284ab74d8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -24,39 +24,25 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SKIP; import static com.google.android.exoplayer2.Player.DiscontinuityReason; import static com.google.android.exoplayer2.Player.Listener; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static com.google.android.exoplayer2.Player.RepeatMode; -import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; import static org.schabi.newpipe.extractor.ServiceList.YouTube; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; -import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION; -import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; +import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; +import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; -import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; -import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; -import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; -import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; -import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; -import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; -import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; @@ -64,50 +50,17 @@ import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.media.AudioManager; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.provider.Settings; -import android.util.DisplayMetrics; import android.util.Log; -import android.util.TypedValue; -import android.view.GestureDetector; -import android.view.Gravity; -import android.view.KeyEvent; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.animation.AnticipateInterpolator; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.SeekBar; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -122,13 +75,10 @@ import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.RecyclerView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player.PositionInfo; @@ -139,13 +89,9 @@ import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; -import com.google.android.exoplayer2.ui.CaptionStyleCompat; -import com.google.android.exoplayer2.ui.SubtitleView; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoSize; -import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; @@ -153,64 +99,47 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.databinding.PlayerBinding; -import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.extractor.Info; -import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.extractor.stream.StreamSegment; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; -import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; -import org.schabi.newpipe.info_list.StreamSegmentAdapter; -import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.MainPlayer.PlayerType; -import org.schabi.newpipe.player.event.DisplayPortion; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.event.PlayerEventListener; -import org.schabi.newpipe.player.event.PlayerGestureListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.LoadController; import org.schabi.newpipe.player.helper.MediaSessionManager; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; -import org.schabi.newpipe.player.listeners.view.PlaybackSpeedClickListener; -import org.schabi.newpipe.player.listeners.view.QualityClickListener; import org.schabi.newpipe.player.mediaitem.MediaItemTag; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlayerMediaSession; -import org.schabi.newpipe.player.playback.SurfaceHolderCallback; import org.schabi.newpipe.player.playqueue.PlayQueue; -import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; -import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder; -import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder; -import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback; import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; -import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper; -import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder; +import org.schabi.newpipe.player.ui.MainPlayerUi; +import org.schabi.newpipe.player.ui.NotificationPlayerUi; +import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.player.ui.PlayerUiList; +import org.schabi.newpipe.player.ui.PopupPlayerUi; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.StreamTypeUtil; -import org.schabi.newpipe.util.external_communication.KoreUtils; -import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.views.ExpandableSurfaceView; -import org.schabi.newpipe.views.player.PlayerFastSeekOverlay; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -221,14 +150,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.disposables.SerialDisposable; -public final class Player implements - PlaybackListener, - Listener, - SeekBar.OnSeekBarChangeListener, - View.OnClickListener, - PopupMenu.OnMenuItemClickListener, - PopupMenu.OnDismissListener, - View.OnLongClickListener { +public final class Player implements PlaybackListener, Listener { public static final boolean DEBUG = MainActivity.DEBUG; public static final String TAG = Player.class.getSimpleName(); @@ -264,18 +186,12 @@ public final class Player implements public static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds public static final int PROGRESS_LOOP_INTERVAL_MILLIS = 1000; // 1 second - public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis - public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds - public static final int DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds - public static final int SEEK_OVERLAY_DURATION = 450; // 450 millis /*////////////////////////////////////////////////////////////////////////// // Other constants //////////////////////////////////////////////////////////////////////////*/ - private static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f}; - - private static final int RENDERER_UNAVAILABLE = -1; + public static final int RENDERER_UNAVAILABLE = -1; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -283,8 +199,6 @@ public final class Player implements // play queue might be null e.g. while player is starting @Nullable private PlayQueue playQueue; - private PlayQueueAdapter playQueueAdapter; - private StreamSegmentAdapter segmentAdapter; @Nullable private MediaSourceManager playQueueManager; @@ -299,7 +213,6 @@ public final class Player implements private ExoPlayer simpleExoPlayer; private AudioReactor audioReactor; private MediaSessionManager mediaSessionManager; - @Nullable private SurfaceHolderCallback surfaceHolderCallback; @NonNull private final DefaultTrackSelector trackSelector; @NonNull private final LoadController loadController; @@ -308,13 +221,13 @@ public final class Player implements @NonNull private final VideoPlaybackResolver videoResolver; @NonNull private final AudioPlaybackResolver audioResolver; - private final MainPlayer service; //TODO try to remove and replace everything with context + private final PlayerService service; //TODO try to remove and replace everything with context /*////////////////////////////////////////////////////////////////////////// // Player states //////////////////////////////////////////////////////////////////////////*/ - private PlayerType playerType = PlayerType.VIDEO; + private PlayerType playerType = PlayerType.MAIN; private int currentState = STATE_PREFLIGHT; // audio only mode does not mean that player type is background, but that the player was @@ -322,81 +235,17 @@ public final class Player implements private boolean isAudioOnly = false; private boolean isPrepared = false; private boolean wasPlaying = false; - private boolean isFullscreen = false; - private boolean isVerticalVideo = false; - private boolean fragmentIsVisible = false; - - private List availableStreams; - private int selectedStreamIndex; /*////////////////////////////////////////////////////////////////////////// - // Views + // UIs, listeners and disposables //////////////////////////////////////////////////////////////////////////*/ - private PlayerBinding binding; - - private final Handler controlsVisibilityHandler = new Handler(); - - // fullscreen player - private boolean isQueueVisible = false; - private boolean areSegmentsVisible = false; - private ItemTouchHelper itemTouchHelper; - - /*////////////////////////////////////////////////////////////////////////// - // Popup menus ("popup" means that they pop up, not that they belong to the popup player) - //////////////////////////////////////////////////////////////////////////*/ - - private static final int POPUP_MENU_ID_QUALITY = 69; - private static final int POPUP_MENU_ID_PLAYBACK_SPEED = 79; - private static final int POPUP_MENU_ID_CAPTION = 89; - - private boolean isSomePopupMenuVisible = false; - private PopupMenu qualityPopupMenu; - private PopupMenu playbackSpeedPopupMenu; - private PopupMenu captionPopupMenu; - - /*////////////////////////////////////////////////////////////////////////// - // Popup player - //////////////////////////////////////////////////////////////////////////*/ - - private PlayerPopupCloseOverlayBinding closeOverlayBinding; - - private boolean isPopupClosing = false; - - private float screenWidth; - private float screenHeight; - - /*////////////////////////////////////////////////////////////////////////// - // Popup player window manager - //////////////////////////////////////////////////////////////////////////*/ - - public static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - public static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; - - @Nullable private WindowManager.LayoutParams popupLayoutParams; // null if player is not popup - @Nullable private final WindowManager windowManager; - - /*////////////////////////////////////////////////////////////////////////// - // Gestures - //////////////////////////////////////////////////////////////////////////*/ - - private static final float MAX_GESTURE_LENGTH = 0.75f; - - private int maxGestureLength; // scaled - private GestureDetector gestureDetector; - private PlayerGestureListener playerGestureListener; - - /*////////////////////////////////////////////////////////////////////////// - // Listeners and disposables - //////////////////////////////////////////////////////////////////////////*/ + private final PlayerUiList UIs = new PlayerUiList(); private BroadcastReceiver broadcastReceiver; private IntentFilter intentFilter; - private PlayerServiceEventListener fragmentListener; - private PlayerEventListener activityListener; - private ContentObserver settingsContentObserver; + @Nullable private PlayerServiceEventListener fragmentListener = null; + @Nullable private PlayerEventListener activityListener = null; @NonNull private final SerialDisposable progressUpdateDisposable = new SerialDisposable(); @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable(); @@ -409,16 +258,13 @@ public final class Player implements @NonNull private final SharedPreferences prefs; @NonNull private final HistoryRecordManager recordManager; - @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = - new SeekbarPreviewThumbnailHolder(); - /*////////////////////////////////////////////////////////////////////////// // Constructor //////////////////////////////////////////////////////////////////////////*/ //region Constructor - public Player(@NonNull final MainPlayer service) { + public Player(@NonNull final PlayerService service) { this.service = service; context = service; prefs = PreferenceManager.getDefaultSharedPreferences(context); @@ -434,8 +280,6 @@ public final class Player implements videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); - - windowManager = ContextCompat.getSystemService(context, WindowManager.class); } private VideoPlaybackResolver.QualityResolver getQualityResolver() { @@ -460,235 +304,6 @@ public final class Player implements - /*////////////////////////////////////////////////////////////////////////// - // Setup and initialization - //////////////////////////////////////////////////////////////////////////*/ - //region Setup and initialization - - public void setupFromView(@NonNull final PlayerBinding playerBinding) { - initViews(playerBinding); - if (exoPlayerIsNull()) { - initPlayer(true); - } - initListeners(); - - setupPlayerSeekOverlay(); - } - - private void initViews(@NonNull final PlayerBinding playerBinding) { - binding = playerBinding; - setupSubtitleView(); - - binding.resizeTextView - .setText(PlayerHelper.resizeTypeOf(context, binding.surfaceView.getResizeMode())); - - binding.playbackSeekBar.getThumb() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); - binding.playbackSeekBar.getProgressDrawable() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)); - - final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(getContext(), - R.style.DarkPopupMenu); - - qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView); - playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed); - captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView); - - binding.progressBarLoadingPanel.getIndeterminateDrawable() - .setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)); - - binding.titleTextView.setSelected(true); - binding.channelTextView.setSelected(true); - - // Prevent hiding of bottom sheet via swipe inside queue - binding.itemsList.setNestedScrollingEnabled(false); - } - - private void initPlayer(final boolean playOnReady) { - if (DEBUG) { - Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]"); - } - - simpleExoPlayer = new ExoPlayer.Builder(context, renderFactory) - .setTrackSelector(trackSelector) - .setLoadControl(loadController) - .setUsePlatformDiagnostics(false) - .build(); - simpleExoPlayer.addListener(this); - simpleExoPlayer.setPlayWhenReady(playOnReady); - simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context)); - simpleExoPlayer.setWakeMode(C.WAKE_MODE_NETWORK); - simpleExoPlayer.setHandleAudioBecomingNoisy(true); - - audioReactor = new AudioReactor(context, simpleExoPlayer); - mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, - new PlayerMediaSession(this)); - - registerBroadcastReceiver(); - - // Setup video view - setupVideoSurface(); - - // enable media tunneling - if (DEBUG && PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.disable_media_tunneling_key), false)) { - Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] " - + "media tunneling disabled in debug preferences"); - } else if (DeviceUtils.shouldSupportMediaTunneling()) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setTunnelingEnabled(true)); - } else if (DEBUG) { - Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling"); - } - } - - private void initListeners() { - binding.qualityTextView.setOnClickListener( - new QualityClickListener(this, qualityPopupMenu)); - binding.playbackSpeed.setOnClickListener( - new PlaybackSpeedClickListener(this, playbackSpeedPopupMenu)); - - binding.playbackSeekBar.setOnSeekBarChangeListener(this); - binding.captionTextView.setOnClickListener(this); - binding.resizeTextView.setOnClickListener(this); - binding.playbackLiveSync.setOnClickListener(this); - - playerGestureListener = new PlayerGestureListener(this, service); - gestureDetector = new GestureDetector(context, playerGestureListener); - binding.getRoot().setOnTouchListener(playerGestureListener); - - binding.queueButton.setOnClickListener(v -> onQueueClicked()); - binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked()); - binding.repeatButton.setOnClickListener(v -> onRepeatClicked()); - binding.shuffleButton.setOnClickListener(v -> onShuffleClicked()); - binding.addToPlaylistButton.setOnClickListener(v -> { - if (getParentActivity() != null) { - onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager()); - } - }); - - binding.playPauseButton.setOnClickListener(this); - binding.playPreviousButton.setOnClickListener(this); - binding.playNextButton.setOnClickListener(this); - - binding.moreOptionsButton.setOnClickListener(this); - binding.moreOptionsButton.setOnLongClickListener(this); - binding.share.setOnClickListener(this); - binding.share.setOnLongClickListener(this); - binding.fullScreenButton.setOnClickListener(this); - binding.screenRotationButton.setOnClickListener(this); - binding.playWithKodi.setOnClickListener(this); - binding.openInBrowser.setOnClickListener(this); - binding.playerCloseButton.setOnClickListener(this); - binding.switchMute.setOnClickListener(this); - - settingsContentObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(final boolean selfChange) { - setupScreenRotationButton(); - } - }; - context.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, - settingsContentObserver); - binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); - - ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, (view, windowInsets) -> { - final Insets cutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()); - if (!cutout.equals(Insets.NONE)) { - view.setPadding(cutout.left, cutout.top, cutout.right, cutout.bottom); - } - return windowInsets; - }); - - // PlaybackControlRoot already consumed window insets but we should pass them to - // player_overlays and fast_seek_overlay too. Without it they will be off-centered. - binding.playbackControlRoot.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - binding.playerOverlays.setPadding( - v.getPaddingLeft(), - v.getPaddingTop(), - v.getPaddingRight(), - v.getPaddingBottom()); - - // If we added padding to the fast seek overlay, too, it would not go under the - // system ui. Instead we apply negative margins equal to the window insets of - // the opposite side, so that the view covers all of the player (overflowing on - // some sides) and its center coincides with the center of other controls. - final RelativeLayout.LayoutParams fastSeekParams = (RelativeLayout.LayoutParams) - binding.fastSeekOverlay.getLayoutParams(); - fastSeekParams.leftMargin = -v.getPaddingRight(); - fastSeekParams.topMargin = -v.getPaddingBottom(); - fastSeekParams.rightMargin = -v.getPaddingLeft(); - fastSeekParams.bottomMargin = -v.getPaddingTop(); - }); - } - - /** - * Initializes the Fast-For/Backward overlay. - */ - private void setupPlayerSeekOverlay() { - binding.fastSeekOverlay - .seekSecondsSupplier(() -> retrieveSeekDurationFromPreferences(this) / 1000) - .performListener(new PlayerFastSeekOverlay.PerformListener() { - - @Override - public void onDoubleTap() { - animate(binding.fastSeekOverlay, true, SEEK_OVERLAY_DURATION); - } - - @Override - public void onDoubleTapEnd() { - animate(binding.fastSeekOverlay, false, SEEK_OVERLAY_DURATION); - } - - @NonNull - @Override - public FastSeekDirection getFastSeekDirection( - @NonNull final DisplayPortion portion - ) { - if (exoPlayerIsNull()) { - // Abort seeking - playerGestureListener.endMultiDoubleTap(); - return FastSeekDirection.NONE; - } - if (portion == DisplayPortion.LEFT) { - // Check if it's possible to rewind - // Small puffer to eliminate infinite rewind seeking - if (simpleExoPlayer.getCurrentPosition() < 500L) { - return FastSeekDirection.NONE; - } - return FastSeekDirection.BACKWARD; - } else if (portion == DisplayPortion.RIGHT) { - // Check if it's possible to fast-forward - if (currentState == STATE_COMPLETED - || simpleExoPlayer.getCurrentPosition() - >= simpleExoPlayer.getDuration()) { - return FastSeekDirection.NONE; - } - return FastSeekDirection.FORWARD; - } - /* portion == DisplayPortion.MIDDLE */ - return FastSeekDirection.NONE; - } - - @Override - public void seek(final boolean forward) { - playerGestureListener.keepInDoubleTapMode(); - if (forward) { - fastForward(); - } else { - fastRewind(); - } - } - }); - playerGestureListener.doubleTapControls(binding.fastSeekOverlay); - } - - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Playback initialization via intent //////////////////////////////////////////////////////////////////////////*/ @@ -708,6 +323,7 @@ public final class Player implements final PlayerType oldPlayerType = playerType; playerType = retrievePlayerTypeFromIntent(intent); + initUIsForCurrentPlayerType(); // We need to setup audioOnly before super(), see "sourceOf" isAudioOnly = audioPlayerSelected(); @@ -728,9 +344,6 @@ public final class Player implements return; } - // needed for tablets, check the function for a better explanation - directlyOpenFullscreenIfNeeded(); - final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this); final float playbackSpeed = savedParameters.speed; final float playbackPitch = savedParameters.pitch; @@ -828,46 +441,49 @@ public final class Player implements reloadPlayQueueManager(); } - setupElementsVisibility(); - setupElementsSize(); - - if (audioPlayerSelected()) { - service.removeViewFromParent(); - } else if (popupPlayerSelected()) { - binding.getRoot().setVisibility(View.VISIBLE); - initPopup(); - initPopupCloseOverlay(); - binding.playPauseButton.requestFocus(); - } else { - binding.getRoot().setVisibility(View.VISIBLE); - initVideoPlayer(); - closeItemsList(); - // Android TV: without it focus will frame the whole player - binding.playPauseButton.requestFocus(); - - // Note: This is for automatically playing (when "Resume playback" is off), see #6179 - if (getPlayWhenReady()) { - play(); - } else { - pause(); - } - } + UIs.call(PlayerUi::setupAfterIntent); NavigationHelper.sendPlayerStartedEvent(context); } - /** - * Open fullscreen on tablets where the option to have the main player start automatically in - * fullscreen mode is on. Rotating the device to landscape is already done in {@link - * VideoDetailFragment#openVideoPlayer(boolean)} when the thumbnail is clicked, and that's - * enough for phones, but not for tablets since the mini player can be also shown in landscape. - */ - private void directlyOpenFullscreenIfNeeded() { - if (fragmentListener != null - && PlayerHelper.isStartMainPlayerFullscreenEnabled(service) - && DeviceUtils.isTablet(service) - && videoPlayerSelected() - && PlayerHelper.globalScreenOrientationLocked(service)) { - fragmentListener.onScreenRotationButtonClicked(); + private void initUIsForCurrentPlayerType() { + //noinspection SimplifyOptionalCallChains + if (!UIs.get(NotificationPlayerUi.class).isPresent()) { + UIs.add(new NotificationPlayerUi(this)); + } + + if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) + || (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) { + // correct UI already in place + return; + } + + // try to reuse binding if possible + final PlayerBinding binding = UIs.get(VideoPlayerUi.class).map(VideoPlayerUi::getBinding) + .orElseGet(() -> { + if (playerType == PlayerType.AUDIO) { + return null; + } else { + return PlayerBinding.inflate(LayoutInflater.from(context)); + } + }); + + switch (playerType) { + case MAIN: + UIs.destroyAll(PopupPlayerUi.class); + UIs.add(new MainPlayerUi(this, binding)); + break; + case AUDIO: + UIs.destroyAll(VideoPlayerUi.class); + break; + case POPUP: + UIs.destroyAll(MainPlayerUi.class); + UIs.add(new PopupPlayerUi(this, binding)); + break; + } + + if (fragmentListener != null) { + // make sure UIs know whether a service is connected or not + UIs.call(PlayerUi::onFragmentListenerSet); } } @@ -881,23 +497,55 @@ public final class Player implements destroyPlayer(); initPlayer(playOnReady); setRepeatMode(repeatMode); - // #6825 - Ensure that the shuffle-button is in the correct state on the UI - setShuffleButton(binding.shuffleButton, simpleExoPlayer.getShuffleModeEnabled()); setPlaybackParameters(playbackSpeed, playbackPitch, playbackSkipSilence); playQueue = queue; playQueue.init(); reloadPlayQueueManager(); - if (playQueueAdapter != null) { - playQueueAdapter.dispose(); - } - playQueueAdapter = new PlayQueueAdapter(context, playQueue); - segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener()); + UIs.call(PlayerUi::initPlayback); simpleExoPlayer.setVolume(isMuted ? 0 : 1); notifyQueueUpdateToListeners(); } + + private void initPlayer(final boolean playOnReady) { + if (DEBUG) { + Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]"); + } + + simpleExoPlayer = new ExoPlayer.Builder(context, renderFactory) + .setTrackSelector(trackSelector) + .setLoadControl(loadController) + .setUsePlatformDiagnostics(false) + .build(); + simpleExoPlayer.addListener(this); + simpleExoPlayer.setPlayWhenReady(playOnReady); + simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context)); + simpleExoPlayer.setWakeMode(C.WAKE_MODE_NETWORK); + simpleExoPlayer.setHandleAudioBecomingNoisy(true); + + audioReactor = new AudioReactor(context, simpleExoPlayer); + mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, + new PlayerMediaSession(this)); + + registerBroadcastReceiver(); + + // Setup UIs + UIs.call(PlayerUi::initPlayer); + + // enable media tunneling + if (DEBUG && PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.disable_media_tunneling_key), false)) { + Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] " + + "media tunneling disabled in debug preferences"); + } else if (DeviceUtils.shouldSupportMediaTunneling()) { + trackSelector.setParameters(trackSelector.buildUponParameters() + .setTunnelingEnabled(true)); + } else if (DEBUG) { + Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling"); + } + } //endregion @@ -911,8 +559,7 @@ public final class Player implements if (DEBUG) { Log.d(TAG, "destroyPlayer() called"); } - - cleanupVideoSurface(); + UIs.call(PlayerUi::destroyPlayer); if (!exoPlayerIsNull()) { simpleExoPlayer.removeListener(this); @@ -934,17 +581,17 @@ public final class Player implements if (mediaSessionManager != null) { mediaSessionManager.dispose(); } - - if (playQueueAdapter != null) { - playQueueAdapter.unsetSelectedListener(); - playQueueAdapter.dispose(); - } } public void destroy() { if (DEBUG) { Log.d(TAG, "destroy() called"); } + + saveStreamProgressState(); + setRecovery(); + stopActivityBinding(); + destroyPlayer(); unregisterBroadcastReceiver(); @@ -952,11 +599,7 @@ public final class Player implements progressUpdateDisposable.set(null); PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading - if (binding != null) { - binding.endScreen.setImageBitmap(null); - } - - context.getContentResolver().unregisterContentObserver(settingsContentObserver); + UIs.call(PlayerUi::destroy); } public void setRecovery() { @@ -983,7 +626,7 @@ public final class Player implements playQueue.setRecovery(queuePos, windowPos); } - private void reloadPlayQueueManager() { + public void reloadPlayQueueManager() { if (playQueueManager != null) { playQueueManager.dispose(); } @@ -1002,185 +645,11 @@ public final class Player implements service.stopService(); } - public void smoothStopPlayer() { + public void smoothStopForImmediateReusing() { // Pausing would make transition from one stream to a new stream not smooth, so only stop simpleExoPlayer.stop(); - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Player type specific setup - //////////////////////////////////////////////////////////////////////////*/ - //region Player type specific setup - - private void initVideoPlayer() { - // restore last resize mode - setResizeMode(PlayerHelper.retrieveResizeModeFromPrefs(this)); - binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); - } - - @SuppressLint("RtlHardcoded") - private void initPopup() { - if (DEBUG) { - Log.d(TAG, "initPopup() called"); - } - - // Popup is already added to windowManager - if (popupHasParent()) { - return; - } - - updateScreenSize(); - - popupLayoutParams = retrievePopupLayoutParamsFromPrefs(this); - binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); - - checkPopupPositionBounds(); - - binding.loadingPanel.setMinimumWidth(popupLayoutParams.width); - binding.loadingPanel.setMinimumHeight(popupLayoutParams.height); - - service.removeViewFromParent(); - Objects.requireNonNull(windowManager).addView(binding.getRoot(), popupLayoutParams); - - // Popup doesn't have aspectRatio selector, using FIT automatically - setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); - } - - @SuppressLint("RtlHardcoded") - private void initPopupCloseOverlay() { - if (DEBUG) { - Log.d(TAG, "initPopupCloseOverlay() called"); - } - - // closeOverlayView is already added to windowManager - if (closeOverlayBinding != null) { - return; - } - - closeOverlayBinding = PlayerPopupCloseOverlayBinding.inflate(LayoutInflater.from(context)); - - final WindowManager.LayoutParams closeOverlayLayoutParams = buildCloseOverlayLayoutParams(); - closeOverlayBinding.closeButton.setVisibility(View.GONE); - Objects.requireNonNull(windowManager).addView( - closeOverlayBinding.getRoot(), closeOverlayLayoutParams); - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Elements visibility and size: popup and main players have different look - //////////////////////////////////////////////////////////////////////////*/ - //region Elements visibility and size: popup and main players have different look - - /** - * This method ensures that popup and main players have different look. - * We use one layout for both players and need to decide what to show and what to hide. - * Additional measuring should be done inside {@link #setupElementsSize}. - */ - private void setupElementsVisibility() { - if (popupPlayerSelected()) { - binding.fullScreenButton.setVisibility(View.VISIBLE); - binding.screenRotationButton.setVisibility(View.GONE); - binding.resizeTextView.setVisibility(View.GONE); - binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.GONE); - binding.queueButton.setVisibility(View.GONE); - binding.segmentsButton.setVisibility(View.GONE); - binding.moreOptionsButton.setVisibility(View.GONE); - binding.topControls.setOrientation(LinearLayout.HORIZONTAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.WRAP_CONTENT; - binding.secondaryControls.setAlpha(1.0f); - binding.secondaryControls.setVisibility(View.VISIBLE); - binding.secondaryControls.setTranslationY(0); - binding.share.setVisibility(View.GONE); - binding.playWithKodi.setVisibility(View.GONE); - binding.openInBrowser.setVisibility(View.GONE); - binding.switchMute.setVisibility(View.GONE); - binding.playerCloseButton.setVisibility(View.GONE); - binding.topControls.bringToFront(); - binding.topControls.setClickable(false); - binding.topControls.setFocusable(false); - binding.bottomControls.bringToFront(); - closeItemsList(); - } else if (videoPlayerSelected()) { - binding.fullScreenButton.setVisibility(View.GONE); - setupScreenRotationButton(); - binding.resizeTextView.setVisibility(View.VISIBLE); - binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); - binding.moreOptionsButton.setVisibility(View.VISIBLE); - binding.topControls.setOrientation(LinearLayout.VERTICAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.MATCH_PARENT; - binding.secondaryControls.setVisibility(View.INVISIBLE); - binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context, - R.drawable.ic_expand_more)); - binding.share.setVisibility(View.VISIBLE); - binding.openInBrowser.setVisibility(View.VISIBLE); - binding.switchMute.setVisibility(View.VISIBLE); - binding.playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE); - // Top controls have a large minHeight which is allows to drag the player - // down in fullscreen mode (just larger area to make easy to locate by finger) - binding.topControls.setClickable(true); - binding.topControls.setFocusable(true); - } - showHideKodiButton(); - - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - } - setMuteButton(binding.switchMute, isMuted()); - - animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0); - } - - /** - * Changes padding, size of elements based on player selected right now. - * Popup player has small padding in comparison with the main player - */ - private void setupElementsSize() { - final Resources res = context.getResources(); - final int buttonsMinWidth; - final int playerTopPad; - final int controlsPad; - final int buttonsPad; - - if (popupPlayerSelected()) { - buttonsMinWidth = 0; - playerTopPad = 0; - controlsPad = res.getDimensionPixelSize(R.dimen.player_popup_controls_padding); - buttonsPad = res.getDimensionPixelSize(R.dimen.player_popup_buttons_padding); - } else if (videoPlayerSelected()) { - buttonsMinWidth = res.getDimensionPixelSize(R.dimen.player_main_buttons_min_width); - playerTopPad = res.getDimensionPixelSize(R.dimen.player_main_top_padding); - controlsPad = res.getDimensionPixelSize(R.dimen.player_main_controls_padding); - buttonsPad = res.getDimensionPixelSize(R.dimen.player_main_buttons_padding); - } else { - return; - } - - binding.topControls.setPaddingRelative(controlsPad, playerTopPad, controlsPad, 0); - binding.bottomControls.setPaddingRelative(controlsPad, 0, controlsPad, 0); - binding.qualityTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); - binding.playbackSpeed.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); - binding.playbackSpeed.setMinimumWidth(buttonsMinWidth); - binding.captionTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); - } - - private void showHideKodiButton() { - // show kodi button if it supports the current service and it is enabled in settings - binding.playWithKodi.setVisibility(videoPlayerSelected() - && playQueue != null && playQueue.getItem() != null - && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) - ? View.VISIBLE : View.GONE); + setRecovery(); + UIs.call(PlayerUi::smoothStopForImmediateReusing); } //endregion @@ -1243,11 +712,6 @@ public final class Player implements break; case ACTION_PLAY_PAUSE: playPause(); - if (!fragmentIsVisible) { - // Ensure that we have audio-only stream playing when a user - // started to play from notification's play button from outside of the app - onFragmentStopped(); - } break; case ACTION_PLAY_PREVIOUS: playPrevious(); @@ -1262,55 +726,19 @@ public final class Player implements fastForward(); break; case ACTION_REPEAT: - onRepeatClicked(); + cycleNextRepeatMode(); break; case ACTION_SHUFFLE: - onShuffleClicked(); + toggleShuffleModeEnabled(); break; case ACTION_RECREATE_NOTIFICATION: NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); break; - case VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED: - fragmentIsVisible = true; - useVideoSource(true); - break; - case VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED: - fragmentIsVisible = false; - onFragmentStopped(); - break; case Intent.ACTION_CONFIGURATION_CHANGED: assureCorrectAppLanguage(service); if (DEBUG) { Log.d(TAG, "onConfigurationChanged() called"); } - if (popupPlayerSelected()) { - updateScreenSize(); - changePopupSize(popupLayoutParams.width); - checkPopupPositionBounds(); - } - // Close it because when changing orientation from portrait - // (in fullscreen mode) the size of queue layout can be larger than the screen size - closeItemsList(); - // When the orientation changed, the screen height might be smaller. - // If the end screen thumbnail is not re-scaled, - // it can be larger than the current screen height - // and thus enlarging the whole player. - // This causes the seekbar to be ouf the visible area. - updateEndScreenThumbnail(); - break; - case Intent.ACTION_SCREEN_ON: - // Interrupt playback only when screen turns on - // and user is watching video in popup player. - // Same actions for video player will be handled in ACTION_VIDEO_FRAGMENT_RESUMED - if (popupPlayerSelected() && (isPlaying() || isLoading())) { - useVideoSource(true); - } - break; - case Intent.ACTION_SCREEN_OFF: - // Interrupt playback only when screen turns off with popup player working - if (popupPlayerSelected() && (isPlaying() || isLoading())) { - useVideoSource(false); - } break; case Intent.ACTION_HEADSET_PLUG: //FIXME /*notificationManager.cancel(NOTIFICATION_ID); @@ -1318,6 +746,8 @@ public final class Player implements mediaSessionManager.enable(getBaseContext(), basePlayerImpl.simpleExoPlayer);*/ break; } + + UIs.call(playerUi -> playerUi.onBroadcastReceived(intent)); } private void registerBroadcastReceiver() { @@ -1366,12 +796,12 @@ public final class Player implements NotificationUtil.getInstance() .createNotificationIfNeededAndUpdate(Player.this, false); // there is a new thumbnail, so changed the end screen thumbnail, too. - updateEndScreenThumbnail(); + UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); } @Override public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - Log.e(TAG, "Thumbnail - onBitmapFailed() called with: url = [" + url + "]", e); + Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); currentThumbnail = null; NotificationUtil.getInstance() .createNotificationIfNeededAndUpdate(Player.this, false); @@ -1380,258 +810,11 @@ public final class Player implements @Override public void onPrepareLoad(final Drawable placeHolderDrawable) { if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingStarted() called with: url = [" + url + "]"); + Log.d(TAG, "Thumbnail - onPrepareLoad() called: url = [" + url + "]"); } } }); } - - /** - * Scale the player audio / end screen thumbnail down if necessary. - *

- * This is necessary when the thumbnail's height is larger than the device's height - * and thus is enlarging the player's height - * causing the bottom playback controls to be out of the visible screen. - *

- */ - public void updateEndScreenThumbnail() { - if (currentThumbnail == null) { - return; - } - - final float endScreenHeight = calculateMaxEndScreenThumbnailHeight(); - - final Bitmap endScreenBitmap = Bitmap.createScaledBitmap( - currentThumbnail, - (int) (currentThumbnail.getWidth() - / (currentThumbnail.getHeight() / endScreenHeight)), - (int) endScreenHeight, - true); - - if (DEBUG) { - Log.d(TAG, "Thumbnail - updateEndScreenThumbnail() called with: " - + "currentThumbnail = [" + currentThumbnail + "], " - + currentThumbnail.getWidth() + "x" + currentThumbnail.getHeight() - + ", scaled end screen height = " + endScreenHeight - + ", scaled end screen width = " + endScreenBitmap.getWidth()); - } - - binding.endScreen.setImageBitmap(endScreenBitmap); - } - - /** - * Calculate the maximum allowed height for the {@link R.id.endScreen} - * to prevent it from enlarging the player. - *

- * The calculating follows these rules: - *

    - *
  • - * Show at least stream title and content creator on TVs and tablets - * when in landscape (always the case for TVs) and not in fullscreen mode. - * This requires to have at least 85dp free space for {@link R.id.detail_root} - * and additional space for the stream title text size - * ({@link R.id.detail_title_root_layout}). - * The text size is 15sp on tablets and 16sp on TVs, - * see {@link R.id.titleTextView}. - *
  • - *
  • - * Otherwise, the max thumbnail height is the screen height. - *
  • - *
- * - * @return the maximum height for the end screen thumbnail - */ - private float calculateMaxEndScreenThumbnailHeight() { - // ensure that screenHeight is initialized and thus not 0 - updateScreenSize(); - - if (DeviceUtils.isTv(context) && !isFullscreen) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context); - return Math.min(currentThumbnail.getHeight(), screenHeight - videoInfoHeight); - } else if (DeviceUtils.isTablet(context) && service.isLandscape() && !isFullscreen) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context); - return Math.min(currentThumbnail.getHeight(), screenHeight - videoInfoHeight); - } else { // fullscreen player: max height is the device height - return Math.min(currentThumbnail.getHeight(), screenHeight); - } - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Popup player utils - //////////////////////////////////////////////////////////////////////////*/ - //region Popup player utils - - /** - * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary - * that goes from (0, 0) to (screenWidth, screenHeight). - *

- * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed - * and {@code true} is returned to represent this change. - *

- */ - public void checkPopupPositionBounds() { - if (DEBUG) { - Log.d(TAG, "checkPopupPositionBounds() called with: " - + "screenWidth = [" + screenWidth + "], " - + "screenHeight = [" + screenHeight + "]"); - } - if (popupLayoutParams == null) { - return; - } - - if (popupLayoutParams.x < 0) { - popupLayoutParams.x = 0; - } else if (popupLayoutParams.x > screenWidth - popupLayoutParams.width) { - popupLayoutParams.x = (int) (screenWidth - popupLayoutParams.width); - } - - if (popupLayoutParams.y < 0) { - popupLayoutParams.y = 0; - } else if (popupLayoutParams.y > screenHeight - popupLayoutParams.height) { - popupLayoutParams.y = (int) (screenHeight - popupLayoutParams.height); - } - } - - public void updateScreenSize() { - if (windowManager != null) { - final DisplayMetrics metrics = new DisplayMetrics(); - windowManager.getDefaultDisplay().getMetrics(metrics); - - screenWidth = metrics.widthPixels; - screenHeight = metrics.heightPixels; - if (DEBUG) { - Log.d(TAG, "updateScreenSize() called: screenWidth = [" - + screenWidth + "], screenHeight = [" + screenHeight + "]"); - } - } - } - - /** - * Changes the size of the popup based on the width. - * @param width the new width, height is calculated with - * {@link PlayerHelper#getMinimumVideoHeight(float)} - */ - public void changePopupSize(final int width) { - if (DEBUG) { - Log.d(TAG, "changePopupSize() called with: width = [" + width + "]"); - } - - if (anyPopupViewIsNull()) { - return; - } - - final float minimumWidth = context.getResources().getDimension(R.dimen.popup_minimum_width); - final int actualWidth = (int) (width > screenWidth ? screenWidth - : (width < minimumWidth ? minimumWidth : width)); - final int actualHeight = (int) getMinimumVideoHeight(width); - if (DEBUG) { - Log.d(TAG, "updatePopupSize() updated values:" - + " width = [" + actualWidth + "], height = [" + actualHeight + "]"); - } - - popupLayoutParams.width = actualWidth; - popupLayoutParams.height = actualHeight; - binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); - Objects.requireNonNull(windowManager) - .updateViewLayout(binding.getRoot(), popupLayoutParams); - } - - private void changePopupWindowFlags(final int flags) { - if (DEBUG) { - Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); - } - - if (!anyPopupViewIsNull()) { - popupLayoutParams.flags = flags; - Objects.requireNonNull(windowManager) - .updateViewLayout(binding.getRoot(), popupLayoutParams); - } - } - - public void closePopup() { - if (DEBUG) { - Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing); - } - if (isPopupClosing) { - return; - } - isPopupClosing = true; - - saveStreamProgressState(); - Objects.requireNonNull(windowManager).removeView(binding.getRoot()); - - animatePopupOverlayAndFinishService(); - } - - public void removePopupFromView() { - if (windowManager != null) { - // wrap in try-catch since it could sometimes generate errors randomly - try { - if (popupHasParent()) { - windowManager.removeView(binding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup from window manager", e); - } - - try { - final boolean closeOverlayHasParent = closeOverlayBinding != null - && closeOverlayBinding.getRoot().getParent() != null; - if (closeOverlayHasParent) { - windowManager.removeView(closeOverlayBinding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup overlay from window manager", e); - } - } - } - - private void animatePopupOverlayAndFinishService() { - final int targetTranslationY = - (int) (closeOverlayBinding.closeButton.getRootView().getHeight() - - closeOverlayBinding.closeButton.getY()); - - closeOverlayBinding.closeButton.animate().setListener(null).cancel(); - closeOverlayBinding.closeButton.animate() - .setInterpolator(new AnticipateInterpolator()) - .translationY(targetTranslationY) - .setDuration(400) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(final Animator animation) { - end(); - } - - @Override - public void onAnimationEnd(final Animator animation) { - end(); - } - - private void end() { - Objects.requireNonNull(windowManager) - .removeView(closeOverlayBinding.getRoot()); - closeOverlayBinding = null; - service.stopService(); - } - }).start(); - } - - private boolean popupHasParent() { - return binding != null - && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams - && binding.getRoot().getParent() != null; - } - - private boolean anyPopupViewIsNull() { - // TODO understand why checking getParentActivity() != null - return popupLayoutParams == null || windowManager == null - || getParentActivity() != null || binding.getRoot().getParent() == null; - } //endregion @@ -1645,7 +828,7 @@ public final class Player implements return getPlaybackParameters().speed; } - private void setPlaybackSpeed(final float speed) { + public void setPlaybackSpeed(final float speed) { setPlaybackParameters(speed, getPlaybackPitch(), getPlaybackSkipSilence()); } @@ -1694,40 +877,13 @@ public final class Player implements private void onUpdateProgress(final int currentProgress, final int duration, final int bufferPercent) { - if (!isPrepared) { - return; - } - - if (duration != binding.playbackSeekBar.getMax()) { - setVideoDurationToControls(duration); - } - if (currentState != STATE_PAUSED) { - updatePlayBackElementsCurrentDuration(currentProgress); - } - if (simpleExoPlayer.isLoading() || bufferPercent > 90) { - binding.playbackSeekBar.setSecondaryProgress( - (int) (binding.playbackSeekBar.getMax() * ((float) bufferPercent / 100))); - } - if (DEBUG && bufferPercent % 20 == 0) { //Limit log - Log.d(TAG, "notifyProgressUpdateToListeners() called with: " - + "isVisible = " + isControlsVisible() + ", " - + "currentProgress = [" + currentProgress + "], " - + "duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]"); - } - binding.playbackLiveSync.setClickable(!isLiveEdge()); - - notifyProgressUpdateToListeners(currentProgress, duration, bufferPercent); - - if (areSegmentsVisible) { - segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress)); - } - - if (isQueueVisible) { - updateQueueTime(currentProgress); + if (isPrepared) { + UIs.call(ui -> ui.onUpdateProgress(currentProgress, duration, bufferPercent)); + notifyProgressUpdateToListeners(currentProgress, duration, bufferPercent); } } - private void startProgressLoop() { + public void startProgressLoop() { progressUpdateDisposable.set(getProgressUpdateDisposable()); } @@ -1735,11 +891,11 @@ public final class Player implements progressUpdateDisposable.set(null); } - private boolean isProgressLoopRunning() { + public boolean isProgressLoopRunning() { return progressUpdateDisposable.get() != null; } - private void triggerProgressUpdate() { + public void triggerProgressUpdate() { if (exoPlayerIsNull()) { return; } @@ -1756,228 +912,12 @@ public final class Player implements error -> Log.e(TAG, "Progress update failure: ", error)); } - @Override // seekbar listener - public void onProgressChanged(final SeekBar seekBar, final int progress, - final boolean fromUser) { - // Currently we don't need method execution when fromUser is false - if (!fromUser) { - return; - } - if (DEBUG) { - Log.d(TAG, "onProgressChanged() called with: " - + "seekBar = [" + seekBar + "], progress = [" + progress + "]"); - } - - binding.currentDisplaySeek.setText(getTimeString(progress)); - - // Seekbar Preview Thumbnail - SeekbarPreviewThumbnailHelper - .tryResizeAndSetSeekbarPreviewThumbnail( - getContext(), - seekbarPreviewThumbnailHolder.getBitmapAt(progress), - binding.currentSeekbarPreviewThumbnail, - binding.subtitleView::getWidth); - - adjustSeekbarPreviewContainer(); - } - - private void adjustSeekbarPreviewContainer() { - try { - // Should only be required when an error occurred before - // and the layout was positioned in the center - binding.bottomSeekbarPreviewLayout.setGravity(Gravity.NO_GRAVITY); - - // Calculate the current left position of seekbar progress in px - // More info: https://stackoverflow.com/q/20493577 - final int currentSeekbarLeft = - binding.playbackSeekBar.getLeft() - + binding.playbackSeekBar.getPaddingLeft() - + binding.playbackSeekBar.getThumb().getBounds().left; - - // Calculate the (unchecked) left position of the container - final int uncheckedContainerLeft = - currentSeekbarLeft - (binding.seekbarPreviewContainer.getWidth() / 2); - - // Fix the position so it's within the boundaries - final int checkedContainerLeft = - Math.max( - Math.min( - uncheckedContainerLeft, - // Max left - binding.playbackWindowRoot.getWidth() - - binding.seekbarPreviewContainer.getWidth() - ), - 0 // Min left - ); - - // See also: https://stackoverflow.com/a/23249734 - final LinearLayout.LayoutParams params = - new LinearLayout.LayoutParams( - binding.seekbarPreviewContainer.getLayoutParams()); - params.setMarginStart(checkedContainerLeft); - binding.seekbarPreviewContainer.setLayoutParams(params); - } catch (final Exception ex) { - Log.e(TAG, "Failed to adjust seekbarPreviewContainer", ex); - // Fallback - position in the middle - binding.bottomSeekbarPreviewLayout.setGravity(Gravity.CENTER); - } - } - - @Override // seekbar listener - public void onStartTrackingTouch(final SeekBar seekBar) { - if (DEBUG) { - Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]"); - } - if (currentState != STATE_PAUSED_SEEK) { - changeState(STATE_PAUSED_SEEK); - } - - saveWasPlaying(); - if (isPlaying()) { - simpleExoPlayer.pause(); - } - - showControls(0); - animate(binding.currentDisplaySeek, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SCALE_AND_ALPHA); - animate(binding.currentSeekbarPreviewThumbnail, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SCALE_AND_ALPHA); - } - - @Override // seekbar listener - public void onStopTrackingTouch(final SeekBar seekBar) { - if (DEBUG) { - Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]"); - } - - seekTo(seekBar.getProgress()); - if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) { - simpleExoPlayer.play(); - } - - binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); - animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); - animate(binding.currentSeekbarPreviewThumbnail, false, 200, AnimationType.SCALE_AND_ALPHA); - - if (currentState == STATE_PAUSED_SEEK) { - changeState(STATE_BUFFERING); - } - if (!isProgressLoopRunning()) { - startProgressLoop(); - } - if (wasPlaying) { - showControlsThenHide(); - } - } - public void saveWasPlaying() { this.wasPlaying = getPlayWhenReady(); } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// - // Controls showing / hiding - //////////////////////////////////////////////////////////////////////////*/ - //region Controls showing / hiding - - public boolean isControlsVisible() { - return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE; - } - - public void showControlsThenHide() { - if (DEBUG) { - Log.d(TAG, "showControlsThenHide() called"); - } - showOrHideButtons(); - showSystemUIPartially(); - - final int hideTime = binding.playbackControlRoot.isInTouchMode() - ? DEFAULT_CONTROLS_HIDE_TIME - : DPAD_CONTROLS_HIDE_TIME; - - showHideShadow(true, DEFAULT_CONTROLS_DURATION); - animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, - AnimationType.ALPHA, 0, () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime)); - } - - public void showControls(final long duration) { - if (DEBUG) { - Log.d(TAG, "showControls() called"); - } - showOrHideButtons(); - showSystemUIPartially(); - controlsVisibilityHandler.removeCallbacksAndMessages(null); - showHideShadow(true, duration); - animate(binding.playbackControlRoot, true, duration); - } - - public void hideControls(final long duration, final long delay) { - if (DEBUG) { - Log.d(TAG, "hideControls() called with: duration = [" + duration - + "], delay = [" + delay + "]"); - } - - showOrHideButtons(); - - controlsVisibilityHandler.removeCallbacksAndMessages(null); - controlsVisibilityHandler.postDelayed(() -> { - showHideShadow(false, duration); - animate(binding.playbackControlRoot, false, duration, AnimationType.ALPHA, - 0, this::hideSystemUIIfNeeded); - }, delay); - } - - public void showHideShadow(final boolean show, final long duration) { - animate(binding.playbackControlsShadow, show, duration, AnimationType.ALPHA, 0, null); - animate(binding.playerTopShadow, show, duration, AnimationType.ALPHA, 0, null); - animate(binding.playerBottomShadow, show, duration, AnimationType.ALPHA, 0, null); - } - - private void showOrHideButtons() { - if (playQueue == null) { - return; - } - - final boolean showPrev = playQueue.getIndex() != 0; - final boolean showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size(); - final boolean showQueue = playQueue.getStreams().size() > 1 && !popupPlayerSelected(); - /* only when stream has segments and is not playing in popup player */ - final boolean showSegment = !popupPlayerSelected() - && !getCurrentStreamInfo() - .map(StreamInfo::getStreamSegments) - .map(List::isEmpty) - .orElse(/*no stream info=*/true); - - binding.playPreviousButton.setVisibility(showPrev ? View.VISIBLE : View.INVISIBLE); - binding.playPreviousButton.setAlpha(showPrev ? 1.0f : 0.0f); - binding.playNextButton.setVisibility(showNext ? View.VISIBLE : View.INVISIBLE); - binding.playNextButton.setAlpha(showNext ? 1.0f : 0.0f); - binding.queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE); - binding.queueButton.setAlpha(showQueue ? 1.0f : 0.0f); - binding.segmentsButton.setVisibility(showSegment ? View.VISIBLE : View.GONE); - binding.segmentsButton.setAlpha(showSegment ? 1.0f : 0.0f); - } - - private void showSystemUIPartially() { - final AppCompatActivity activity = getParentActivity(); - if (isFullscreen && activity != null) { - activity.getWindow().setStatusBarColor(Color.TRANSPARENT); - activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); - - final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - activity.getWindow().getDecorView().setSystemUiVisibility(visibility); - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - } - } - - private void hideSystemUIIfNeeded() { - if (fragmentListener != null) { - fragmentListener.hideSystemUiIfNeeded(); - } + public boolean wasPlaying() { + return wasPlaying; } //endregion @@ -2011,7 +951,7 @@ public final class Player implements private void updatePlaybackState(final boolean playWhenReady, final int playbackState) { if (DEBUG) { - Log.d(TAG, "ExoPlayer - onPlayerStateChanged() called with: " + Log.d(TAG, "ExoPlayer - updatePlaybackState() called with: " + "playWhenReady = [" + playWhenReady + "], " + "playbackState = [" + playbackState + "]"); } @@ -2122,9 +1062,7 @@ public final class Player implements Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); } - setVideoDurationToControls((int) simpleExoPlayer.getDuration()); - - binding.playbackSpeed.setText(formatSpeed(getPlaybackSpeed())); + UIs.call(PlayerUi::onPrepared); if (playWhenReady) { audioReactor.requestAudioFocus(); @@ -2139,20 +1077,7 @@ public final class Player implements startProgressLoop(); } - // if we are e.g. switching players, hide controls - hideControls(DEFAULT_CONTROLS_DURATION, 0); - - binding.playbackSeekBar.setEnabled(false); - binding.playbackSeekBar.getThumb() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); - - binding.loadingPanel.setBackgroundColor(Color.BLACK); - animate(binding.loadingPanel, true, 0); - animate(binding.surfaceForeground, true, 100); - - binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); - animatePlayButtons(false, 100); - binding.getRoot().setKeepScreenOn(false); + UIs.call(PlayerUi::onBlocked); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @@ -2165,28 +1090,7 @@ public final class Player implements startProgressLoop(); } - updateStreamRelatedViews(); - - binding.playbackSeekBar.setEnabled(true); - binding.playbackSeekBar.getThumb() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); - - binding.loadingPanel.setVisibility(View.GONE); - - animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); - - animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, - () -> { - binding.playPauseButton.setImageResource(R.drawable.ic_pause); - animatePlayButtons(true, 200); - if (!isQueueVisible) { - binding.playPauseButton.requestFocus(); - } - }); - - changePopupWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS); - checkLandscape(); - binding.getRoot().setKeepScreenOn(true); + UIs.call(PlayerUi::onPlaying); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @@ -2195,10 +1099,8 @@ public final class Player implements if (DEBUG) { Log.d(TAG, "onBuffering() called"); } - binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT); - binding.loadingPanel.setVisibility(View.VISIBLE); - binding.getRoot().setKeepScreenOn(true); + UIs.call(PlayerUi::onBuffering); if (NotificationUtil.getInstance().shouldUpdateBufferingSlot()) { NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); @@ -2214,22 +1116,7 @@ public final class Player implements stopProgressLoop(); } - // Don't let UI elements popup during double tap seeking. This state is entered sometimes - // during seeking/loading. This if-else check ensures that the controls aren't popping up. - if (!playerGestureListener.isDoubleTapping()) { - showControls(400); - binding.loadingPanel.setVisibility(View.GONE); - - animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, - () -> { - binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); - animatePlayButtons(true, 200); - if (!isQueueVisible) { - binding.playPauseButton.requestFocus(); - } - }); - } - changePopupWindowFlags(IDLE_WINDOW_FLAGS); + UIs.call(PlayerUi::onPaused); // Remove running notification when user does not want minimization to background or popup if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE @@ -2238,8 +1125,6 @@ public final class Player implements } else { NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } - - binding.getRoot().setKeepScreenOn(false); } private void onPausedSeek() { @@ -2247,8 +1132,7 @@ public final class Player implements Log.d(TAG, "onPausedSeek() called"); } - animatePlayButtons(false, 100); - binding.getRoot().setKeepScreenOn(true); + UIs.call(PlayerUi::onPausedSeek); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @@ -2261,19 +1145,8 @@ public final class Player implements return; } - animate(binding.playPauseButton, false, 0, AnimationType.SCALE_AND_ALPHA, 0, - () -> { - binding.playPauseButton.setImageResource(R.drawable.ic_replay); - animatePlayButtons(true, DEFAULT_CONTROLS_DURATION); - }); - - binding.getRoot().setKeepScreenOn(false); - changePopupWindowFlags(IDLE_WINDOW_FLAGS); - + UIs.call(PlayerUi::onCompleted); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - if (isFullscreen) { - toggleFullscreen(); - } if (playQueue.getIndex() < playQueue.size() - 1) { playQueue.offsetIndex(+1); @@ -2281,38 +1154,6 @@ public final class Player implements if (isProgressLoopRunning()) { stopProgressLoop(); } - - // When a (short) video ends the elements have to display the correct values - see #6180 - updatePlayBackElementsCurrentDuration(binding.playbackSeekBar.getMax()); - - showControls(500); - animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); - binding.loadingPanel.setVisibility(View.GONE); - animate(binding.surfaceForeground, true, 100); - } - - private void animatePlayButtons(final boolean show, final int duration) { - animate(binding.playPauseButton, show, duration, AnimationType.SCALE_AND_ALPHA); - - boolean showQueueButtons = show; - if (playQueue == null) { - showQueueButtons = false; - } - - if (!showQueueButtons || playQueue.getIndex() > 0) { - animate( - binding.playPreviousButton, - showQueueButtons, - duration, - AnimationType.SCALE_AND_ALPHA); - } - if (!showQueueButtons || playQueue.getIndex() + 1 < playQueue.getStreams().size()) { - animate( - binding.playNextButton, - showQueueButtons, - duration, - AnimationType.SCALE_AND_ALPHA); - } } //endregion @@ -2323,34 +1164,20 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ //region Repeat and shuffle - public void onRepeatClicked() { - if (DEBUG) { - Log.d(TAG, "onRepeatClicked() called"); - } - setRepeatMode(nextRepeatMode(getRepeatMode())); - } - - public void onShuffleClicked() { - if (DEBUG) { - Log.d(TAG, "onShuffleClicked() called"); - } - - if (exoPlayerIsNull()) { - return; - } - simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); - } - @RepeatMode public int getRepeatMode() { return exoPlayerIsNull() ? REPEAT_MODE_OFF : simpleExoPlayer.getRepeatMode(); } - private void setRepeatMode(@RepeatMode final int repeatMode) { + public void setRepeatMode(@RepeatMode final int repeatMode) { if (!exoPlayerIsNull()) { simpleExoPlayer.setRepeatMode(repeatMode); } } + + public void cycleNextRepeatMode() { + setRepeatMode(nextRepeatMode(getRepeatMode())); + } @Override public void onRepeatModeChanged(@RepeatMode final int repeatMode) { @@ -2358,7 +1185,7 @@ public final class Player implements Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: " + "repeatMode = [" + repeatMode + "]"); } - setRepeatModeButton(binding.repeatButton, repeatMode); + UIs.call(playerUi -> playerUi.onRepeatModeChanged(repeatMode)); onShuffleOrRepeatModeChanged(); } @@ -2377,39 +1204,26 @@ public final class Player implements } } - setShuffleButton(binding.shuffleButton, shuffleModeEnabled); + UIs.call(playerUi -> playerUi.onShuffleModeEnabledChanged(shuffleModeEnabled)); onShuffleOrRepeatModeChanged(); } + + public void toggleShuffleModeEnabled() { + if (!exoPlayerIsNull()) { + simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); + } + } private void onShuffleOrRepeatModeChanged() { notifyPlaybackUpdateToListeners(); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } - - private void setRepeatModeButton(final AppCompatImageButton imageButton, - @RepeatMode final int repeatMode) { - switch (repeatMode) { - case REPEAT_MODE_OFF: - imageButton.setImageResource(R.drawable.exo_controls_repeat_off); - break; - case REPEAT_MODE_ONE: - imageButton.setImageResource(R.drawable.exo_controls_repeat_one); - break; - case REPEAT_MODE_ALL: - imageButton.setImageResource(R.drawable.exo_controls_repeat_all); - break; - } - } - - private void setShuffleButton(@NonNull final ImageButton button, final boolean shuffled) { - button.setImageAlpha(shuffled ? 255 : 77); - } //endregion /*////////////////////////////////////////////////////////////////////////// - // Playlist append + // Playlist append TODO this does not make sense here //////////////////////////////////////////////////////////////////////////*/ //region Playlist append @@ -2439,23 +1253,16 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ //region Mute / Unmute - public void onMuteUnmuteButtonClicked() { - if (DEBUG) { - Log.d(TAG, "onMuteUnmuteButtonClicked() called"); - } - simpleExoPlayer.setVolume(isMuted() ? 1 : 0); + public void toggleMute() { + final boolean wasMuted = isMuted(); + simpleExoPlayer.setVolume(wasMuted ? 1 : 0); + UIs.call(playerUi -> playerUi.onMuteUnmuteChanged(!wasMuted)); notifyPlaybackUpdateToListeners(); - setMuteButton(binding.switchMute, isMuted()); } - boolean isMuted() { + public boolean isMuted() { return !exoPlayerIsNull() && simpleExoPlayer.getVolume() == 0; } - - private void setMuteButton(@NonNull final ImageButton button, final boolean isMuted) { - button.setImageDrawable(AppCompatResources.getDrawable(context, isMuted - ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); - } //endregion @@ -2519,7 +1326,7 @@ public final class Player implements Log.d(TAG, "ExoPlayer - onTracksChanged(), " + "track group size = " + tracks.getGroups().size()); } - onTextTracksChanged(tracks); + UIs.call(playerUi -> playerUi.onTextTracksChanged(tracks)); } @Override @@ -2528,7 +1335,7 @@ public final class Player implements Log.d(TAG, "ExoPlayer - playbackParameters(), speed = [" + playbackParameters.speed + "], pitch = [" + playbackParameters.pitch + "]"); } - binding.playbackSpeed.setText(formatSpeed(playbackParameters.speed)); + UIs.call(playerUi -> playerUi.onPlaybackParametersChanged(playbackParameters)); } @Override @@ -2580,13 +1387,12 @@ public final class Player implements @Override public void onRenderedFirstFrame() { - //TODO check if this causes black screen when switching to fullscreen - animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION); + UIs.call(PlayerUi::onRenderedFirstFrame); } @Override public void onCues(@NonNull final CueGroup cueGroup) { - binding.subtitleView.setCues(cueGroup.cues); + UIs.call(playerUi -> playerUi.onCues(cueGroup.cues)); } //endregion @@ -2627,7 +1433,7 @@ public final class Player implements // Any error code not explicitly covered here are either unrelated to NewPipe use case // (e.g. DRM) or not recoverable (e.g. Decoder error). In both cases, the player should // shutdown. - @SuppressLint("SwitchIntDef") + @SuppressWarnings("SwitchIntDef") @Override public void onPlayerError(@NonNull final PlaybackException error) { Log.e(TAG, "ExoPlayer - onPlayerError() called with:", error); @@ -2706,18 +1512,6 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ //region Playback position and seek - /** - * Sets the current duration into the corresponding elements. - * @param currentProgress - */ - private void updatePlayBackElementsCurrentDuration(final int currentProgress) { - // Don't set seekbar progress while user is seeking - if (currentState != STATE_PAUSED_SEEK) { - binding.playbackSeekBar.setProgress(currentProgress); - } - binding.playbackCurrentTime.setText(getTimeString(currentProgress)); - } - @Override // own playback listener (this is a getter) public boolean isApproachingPlaybackEdge(final long timeToEndMillis) { // If live, then not near playback edge @@ -2835,20 +1629,6 @@ public final class Player implements simpleExoPlayer.seekToDefaultPosition(); } } - - /** - * Sets the video duration time into all control components (e.g. seekbar). - * @param duration - */ - private void setVideoDurationToControls(final int duration) { - binding.playbackEndTime.setText(getTimeString(duration)); - - binding.playbackSeekBar.setMax(duration); - // This is important for Android TVs otherwise it would apply the default from - // setMax/Min methods which is (max - min) / 20 - binding.playbackSeekBar.setKeyProgressIncrement( - PlayerHelper.retrieveSeekDurationFromPreferences(this)); - } //endregion @@ -2972,6 +1752,7 @@ public final class Player implements } private void saveStreamProgressState(final long progressMillis) { + //noinspection SimplifyOptionalCallChains if (!getCurrentStreamInfo().isPresent() || !prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) { return; @@ -3026,17 +1807,10 @@ public final class Player implements Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName()); } + UIs.call(playerUi -> playerUi.onMetadataChanged(info)); + initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); - updateStreamRelatedViews(); - showHideKodiButton(); - - binding.titleTextView.setText(info.getName()); - binding.channelTextView.setText(info.getUploaderName()); - - this.seekbarPreviewThumbnailHolder.resetFrom(this.getContext(), info.getPreviewFrames()); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); final boolean showThumbnail = prefs.getBoolean( context.getString(R.string.show_thumbnail_key), true); @@ -3048,17 +1822,7 @@ public final class Player implements ); notifyMetadataUpdateToListeners(); - - if (areSegmentsVisible) { - if (segmentAdapter.setItems(info)) { - final int adapterPosition = getNearestStreamSegmentPosition( - simpleExoPlayer.getCurrentPosition()); - segmentAdapter.selectSegmentAt(adapterPosition); - binding.itemsList.scrollToPosition(adapterPosition); - } else { - closeItemsList(); - } - } + NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void updateMetadataWith(@NonNull final StreamInfo streamInfo) { @@ -3072,15 +1836,15 @@ public final class Player implements } @NonNull - private String getVideoUrl() { + public String getVideoUrl() { return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getStreamUrl(); } @NonNull - private String getVideoUrlAtCurrentTime() { - final int timeSeconds = binding.playbackSeekBar.getProgress() / 1000; + public String getVideoUrlAtCurrentTime() { + final long timeSeconds = simpleExoPlayer.getCurrentPosition() / 1000; String videoUrl = getVideoUrl(); if (!isLive() && timeSeconds >= 0 && currentMetadata != null && currentMetadata.getServiceId() == YouTube.getServiceId()) { @@ -3156,190 +1920,10 @@ public final class Player implements @Override public void onPlayQueueEdited() { notifyPlaybackUpdateToListeners(); - showOrHideButtons(); + UIs.call(PlayerUi::onPlayQueueEdited); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } - private void onQueueClicked() { - isQueueVisible = true; - - hideSystemUIIfNeeded(); - buildQueue(); - - binding.itemsListHeaderTitle.setVisibility(View.GONE); - binding.itemsListHeaderDuration.setVisibility(View.VISIBLE); - binding.shuffleButton.setVisibility(View.VISIBLE); - binding.repeatButton.setVisibility(View.VISIBLE); - binding.addToPlaylistButton.setVisibility(View.VISIBLE); - - hideControls(0, 0); - binding.itemsListPanel.requestFocus(); - animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA); - - binding.itemsList.scrollToPosition(playQueue.getIndex()); - - updateQueueTime((int) simpleExoPlayer.getCurrentPosition()); - } - - private void buildQueue() { - binding.itemsList.setAdapter(playQueueAdapter); - binding.itemsList.setClickable(true); - binding.itemsList.setLongClickable(true); - - binding.itemsList.clearOnScrollListeners(); - binding.itemsList.addOnScrollListener(getQueueScrollListener()); - - itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); - itemTouchHelper.attachToRecyclerView(binding.itemsList); - - playQueueAdapter.setSelectedListener(getOnSelectedListener()); - - binding.itemsListClose.setOnClickListener(view -> closeItemsList()); - } - - private void onSegmentsClicked() { - areSegmentsVisible = true; - - hideSystemUIIfNeeded(); - buildSegments(); - - binding.itemsListHeaderTitle.setVisibility(View.VISIBLE); - binding.itemsListHeaderDuration.setVisibility(View.GONE); - binding.shuffleButton.setVisibility(View.GONE); - binding.repeatButton.setVisibility(View.GONE); - binding.addToPlaylistButton.setVisibility(View.GONE); - - hideControls(0, 0); - binding.itemsListPanel.requestFocus(); - animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA); - - final int adapterPosition = getNearestStreamSegmentPosition(simpleExoPlayer - .getCurrentPosition()); - segmentAdapter.selectSegmentAt(adapterPosition); - binding.itemsList.scrollToPosition(adapterPosition); - } - - private void buildSegments() { - binding.itemsList.setAdapter(segmentAdapter); - binding.itemsList.setClickable(true); - binding.itemsList.setLongClickable(false); - - binding.itemsList.clearOnScrollListeners(); - if (itemTouchHelper != null) { - itemTouchHelper.attachToRecyclerView(null); - } - - getCurrentStreamInfo().ifPresent(segmentAdapter::setItems); - - binding.shuffleButton.setVisibility(View.GONE); - binding.repeatButton.setVisibility(View.GONE); - binding.addToPlaylistButton.setVisibility(View.GONE); - binding.itemsListClose.setOnClickListener(view -> closeItemsList()); - } - - public void closeItemsList() { - if (isQueueVisible || areSegmentsVisible) { - isQueueVisible = false; - areSegmentsVisible = false; - - if (itemTouchHelper != null) { - itemTouchHelper.attachToRecyclerView(null); - } - - animate(binding.itemsListPanel, false, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA, 0, () -> { - // Even when queueLayout is GONE it receives touch events - // and ruins normal behavior of the app. This line fixes it - binding.itemsListPanel.setTranslationY( - -binding.itemsListPanel.getHeight() * 5); - }); - - // clear focus, otherwise a white rectangle remains on top of the player - binding.itemsListClose.clearFocus(); - binding.playPauseButton.requestFocus(); - } - } - - private OnScrollBelowItemsListener getQueueScrollListener() { - return new OnScrollBelowItemsListener() { - @Override - public void onScrolledDown(final RecyclerView recyclerView) { - if (playQueue != null && !playQueue.isComplete()) { - playQueue.fetch(); - } else if (binding != null) { - binding.itemsList.clearOnScrollListeners(); - } - } - }; - } - - private StreamSegmentAdapter.StreamSegmentListener getStreamSegmentListener() { - return (item, seconds) -> { - segmentAdapter.selectSegment(item); - seekTo(seconds * 1000L); - triggerProgressUpdate(); - }; - } - - private int getNearestStreamSegmentPosition(final long playbackPosition) { - int nearestPosition = 0; - final List segments = getCurrentStreamInfo() - .map(StreamInfo::getStreamSegments) - .orElse(Collections.emptyList()); - - for (int i = 0; i < segments.size(); i++) { - if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) { - break; - } - nearestPosition++; - } - return Math.max(0, nearestPosition - 1); - } - - private ItemTouchHelper.SimpleCallback getItemTouchCallback() { - return new PlayQueueItemTouchCallback() { - @Override - public void onMove(final int sourceIndex, final int targetIndex) { - if (playQueue != null) { - playQueue.move(sourceIndex, targetIndex); - } - } - - @Override - public void onSwiped(final int index) { - if (index != -1) { - playQueue.remove(index); - } - } - }; - } - - private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() { - return new PlayQueueItemBuilder.OnSelectedListener() { - @Override - public void selected(final PlayQueueItem item, final View view) { - selectQueueItem(item); - } - - @Override - public void held(final PlayQueueItem item, final View view) { - if (playQueue.indexOf(item) != -1) { - openPopupMenu(playQueue, item, view, true, - getParentActivity().getSupportFragmentManager(), context); - } - } - - @Override - public void onStartDrag(final PlayQueueItemHolder viewHolder) { - if (itemTouchHelper != null) { - itemTouchHelper.startDrag(viewHolder); - } - } - }; - } - @Override // own playback listener @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { @@ -3372,279 +1956,21 @@ public final class Player implements @Nullable public VideoStream getSelectedVideoStream() { - return (selectedStreamIndex >= 0 && availableStreams != null - && availableStreams.size() > selectedStreamIndex) - ? availableStreams.get(selectedStreamIndex) : null; - } - - private void updateStreamRelatedViews() { - if (!getCurrentStreamInfo().isPresent()) { - return; - } - final StreamInfo info = getCurrentStreamInfo().get(); - - binding.qualityTextView.setVisibility(View.GONE); - binding.playbackSpeed.setVisibility(View.GONE); - - binding.playbackEndTime.setVisibility(View.GONE); - binding.playbackLiveSync.setVisibility(View.GONE); - - switch (info.getStreamType()) { - case AUDIO_STREAM: - case POST_LIVE_AUDIO_STREAM: - binding.surfaceView.setVisibility(View.GONE); - binding.endScreen.setVisibility(View.VISIBLE); - binding.playbackEndTime.setVisibility(View.VISIBLE); - break; - - case AUDIO_LIVE_STREAM: - binding.surfaceView.setVisibility(View.GONE); - binding.endScreen.setVisibility(View.VISIBLE); - binding.playbackLiveSync.setVisibility(View.VISIBLE); - break; - - case LIVE_STREAM: - binding.surfaceView.setVisibility(View.VISIBLE); - binding.endScreen.setVisibility(View.GONE); - binding.playbackLiveSync.setVisibility(View.VISIBLE); - break; - - case VIDEO_STREAM: - case POST_LIVE_STREAM: - if (currentMetadata == null - || !currentMetadata.getMaybeQuality().isPresent() - || (info.getVideoStreams().isEmpty() - && info.getVideoOnlyStreams().isEmpty())) { - break; - } - - availableStreams = currentMetadata.getMaybeQuality().get().getSortedVideoStreams(); - selectedStreamIndex = - currentMetadata.getMaybeQuality().get().getSelectedVideoStreamIndex(); - buildQualityMenu(); - - binding.qualityTextView.setVisibility(View.VISIBLE); - binding.surfaceView.setVisibility(View.VISIBLE); - default: - binding.endScreen.setVisibility(View.GONE); - binding.playbackEndTime.setVisibility(View.VISIBLE); - break; + @Nullable final MediaItemTag.Quality quality = Optional.ofNullable(currentMetadata) + .flatMap(MediaItemTag::getMaybeQuality) + .orElse(null); + if (quality == null) { + return null; } - buildPlaybackSpeedMenu(); - binding.playbackSpeed.setVisibility(View.VISIBLE); - } + final List availableStreams = quality.getSortedVideoStreams(); + final int selectedStreamIndex = quality.getSelectedVideoStreamIndex(); - private void updateQueueTime(final int currentTime) { - final int currentStream = playQueue.getIndex(); - int before = 0; - int after = 0; - - final List streams = playQueue.getStreams(); - final int nStreams = streams.size(); - - for (int i = 0; i < nStreams; i++) { - if (i < currentStream) { - before += streams.get(i).getDuration(); - } else { - after += streams.get(i).getDuration(); - } + if (selectedStreamIndex >= 0 && availableStreams.size() > selectedStreamIndex) { + return availableStreams.get(selectedStreamIndex); + } else { + return null; } - - before *= 1000; - after *= 1000; - - binding.itemsListHeaderDuration.setText( - String.format("%s/%s", - getTimeString(currentTime + before), - getTimeString(before + after) - )); - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Popup menus ("popup" means that they pop up, not that they belong to the popup player) - //////////////////////////////////////////////////////////////////////////*/ - //region Popup menus ("popup" means that they pop up, not that they belong to the popup player) - - private void buildQualityMenu() { - if (qualityPopupMenu == null) { - return; - } - qualityPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_QUALITY); - - for (int i = 0; i < availableStreams.size(); i++) { - final VideoStream videoStream = availableStreams.get(i); - qualityPopupMenu.getMenu().add(POPUP_MENU_ID_QUALITY, i, Menu.NONE, MediaFormat - .getNameById(videoStream.getFormatId()) + " " + videoStream.getResolution()); - } - if (getSelectedVideoStream() != null) { - binding.qualityTextView.setText(getSelectedVideoStream().getResolution()); - } - qualityPopupMenu.setOnMenuItemClickListener(this); - qualityPopupMenu.setOnDismissListener(this); - } - - private void buildPlaybackSpeedMenu() { - if (playbackSpeedPopupMenu == null) { - return; - } - playbackSpeedPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_PLAYBACK_SPEED); - - for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) { - playbackSpeedPopupMenu.getMenu().add(POPUP_MENU_ID_PLAYBACK_SPEED, i, Menu.NONE, - formatSpeed(PLAYBACK_SPEEDS[i])); - } - binding.playbackSpeed.setText(formatSpeed(getPlaybackSpeed())); - playbackSpeedPopupMenu.setOnMenuItemClickListener(this); - playbackSpeedPopupMenu.setOnDismissListener(this); - } - - private void buildCaptionMenu(@NonNull final List availableLanguages) { - if (captionPopupMenu == null) { - return; - } - captionPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_CAPTION); - captionPopupMenu.setOnDismissListener(this); - - // Add option for turning off caption - final MenuItem captionOffItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, - 0, Menu.NONE, R.string.caption_none); - captionOffItem.setOnMenuItemClickListener(menuItem -> { - final int textRendererIndex = getCaptionRendererIndex(); - if (textRendererIndex != RENDERER_UNAVAILABLE) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setRendererDisabled(textRendererIndex, true)); - } - prefs.edit().remove(context.getString(R.string.caption_user_set_key)).apply(); - return true; - }); - - // Add all available captions - for (int i = 0; i < availableLanguages.size(); i++) { - final String captionLanguage = availableLanguages.get(i); - final MenuItem captionItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, - i + 1, Menu.NONE, captionLanguage); - captionItem.setOnMenuItemClickListener(menuItem -> { - final int textRendererIndex = getCaptionRendererIndex(); - if (textRendererIndex != RENDERER_UNAVAILABLE) { - // DefaultTrackSelector will select for text tracks in the following order. - // When multiple tracks share the same rank, a random track will be chosen. - // 1. ANY track exactly matching preferred language name - // 2. ANY track exactly matching preferred language stem - // 3. ROLE_FLAG_CAPTION track matching preferred language stem - // 4. ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND track matching preferred language stem - // This means if a caption track of preferred language is not available, - // then an auto-generated track of that language will be chosen automatically. - trackSelector.setParameters(trackSelector.buildUponParameters() - .setPreferredTextLanguages(captionLanguage, - PlayerHelper.captionLanguageStemOf(captionLanguage)) - .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) - .setRendererDisabled(textRendererIndex, false)); - prefs.edit().putString(context.getString(R.string.caption_user_set_key), - captionLanguage).apply(); - } - return true; - }); - } - - // apply caption language from previous user preference - final int textRendererIndex = getCaptionRendererIndex(); - if (textRendererIndex == RENDERER_UNAVAILABLE) { - return; - } - - // If user prefers to show no caption, then disable the renderer. - // Otherwise, DefaultTrackSelector may automatically find an available caption - // and display that. - final String userPreferredLanguage = - prefs.getString(context.getString(R.string.caption_user_set_key), null); - if (userPreferredLanguage == null) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setRendererDisabled(textRendererIndex, true)); - return; - } - - // Only set preferred language if it does not match the user preference, - // otherwise there might be an infinite cycle at onTextTracksChanged. - final List selectedPreferredLanguages = - trackSelector.getParameters().preferredTextLanguages; - if (!selectedPreferredLanguages.contains(userPreferredLanguage)) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setPreferredTextLanguages(userPreferredLanguage, - PlayerHelper.captionLanguageStemOf(userPreferredLanguage)) - .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) - .setRendererDisabled(textRendererIndex, false)); - } - } - - /** - * Called when an item of the quality selector or the playback speed selector is selected. - */ - @Override - public boolean onMenuItemClick(@NonNull final MenuItem menuItem) { - if (DEBUG) { - Log.d(TAG, "onMenuItemClick() called with: " - + "menuItem = [" + menuItem + "], " - + "menuItem.getItemId = [" + menuItem.getItemId() + "]"); - } - - if (menuItem.getGroupId() == POPUP_MENU_ID_QUALITY) { - final int menuItemIndex = menuItem.getItemId(); - if (selectedStreamIndex == menuItemIndex || availableStreams == null - || availableStreams.size() <= menuItemIndex) { - return true; - } - - saveStreamProgressState(); //TODO added, check if good - final String newResolution = availableStreams.get(menuItemIndex).getResolution(); - setRecovery(); - setPlaybackQuality(newResolution); - reloadPlayQueueManager(); - - binding.qualityTextView.setText(menuItem.getTitle()); - return true; - } else if (menuItem.getGroupId() == POPUP_MENU_ID_PLAYBACK_SPEED) { - final int speedIndex = menuItem.getItemId(); - final float speed = PLAYBACK_SPEEDS[speedIndex]; - - setPlaybackSpeed(speed); - binding.playbackSpeed.setText(formatSpeed(speed)); - } - - return false; - } - - /** - * Called when some popup menu is dismissed. - */ - @Override - public void onDismiss(@Nullable final PopupMenu menu) { - if (DEBUG) { - Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); - } - isSomePopupMenuVisible = false; //TODO check if this works - if (getSelectedVideoStream() != null) { - binding.qualityTextView.setText(getSelectedVideoStream().getResolution()); - } - if (isPlaying()) { - hideControls(DEFAULT_CONTROLS_DURATION, 0); - hideSystemUIIfNeeded(); - } - } - - private void onCaptionClicked() { - if (DEBUG) { - Log.d(TAG, "onCaptionClicked() called"); - } - captionPopupMenu.show(); - isSomePopupMenuVisible = true; - } - - private void setPlaybackQuality(@Nullable final String quality) { - videoResolver.setPlaybackQuality(quality); } //endregion @@ -3655,68 +1981,7 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ //region Captions (text tracks) - private void setupSubtitleView() { - final float captionScale = PlayerHelper.getCaptionScale(context); - final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context); - if (popupPlayerSelected()) { - final float captionRatio = (captionScale - 1.0f) / 5.0f + 1.0f; - binding.subtitleView.setFractionalTextSize( - SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio); - } else { - final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); - final float captionRatioInverse = 20f + 4f * (1.0f - captionScale); - binding.subtitleView.setFixedTextSize( - TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse); - } - binding.subtitleView.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT); - binding.subtitleView.setStyle(captionStyle); - } - - private void onTextTracksChanged(@NonNull final Tracks currentTrack) { - if (binding == null) { - return; - } - - final boolean trackTypeTextSupported = !currentTrack.containsType(C.TRACK_TYPE_TEXT) - || currentTrack.isTypeSupported(C.TRACK_TYPE_TEXT, false); - if (trackSelector.getCurrentMappedTrackInfo() == null || !trackTypeTextSupported) { - binding.captionTextView.setVisibility(View.GONE); - return; - } - - // Extract all loaded languages - final List textTracks = currentTrack - .getGroups() - .stream() - .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) - .collect(Collectors.toList()); - final List availableLanguages = textTracks.stream() - .map(Tracks.Group::getMediaTrackGroup) - .filter(textTrack -> textTrack.length > 0) - .map(textTrack -> textTrack.getFormat(0).language) - .collect(Collectors.toList()); - - // Find selected text track - final Optional selectedTracks = textTracks.stream() - .filter(Tracks.Group::isSelected) - .filter(info -> info.getMediaTrackGroup().length >= 1) - .map(info -> info.getMediaTrackGroup().getFormat(0)) - .findFirst(); - - // Build UI - buildCaptionMenu(availableLanguages); - if (trackSelector.getParameters().getRendererDisabled(getCaptionRendererIndex()) - || !selectedTracks.isPresent()) { - binding.captionTextView.setText(R.string.caption_none); - } else { - binding.captionTextView.setText(selectedTracks.get().language); - } - binding.captionTextView.setVisibility( - availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); - } - - private int getCaptionRendererIndex() { + public int getCaptionRendererIndex() { if (exoPlayerIsNull()) { return RENDERER_UNAVAILABLE; } @@ -3732,218 +1997,10 @@ public final class Player implements //endregion - - /*////////////////////////////////////////////////////////////////////////// - // Click listeners - //////////////////////////////////////////////////////////////////////////*/ - //region Click listeners - - @Override - public void onClick(final View v) { - if (DEBUG) { - Log.d(TAG, "onClick() called with: v = [" + v + "]"); - } - if (v.getId() == binding.resizeTextView.getId()) { - onResizeClicked(); - } else if (v.getId() == binding.captionTextView.getId()) { - onCaptionClicked(); - } else if (v.getId() == binding.playbackLiveSync.getId()) { - seekToDefault(); - } else if (v.getId() == binding.playPauseButton.getId()) { - playPause(); - } else if (v.getId() == binding.playPreviousButton.getId()) { - playPrevious(); - } else if (v.getId() == binding.playNextButton.getId()) { - playNext(); - } else if (v.getId() == binding.moreOptionsButton.getId()) { - onMoreOptionsClicked(); - } else if (v.getId() == binding.share.getId()) { - ShareUtils.shareText(context, getVideoTitle(), getVideoUrlAtCurrentTime(), - currentItem.getThumbnailUrl()); - } else if (v.getId() == binding.playWithKodi.getId()) { - onPlayWithKodiClicked(); - } else if (v.getId() == binding.openInBrowser.getId()) { - onOpenInBrowserClicked(); - } else if (v.getId() == binding.fullScreenButton.getId()) { - setRecovery(); - NavigationHelper.playOnMainPlayer(context, playQueue, true); - return; - } else if (v.getId() == binding.screenRotationButton.getId()) { - // Only if it's not a vertical video or vertical video but in landscape with locked - // orientation a screen orientation can be changed automatically - if (!isVerticalVideo - || (service.isLandscape() && globalScreenOrientationLocked(context))) { - fragmentListener.onScreenRotationButtonClicked(); - } else { - toggleFullscreen(); - } - } else if (v.getId() == binding.switchMute.getId()) { - onMuteUnmuteButtonClicked(); - } else if (v.getId() == binding.playerCloseButton.getId()) { - context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)); - } - - manageControlsAfterOnClick(v); - } - - /** - * Manages the controls after a click occurred on the player UI. - * @param v – The view that was clicked - */ - public void manageControlsAfterOnClick(@NonNull final View v) { - if (currentState == STATE_COMPLETED) { - return; - } - - controlsVisibilityHandler.removeCallbacksAndMessages(null); - showHideShadow(true, DEFAULT_CONTROLS_DURATION); - animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, - AnimationType.ALPHA, 0, () -> { - if (currentState == STATE_PLAYING && !isSomePopupMenuVisible) { - if (v.getId() == binding.playPauseButton.getId() - // Hide controls in fullscreen immediately - || (v.getId() == binding.screenRotationButton.getId() - && isFullscreen)) { - hideControls(0, 0); - } else { - hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); - } - } - }); - } - - @Override - public boolean onLongClick(final View v) { - if (v.getId() == binding.moreOptionsButton.getId() && isFullscreen) { - fragmentListener.onMoreOptionsLongClicked(); - hideControls(0, 0); - hideSystemUIIfNeeded(); - } else if (v.getId() == binding.share.getId()) { - ShareUtils.copyToClipboard(context, getVideoUrlAtCurrentTime()); - } - return true; - } - - public boolean onKeyDown(final int keyCode) { - switch (keyCode) { - default: - break; - case KeyEvent.KEYCODE_SPACE: - if (isFullscreen) { - playPause(); - if (isPlaying()) { - hideControls(0, 0); - } - return true; - } - break; - case KeyEvent.KEYCODE_BACK: - if (DeviceUtils.isTv(context) && isControlsVisible()) { - hideControls(0, 0); - return true; - } - break; - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_CENTER: - if ((binding.getRoot().hasFocus() && !binding.playbackControlRoot.hasFocus()) - || isQueueVisible) { - // do not interfere with focus in playlist and play queue etc. - return false; - } - - if (currentState == Player.STATE_BLOCKED) { - return true; - } - - if (isControlsVisible()) { - hideControls(DEFAULT_CONTROLS_DURATION, DPAD_CONTROLS_HIDE_TIME); - } else { - binding.playPauseButton.requestFocus(); - showControlsThenHide(); - showSystemUIPartially(); - return true; - } - break; - } - - return false; - } - - private void onMoreOptionsClicked() { - if (DEBUG) { - Log.d(TAG, "onMoreOptionsClicked() called"); - } - - final boolean isMoreControlsVisible = - binding.secondaryControls.getVisibility() == View.VISIBLE; - - animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, - isMoreControlsVisible ? 0 : 180); - animate(binding.secondaryControls, !isMoreControlsVisible, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA, 0, () -> { - // Fix for a ripple effect on background drawable. - // When view returns from GONE state it takes more milliseconds than returning - // from INVISIBLE state. And the delay makes ripple background end to fast - if (isMoreControlsVisible) { - binding.secondaryControls.setVisibility(View.INVISIBLE); - } - }); - showControls(DEFAULT_CONTROLS_DURATION); - } - - private void onPlayWithKodiClicked() { - if (currentMetadata != null) { - pause(); - try { - NavigationHelper.playWithKore(context, Uri.parse(getVideoUrl())); - } catch (final Exception e) { - if (DEBUG) { - Log.i(TAG, "Failed to start kore", e); - } - KoreUtils.showInstallKoreDialog(getParentActivity()); - } - } - } - - private void onOpenInBrowserClicked() { - getCurrentStreamInfo() - .map(Info::getOriginalUrl) - .ifPresent(originalUrl -> ShareUtils.openUrlInBrowser( - Objects.requireNonNull(getParentActivity()), originalUrl)); - } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Video size, resize, orientation, fullscreen //////////////////////////////////////////////////////////////////////////*/ //region Video size, resize, orientation, fullscreen - - private void setupScreenRotationButton() { - binding.screenRotationButton.setVisibility(videoPlayerSelected() - && (globalScreenOrientationLocked(context) || isVerticalVideo - || DeviceUtils.isTablet(context)) - ? View.VISIBLE : View.GONE); - binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(context, - isFullscreen ? R.drawable.ic_fullscreen_exit - : R.drawable.ic_fullscreen)); - } - - private void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { - binding.surfaceView.setResizeMode(resizeMode); - binding.resizeTextView.setText(PlayerHelper.resizeTypeOf(context, resizeMode)); - } - - void onResizeClicked() { - if (binding != null) { - setResizeMode(nextResizeModeAndSaveToPrefs(this, binding.surfaceView.getResizeMode())); - } - } - @Override // exoplayer listener public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { if (DEBUG) { @@ -3954,137 +2011,11 @@ public final class Player implements + "pixelWidthHeightRatio = [" + videoSize.pixelWidthHeightRatio + "]"); } - binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height); - isVerticalVideo = videoSize.width < videoSize.height; - - if (globalScreenOrientationLocked(context) - && isFullscreen - && service.isLandscape() == isVerticalVideo - && !DeviceUtils.isTv(context) - && !DeviceUtils.isTablet(context) - && fragmentListener != null) { - // set correct orientation - fragmentListener.onScreenRotationButtonClicked(); - } - - setupScreenRotationButton(); - } - - public void toggleFullscreen() { - if (DEBUG) { - Log.d(TAG, "toggleFullscreen() called"); - } - if (popupPlayerSelected() || exoPlayerIsNull() || fragmentListener == null) { - return; - } - - isFullscreen = !isFullscreen; - if (!isFullscreen) { - // Apply window insets because Android will not do it when orientation changes - // from landscape to portrait (open vertical video to reproduce) - binding.playbackControlRoot.setPadding(0, 0, 0, 0); - } else { - // Android needs tens milliseconds to send new insets but a user is able to see - // how controls changes it's position from `0` to `nav bar height` padding. - // So just hide the controls to hide this visual inconsistency - hideControls(0, 0); - } - fragmentListener.onFullscreenStateChanged(isFullscreen); - - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - binding.playerCloseButton.setVisibility(View.GONE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - binding.playerCloseButton.setVisibility( - videoPlayerSelected() ? View.VISIBLE : View.GONE); - } - setupScreenRotationButton(); - } - - public void checkLandscape() { - final AppCompatActivity parent = getParentActivity(); - final boolean videoInLandscapeButNotInFullscreen = - service.isLandscape() && !isFullscreen && videoPlayerSelected() && !isAudioOnly; - - final boolean notPaused = currentState != STATE_COMPLETED && currentState != STATE_PAUSED; - if (parent != null - && videoInLandscapeButNotInFullscreen - && notPaused - && !DeviceUtils.isTablet(context)) { - toggleFullscreen(); - } + UIs.call(playerUi -> playerUi.onVideoSizeChanged(videoSize)); } //endregion - - /*////////////////////////////////////////////////////////////////////////// - // Gestures - //////////////////////////////////////////////////////////////////////////*/ - //region Gestures - - @SuppressWarnings("checkstyle:ParameterNumber") - private void onLayoutChange(final View view, final int l, final int t, final int r, final int b, - final int ol, final int ot, final int or, final int ob) { - if (l != ol || t != ot || r != or || b != ob) { - // Use smaller value to be consistent between screen orientations - // (and to make usage easier) - final int width = r - l; - final int height = b - t; - final int min = Math.min(width, height); - maxGestureLength = (int) (min * MAX_GESTURE_LENGTH); - - if (DEBUG) { - Log.d(TAG, "maxGestureLength = " + maxGestureLength); - } - - binding.volumeProgressBar.setMax(maxGestureLength); - binding.brightnessProgressBar.setMax(maxGestureLength); - - setInitialGestureValues(); - binding.itemsListPanel.getLayoutParams().height - = height - binding.itemsListPanel.getTop(); - } - } - - private void setInitialGestureValues() { - if (audioReactor != null) { - final float currentVolumeNormalized = - (float) audioReactor.getVolume() / audioReactor.getMaxVolume(); - binding.volumeProgressBar.setProgress( - (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized)); - } - } - - private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) { - final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft() - + closeOverlayBinding.closeButton.getWidth() / 2; - final int closeOverlayButtonY = closeOverlayBinding.closeButton.getTop() - + closeOverlayBinding.closeButton.getHeight() / 2; - - final float fingerX = popupLayoutParams.x + popupMotionEvent.getX(); - final float fingerY = popupLayoutParams.y + popupMotionEvent.getY(); - - return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) - + Math.pow(closeOverlayButtonY - fingerY, 2)); - } - - private float getClosingRadius() { - final int buttonRadius = closeOverlayBinding.closeButton.getWidth() / 2; - // 20% wider than the button itself - return buttonRadius * 1.2f; - } - - public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent) { - return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius(); - } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Activity / fragment binding //////////////////////////////////////////////////////////////////////////*/ @@ -4092,13 +2023,7 @@ public final class Player implements public void setFragmentListener(final PlayerServiceEventListener listener) { fragmentListener = listener; - fragmentIsVisible = true; - // Apply window insets because Android will not do it when orientation changes - // from landscape to portrait - if (!isFullscreen) { - binding.playbackControlRoot.setPadding(0, 0, 0, 0); - } - binding.itemsListPanel.setPadding(0, 0, 0, 0); + UIs.call(PlayerUi::onFragmentListenerSet); notifyQueueUpdateToListeners(); notifyMetadataUpdateToListeners(); notifyPlaybackUpdateToListeners(); @@ -4136,28 +2061,6 @@ public final class Player implements } } - /** - * This will be called when a user goes to another app/activity, turns off a screen. - * We don't want to interrupt playback and don't want to see notification so - * next lines of code will enable audio-only playback only if needed - */ - private void onFragmentStopped() { - if (videoPlayerSelected() && (isPlaying() || isLoading())) { - switch (getMinimizeOnExitAction(context)) { - case MINIMIZE_ON_EXIT_MODE_BACKGROUND: - useVideoSource(false); - break; - case MINIMIZE_ON_EXIT_MODE_POPUP: - setRecovery(); - NavigationHelper.playOnPopupPlayer(getParentActivity(), playQueue, true); - break; - case MINIMIZE_ON_EXIT_MODE_NONE: default: - pause(); - break; - } - } - } - private void notifyQueueUpdateToListeners() { if (fragmentListener != null && playQueue != null) { fragmentListener.onQueueUpdate(playQueue); @@ -4200,27 +2103,12 @@ public final class Player implements } } - @Nullable - public AppCompatActivity getParentActivity() { - // ! instanceof ViewGroup means that view was added via windowManager for Popup - if (binding == null || !(binding.getRoot().getParent() instanceof ViewGroup)) { - return null; - } - - return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext(); - } - - private void useVideoSource(final boolean videoEnabled) { + public void useVideoSource(final boolean videoEnabled) { if (playQueue == null || isAudioOnly == !videoEnabled || audioPlayerSelected()) { return; } isAudioOnly = !videoEnabled; - // When a user returns from background, controls could be hidden but SystemUI will be shown - // 100%. Hide it. - if (!isAudioOnly && !isControlsVisible()) { - hideSystemUIIfNeeded(); - } // The current metadata may be null sometimes (for e.g. when using an unstable connection // in livestreams) so we will be not able to execute the block below. @@ -4332,7 +2220,7 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ //region Getters - private Optional getCurrentStreamInfo() { + public Optional getCurrentStreamInfo() { return Optional.ofNullable(currentMetadata).flatMap(MediaItemTag::getMaybeStreamInfo); } @@ -4344,6 +2232,10 @@ public final class Player implements return simpleExoPlayer == null; } + public ExoPlayer getExoPlayer() { + return simpleExoPlayer; + } + public boolean isStopped() { return exoPlayerIsNull() || simpleExoPlayer.getPlaybackState() == ExoPlayer.STATE_IDLE; } @@ -4356,7 +2248,7 @@ public final class Player implements return !exoPlayerIsNull() && simpleExoPlayer.getPlayWhenReady(); } - private boolean isLoading() { + public boolean isLoading() { return !exoPlayerIsNull() && simpleExoPlayer.isLoading(); } @@ -4372,6 +2264,10 @@ public final class Player implements } } + public void setPlaybackQuality(@Nullable final String quality) { + videoResolver.setPlaybackQuality(quality); + } + @NonNull public Context getContext() { @@ -4397,7 +2293,7 @@ public final class Player implements } public boolean videoPlayerSelected() { - return playerType == PlayerType.VIDEO; + return playerType == PlayerType.MAIN; } public boolean popupPlayerSelected() { @@ -4414,157 +2310,40 @@ public final class Player implements return audioReactor; } - public GestureDetector getGestureDetector() { - return gestureDetector; + public PlayerService getService() { + return service; } - public boolean isFullscreen() { - return isFullscreen; + public boolean isAudioOnly() { + return isAudioOnly; } - public boolean isVerticalVideo() { - return isVerticalVideo; - } - - public boolean isPopupClosing() { - return isPopupClosing; - } - - - public boolean isSomePopupMenuVisible() { - return isSomePopupMenuVisible; - } - - public void setSomePopupMenuVisible(final boolean somePopupMenuVisible) { - isSomePopupMenuVisible = somePopupMenuVisible; - } - - public ImageButton getPlayPauseButton() { - return binding.playPauseButton; - } - - public View getClosingOverlayView() { - return binding.closingOverlay; - } - - public ProgressBar getVolumeProgressBar() { - return binding.volumeProgressBar; - } - - public ProgressBar getBrightnessProgressBar() { - return binding.brightnessProgressBar; - } - - public int getMaxGestureLength() { - return maxGestureLength; - } - - public ImageView getVolumeImageView() { - return binding.volumeImageView; - } - - public RelativeLayout getVolumeRelativeLayout() { - return binding.volumeRelativeLayout; - } - - public ImageView getBrightnessImageView() { - return binding.brightnessImageView; - } - - public RelativeLayout getBrightnessRelativeLayout() { - return binding.brightnessRelativeLayout; - } - - public FloatingActionButton getCloseOverlayButton() { - return closeOverlayBinding.closeButton; - } - - public View getLoadingPanel() { - return binding.loadingPanel; - } - - public TextView getCurrentDisplaySeek() { - return binding.currentDisplaySeek; - } - - public PlayerFastSeekOverlay getFastSeekOverlay() { - return binding.fastSeekOverlay; + @NonNull + public DefaultTrackSelector getTrackSelector() { + return trackSelector; } @Nullable - public WindowManager.LayoutParams getPopupLayoutParams() { - return popupLayoutParams; + public MediaItemTag getCurrentMetadata() { + return currentMetadata; } @Nullable - public WindowManager getWindowManager() { - return windowManager; + public PlayQueueItem getCurrentItem() { + return currentItem; } - public float getScreenWidth() { - return screenWidth; + public Optional getFragmentListener() { + return Optional.ofNullable(fragmentListener); } - public float getScreenHeight() { - return screenHeight; + /** + * @return the user interfaces connected with the player + */ + public PlayerUiList UIs() { + return UIs; } - public View getRootView() { - return binding.getRoot(); - } - - public ExpandableSurfaceView getSurfaceView() { - return binding.surfaceView; - } - - public PlayQueueAdapter getPlayQueueAdapter() { - return playQueueAdapter; - } - - public PlayerBinding getBinding() { - return binding; - } - - //endregion - - - /*////////////////////////////////////////////////////////////////////////// - // SurfaceHolderCallback helpers - //////////////////////////////////////////////////////////////////////////*/ - //region SurfaceHolderCallback helpers - - private void setupVideoSurface() { - // make sure there is nothing left over from previous calls - cleanupVideoSurface(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 - surfaceHolderCallback = new SurfaceHolderCallback(context, simpleExoPlayer); - binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); - final Surface surface = binding.surfaceView.getHolder().getSurface(); - // ensure player is using an unreleased surface, which the surfaceView might not be - // when starting playback on background or during player switching - if (surface.isValid()) { - // initially set the surface manually otherwise - // onRenderedFirstFrame() will not be called - simpleExoPlayer.setVideoSurface(surface); - } - } else { - simpleExoPlayer.setVideoSurfaceView(binding.surfaceView); - } - } - - private void cleanupVideoSurface() { - // Only for API >= 23 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && surfaceHolderCallback != null) { - if (binding != null) { - binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); - } - surfaceHolderCallback.release(); - surfaceHolderCallback = null; - } - } - //endregion - /** * Get the video renderer index of the current playing stream. * @@ -4592,4 +2371,5 @@ public final class Player implements // No video renderer index with at least one track found: return unavailable index .orElse(RENDERER_UNAVAILABLE); } + //endregion } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java similarity index 63% rename from app/src/main/java/org/schabi/newpipe/player/MainPlayer.java rename to app/src/main/java/org/schabi/newpipe/player/PlayerService.java index a9b9f4c87..cf83dc5c2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -19,44 +19,35 @@ 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.IBinder; import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; - -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import org.schabi.newpipe.App; -import org.schabi.newpipe.databinding.PlayerBinding; -import org.schabi.newpipe.util.DeviceUtils; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.ThemeHelper; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - /** * One service for all players. * * @author mauriciocolli */ -public final class MainPlayer extends Service { - private static final String TAG = "MainPlayer"; +public final class PlayerService extends Service { + private static final String TAG = PlayerService.class.getSimpleName(); private static final boolean DEBUG = Player.DEBUG; private Player player; - private WindowManager windowManager; - private final IBinder mBinder = new MainPlayer.LocalBinder(); + private final IBinder mBinder = new PlayerService.LocalBinder(); public enum PlayerType { - VIDEO, + MAIN, AUDIO, POPUP } @@ -67,7 +58,7 @@ public final class MainPlayer extends Service { static final String ACTION_CLOSE = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; - static final String ACTION_PLAY_PAUSE + public static final String ACTION_PLAY_PAUSE = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; static final String ACTION_REPEAT = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; @@ -94,19 +85,12 @@ public final class MainPlayer extends Service { Log.d(TAG, "onCreate() called"); } assureCorrectAppLanguage(this); - windowManager = ContextCompat.getSystemService(this, WindowManager.class); - ThemeHelper.setTheme(this); - createView(); - } - - private void createView() { - final PlayerBinding binding = PlayerBinding.inflate(LayoutInflater.from(this)); player = new Player(this); - player.setupFromView(binding); - - NotificationUtil.getInstance().createNotificationAndStartForeground(player, this); + /*final MainPlayerUi mainPlayerUi = new MainPlayerUi(player, + PlayerBinding.inflate(LayoutInflater.from(this))); + player.UIs().add(mainPlayerUi);*/ } @Override @@ -121,11 +105,6 @@ public final class MainPlayer extends Service { return START_NOT_STICKY; } - if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) - || intent.getStringExtra(Player.PLAY_QUEUE_KEY) != null) { - NotificationUtil.getInstance().createNotificationAndStartForeground(player, this); - } - player.handleIntent(intent); if (player.getMediaSessionManager() != null) { player.getMediaSessionManager().handleMediaButtonIntent(intent); @@ -144,13 +123,7 @@ public final class MainPlayer extends Service { // Releases wifi & cpu, disables keepScreenOn, etc. // We can't just pause the player here because it will make transition // from one stream to a new stream not smooth - player.smoothStopPlayer(); - player.setRecovery(); - - // Android TV will handle back button in case controls will be visible - // (one more additional unneeded click while the player is hidden) - player.hideControls(0, 0); - player.closeItemsList(); + player.smoothStopForImmediateReusing(); // Notification shows information about old stream but if a user selects // a stream from backStack it's not actual anymore @@ -180,18 +153,7 @@ public final class MainPlayer extends Service { private void cleanup() { if (player != null) { - // Exit from fullscreen when user closes the player via notification - if (player.isFullscreen()) { - player.toggleFullscreen(); - } - removeViewFromParent(); - - player.saveStreamProgressState(); - player.setRecovery(); - player.stopActivityBinding(); - player.removePopupFromView(); player.destroy(); - player = null; } } @@ -212,48 +174,14 @@ public final class MainPlayer extends Service { return mBinder; } - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - - boolean isLandscape() { - // DisplayMetrics from activity context knows about MultiWindow feature - // while DisplayMetrics from app context doesn't - return DeviceUtils.isLandscape(player != null && player.getParentActivity() != null - ? player.getParentActivity() : this); - } - - @Nullable - public View getView() { - if (player == null) { - return null; - } - - return player.getRootView(); - } - - public void removeViewFromParent() { - if (getView() != null && getView().getParent() != null) { - if (player.getParentActivity() != null) { - // This means view was added to fragment - final ViewGroup parent = (ViewGroup) getView().getParent(); - parent.removeView(getView()); - } else { - // This means view was added by windowManager for popup player - windowManager.removeViewImmediate(getView()); - } - } - } - - public class LocalBinder extends Binder { - public MainPlayer getService() { - return MainPlayer.this; + public PlayerService getService() { + return PlayerService.this; } public Player getPlayer() { - return MainPlayer.this.player; + return PlayerService.this.player; } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt deleted file mode 100644 index c89eabb47..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt +++ /dev/null @@ -1,520 +0,0 @@ -package org.schabi.newpipe.player.event - -import android.content.Context -import android.os.Handler -import android.util.Log -import android.view.GestureDetector -import android.view.MotionEvent -import android.view.View -import android.view.ViewConfiguration -import org.schabi.newpipe.ktx.animate -import org.schabi.newpipe.player.MainPlayer -import org.schabi.newpipe.player.Player -import org.schabi.newpipe.player.helper.PlayerHelper -import org.schabi.newpipe.player.helper.PlayerHelper.savePopupPositionAndSizeToPrefs -import kotlin.math.abs -import kotlin.math.hypot -import kotlin.math.max -import kotlin.math.min - -/** - * Base gesture handling for [Player] - * - * This class contains the logic for the player gestures like View preparations - * and provides some abstract methods to make it easier separating the logic from the UI. - */ -abstract class BasePlayerGestureListener( - @JvmField - protected val player: Player, - @JvmField - protected val service: MainPlayer -) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener { - - // /////////////////////////////////////////////////////////////////// - // Abstract methods for VIDEO and POPUP - // /////////////////////////////////////////////////////////////////// - - abstract fun onDoubleTap(event: MotionEvent, portion: DisplayPortion) - - abstract fun onSingleTap(playerType: MainPlayer.PlayerType) - - abstract fun onScroll( - playerType: MainPlayer.PlayerType, - portion: DisplayPortion, - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ) - - abstract fun onScrollEnd(playerType: MainPlayer.PlayerType, event: MotionEvent) - - // /////////////////////////////////////////////////////////////////// - // Abstract methods for POPUP (exclusive) - // /////////////////////////////////////////////////////////////////// - - abstract fun onPopupResizingStart() - - abstract fun onPopupResizingEnd() - - private var initialPopupX: Int = -1 - private var initialPopupY: Int = -1 - - private var isMovingInMain = false - private var isMovingInPopup = false - private var isResizing = false - - private val tossFlingVelocity = PlayerHelper.getTossFlingVelocity() - - // [popup] initial coordinates and distance between fingers - private var initPointerDistance = -1.0 - private var initFirstPointerX = -1f - private var initFirstPointerY = -1f - private var initSecPointerX = -1f - private var initSecPointerY = -1f - - // /////////////////////////////////////////////////////////////////// - // onTouch implementation - // /////////////////////////////////////////////////////////////////// - - override fun onTouch(v: View, event: MotionEvent): Boolean { - return if (player.popupPlayerSelected()) { - onTouchInPopup(v, event) - } else { - onTouchInMain(v, event) - } - } - - private fun onTouchInMain(v: View, event: MotionEvent): Boolean { - player.gestureDetector.onTouchEvent(event) - if (event.action == MotionEvent.ACTION_UP && isMovingInMain) { - isMovingInMain = false - onScrollEnd(MainPlayer.PlayerType.VIDEO, event) - } - return when (event.action) { - MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { - v.parent.requestDisallowInterceptTouchEvent(player.isFullscreen) - true - } - MotionEvent.ACTION_UP -> { - v.parent.requestDisallowInterceptTouchEvent(false) - false - } - else -> true - } - } - - private fun onTouchInPopup(v: View, event: MotionEvent): Boolean { - player.gestureDetector.onTouchEvent(event) - if (event.pointerCount == 2 && !isMovingInPopup && !isResizing) { - if (DEBUG) { - Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.") - } - onPopupResizingStart() - - // record coordinates of fingers - initFirstPointerX = event.getX(0) - initFirstPointerY = event.getY(0) - initSecPointerX = event.getX(1) - initSecPointerY = event.getY(1) - // record distance between fingers - initPointerDistance = hypot( - initFirstPointerX - initSecPointerX.toDouble(), - initFirstPointerY - initSecPointerY.toDouble() - ) - - isResizing = true - } - if (event.action == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) { - if (DEBUG) { - Log.d( - TAG, - "onTouch() ACTION_MOVE > v = [$v], e1.getRaw =" + - "[${event.rawX}, ${event.rawY}]" - ) - } - return handleMultiDrag(event) - } - if (event.action == MotionEvent.ACTION_UP) { - if (DEBUG) { - Log.d( - TAG, - "onTouch() ACTION_UP > v = [$v], e1.getRaw =" + - " [${event.rawX}, ${event.rawY}]" - ) - } - if (isMovingInPopup) { - isMovingInPopup = false - onScrollEnd(MainPlayer.PlayerType.POPUP, event) - } - if (isResizing) { - isResizing = false - - initPointerDistance = (-1).toDouble() - initFirstPointerX = (-1).toFloat() - initFirstPointerY = (-1).toFloat() - initSecPointerX = (-1).toFloat() - initSecPointerY = (-1).toFloat() - - onPopupResizingEnd() - player.changeState(player.currentState) - } - if (!player.isPopupClosing) { - savePopupPositionAndSizeToPrefs(player) - } - } - - v.performClick() - return true - } - - private fun handleMultiDrag(event: MotionEvent): Boolean { - if (initPointerDistance != -1.0 && event.pointerCount == 2) { - // get the movements of the fingers - val firstPointerMove = hypot( - event.getX(0) - initFirstPointerX.toDouble(), - event.getY(0) - initFirstPointerY.toDouble() - ) - val secPointerMove = hypot( - event.getX(1) - initSecPointerX.toDouble(), - event.getY(1) - initSecPointerY.toDouble() - ) - - // minimum threshold beyond which pinch gesture will work - val minimumMove = ViewConfiguration.get(service).scaledTouchSlop - - if (max(firstPointerMove, secPointerMove) > minimumMove) { - // calculate current distance between the pointers - val currentPointerDistance = hypot( - event.getX(0) - event.getX(1).toDouble(), - event.getY(0) - event.getY(1).toDouble() - ) - - val popupWidth = player.popupLayoutParams!!.width.toDouble() - // change co-ordinates of popup so the center stays at the same position - val newWidth = popupWidth * currentPointerDistance / initPointerDistance - initPointerDistance = currentPointerDistance - player.popupLayoutParams!!.x += ((popupWidth - newWidth) / 2.0).toInt() - - player.checkPopupPositionBounds() - player.updateScreenSize() - player.changePopupSize(min(player.screenWidth.toDouble(), newWidth).toInt()) - return true - } - } - return false - } - - // /////////////////////////////////////////////////////////////////// - // Simple gestures - // /////////////////////////////////////////////////////////////////// - - override fun onDown(e: MotionEvent): Boolean { - if (DEBUG) - Log.d(TAG, "onDown called with e = [$e]") - - if (isDoubleTapping && isDoubleTapEnabled) { - doubleTapControls?.onDoubleTapProgressDown(getDisplayPortion(e)) - return true - } - - return if (player.popupPlayerSelected()) - onDownInPopup(e) - else - true - } - - private fun onDownInPopup(e: MotionEvent): Boolean { - // Fix popup position when the user touch it, it may have the wrong one - // because the soft input is visible (the draggable area is currently resized). - player.updateScreenSize() - player.checkPopupPositionBounds() - player.popupLayoutParams?.let { - initialPopupX = it.x - initialPopupY = it.y - } - return super.onDown(e) - } - - override fun onDoubleTap(e: MotionEvent): Boolean { - if (DEBUG) - Log.d(TAG, "onDoubleTap called with e = [$e]") - - onDoubleTap(e, getDisplayPortion(e)) - return true - } - - override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - if (DEBUG) - Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") - - if (isDoubleTapping) - return true - - if (player.popupPlayerSelected()) { - if (player.exoPlayerIsNull()) - return false - - onSingleTap(MainPlayer.PlayerType.POPUP) - return true - } else { - super.onSingleTapConfirmed(e) - if (player.currentState == Player.STATE_BLOCKED) - return true - - onSingleTap(MainPlayer.PlayerType.VIDEO) - } - return true - } - - override fun onLongPress(e: MotionEvent?) { - if (player.popupPlayerSelected()) { - player.updateScreenSize() - player.checkPopupPositionBounds() - player.changePopupSize(player.screenWidth.toInt()) - } - } - - override fun onScroll( - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - return if (player.popupPlayerSelected()) { - onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY) - } else { - onScrollInMain(initialEvent, movingEvent, distanceX, distanceY) - } - } - - override fun onFling( - e1: MotionEvent?, - e2: MotionEvent?, - velocityX: Float, - velocityY: Float - ): Boolean { - return if (player.popupPlayerSelected()) { - val absVelocityX = abs(velocityX) - val absVelocityY = abs(velocityY) - if (absVelocityX.coerceAtLeast(absVelocityY) > tossFlingVelocity) { - if (absVelocityX > tossFlingVelocity) { - player.popupLayoutParams!!.x = velocityX.toInt() - } - if (absVelocityY > tossFlingVelocity) { - player.popupLayoutParams!!.y = velocityY.toInt() - } - player.checkPopupPositionBounds() - player.windowManager!!.updateViewLayout(player.rootView, player.popupLayoutParams) - return true - } - return false - } else { - true - } - } - - private fun onScrollInMain( - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - - if (!player.isFullscreen) { - return false - } - - val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(service) - val isTouchingNavigationBar: Boolean = - initialEvent.y > (player.rootView.height - getNavigationBarHeight(service)) - if (isTouchingStatusBar || isTouchingNavigationBar) { - return false - } - - val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD - if ( - !isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) || - player.currentState == Player.STATE_COMPLETED - ) { - return false - } - - isMovingInMain = true - - onScroll( - MainPlayer.PlayerType.VIDEO, - getDisplayHalfPortion(initialEvent), - initialEvent, - movingEvent, - distanceX, - distanceY - ) - - return true - } - - private fun onScrollInPopup( - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - - if (isResizing) { - return super.onScroll(initialEvent, movingEvent, distanceX, distanceY) - } - - if (!isMovingInPopup) { - player.closeOverlayButton.animate(true, 200) - } - - isMovingInPopup = true - - val diffX: Float = (movingEvent.rawX - initialEvent.rawX) - var posX: Float = (initialPopupX + diffX) - val diffY: Float = (movingEvent.rawY - initialEvent.rawY) - var posY: Float = (initialPopupY + diffY) - - if (posX > player.screenWidth - player.popupLayoutParams!!.width) { - posX = (player.screenWidth - player.popupLayoutParams!!.width) - } else if (posX < 0) { - posX = 0f - } - - if (posY > player.screenHeight - player.popupLayoutParams!!.height) { - posY = (player.screenHeight - player.popupLayoutParams!!.height) - } else if (posY < 0) { - posY = 0f - } - - player.popupLayoutParams!!.x = posX.toInt() - player.popupLayoutParams!!.y = posY.toInt() - - onScroll( - MainPlayer.PlayerType.POPUP, - getDisplayHalfPortion(initialEvent), - initialEvent, - movingEvent, - distanceX, - distanceY - ) - - player.windowManager!!.updateViewLayout(player.rootView, player.popupLayoutParams) - return true - } - - // /////////////////////////////////////////////////////////////////// - // Multi double tapping - // /////////////////////////////////////////////////////////////////// - - var doubleTapControls: DoubleTapListener? = null - private set - - private val isDoubleTapEnabled: Boolean - get() = doubleTapDelay > 0 - - var isDoubleTapping = false - private set - - fun doubleTapControls(listener: DoubleTapListener) = apply { - doubleTapControls = listener - } - - private var doubleTapDelay = DOUBLE_TAP_DELAY - private val doubleTapHandler: Handler = Handler() - private val doubleTapRunnable = Runnable { - if (DEBUG) - Log.d(TAG, "doubleTapRunnable called") - - isDoubleTapping = false - doubleTapControls?.onDoubleTapFinished() - } - - fun startMultiDoubleTap(e: MotionEvent) { - if (!isDoubleTapping) { - if (DEBUG) - Log.d(TAG, "startMultiDoubleTap called with e = [$e]") - - keepInDoubleTapMode() - doubleTapControls?.onDoubleTapStarted(getDisplayPortion(e)) - } - } - - fun keepInDoubleTapMode() { - if (DEBUG) - Log.d(TAG, "keepInDoubleTapMode called") - - isDoubleTapping = true - doubleTapHandler.removeCallbacks(doubleTapRunnable) - doubleTapHandler.postDelayed(doubleTapRunnable, doubleTapDelay) - } - - fun endMultiDoubleTap() { - if (DEBUG) - Log.d(TAG, "endMultiDoubleTap called") - - isDoubleTapping = false - doubleTapHandler.removeCallbacks(doubleTapRunnable) - doubleTapControls?.onDoubleTapFinished() - } - - // /////////////////////////////////////////////////////////////////// - // Utils - // /////////////////////////////////////////////////////////////////// - - private fun getDisplayPortion(e: MotionEvent): DisplayPortion { - return if (player.playerType == MainPlayer.PlayerType.POPUP && player.popupLayoutParams != null) { - when { - e.x < player.popupLayoutParams!!.width / 3.0 -> DisplayPortion.LEFT - e.x > player.popupLayoutParams!!.width * 2.0 / 3.0 -> DisplayPortion.RIGHT - else -> DisplayPortion.MIDDLE - } - } else /* MainPlayer.PlayerType.VIDEO */ { - when { - e.x < player.rootView.width / 3.0 -> DisplayPortion.LEFT - e.x > player.rootView.width * 2.0 / 3.0 -> DisplayPortion.RIGHT - else -> DisplayPortion.MIDDLE - } - } - } - - // Currently needed for scrolling since there is no action more the middle portion - private fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion { - return if (player.playerType == MainPlayer.PlayerType.POPUP) { - when { - e.x < player.popupLayoutParams!!.width / 2.0 -> DisplayPortion.LEFT_HALF - else -> DisplayPortion.RIGHT_HALF - } - } else /* MainPlayer.PlayerType.VIDEO */ { - when { - e.x < player.rootView.width / 2.0 -> DisplayPortion.LEFT_HALF - else -> DisplayPortion.RIGHT_HALF - } - } - } - - private fun getNavigationBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("navigation_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } - - private fun getStatusBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("status_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } - - companion object { - private const val TAG = "BasePlayerGestListener" - private val DEBUG = Player.DEBUG - - private const val DOUBLE_TAP_DELAY = 550L - private const val MOVEMENT_THRESHOLD = 40 - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java index b5520e8be..84bd9d277 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.player.event; - import com.google.android.exoplayer2.PlaybackParameters; import org.schabi.newpipe.extractor.stream.StreamInfo; diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java deleted file mode 100644 index a7fb40c47..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.schabi.newpipe.player.event; - -import static org.schabi.newpipe.ktx.AnimationType.ALPHA; -import static org.schabi.newpipe.ktx.AnimationType.SCALE_AND_ALPHA; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_DURATION; -import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_HIDE_TIME; -import static org.schabi.newpipe.player.Player.STATE_PLAYING; - -import android.app.Activity; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.widget.ProgressBar; - -import androidx.annotation.NonNull; -import androidx.appcompat.content.res.AppCompatResources; - -import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.R; -import org.schabi.newpipe.player.MainPlayer; -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.helper.PlayerHelper; - -/** - * GestureListener for the player - * - * While {@link BasePlayerGestureListener} contains the logic behind the single gestures - * this class focuses on the visual aspect like hiding and showing the controls or changing - * volume/brightness during scrolling for specific events. - */ -public class PlayerGestureListener - extends BasePlayerGestureListener - implements View.OnTouchListener { - private static final String TAG = PlayerGestureListener.class.getSimpleName(); - private static final boolean DEBUG = MainActivity.DEBUG; - - private final int maxVolume; - - public PlayerGestureListener(final Player player, final MainPlayer service) { - super(player, service); - maxVolume = player.getAudioReactor().getMaxVolume(); - } - - @Override - public void onDoubleTap(@NonNull final MotionEvent event, - @NonNull final DisplayPortion portion) { - if (DEBUG) { - Log.d(TAG, "onDoubleTap called with playerType = [" - + player.getPlayerType() + "], portion = [" + portion + "]"); - } - if (player.isSomePopupMenuVisible()) { - player.hideControls(0, 0); - } - - if (portion == DisplayPortion.LEFT || portion == DisplayPortion.RIGHT) { - startMultiDoubleTap(event); - } else if (portion == DisplayPortion.MIDDLE) { - player.playPause(); - } - } - - @Override - public void onSingleTap(@NonNull final MainPlayer.PlayerType playerType) { - if (DEBUG) { - Log.d(TAG, "onSingleTap called with playerType = [" + player.getPlayerType() + "]"); - } - - if (player.isControlsVisible()) { - player.hideControls(150, 0); - return; - } - // -- Controls are not visible -- - - // When player is completed show controls and don't hide them later - if (player.getCurrentState() == Player.STATE_COMPLETED) { - player.showControls(0); - } else { - player.showControlsThenHide(); - } - } - - @Override - public void onScroll(@NonNull final MainPlayer.PlayerType playerType, - @NonNull final DisplayPortion portion, - @NonNull final MotionEvent initialEvent, - @NonNull final MotionEvent movingEvent, - final float distanceX, final float distanceY) { - if (DEBUG) { - Log.d(TAG, "onScroll called with playerType = [" - + player.getPlayerType() + "], portion = [" + portion + "]"); - } - if (playerType == MainPlayer.PlayerType.VIDEO) { - - // -- Brightness and Volume control -- - final boolean isBrightnessGestureEnabled = - PlayerHelper.isBrightnessGestureEnabled(service); - final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service); - - if (isBrightnessGestureEnabled && isVolumeGestureEnabled) { - if (portion == DisplayPortion.LEFT_HALF) { - onScrollMainBrightness(distanceX, distanceY); - - } else /* DisplayPortion.RIGHT_HALF */ { - onScrollMainVolume(distanceX, distanceY); - } - } else if (isBrightnessGestureEnabled) { - onScrollMainBrightness(distanceX, distanceY); - } else if (isVolumeGestureEnabled) { - onScrollMainVolume(distanceX, distanceY); - } - - } else /* MainPlayer.PlayerType.POPUP */ { - - // -- Determine if the ClosingOverlayView (red X) has to be shown or hidden -- - final View closingOverlayView = player.getClosingOverlayView(); - final boolean showClosingOverlayView = player.isInsideClosingRadius(movingEvent); - // Check if an view is in expected state and if not animate it into the correct state - final int expectedVisibility = showClosingOverlayView ? View.VISIBLE : View.GONE; - if (closingOverlayView.getVisibility() != expectedVisibility) { - animate(closingOverlayView, showClosingOverlayView, 200); - } - } - } - - private void onScrollMainVolume(final float distanceX, final float distanceY) { - // If we just started sliding, change the progress bar to match the system volume - if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) { - final float volumePercent = player - .getAudioReactor().getVolume() / (float) maxVolume; - player.getVolumeProgressBar().setProgress( - (int) (volumePercent * player.getMaxGestureLength())); - } - - player.getVolumeProgressBar().incrementProgressBy((int) distanceY); - final float currentProgressPercent = (float) player - .getVolumeProgressBar().getProgress() / player.getMaxGestureLength(); - final int currentVolume = (int) (maxVolume * currentProgressPercent); - player.getAudioReactor().setVolume(currentVolume); - - if (DEBUG) { - Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume); - } - - player.getVolumeImageView().setImageDrawable( - AppCompatResources.getDrawable(service, currentProgressPercent <= 0 - ? R.drawable.ic_volume_off - : currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute - : currentProgressPercent < 0.75 ? R.drawable.ic_volume_down - : R.drawable.ic_volume_up) - ); - - if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) { - animate(player.getVolumeRelativeLayout(), true, 200, SCALE_AND_ALPHA); - } - if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { - player.getBrightnessRelativeLayout().setVisibility(View.GONE); - } - } - - private void onScrollMainBrightness(final float distanceX, final float distanceY) { - final Activity parent = player.getParentActivity(); - if (parent == null) { - return; - } - - final Window window = parent.getWindow(); - final WindowManager.LayoutParams layoutParams = window.getAttributes(); - final ProgressBar bar = player.getBrightnessProgressBar(); - final float oldBrightness = layoutParams.screenBrightness; - bar.setProgress((int) (bar.getMax() * Math.max(0, Math.min(1, oldBrightness)))); - bar.incrementProgressBy((int) distanceY); - - final float currentProgressPercent = (float) bar.getProgress() / bar.getMax(); - layoutParams.screenBrightness = currentProgressPercent; - window.setAttributes(layoutParams); - - // Save current brightness level - PlayerHelper.setScreenBrightness(parent, currentProgressPercent); - - if (DEBUG) { - Log.d(TAG, "onScroll().brightnessControl, " - + "currentBrightness = " + currentProgressPercent); - } - - player.getBrightnessImageView().setImageDrawable( - AppCompatResources.getDrawable(service, - currentProgressPercent < 0.25 - ? R.drawable.ic_brightness_low - : currentProgressPercent < 0.75 - ? R.drawable.ic_brightness_medium - : R.drawable.ic_brightness_high) - ); - - if (player.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) { - animate(player.getBrightnessRelativeLayout(), true, 200, SCALE_AND_ALPHA); - } - if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { - player.getVolumeRelativeLayout().setVisibility(View.GONE); - } - } - - @Override - public void onScrollEnd(@NonNull final MainPlayer.PlayerType playerType, - @NonNull final MotionEvent event) { - if (DEBUG) { - Log.d(TAG, "onScrollEnd called with playerType = [" - + player.getPlayerType() + "]"); - } - - if (player.isControlsVisible() && player.getCurrentState() == STATE_PLAYING) { - player.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); - } - - if (playerType == MainPlayer.PlayerType.VIDEO) { - if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { - animate(player.getVolumeRelativeLayout(), false, 200, SCALE_AND_ALPHA, - 200); - } - if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { - animate(player.getBrightnessRelativeLayout(), false, 200, SCALE_AND_ALPHA, - 200); - } - } else /* Popup-Player */ { - if (player.isInsideClosingRadius(event)) { - player.closePopup(); - } else if (!player.isPopupClosing()) { - animate(player.getCloseOverlayButton(), false, 200); - animate(player.getClosingOverlayView(), false, 200); - } - } - } - - @Override - public void onPopupResizingStart() { - if (DEBUG) { - Log.d(TAG, "onPopupResizingStart called"); - } - player.getLoadingPanel().setVisibility(View.GONE); - - player.hideControls(0, 0); - animate(player.getFastSeekOverlay(), false, 0); - animate(player.getCurrentDisplaySeek(), false, 0, ALPHA, 0); - } - - @Override - public void onPopupResizingEnd() { - if (DEBUG) { - Log.d(TAG, "onPopupResizingEnd called"); - } - } -} - - diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java index 359eab8b2..8c18fd2ad 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java @@ -3,6 +3,8 @@ package org.schabi.newpipe.player.event; import com.google.android.exoplayer2.PlaybackException; public interface PlayerServiceEventListener extends PlayerEventListener { + void onViewCreated(); + void onFullscreenStateChanged(boolean fullscreen); void onScreenRotationButtonClicked(); 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 index f774c90a0..8effe2f0e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java @@ -1,11 +1,11 @@ package org.schabi.newpipe.player.event; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener { void onServiceConnected(Player player, - MainPlayer playerService, + PlayerService playerService, boolean playAfterConnect); void onServiceDisconnected(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt new file mode 100644 index 000000000..bd5d6f1c5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -0,0 +1,182 @@ +package org.schabi.newpipe.player.gesture + +import android.os.Handler +import android.util.Log +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View +import org.schabi.newpipe.databinding.PlayerBinding +import org.schabi.newpipe.player.Player +import org.schabi.newpipe.player.ui.VideoPlayerUi + +/** + * Base gesture handling for [Player] + * + * This class contains the logic for the player gestures like View preparations + * and provides some abstract methods to make it easier separating the logic from the UI. + */ +abstract class BasePlayerGestureListener( + private val playerUi: VideoPlayerUi, +) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener { + + protected val player: Player = playerUi.player + protected val binding: PlayerBinding = playerUi.binding + + override fun onTouch(v: View, event: MotionEvent): Boolean { + playerUi.gestureDetector.onTouchEvent(event) + return false + } + + private fun onDoubleTap( + event: MotionEvent, + portion: DisplayPortion + ) { + if (DEBUG) { + Log.d( + TAG, + "onDoubleTap called with playerType = [" + + player.playerType + "], portion = [" + portion + "]" + ) + } + if (playerUi.isSomePopupMenuVisible) { + playerUi.hideControls(0, 0) + } + if (portion === DisplayPortion.LEFT || portion === DisplayPortion.RIGHT) { + startMultiDoubleTap(event) + } else if (portion === DisplayPortion.MIDDLE) { + player.playPause() + } + } + + protected fun onSingleTap() { + if (playerUi.isControlsVisible) { + playerUi.hideControls(150, 0) + return + } + // -- Controls are not visible -- + + // When player is completed show controls and don't hide them later + if (player.currentState == Player.STATE_COMPLETED) { + playerUi.showControls(0) + } else { + playerUi.showControlsThenHide() + } + } + + open fun onScrollEnd(event: MotionEvent) { + if (DEBUG) { + Log.d( + TAG, + "onScrollEnd called with playerType = [" + + player.playerType + "]" + ) + } + if (playerUi.isControlsVisible && player.currentState == Player.STATE_PLAYING) { + playerUi.hideControls( + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, + VideoPlayerUi.DEFAULT_CONTROLS_HIDE_TIME + ) + } + } + + // /////////////////////////////////////////////////////////////////// + // Simple gestures + // /////////////////////////////////////////////////////////////////// + + override fun onDown(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onDown called with e = [$e]") + + if (isDoubleTapping && isDoubleTapEnabled) { + doubleTapControls?.onDoubleTapProgressDown(getDisplayPortion(e)) + return true + } + + return if (onDownNotDoubleTapping(e)) super.onDown(e) else true + } + + /** + * @return true if `super.onDown(e)` should be called, false otherwise + */ + open fun onDownNotDoubleTapping(e: MotionEvent): Boolean { + return false // do not call super.onDown(e) by default, overridden for popup player + } + + override fun onDoubleTap(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onDoubleTap called with e = [$e]") + + onDoubleTap(e, getDisplayPortion(e)) + return true + } + + // /////////////////////////////////////////////////////////////////// + // Multi double tapping + // /////////////////////////////////////////////////////////////////// + + private var doubleTapControls: DoubleTapListener? = null + + private val isDoubleTapEnabled: Boolean + get() = doubleTapDelay > 0 + + var isDoubleTapping = false + private set + + fun doubleTapControls(listener: DoubleTapListener) = apply { + doubleTapControls = listener + } + + private var doubleTapDelay = DOUBLE_TAP_DELAY + private val doubleTapHandler: Handler = Handler() + private val doubleTapRunnable = Runnable { + if (DEBUG) + Log.d(TAG, "doubleTapRunnable called") + + isDoubleTapping = false + doubleTapControls?.onDoubleTapFinished() + } + + private fun startMultiDoubleTap(e: MotionEvent) { + if (!isDoubleTapping) { + if (DEBUG) + Log.d(TAG, "startMultiDoubleTap called with e = [$e]") + + keepInDoubleTapMode() + doubleTapControls?.onDoubleTapStarted(getDisplayPortion(e)) + } + } + + fun keepInDoubleTapMode() { + if (DEBUG) + Log.d(TAG, "keepInDoubleTapMode called") + + isDoubleTapping = true + doubleTapHandler.removeCallbacks(doubleTapRunnable) + doubleTapHandler.postDelayed(doubleTapRunnable, doubleTapDelay) + } + + fun endMultiDoubleTap() { + if (DEBUG) + Log.d(TAG, "endMultiDoubleTap called") + + isDoubleTapping = false + doubleTapHandler.removeCallbacks(doubleTapRunnable) + doubleTapControls?.onDoubleTapFinished() + } + + // /////////////////////////////////////////////////////////////////// + // Utils + // /////////////////////////////////////////////////////////////////// + + abstract fun getDisplayPortion(e: MotionEvent): DisplayPortion + + // Currently needed for scrolling since there is no action more the middle portion + abstract fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion + + companion object { + private const val TAG = "BasePlayerGestListener" + private val DEBUG = Player.DEBUG + + private const val DOUBLE_TAP_DELAY = 550L + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java similarity index 98% rename from app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java rename to app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java index a5de56e75..240009105 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.event; +package org.schabi.newpipe.player.gesture; import android.content.Context; import android.graphics.Rect; diff --git a/app/src/main/java/org/schabi/newpipe/player/event/DisplayPortion.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt similarity index 65% rename from app/src/main/java/org/schabi/newpipe/player/event/DisplayPortion.kt rename to app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt index f15e42897..684f6d326 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/DisplayPortion.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.event +package org.schabi.newpipe.player.gesture enum class DisplayPortion { LEFT, MIDDLE, RIGHT, LEFT_HALF, RIGHT_HALF diff --git a/app/src/main/java/org/schabi/newpipe/player/event/DoubleTapListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt similarity index 81% rename from app/src/main/java/org/schabi/newpipe/player/event/DoubleTapListener.kt rename to app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt index 84cfb9b8d..1a0b141e6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/DoubleTapListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.event +package org.schabi.newpipe.player.gesture interface DoubleTapListener { fun onDoubleTapStarted(portion: DisplayPortion) {} diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt new file mode 100644 index 000000000..17205fb9a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -0,0 +1,232 @@ +package org.schabi.newpipe.player.gesture + +import android.app.Activity +import android.content.Context +import android.util.Log +import android.view.MotionEvent +import android.view.View +import android.view.View.OnTouchListener +import android.widget.ProgressBar +import androidx.appcompat.content.res.AppCompatResources +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.R +import org.schabi.newpipe.ktx.AnimationType +import org.schabi.newpipe.ktx.animate +import org.schabi.newpipe.player.Player +import org.schabi.newpipe.player.helper.PlayerHelper +import org.schabi.newpipe.player.ui.MainPlayerUi +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +/** + * GestureListener for the player + * + * While [BasePlayerGestureListener] contains the logic behind the single gestures + * this class focuses on the visual aspect like hiding and showing the controls or changing + * volume/brightness during scrolling for specific events. + */ +class MainPlayerGestureListener( + private val playerUi: MainPlayerUi +) : BasePlayerGestureListener(playerUi), OnTouchListener { + private val maxVolume: Int = player.audioReactor.maxVolume + + private var isMoving = false + + override fun onTouch(v: View, event: MotionEvent): Boolean { + super.onTouch(v, event) + if (event.action == MotionEvent.ACTION_UP && isMoving) { + isMoving = false + onScrollEnd(event) + } + return when (event.action) { + MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { + v.parent.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) + true + } + MotionEvent.ACTION_UP -> { + v.parent.requestDisallowInterceptTouchEvent(false) + false + } + else -> true + } + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") + + if (isDoubleTapping) + return true + super.onSingleTapConfirmed(e) + + if (player.currentState != Player.STATE_BLOCKED) + onSingleTap() + return true + } + + private fun onScrollVolume(distanceY: Float) { + // If we just started sliding, change the progress bar to match the system volume + if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { + val volumePercent: Float = player.audioReactor.volume / maxVolume.toFloat() + binding.volumeProgressBar.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt() + } + + binding.volumeProgressBar.incrementProgressBy(distanceY.toInt()) + val currentProgressPercent: Float = + binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH + val currentVolume = (maxVolume * currentProgressPercent).toInt() + player.audioReactor.volume = currentVolume + if (DEBUG) { + Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume") + } + + binding.volumeImageView.setImageDrawable( + AppCompatResources.getDrawable( + player.context, + when { + currentProgressPercent <= 0 -> R.drawable.ic_volume_off + currentProgressPercent < 0.25 -> R.drawable.ic_volume_mute + currentProgressPercent < 0.75 -> R.drawable.ic_volume_down + else -> R.drawable.ic_volume_up + } + ) + ) + + if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { + binding.volumeRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) + } + if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { + binding.volumeRelativeLayout.visibility = View.GONE + } + } + + private fun onScrollBrightness(distanceY: Float) { + val parent: Activity = playerUi.parentActivity + val window = parent.window + val layoutParams = window.attributes + val bar: ProgressBar = binding.brightnessProgressBar + val oldBrightness = layoutParams.screenBrightness + bar.progress = (bar.max * max(0f, min(1f, oldBrightness))).toInt() + bar.incrementProgressBy(distanceY.toInt()) + val currentProgressPercent = bar.progress.toFloat() / bar.max + layoutParams.screenBrightness = currentProgressPercent + window.attributes = layoutParams + + // Save current brightness level + PlayerHelper.setScreenBrightness(parent, currentProgressPercent) + if (DEBUG) { + Log.d( + TAG, + "onScroll().brightnessControl, " + + "currentBrightness = " + currentProgressPercent + ) + } + binding.brightnessImageView.setImageDrawable( + AppCompatResources.getDrawable( + player.context, + if (currentProgressPercent < 0.25) R.drawable.ic_brightness_low else if (currentProgressPercent < 0.75) R.drawable.ic_brightness_medium else R.drawable.ic_brightness_high + ) + ) + if (binding.brightnessRelativeLayout.visibility != View.VISIBLE) { + binding.brightnessRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) + } + if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { + binding.volumeRelativeLayout.visibility = View.GONE + } + } + + override fun onScrollEnd(event: MotionEvent) { + super.onScrollEnd(event) + if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { + binding.volumeRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) + } + if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { + binding.brightnessRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) + } + } + + override fun onScroll( + initialEvent: MotionEvent, + movingEvent: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + + if (!playerUi.isFullscreen) { + return false + } + + val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(player.context) + val isTouchingNavigationBar: Boolean = + initialEvent.y > (binding.root.height - getNavigationBarHeight(player.context)) + if (isTouchingStatusBar || isTouchingNavigationBar) { + return false + } + + val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD + if ( + !isMoving && (insideThreshold || abs(distanceX) > abs(distanceY)) || + player.currentState == Player.STATE_COMPLETED + ) { + return false + } + + isMoving = true + + // -- Brightness and Volume control -- + val isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(player.context) + val isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(player.context) + if (isBrightnessGestureEnabled && isVolumeGestureEnabled) { + if (getDisplayHalfPortion(initialEvent) === DisplayPortion.LEFT_HALF) { + onScrollBrightness(distanceY) + } else /* DisplayPortion.RIGHT_HALF */ { + onScrollVolume(distanceY) + } + } else if (isBrightnessGestureEnabled) { + onScrollBrightness(distanceY) + } else if (isVolumeGestureEnabled) { + onScrollVolume(distanceY) + } + + return true + } + + override fun getDisplayPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < binding.root.width / 3.0 -> DisplayPortion.LEFT + e.x > binding.root.width * 2.0 / 3.0 -> DisplayPortion.RIGHT + else -> DisplayPortion.MIDDLE + } + } + + override fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < binding.root.width / 2.0 -> DisplayPortion.LEFT_HALF + else -> DisplayPortion.RIGHT_HALF + } + } + + companion object { + private val TAG = MainPlayerGestureListener::class.java.simpleName + private val DEBUG = MainActivity.DEBUG + private const val MOVEMENT_THRESHOLD = 40 + const val MAX_GESTURE_LENGTH = 0.75f + + private fun getNavigationBarHeight(context: Context): Int { + val resId = context.resources + .getIdentifier("navigation_bar_height", "dimen", "android") + return if (resId > 0) { + context.resources.getDimensionPixelSize(resId) + } else 0 + } + + private fun getStatusBarHeight(context: Context): Int { + val resId = context.resources + .getIdentifier("status_bar_height", "dimen", "android") + return if (resId > 0) { + context.resources.getDimensionPixelSize(resId) + } else 0 + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt new file mode 100644 index 000000000..b8c1bc54c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -0,0 +1,287 @@ +package org.schabi.newpipe.player.gesture + +import android.util.Log +import android.view.MotionEvent +import android.view.View +import android.view.ViewConfiguration +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.ktx.AnimationType +import org.schabi.newpipe.ktx.animate +import org.schabi.newpipe.player.helper.PlayerHelper +import org.schabi.newpipe.player.ui.PopupPlayerUi +import kotlin.math.abs +import kotlin.math.hypot +import kotlin.math.max +import kotlin.math.min + +class PopupPlayerGestureListener( + private val playerUi: PopupPlayerUi, +) : BasePlayerGestureListener(playerUi) { + + private var isMoving = false + + private var initialPopupX: Int = -1 + private var initialPopupY: Int = -1 + private var isResizing = false + + // initial coordinates and distance between fingers + private var initPointerDistance = -1.0 + private var initFirstPointerX = -1f + private var initFirstPointerY = -1f + private var initSecPointerX = -1f + private var initSecPointerY = -1f + + override fun onTouch(v: View, event: MotionEvent): Boolean { + super.onTouch(v, event) + if (event.pointerCount == 2 && !isMoving && !isResizing) { + if (DEBUG) { + Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.") + } + onPopupResizingStart() + + // record coordinates of fingers + initFirstPointerX = event.getX(0) + initFirstPointerY = event.getY(0) + initSecPointerX = event.getX(1) + initSecPointerY = event.getY(1) + // record distance between fingers + initPointerDistance = hypot( + initFirstPointerX - initSecPointerX.toDouble(), + initFirstPointerY - initSecPointerY.toDouble() + ) + + isResizing = true + } + if (event.action == MotionEvent.ACTION_MOVE && !isMoving && isResizing) { + if (DEBUG) { + Log.d( + TAG, + "onTouch() ACTION_MOVE > v = [$v], e1.getRaw =" + + "[${event.rawX}, ${event.rawY}]" + ) + } + return handleMultiDrag(event) + } + if (event.action == MotionEvent.ACTION_UP) { + if (DEBUG) { + Log.d( + TAG, + "onTouch() ACTION_UP > v = [$v], e1.getRaw =" + + " [${event.rawX}, ${event.rawY}]" + ) + } + if (isMoving) { + isMoving = false + onScrollEnd(event) + } + if (isResizing) { + isResizing = false + + initPointerDistance = (-1).toDouble() + initFirstPointerX = (-1).toFloat() + initFirstPointerY = (-1).toFloat() + initSecPointerX = (-1).toFloat() + initSecPointerY = (-1).toFloat() + + onPopupResizingEnd() + player.changeState(player.currentState) + } + if (!playerUi.isPopupClosing) { + PlayerHelper.savePopupPositionAndSizeToPrefs(playerUi) + } + } + + v.performClick() + return true + } + + override fun onScrollEnd(event: MotionEvent) { + super.onScrollEnd(event) + if (playerUi.isInsideClosingRadius(event)) { + playerUi.closePopup() + } else if (!playerUi.isPopupClosing) { + playerUi.closeOverlayBinding.closeButton.animate(false, 200) + binding.closingOverlay.animate(false, 200) + } + } + + private fun handleMultiDrag(event: MotionEvent): Boolean { + if (initPointerDistance != -1.0 && event.pointerCount == 2) { + // get the movements of the fingers + val firstPointerMove = hypot( + event.getX(0) - initFirstPointerX.toDouble(), + event.getY(0) - initFirstPointerY.toDouble() + ) + val secPointerMove = hypot( + event.getX(1) - initSecPointerX.toDouble(), + event.getY(1) - initSecPointerY.toDouble() + ) + + // minimum threshold beyond which pinch gesture will work + val minimumMove = ViewConfiguration.get(player.context).scaledTouchSlop + + if (max(firstPointerMove, secPointerMove) > minimumMove) { + // calculate current distance between the pointers + val currentPointerDistance = hypot( + event.getX(0) - event.getX(1).toDouble(), + event.getY(0) - event.getY(1).toDouble() + ) + + val popupWidth = playerUi.popupLayoutParams.width.toDouble() + // change co-ordinates of popup so the center stays at the same position + val newWidth = popupWidth * currentPointerDistance / initPointerDistance + initPointerDistance = currentPointerDistance + playerUi.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt() + + playerUi.checkPopupPositionBounds() + playerUi.updateScreenSize() + playerUi.changePopupSize(min(playerUi.screenWidth.toDouble(), newWidth).toInt()) + return true + } + } + return false + } + + private fun onPopupResizingStart() { + if (DEBUG) { + Log.d(TAG, "onPopupResizingStart called") + } + binding.loadingPanel.visibility = View.GONE + playerUi.hideControls(0, 0) + binding.fastSeekOverlay.animate(false, 0) + binding.currentDisplaySeek.animate(false, 0, AnimationType.ALPHA, 0) + } + + private fun onPopupResizingEnd() { + if (DEBUG) { + Log.d(TAG, "onPopupResizingEnd called") + } + } + + override fun onLongPress(e: MotionEvent?) { + playerUi.updateScreenSize() + playerUi.checkPopupPositionBounds() + playerUi.changePopupSize(playerUi.screenWidth) + } + + override fun onFling( + e1: MotionEvent?, + e2: MotionEvent?, + velocityX: Float, + velocityY: Float + ): Boolean { + return if (player.popupPlayerSelected()) { + val absVelocityX = abs(velocityX) + val absVelocityY = abs(velocityY) + if (absVelocityX.coerceAtLeast(absVelocityY) > TOSS_FLING_VELOCITY) { + if (absVelocityX > TOSS_FLING_VELOCITY) { + playerUi.popupLayoutParams.x = velocityX.toInt() + } + if (absVelocityY > TOSS_FLING_VELOCITY) { + playerUi.popupLayoutParams.y = velocityY.toInt() + } + playerUi.checkPopupPositionBounds() + playerUi.windowManager.updateViewLayout(binding.root, playerUi.popupLayoutParams) + return true + } + return false + } else { + true + } + } + + override fun onDownNotDoubleTapping(e: MotionEvent): Boolean { + // Fix popup position when the user touch it, it may have the wrong one + // because the soft input is visible (the draggable area is currently resized). + playerUi.updateScreenSize() + playerUi.checkPopupPositionBounds() + playerUi.popupLayoutParams.let { + initialPopupX = it.x + initialPopupY = it.y + } + return true // we want `super.onDown(e)` to be called + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") + + if (isDoubleTapping) + return true + if (player.exoPlayerIsNull()) + return false + + onSingleTap() + return true + } + + override fun onScroll( + initialEvent: MotionEvent, + movingEvent: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + + if (isResizing) { + return super.onScroll(initialEvent, movingEvent, distanceX, distanceY) + } + + if (!isMoving) { + playerUi.closeOverlayBinding.closeButton.animate(true, 200) + } + + isMoving = true + + val diffX: Float = (movingEvent.rawX - initialEvent.rawX) + var posX: Float = (initialPopupX + diffX) + val diffY: Float = (movingEvent.rawY - initialEvent.rawY) + var posY: Float = (initialPopupY + diffY) + + if (posX > playerUi.screenWidth - playerUi.popupLayoutParams.width) { + posX = (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() + } else if (posX < 0) { + posX = 0f + } + + if (posY > playerUi.screenHeight - playerUi.popupLayoutParams.height) { + posY = (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() + } else if (posY < 0) { + posY = 0f + } + + playerUi.popupLayoutParams.x = posX.toInt() + playerUi.popupLayoutParams.y = posY.toInt() + + // -- Determine if the ClosingOverlayView (red X) has to be shown or hidden -- + val showClosingOverlayView: Boolean = playerUi.isInsideClosingRadius(movingEvent) + // Check if an view is in expected state and if not animate it into the correct state + val expectedVisibility = if (showClosingOverlayView) View.VISIBLE else View.GONE + if (binding.closingOverlay.visibility != expectedVisibility) { + binding.closingOverlay.animate(showClosingOverlayView, 200) + } + + playerUi.windowManager.updateViewLayout(binding.root, playerUi.popupLayoutParams) + return true + } + + override fun getDisplayPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < playerUi.popupLayoutParams.width / 3.0 -> DisplayPortion.LEFT + e.x > playerUi.popupLayoutParams.width * 2.0 / 3.0 -> DisplayPortion.RIGHT + else -> DisplayPortion.MIDDLE + } + } + + override fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < playerUi.popupLayoutParams.width / 2.0 -> DisplayPortion.LEFT_HALF + else -> DisplayPortion.RIGHT_HALF + } + } + + companion object { + private val TAG = PopupPlayerGestureListener::class.java.simpleName + private val DEBUG = MainActivity.DEBUG + private const val TOSS_FLING_VELOCITY = 2500 + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 19a5a645b..8a5a4f8d2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -26,7 +26,7 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; -import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; @@ -207,7 +207,7 @@ public class PlaybackParameterDialog extends DialogFragment { ? View.VISIBLE : View.GONE); animateRotation(binding.pitchToogleControlModes, - Player.DEFAULT_CONTROLS_DURATION, + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, isCurrentlyVisible ? 180 : 0); }); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 2131861bf..ec4cf8602 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.player.helper; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.Player.IDLE_WINDOW_FLAGS; import static org.schabi.newpipe.player.Player.PLAYER_TYPE; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER; @@ -11,6 +10,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLA import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; +import static org.schabi.newpipe.player.ui.PopupPlayerUi.IDLE_WINDOW_FLAGS; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.SuppressLint; @@ -49,11 +49,12 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.utils.Utils; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.player.ui.PopupPlayerUi; import org.schabi.newpipe.util.ListHelper; import java.lang.annotation.Retention; @@ -339,10 +340,6 @@ public final class PlayerHelper { return true; } - public static int getTossFlingVelocity() { - return 2500; - } - @NonNull public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) { final CaptioningManager captioningManager = ContextCompat.getSystemService(context, @@ -452,10 +449,10 @@ public final class PlayerHelper { // Utils used by player //////////////////////////////////////////////////////////////////////////// - public static MainPlayer.PlayerType retrievePlayerTypeFromIntent(final Intent intent) { + public static PlayerService.PlayerType retrievePlayerTypeFromIntent(final Intent intent) { // If you want to open popup from the app just include Constants.POPUP_ONLY into an extra - return MainPlayer.PlayerType.values()[ - intent.getIntExtra(PLAYER_TYPE, MainPlayer.PlayerType.VIDEO.ordinal())]; + return PlayerService.PlayerType.values()[ + intent.getIntExtra(PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal())]; } public static boolean isPlaybackResumeEnabled(final Player player) { @@ -529,19 +526,20 @@ public final class PlayerHelper { } /** - * @param player {@code screenWidth} and {@code screenHeight} must have been initialized + * @param playerUi {@code screenWidth} and {@code screenHeight} must have been initialized * @return the popup starting layout params */ @SuppressLint("RtlHardcoded") public static WindowManager.LayoutParams retrievePopupLayoutParamsFromPrefs( - final Player player) { - final boolean popupRememberSizeAndPos = player.getPrefs().getBoolean( - player.getContext().getString(R.string.popup_remember_size_pos_key), true); - final float defaultSize = - player.getContext().getResources().getDimension(R.dimen.popup_default_width); + final PopupPlayerUi playerUi) { + final SharedPreferences prefs = playerUi.getPlayer().getPrefs(); + final Context context = playerUi.getPlayer().getContext(); + + final boolean popupRememberSizeAndPos = prefs.getBoolean( + context.getString(R.string.popup_remember_size_pos_key), true); + final float defaultSize = context.getResources().getDimension(R.dimen.popup_default_width); final float popupWidth = popupRememberSizeAndPos - ? player.getPrefs().getFloat(player.getContext().getString( - R.string.popup_saved_width_key), defaultSize) + ? prefs.getFloat(context.getString(R.string.popup_saved_width_key), defaultSize) : defaultSize; final float popupHeight = getMinimumVideoHeight(popupWidth); @@ -553,27 +551,26 @@ public final class PlayerHelper { popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - final int centerX = (int) (player.getScreenWidth() / 2f - popupWidth / 2f); - final int centerY = (int) (player.getScreenHeight() / 2f - popupHeight / 2f); + final int centerX = (int) (playerUi.getScreenWidth() / 2f - popupWidth / 2f); + final int centerY = (int) (playerUi.getScreenHeight() / 2f - popupHeight / 2f); popupLayoutParams.x = popupRememberSizeAndPos - ? player.getPrefs().getInt(player.getContext().getString( - R.string.popup_saved_x_key), centerX) : centerX; + ? prefs.getInt(context.getString(R.string.popup_saved_x_key), centerX) : centerX; popupLayoutParams.y = popupRememberSizeAndPos - ? player.getPrefs().getInt(player.getContext().getString( - R.string.popup_saved_y_key), centerY) : centerY; + ? prefs.getInt(context.getString(R.string.popup_saved_y_key), centerY) : centerY; return popupLayoutParams; } - public static void savePopupPositionAndSizeToPrefs(final Player player) { - if (player.getPopupLayoutParams() != null) { - player.getPrefs().edit() - .putFloat(player.getContext().getString(R.string.popup_saved_width_key), - player.getPopupLayoutParams().width) - .putInt(player.getContext().getString(R.string.popup_saved_x_key), - player.getPopupLayoutParams().x) - .putInt(player.getContext().getString(R.string.popup_saved_y_key), - player.getPopupLayoutParams().y) + public static void savePopupPositionAndSizeToPrefs(final PopupPlayerUi playerUi) { + if (playerUi.getPopupLayoutParams() != null) { + final Context context = playerUi.getPlayer().getContext(); + playerUi.getPlayer().getPrefs().edit() + .putFloat(context.getString(R.string.popup_saved_width_key), + playerUi.getPopupLayoutParams().width) + .putInt(context.getString(R.string.popup_saved_x_key), + playerUi.getPopupLayoutParams().x) + .putInt(context.getString(R.string.popup_saved_y_key), + playerUi.getPopupLayoutParams().y) .apply(); } } 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 4c09ed3c1..cb613f854 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 @@ -16,7 +16,7 @@ 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.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; @@ -42,17 +42,17 @@ public final class PlayerHolder { private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection(); private boolean bound; - @Nullable private MainPlayer playerService; + @Nullable private PlayerService playerService; @Nullable private Player player; /** - * Returns the current {@link MainPlayer.PlayerType} of the {@link MainPlayer} service, + * Returns the current {@link PlayerService.PlayerType} of the {@link PlayerService} service, * otherwise `null` if no service running. * * @return Current PlayerType */ @Nullable - public MainPlayer.PlayerType getType() { + public PlayerService.PlayerType getType() { if (player == null) { return null; } @@ -122,7 +122,7 @@ 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, MainPlayer.class)); + ContextCompat.startForegroundService(context, new Intent(context, PlayerService.class)); serviceConnection.doPlayAfterConnect(playAfterConnect); bind(context); } @@ -130,7 +130,7 @@ public final class PlayerHolder { public void stopService() { final Context context = getCommonContext(); unbind(context); - context.stopService(new Intent(context, MainPlayer.class)); + context.stopService(new Intent(context, PlayerService.class)); } class PlayerServiceConnection implements ServiceConnection { @@ -156,7 +156,7 @@ public final class PlayerHolder { if (DEBUG) { Log.d(TAG, "Player service is connected"); } - final MainPlayer.LocalBinder localBinder = (MainPlayer.LocalBinder) service; + final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service; playerService = localBinder.getService(); player = localBinder.getPlayer(); @@ -172,7 +172,7 @@ public final class PlayerHolder { Log.d(TAG, "bind() called"); } - final Intent serviceIntent = new Intent(context, MainPlayer.class); + final Intent serviceIntent = new Intent(context, PlayerService.class); bound = context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); if (!bound) { @@ -211,6 +211,13 @@ public final class PlayerHolder { private final PlayerServiceEventListener internalListener = new PlayerServiceEventListener() { + @Override + public void onViewCreated() { + if (listener != null) { + listener.onViewCreated(); + } + } + @Override public void onFullscreenStateChanged(final boolean fullscreen) { if (listener != null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt b/app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt deleted file mode 100644 index 52eff5a1c..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.schabi.newpipe.player.listeners.view - -import android.util.Log -import android.view.View -import androidx.appcompat.widget.PopupMenu -import org.schabi.newpipe.MainActivity -import org.schabi.newpipe.player.Player -import org.schabi.newpipe.player.helper.PlaybackParameterDialog - -/** - * Click listener for the playbackSpeed textview of the player - */ -class PlaybackSpeedClickListener( - private val player: Player, - private val playbackSpeedPopupMenu: PopupMenu -) : View.OnClickListener { - - companion object { - private const val TAG: String = "PlaybSpeedClickListener" - } - - override fun onClick(v: View) { - if (MainActivity.DEBUG) { - Log.d(TAG, "onPlaybackSpeedClicked() called") - } - - if (player.videoPlayerSelected()) { - PlaybackParameterDialog.newInstance( - player.playbackSpeed.toDouble(), - player.playbackPitch.toDouble(), - player.playbackSkipSilence - ) { speed: Float, pitch: Float, skipSilence: Boolean -> - player.setPlaybackParameters( - speed, - pitch, - skipSilence - ) - } - .show(player.parentActivity!!.supportFragmentManager, null) - } else { - playbackSpeedPopupMenu.show() - player.isSomePopupMenuVisible = true - } - - player.manageControlsAfterOnClick(v) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt b/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt deleted file mode 100644 index 43e8288e6..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.schabi.newpipe.player.listeners.view - -import android.annotation.SuppressLint -import android.util.Log -import android.view.View -import androidx.appcompat.widget.PopupMenu -import org.schabi.newpipe.MainActivity -import org.schabi.newpipe.extractor.MediaFormat -import org.schabi.newpipe.player.Player - -/** - * Click listener for the qualityTextView of the player - */ -class QualityClickListener( - private val player: Player, - private val qualityPopupMenu: PopupMenu -) : View.OnClickListener { - - companion object { - private const val TAG: String = "QualityClickListener" - } - - @SuppressLint("SetTextI18n") // we don't need I18N because of a " " - override fun onClick(v: View) { - if (MainActivity.DEBUG) { - Log.d(TAG, "onQualitySelectorClicked() called") - } - - qualityPopupMenu.show() - player.isSomePopupMenuVisible = true - - val videoStream = player.selectedVideoStream - if (videoStream != null) { - player.binding.qualityTextView.text = - MediaFormat.getNameById(videoStream.formatId) + " " + videoStream.getResolution() - } - - player.saveWasPlaying() - player.manageControlsAfterOnClick(v) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java index ee0a6f118..2f261a0fa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java @@ -8,6 +8,9 @@ import android.support.v4.media.MediaMetadataCompat; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.mediasession.MediaSessionCallback; import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.player.ui.VideoPlayerUi; + +import java.util.Optional; public class PlayerMediaSession implements MediaSessionCallback { private final Player player; @@ -89,7 +92,7 @@ public class PlayerMediaSession implements MediaSessionCallback { public void play() { player.play(); // hide the player controls even if the play command came from the media session - player.hideControls(0, 0); + player.UIs().get(VideoPlayerUi.class).ifPresent(playerUi -> playerUi.hideControls(0, 0)); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java new file mode 100644 index 000000000..10ed424ba --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -0,0 +1,937 @@ +package org.schabi.newpipe.player.ui; + +import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.player.Player.STATE_COMPLETED; +import static org.schabi.newpipe.player.Player.STATE_PAUSED; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; +import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; +import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; +import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; + +import android.content.Intent; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Build; +import android.os.Handler; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.exoplayer2.video.VideoSize; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.PlayerBinding; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.StreamSegment; +import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; +import org.schabi.newpipe.fragments.detail.VideoDetailFragment; +import org.schabi.newpipe.info_list.StreamSegmentAdapter; +import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.event.PlayerServiceEventListener; +import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; +import org.schabi.newpipe.player.gesture.MainPlayerGestureListener; +import org.schabi.newpipe.player.helper.PlaybackParameterDialog; +import org.schabi.newpipe.player.helper.PlayerHelper; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder; +import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder; +import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback; +import org.schabi.newpipe.util.DeviceUtils; +import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.external_communication.KoreUtils; + +import java.util.List; +import java.util.Objects; + +public final class MainPlayerUi extends VideoPlayerUi { + private static final String TAG = MainPlayerUi.class.getSimpleName(); + + private boolean isFullscreen = false; + private boolean isVerticalVideo = false; + private boolean fragmentIsVisible = false; + + private ContentObserver settingsContentObserver; + + private PlayQueueAdapter playQueueAdapter; + private StreamSegmentAdapter segmentAdapter; + private boolean isQueueVisible = false; + private boolean areSegmentsVisible = false; + + // fullscreen player + private ItemTouchHelper itemTouchHelper; + + public MainPlayerUi(@NonNull final Player player, + @NonNull final PlayerBinding playerBinding) { + super(player, playerBinding); + } + + /** + * Open fullscreen on tablets where the option to have the main player start automatically in + * fullscreen mode is on. Rotating the device to landscape is already done in {@link + * VideoDetailFragment#openVideoPlayer(boolean)} when the thumbnail is clicked, and that's + * enough for phones, but not for tablets since the mini player can be also shown in landscape. + */ + private void directlyOpenFullscreenIfNeeded() { + if (PlayerHelper.isStartMainPlayerFullscreenEnabled(player.getService()) + && DeviceUtils.isTablet(player.getService()) + && PlayerHelper.globalScreenOrientationLocked(player.getService())) { + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onScreenRotationButtonClicked); + } + } + + @Override + public void setupAfterIntent() { + // needed for tablets, check the function for a better explanation + directlyOpenFullscreenIfNeeded(); + + super.setupAfterIntent(); + + binding.getRoot().setVisibility(View.VISIBLE); + initVideoPlayer(); + // Android TV: without it focus will frame the whole player + binding.playPauseButton.requestFocus(); + + // Note: This is for automatically playing (when "Resume playback" is off), see #6179 + if (player.getPlayWhenReady()) { + player.play(); + } else { + player.pause(); + } + } + + @Override + BasePlayerGestureListener buildGestureListener() { + return new MainPlayerGestureListener(this); + } + + @Override + protected void initListeners() { + super.initListeners(); + + binding.queueButton.setOnClickListener(v -> onQueueClicked()); + binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked()); + + binding.addToPlaylistButton.setOnClickListener(v -> + player.onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager())); + + settingsContentObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(final boolean selfChange) { + setupScreenRotationButton(); + } + }; + context.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, + settingsContentObserver); + + binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); + } + + @Override + public void initPlayback() { + super.initPlayback(); + + if (playQueueAdapter != null) { + playQueueAdapter.dispose(); + } + playQueueAdapter = new PlayQueueAdapter(context, + Objects.requireNonNull(player.getPlayQueue())); + segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener()); + } + + @Override + public void removeViewFromParent() { + // view was added to fragment + final ViewParent parent = binding.getRoot().getParent(); + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).removeView(binding.getRoot()); + } + } + + @Override + public void destroy() { + super.destroy(); + context.getContentResolver().unregisterContentObserver(settingsContentObserver); + + // Exit from fullscreen when user closes the player via notification + if (isFullscreen) { + toggleFullscreen(); + } + + removeViewFromParent(); + } + + @Override + public void destroyPlayer() { + super.destroyPlayer(); + + if (playQueueAdapter != null) { + playQueueAdapter.unsetSelectedListener(); + playQueueAdapter.dispose(); + } + } + + @Override + public void smoothStopForImmediateReusing() { + super.smoothStopForImmediateReusing(); + // Android TV will handle back button in case controls will be visible + // (one more additional unneeded click while the player is hidden) + hideControls(0, 0); + closeItemsList(); + } + + private void initVideoPlayer() { + // restore last resize mode + setResizeMode(PlayerHelper.retrieveResizeModeFromPrefs(player)); + binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + } + + @Override + protected void setupElementsVisibility() { + super.setupElementsVisibility(); + + closeItemsList(); + showHideKodiButton(); + binding.fullScreenButton.setVisibility(View.GONE); + setupScreenRotationButton(); + binding.resizeTextView.setVisibility(View.VISIBLE); + binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); + binding.moreOptionsButton.setVisibility(View.VISIBLE); + binding.topControls.setOrientation(LinearLayout.VERTICAL); + binding.primaryControls.getLayoutParams().width + = LinearLayout.LayoutParams.MATCH_PARENT; + binding.secondaryControls.setVisibility(View.INVISIBLE); + binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context, + R.drawable.ic_expand_more)); + binding.share.setVisibility(View.VISIBLE); + binding.openInBrowser.setVisibility(View.VISIBLE); + binding.switchMute.setVisibility(View.VISIBLE); + binding.playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE); + // Top controls have a large minHeight which is allows to drag the player + // down in fullscreen mode (just larger area to make easy to locate by finger) + binding.topControls.setClickable(true); + binding.topControls.setFocusable(true); + + if (isFullscreen) { + binding.titleTextView.setVisibility(View.VISIBLE); + binding.channelTextView.setVisibility(View.VISIBLE); + } else { + binding.titleTextView.setVisibility(View.GONE); + binding.channelTextView.setVisibility(View.GONE); + } + } + + @Override + protected void setupElementsSize(final Resources resources) { + setupElementsSize( + resources.getDimensionPixelSize(R.dimen.player_main_buttons_min_width), + resources.getDimensionPixelSize(R.dimen.player_main_top_padding), + resources.getDimensionPixelSize(R.dimen.player_main_controls_padding), + resources.getDimensionPixelSize(R.dimen.player_main_buttons_padding) + ); + } + + + /*////////////////////////////////////////////////////////////////////////// + // Broadcast receiver + //////////////////////////////////////////////////////////////////////////*/ + //region Broadcast receiver + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + // Close it because when changing orientation from portrait + // (in fullscreen mode) the size of queue layout can be larger than the screen size + closeItemsList(); + } else if (ACTION_PLAY_PAUSE.equals(intent.getAction())) { + // Ensure that we have audio-only stream playing when a user + // started to play from notification's play button from outside of the app + if (!fragmentIsVisible) { + onFragmentStopped(); + } + } else if (VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED.equals(intent.getAction())) { + fragmentIsVisible = false; + onFragmentStopped(); + } else if (VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED.equals(intent.getAction())) { + // Restore video source when user returns to the fragment + fragmentIsVisible = true; + player.useVideoSource(true); + + // When a user returns from background, the system UI will always be shown even if + // controls are invisible: hide it in that case + if (!isControlsVisible()) { + hideSystemUIIfNeeded(); + } + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Fragment binding + //////////////////////////////////////////////////////////////////////////*/ + //region Fragment binding + @Override + public void onFragmentListenerSet() { + super.onFragmentListenerSet(); + fragmentIsVisible = true; + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait + if (!isFullscreen) { + binding.playbackControlRoot.setPadding(0, 0, 0, 0); + } + binding.itemsListPanel.setPadding(0, 0, 0, 0); + player.getFragmentListener().ifPresent(PlayerServiceEventListener::onViewCreated); + } + + /** + * This will be called when a user goes to another app/activity, turns off a screen. + * We don't want to interrupt playback and don't want to see notification so + * next lines of code will enable audio-only playback only if needed + */ + private void onFragmentStopped() { + if (player.isPlaying() || player.isLoading()) { + switch (getMinimizeOnExitAction(context)) { + case MINIMIZE_ON_EXIT_MODE_BACKGROUND: + player.useVideoSource(false); + break; + case MINIMIZE_ON_EXIT_MODE_POPUP: + player.setRecovery(); + NavigationHelper.playOnPopupPlayer(getParentActivity(), + player.getPlayQueue(), true); + break; + case MINIMIZE_ON_EXIT_MODE_NONE: default: + player.pause(); + break; + } + } + } + //endregion + + private void showHideKodiButton() { + // show kodi button if it supports the current service and it is enabled in settings + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + binding.playWithKodi.setVisibility(playQueue != null && playQueue.getItem() != null + && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) + ? View.VISIBLE : View.GONE); + } + + @Override + public void onUpdateProgress(final int currentProgress, + final int duration, + final int bufferPercent) { + super.onUpdateProgress(currentProgress, duration, bufferPercent); + + if (areSegmentsVisible) { + segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress)); + } + if (isQueueVisible) { + updateQueueTime(currentProgress); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Controls showing / hiding + //////////////////////////////////////////////////////////////////////////*/ + //region Controls showing / hiding + + protected void showOrHideButtons() { + super.showOrHideButtons(); + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + final boolean showQueue = playQueue.getStreams().size() > 1; + final boolean showSegment = !player.getCurrentStreamInfo() + .map(StreamInfo::getStreamSegments) + .map(List::isEmpty) + .orElse(/*no stream info=*/true); + + binding.queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE); + binding.queueButton.setAlpha(showQueue ? 1.0f : 0.0f); + binding.segmentsButton.setVisibility(showSegment ? View.VISIBLE : View.GONE); + binding.segmentsButton.setAlpha(showSegment ? 1.0f : 0.0f); + } + + @Override + public void showSystemUIPartially() { + if (isFullscreen) { + final Window window = getParentActivity().getWindow(); + window.setStatusBarColor(Color.TRANSPARENT); + window.setNavigationBarColor(Color.TRANSPARENT); + final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + window.getDecorView().setSystemUiVisibility(visibility); + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + + @Override + public void hideSystemUIIfNeeded() { + player.getFragmentListener().ifPresent(PlayerServiceEventListener::hideSystemUiIfNeeded); + } + + /** + * Calculate the maximum allowed height for the {@link R.id.endScreen} + * to prevent it from enlarging the player. + *

+ * The calculating follows these rules: + *

    + *
  • + * Show at least stream title and content creator on TVs and tablets + * when in landscape (always the case for TVs) and not in fullscreen mode. + * This requires to have at least 85dp free space for {@link R.id.detail_root} + * and additional space for the stream title text size + * ({@link R.id.detail_title_root_layout}). + * The text size is 15sp on tablets and 16sp on TVs, + * see {@link R.id.titleTextView}. + *
  • + *
  • + * Otherwise, the max thumbnail height is the screen height. + * TODO investigate why this is done on popup player, too + *
  • + *
+ * + * @param bitmap the bitmap that needs to be resized to fit the end screen + * @return the maximum height for the end screen thumbnail + */ + @Override + protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { + final int screenHeight = context.getResources().getDisplayMetrics().heightPixels; + + if (DeviceUtils.isTv(context) && !isFullscreen()) { + final int videoInfoHeight = + DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context); + return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); + } else if (DeviceUtils.isTablet(context) && isLandscape() && !isFullscreen()) { + final int videoInfoHeight = + DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context); + return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); + } else { // fullscreen player: max height is the device height + return Math.min(bitmap.getHeight(), screenHeight); + } + } + //endregion + + @Override + public void onPlaying() { + super.onPlaying(); + checkLandscape(); + } + + @Override + public void onCompleted() { + super.onCompleted(); + if (isFullscreen) { + toggleFullscreen(); + } + } + + + @Override + protected void setupSubtitleView(float captionScale) { + final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); + final float captionRatioInverse = 20f + 4f * (1.0f - captionScale); + binding.subtitleView.setFixedTextSize( + TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse); + } + + + + + /*////////////////////////////////////////////////////////////////////////// + // Gestures + //////////////////////////////////////////////////////////////////////////*/ + //region Gestures + + @SuppressWarnings("checkstyle:ParameterNumber") + private void onLayoutChange(final View view, final int l, final int t, final int r, final int b, + final int ol, final int ot, final int or, final int ob) { + if (l != ol || t != ot || r != or || b != ob) { + // Use smaller value to be consistent between screen orientations + // (and to make usage easier) + final int width = r - l; + final int height = b - t; + final int min = Math.min(width, height); + final int maxGestureLength = (int) (min * MainPlayerGestureListener.MAX_GESTURE_LENGTH); + + if (DEBUG) { + Log.d(TAG, "maxGestureLength = " + maxGestureLength); + } + + binding.volumeProgressBar.setMax(maxGestureLength); + binding.brightnessProgressBar.setMax(maxGestureLength); + + setInitialGestureValues(); + binding.itemsListPanel.getLayoutParams().height + = height - binding.itemsListPanel.getTop(); + } + } + + private void setInitialGestureValues() { + if (player.getAudioReactor() != null) { + final float currentVolumeNormalized = + (float) player.getAudioReactor().getVolume() + / player.getAudioReactor().getMaxVolume(); + binding.volumeProgressBar.setProgress( + (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized)); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Play queue, segments and streams + //////////////////////////////////////////////////////////////////////////*/ + //region Play queue, segments and streams + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + showHideKodiButton(); + if (areSegmentsVisible) { + if (segmentAdapter.setItems(info)) { + final int adapterPosition = getNearestStreamSegmentPosition( + player.getExoPlayer().getCurrentPosition()); + segmentAdapter.selectSegmentAt(adapterPosition); + binding.itemsList.scrollToPosition(adapterPosition); + } else { + closeItemsList(); + } + } + } + + @Override + public void onPlayQueueEdited() { + super.onPlayQueueEdited(); + showOrHideButtons(); + } + + private void onQueueClicked() { + isQueueVisible = true; + + hideSystemUIIfNeeded(); + buildQueue(); + + binding.itemsListHeaderTitle.setVisibility(View.GONE); + binding.itemsListHeaderDuration.setVisibility(View.VISIBLE); + binding.shuffleButton.setVisibility(View.VISIBLE); + binding.repeatButton.setVisibility(View.VISIBLE); + binding.addToPlaylistButton.setVisibility(View.VISIBLE); + + hideControls(0, 0); + binding.itemsListPanel.requestFocus(); + animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA); + + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null) { + binding.itemsList.scrollToPosition(playQueue.getIndex()); + } + + updateQueueTime((int) player.getExoPlayer().getCurrentPosition()); + } + + private void buildQueue() { + binding.itemsList.setAdapter(playQueueAdapter); + binding.itemsList.setClickable(true); + binding.itemsList.setLongClickable(true); + + binding.itemsList.clearOnScrollListeners(); + binding.itemsList.addOnScrollListener(getQueueScrollListener()); + + itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); + itemTouchHelper.attachToRecyclerView(binding.itemsList); + + playQueueAdapter.setSelectedListener(getOnSelectedListener()); + + binding.itemsListClose.setOnClickListener(view -> closeItemsList()); + } + + private void onSegmentsClicked() { + areSegmentsVisible = true; + + hideSystemUIIfNeeded(); + buildSegments(); + + binding.itemsListHeaderTitle.setVisibility(View.VISIBLE); + binding.itemsListHeaderDuration.setVisibility(View.GONE); + binding.shuffleButton.setVisibility(View.GONE); + binding.repeatButton.setVisibility(View.GONE); + binding.addToPlaylistButton.setVisibility(View.GONE); + + hideControls(0, 0); + binding.itemsListPanel.requestFocus(); + animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA); + + final int adapterPosition = getNearestStreamSegmentPosition( + player.getExoPlayer().getCurrentPosition()); + segmentAdapter.selectSegmentAt(adapterPosition); + binding.itemsList.scrollToPosition(adapterPosition); + } + + private void buildSegments() { + binding.itemsList.setAdapter(segmentAdapter); + binding.itemsList.setClickable(true); + binding.itemsList.setLongClickable(false); + + binding.itemsList.clearOnScrollListeners(); + if (itemTouchHelper != null) { + itemTouchHelper.attachToRecyclerView(null); + } + + player.getCurrentStreamInfo().ifPresent(segmentAdapter::setItems); + + binding.shuffleButton.setVisibility(View.GONE); + binding.repeatButton.setVisibility(View.GONE); + binding.addToPlaylistButton.setVisibility(View.GONE); + binding.itemsListClose.setOnClickListener(view -> closeItemsList()); + } + + public void closeItemsList() { + if (isQueueVisible || areSegmentsVisible) { + isQueueVisible = false; + areSegmentsVisible = false; + + if (itemTouchHelper != null) { + itemTouchHelper.attachToRecyclerView(null); + } + + animate(binding.itemsListPanel, false, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA, 0, () -> { + // Even when queueLayout is GONE it receives touch events + // and ruins normal behavior of the app. This line fixes it + binding.itemsListPanel.setTranslationY( + -binding.itemsListPanel.getHeight() * 5); + }); + + // clear focus, otherwise a white rectangle remains on top of the player + binding.itemsListClose.clearFocus(); + binding.playPauseButton.requestFocus(); + } + } + + private OnScrollBelowItemsListener getQueueScrollListener() { + return new OnScrollBelowItemsListener() { + @Override + public void onScrolledDown(final RecyclerView recyclerView) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null && !playQueue.isComplete()) { + playQueue.fetch(); + } else if (binding != null) { + binding.itemsList.clearOnScrollListeners(); + } + } + }; + } + + private StreamSegmentAdapter.StreamSegmentListener getStreamSegmentListener() { + return (item, seconds) -> { + segmentAdapter.selectSegment(item); + player.seekTo(seconds * 1000L); + player.triggerProgressUpdate(); + }; + } + + private int getNearestStreamSegmentPosition(final long playbackPosition) { + //noinspection SimplifyOptionalCallChains + if (!player.getCurrentStreamInfo().isPresent()) { + return 0; + } + + int nearestPosition = 0; + final List segments + = player.getCurrentStreamInfo().get().getStreamSegments(); + + for (int i = 0; i < segments.size(); i++) { + if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) { + break; + } + nearestPosition++; + } + return Math.max(0, nearestPosition - 1); + } + + private ItemTouchHelper.SimpleCallback getItemTouchCallback() { + return new PlayQueueItemTouchCallback() { + @Override + public void onMove(final int sourceIndex, final int targetIndex) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null) { + playQueue.move(sourceIndex, targetIndex); + } + } + + @Override + public void onSwiped(final int index) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null && index != -1) { + playQueue.remove(index); + } + } + }; + } + + private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() { + return new PlayQueueItemBuilder.OnSelectedListener() { + @Override + public void selected(final PlayQueueItem item, final View view) { + player.selectQueueItem(item); + } + + @Override + public void held(final PlayQueueItem item, final View view) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + @Nullable final AppCompatActivity parentActivity = getParentActivity(); + if (playQueue != null && parentActivity != null && playQueue.indexOf(item) != -1) { + openPopupMenu(player.getPlayQueue(), item, view, true, + parentActivity.getSupportFragmentManager(), context); + } + } + + @Override + public void onStartDrag(final PlayQueueItemHolder viewHolder) { + if (itemTouchHelper != null) { + itemTouchHelper.startDrag(viewHolder); + } + } + }; + } + + private void updateQueueTime(final int currentTime) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + final int currentStream = playQueue.getIndex(); + int before = 0; + int after = 0; + + final List streams = playQueue.getStreams(); + final int nStreams = streams.size(); + + for (int i = 0; i < nStreams; i++) { + if (i < currentStream) { + before += streams.get(i).getDuration(); + } else { + after += streams.get(i).getDuration(); + } + } + + before *= 1000; + after *= 1000; + + binding.itemsListHeaderDuration.setText( + String.format("%s/%s", + getTimeString(currentTime + before), + getTimeString(before + after) + )); + } + + @Override + protected boolean isAnyListViewOpen() { + return isQueueVisible || areSegmentsVisible; + } + + @Override + public boolean isFullscreen() { + return isFullscreen; + } + + public boolean isVerticalVideo() { + return isVerticalVideo; + } + + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Click listeners + //////////////////////////////////////////////////////////////////////////*/ + //region Click listeners + @Override + public void onClick(final View v) { + if (v.getId() == binding.screenRotationButton.getId()) { + // Only if it's not a vertical video or vertical video but in landscape with locked + // orientation a screen orientation can be changed automatically + if (!isVerticalVideo || (isLandscape() && globalScreenOrientationLocked(context))) { + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onScreenRotationButtonClicked); + } else { + toggleFullscreen(); + } + } + + // call it later since it calls manageControlsAfterOnClick at the end + super.onClick(v); + } + + @Override + protected void onPlaybackSpeedClicked() { + PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), + player.getPlaybackSkipSilence(), (speed, pitch, skipSilence) + -> player.setPlaybackParameters(speed, pitch, skipSilence)) + .show(getParentActivity().getSupportFragmentManager(), null); + } + + @Override + public boolean onLongClick(final View v) { + if (v.getId() == binding.moreOptionsButton.getId() && isFullscreen) { + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onMoreOptionsLongClicked); + hideControls(0, 0); + hideSystemUIIfNeeded(); + return true; + } + return super.onLongClick(v); + } + + @Override + public boolean onKeyDown(final int keyCode) { + if (keyCode == KeyEvent.KEYCODE_SPACE && isFullscreen) { + player.playPause(); + if (player.isPlaying()) { + hideControls(0, 0); + } + return true; + } + return super.onKeyDown(keyCode); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Video size, resize, orientation, fullscreen + //////////////////////////////////////////////////////////////////////////*/ + //region Video size, resize, orientation, fullscreen + + private void setupScreenRotationButton() { + binding.screenRotationButton.setVisibility(globalScreenOrientationLocked(context) + || isVerticalVideo || DeviceUtils.isTablet(context) + ? View.VISIBLE : View.GONE); + binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(context, + isFullscreen ? R.drawable.ic_fullscreen_exit + : R.drawable.ic_fullscreen)); + } + + @Override + public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { + super.onVideoSizeChanged(videoSize); + isVerticalVideo = videoSize.width < videoSize.height; + + if (globalScreenOrientationLocked(context) + && isFullscreen + && isLandscape() == isVerticalVideo + && !DeviceUtils.isTv(context) + && !DeviceUtils.isTablet(context)) { + // set correct orientation + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onScreenRotationButtonClicked); + } + + setupScreenRotationButton(); + } + + public void toggleFullscreen() { + if (DEBUG) { + Log.d(TAG, "toggleFullscreen() called"); + } + final PlayerServiceEventListener fragmentListener + = player.getFragmentListener().orElse(null); + if (fragmentListener == null || player.exoPlayerIsNull()) { + return; + } + + isFullscreen = !isFullscreen; + if (!isFullscreen) { + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait (open vertical video to reproduce) + binding.playbackControlRoot.setPadding(0, 0, 0, 0); + } else { + // Android needs tens milliseconds to send new insets but a user is able to see + // how controls changes it's position from `0` to `nav bar height` padding. + // So just hide the controls to hide this visual inconsistency + hideControls(0, 0); + } + fragmentListener.onFullscreenStateChanged(isFullscreen); + + if (isFullscreen) { + binding.titleTextView.setVisibility(View.VISIBLE); + binding.channelTextView.setVisibility(View.VISIBLE); + binding.playerCloseButton.setVisibility(View.GONE); + } else { + binding.titleTextView.setVisibility(View.GONE); + binding.channelTextView.setVisibility(View.GONE); + binding.playerCloseButton.setVisibility(View.VISIBLE); + } + setupScreenRotationButton(); + } + + public void checkLandscape() { + // check if landscape is correct + final boolean videoInLandscapeButNotInFullscreen + = isLandscape() && !isFullscreen && !player.isAudioOnly(); + final boolean notPaused = player.getCurrentState() != STATE_COMPLETED + && player.getCurrentState() != STATE_PAUSED; + + if (videoInLandscapeButNotInFullscreen + && notPaused + && !DeviceUtils.isTablet(context)) { + toggleFullscreen(); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Getters + //////////////////////////////////////////////////////////////////////////*/ + //region Getters + public PlayerBinding getBinding() { + return binding; + } + + public AppCompatActivity getParentActivity() { + return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext(); + } + + public boolean isLandscape() { + // DisplayMetrics from activity context knows about MultiWindow feature + // while DisplayMetrics from app context doesn't + return DeviceUtils.isLandscape(getParentActivity()); + } + //endregion +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java new file mode 100644 index 000000000..40c83c6c7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java @@ -0,0 +1,26 @@ +package org.schabi.newpipe.player.ui; + +import androidx.annotation.NonNull; + +import org.schabi.newpipe.player.NotificationUtil; +import org.schabi.newpipe.player.Player; + +public final class NotificationPlayerUi extends PlayerUi { + boolean foregroundNotificationAlreadyCreated = false; + + public NotificationPlayerUi(@NonNull final Player player) { + super(player); + } + + @Override + public void initPlayer() { + super.initPlayer(); + if (!foregroundNotificationAlreadyCreated) { + NotificationUtil.getInstance() + .createNotificationAndStartForeground(player, player.getService()); + foregroundNotificationAlreadyCreated = true; + } + } + + // TODO TODO on destroy remove foreground +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java new file mode 100644 index 000000000..fd63790d6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -0,0 +1,120 @@ +package org.schabi.newpipe.player.ui; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player.RepeatMode; +import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.video.VideoSize; + +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.Player; + +import java.util.List; + +public abstract class PlayerUi { + private static final String TAG = PlayerUi.class.getSimpleName(); + + @NonNull protected Context context; + @NonNull protected Player player; + + public PlayerUi(@NonNull final Player player) { + this.context = player.getContext(); + this.player = player; + } + + @NonNull + public Player getPlayer() { + return player; + } + + + public void setupAfterIntent() { + } + + public void initPlayer() { + } + + public void initPlayback() { + } + + public void destroyPlayer() { + } + + public void destroy() { + } + + public void smoothStopForImmediateReusing() { + } + + public void onFragmentListenerSet() { + } + + public void onBroadcastReceived(final Intent intent) { + } + + public void onUpdateProgress(final int currentProgress, + final int duration, + final int bufferPercent) { + } + + public void onPrepared() { + } + + public void onBlocked() { + } + + public void onPlaying() { + } + + public void onBuffering() { + } + + public void onPaused() { + } + + public void onPausedSeek() { + } + + public void onCompleted() { + } + + public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + } + + public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { + } + + public void onMuteUnmuteChanged(final boolean isMuted) { + } + + public void onTextTracksChanged(@NonNull final Tracks currentTracks) { + } + + public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { + } + + public void onRenderedFirstFrame() { + } + + public void onCues(@NonNull final List cues) { + } + + public void onMetadataChanged(@NonNull final StreamInfo info) { + } + + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + } + + public void onPlayQueueEdited() { + } + + public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java new file mode 100644 index 000000000..8c5c0dbfa --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -0,0 +1,36 @@ +package org.schabi.newpipe.player.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +public final class PlayerUiList { + final List playerUis = new ArrayList<>(); + + public void add(final PlayerUi playerUi) { + playerUis.add(playerUi); + } + + public void destroyAll(final Class playerUiType) { + playerUis.stream() + .filter(playerUiType::isInstance) + .forEach(playerUi -> { + playerUi.destroyPlayer(); + playerUi.destroy(); + }); + playerUis.removeIf(playerUiType::isInstance); + } + + public Optional get(final Class playerUiType) { + return playerUis.stream() + .filter(playerUiType::isInstance) + .map(playerUiType::cast) + .findFirst(); + } + + public void call(final Consumer consumer) { + //noinspection SimplifyStreamApiCallChains + playerUis.stream().forEach(consumer); + } +} 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 new file mode 100644 index 000000000..b8a26a233 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -0,0 +1,460 @@ +package org.schabi.newpipe.player.ui; + +import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; +import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; +import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.AnticipateInterpolator; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.ui.SubtitleView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.PlayerBinding; +import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; +import org.schabi.newpipe.player.gesture.PopupPlayerGestureListener; +import org.schabi.newpipe.player.helper.PlayerHelper; + +public final class PopupPlayerUi extends VideoPlayerUi { + private static final String TAG = PopupPlayerUi.class.getSimpleName(); + + /*////////////////////////////////////////////////////////////////////////// + // Popup player + //////////////////////////////////////////////////////////////////////////*/ + + private PlayerPopupCloseOverlayBinding closeOverlayBinding; + + private boolean isPopupClosing = false; + + private int screenWidth; + private int screenHeight; + + /*////////////////////////////////////////////////////////////////////////// + // Popup player window manager + //////////////////////////////////////////////////////////////////////////*/ + + public static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + public static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + + private WindowManager.LayoutParams popupLayoutParams; // null if player is not popup + private final WindowManager windowManager; + + public PopupPlayerUi(@NonNull final Player player, + @NonNull final PlayerBinding playerBinding) { + super(player, playerBinding); + windowManager = ContextCompat.getSystemService(context, WindowManager.class); + } + + @Override + public void setupAfterIntent() { + setupElementsVisibility(); + binding.getRoot().setVisibility(View.VISIBLE); + initPopup(); + initPopupCloseOverlay(); + binding.playPauseButton.requestFocus(); + } + + @Override + BasePlayerGestureListener buildGestureListener() { + return new PopupPlayerGestureListener(this); + } + + @SuppressLint("RtlHardcoded") + private void initPopup() { + if (DEBUG) { + Log.d(TAG, "initPopup() called"); + } + + // Popup is already added to windowManager + if (popupHasParent()) { + return; + } + + updateScreenSize(); + + popupLayoutParams = retrievePopupLayoutParamsFromPrefs(this); + binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); + + checkPopupPositionBounds(); + + binding.loadingPanel.setMinimumWidth(popupLayoutParams.width); + binding.loadingPanel.setMinimumHeight(popupLayoutParams.height); + + windowManager.addView(binding.getRoot(), popupLayoutParams); + + // Popup doesn't have aspectRatio selector, using FIT automatically + setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); + } + + @SuppressLint("RtlHardcoded") + private void initPopupCloseOverlay() { + if (DEBUG) { + Log.d(TAG, "initPopupCloseOverlay() called"); + } + + // closeOverlayView is already added to windowManager + if (closeOverlayBinding != null) { + return; + } + + closeOverlayBinding = PlayerPopupCloseOverlayBinding.inflate(LayoutInflater.from(context)); + + final WindowManager.LayoutParams closeOverlayLayoutParams = buildCloseOverlayLayoutParams(); + closeOverlayBinding.closeButton.setVisibility(View.GONE); + windowManager.addView(closeOverlayBinding.getRoot(), closeOverlayLayoutParams); + } + + @Override + protected void setupElementsVisibility() { + binding.fullScreenButton.setVisibility(View.VISIBLE); + binding.screenRotationButton.setVisibility(View.GONE); + binding.resizeTextView.setVisibility(View.GONE); + binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.GONE); + binding.queueButton.setVisibility(View.GONE); + binding.segmentsButton.setVisibility(View.GONE); + binding.moreOptionsButton.setVisibility(View.GONE); + binding.topControls.setOrientation(LinearLayout.HORIZONTAL); + binding.primaryControls.getLayoutParams().width + = LinearLayout.LayoutParams.WRAP_CONTENT; + binding.secondaryControls.setAlpha(1.0f); + binding.secondaryControls.setVisibility(View.VISIBLE); + binding.secondaryControls.setTranslationY(0); + binding.share.setVisibility(View.GONE); + binding.playWithKodi.setVisibility(View.GONE); + binding.openInBrowser.setVisibility(View.GONE); + binding.switchMute.setVisibility(View.GONE); + binding.playerCloseButton.setVisibility(View.GONE); + binding.topControls.bringToFront(); + binding.topControls.setClickable(false); + binding.topControls.setFocusable(false); + binding.bottomControls.bringToFront(); + super.setupElementsVisibility(); + } + + @Override + protected void setupElementsSize(final Resources resources) { + setupElementsSize( + 0, + 0, + resources.getDimensionPixelSize(R.dimen.player_popup_controls_padding), + resources.getDimensionPixelSize(R.dimen.player_popup_buttons_padding) + ); + } + + @Override + public void removeViewFromParent() { + // view was added by windowManager for popup player + windowManager.removeViewImmediate(binding.getRoot()); + } + + @Override + public void destroy() { + super.destroy(); + removePopupFromView(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Broadcast receiver + //////////////////////////////////////////////////////////////////////////*/ + //region Broadcast receiver + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + updateScreenSize(); + changePopupSize(popupLayoutParams.width); + checkPopupPositionBounds(); + } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + // Use only audio source when screen turns off while popup player is playing + if (player.isPlaying() || player.isLoading()) { + player.useVideoSource(false); + } + } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + // Restore video source when screen turns on and user is watching video in popup player + if (player.isPlaying() || player.isLoading()) { + player.useVideoSource(true); + } + } + } + //endregion + + + /** + * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary + * that goes from (0, 0) to (screenWidth, screenHeight). + *

+ * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed + * and {@code true} is returned to represent this change. + *

+ */ + public void checkPopupPositionBounds() { + if (DEBUG) { + Log.d(TAG, "checkPopupPositionBounds() called with: " + + "screenWidth = [" + screenWidth + "], " + + "screenHeight = [" + screenHeight + "]"); + } + if (popupLayoutParams == null) { + return; + } + + if (popupLayoutParams.x < 0) { + popupLayoutParams.x = 0; + } else if (popupLayoutParams.x > screenWidth - popupLayoutParams.width) { + popupLayoutParams.x = screenWidth - popupLayoutParams.width; + } + + if (popupLayoutParams.y < 0) { + popupLayoutParams.y = 0; + } else if (popupLayoutParams.y > screenHeight - popupLayoutParams.height) { + popupLayoutParams.y = screenHeight - popupLayoutParams.height; + } + } + + public void updateScreenSize() { + final DisplayMetrics metrics = new DisplayMetrics(); + windowManager.getDefaultDisplay().getMetrics(metrics); + + screenWidth = metrics.widthPixels; + screenHeight = metrics.heightPixels; + if (DEBUG) { + Log.d(TAG, "updateScreenSize() called: screenWidth = [" + + screenWidth + "], screenHeight = [" + screenHeight + "]"); + } + } + + /** + * Changes the size of the popup based on the width. + * @param width the new width, height is calculated with + * {@link PlayerHelper#getMinimumVideoHeight(float)} + */ + public void changePopupSize(final int width) { + if (DEBUG) { + Log.d(TAG, "changePopupSize() called with: width = [" + width + "]"); + } + + if (anyPopupViewIsNull()) { + return; + } + + final float minimumWidth = context.getResources().getDimension(R.dimen.popup_minimum_width); + final int actualWidth = (int) (width > screenWidth ? screenWidth + : (width < minimumWidth ? minimumWidth : width)); + final int actualHeight = (int) getMinimumVideoHeight(width); + if (DEBUG) { + Log.d(TAG, "updatePopupSize() updated values:" + + " width = [" + actualWidth + "], height = [" + actualHeight + "]"); + } + + popupLayoutParams.width = actualWidth; + popupLayoutParams.height = actualHeight; + binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); + windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); + } + + private void changePopupWindowFlags(final int flags) { + if (DEBUG) { + Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); + } + + if (!anyPopupViewIsNull()) { + popupLayoutParams.flags = flags; + windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); + } + } + + public void closePopup() { + if (DEBUG) { + Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing); + } + if (isPopupClosing) { + return; + } + isPopupClosing = true; + + player.saveStreamProgressState(); + windowManager.removeView(binding.getRoot()); + + animatePopupOverlayAndFinishService(); + } + + public boolean isPopupClosing() { + return isPopupClosing; + } + + public void removePopupFromView() { + if (windowManager != null) { + // wrap in try-catch since it could sometimes generate errors randomly + try { + if (popupHasParent()) { + windowManager.removeView(binding.getRoot()); + } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup from window manager", e); + } + + try { + final boolean closeOverlayHasParent = closeOverlayBinding != null + && closeOverlayBinding.getRoot().getParent() != null; + if (closeOverlayHasParent) { + windowManager.removeView(closeOverlayBinding.getRoot()); + } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup overlay from window manager", e); + } + } + } + + private void animatePopupOverlayAndFinishService() { + final int targetTranslationY = + (int) (closeOverlayBinding.closeButton.getRootView().getHeight() + - closeOverlayBinding.closeButton.getY()); + + closeOverlayBinding.closeButton.animate().setListener(null).cancel(); + closeOverlayBinding.closeButton.animate() + .setInterpolator(new AnticipateInterpolator()) + .translationY(targetTranslationY) + .setDuration(400) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(final Animator animation) { + end(); + } + + @Override + public void onAnimationEnd(final Animator animation) { + end(); + } + + private void end() { + windowManager.removeView(closeOverlayBinding.getRoot()); + closeOverlayBinding = null; + player.getService().stopService(); + } + }).start(); + } + + @Override + protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { + // no need for the end screen thumbnail to be resized on popup player: it's only needed + // for the main player so that it is enlarged correctly inside the fragment + return bitmap.getHeight(); + } + + private boolean popupHasParent() { + return binding != null + && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams + && binding.getRoot().getParent() != null; + } + + private boolean anyPopupViewIsNull() { + return popupLayoutParams == null || windowManager == null + || binding.getRoot().getParent() == null; + } + + @Override + public void onPlaying() { + super.onPlaying(); + changePopupWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS); + } + + @Override + public void onPaused() { + super.onPaused(); + changePopupWindowFlags(IDLE_WINDOW_FLAGS); + } + + @Override + public void onCompleted() { + super.onCompleted(); + changePopupWindowFlags(IDLE_WINDOW_FLAGS); + } + + @Override + protected void setupSubtitleView(final float captionScale) { + final float captionRatio = (captionScale - 1.0f) / 5.0f + 1.0f; + binding.subtitleView.setFractionalTextSize( + SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio); + } + + @Override + protected void onPlaybackSpeedClicked() { + playbackSpeedPopupMenu.show(); + isSomePopupMenuVisible = true; + } + + /*////////////////////////////////////////////////////////////////////////// + // Gestures + //////////////////////////////////////////////////////////////////////////*/ + //region Gestures + private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) { + final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft() + + closeOverlayBinding.closeButton.getWidth() / 2; + final int closeOverlayButtonY = closeOverlayBinding.closeButton.getTop() + + closeOverlayBinding.closeButton.getHeight() / 2; + + final float fingerX = popupLayoutParams.x + popupMotionEvent.getX(); + final float fingerY = popupLayoutParams.y + popupMotionEvent.getY(); + + return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) + + Math.pow(closeOverlayButtonY - fingerY, 2)); + } + + private float getClosingRadius() { + final int buttonRadius = closeOverlayBinding.closeButton.getWidth() / 2; + // 20% wider than the button itself + return buttonRadius * 1.2f; + } + + public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent) { + return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius(); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Getters + //////////////////////////////////////////////////////////////////////////*/ + //region Gestures + public PlayerPopupCloseOverlayBinding getCloseOverlayBinding() { + return closeOverlayBinding; + } + + public WindowManager.LayoutParams getPopupLayoutParams() { + return popupLayoutParams; + } + + public WindowManager getWindowManager() { + return windowManager; + } + + public int getScreenHeight() { + return screenHeight; + } + + public int getScreenWidth() { + return screenWidth; + } + //endregion +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java new file mode 100644 index 000000000..99ecb5540 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -0,0 +1,1523 @@ +package org.schabi.newpipe.player.ui; + +import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; +import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; +import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; +import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; +import static org.schabi.newpipe.player.Player.RENDERER_UNAVAILABLE; +import static org.schabi.newpipe.player.Player.STATE_BUFFERING; +import static org.schabi.newpipe.player.Player.STATE_COMPLETED; +import static org.schabi.newpipe.player.Player.STATE_PAUSED; +import static org.schabi.newpipe.player.Player.STATE_PAUSED_SEEK; +import static org.schabi.newpipe.player.Player.STATE_PLAYING; +import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; +import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; +import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs; +import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; + +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.util.Log; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.Surface; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.SeekBar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.view.ContextThemeWrapper; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.appcompat.widget.PopupMenu; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player.RepeatMode; +import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.ui.CaptionStyleCompat; +import com.google.android.exoplayer2.video.VideoSize; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.PlayerBinding; +import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.VideoStream; +import org.schabi.newpipe.fragments.detail.VideoDetailFragment; +import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; +import org.schabi.newpipe.player.gesture.DisplayPortion; +import org.schabi.newpipe.player.helper.PlayerHelper; +import org.schabi.newpipe.player.mediaitem.MediaItemTag; +import org.schabi.newpipe.player.playback.SurfaceHolderCallback; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper; +import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder; +import org.schabi.newpipe.util.DeviceUtils; +import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.external_communication.KoreUtils; +import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.views.player.PlayerFastSeekOverlay; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public abstract class VideoPlayerUi extends PlayerUi + implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { + private static final String TAG = VideoPlayerUi.class.getSimpleName(); + + // time constants + public static final long DEFAULT_CONTROLS_DURATION = 300; // 300 millis + public static final long DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds + public static final long DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds + public static final int SEEK_OVERLAY_DURATION = 450; // 450 millis + + // other constants (TODO remove playback speeds and use normal menu for popup, too) + private static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f}; + + + /*////////////////////////////////////////////////////////////////////////// + // Views + //////////////////////////////////////////////////////////////////////////*/ + + protected PlayerBinding binding; + private final Handler controlsVisibilityHandler = new Handler(); + @Nullable private SurfaceHolderCallback surfaceHolderCallback; + @Nullable private Bitmap thumbnail = null; + + + /*////////////////////////////////////////////////////////////////////////// + // Popup menus ("popup" means that they pop up, not that they belong to the popup player) + //////////////////////////////////////////////////////////////////////////*/ + + private static final int POPUP_MENU_ID_QUALITY = 69; + private static final int POPUP_MENU_ID_PLAYBACK_SPEED = 79; + private static final int POPUP_MENU_ID_CAPTION = 89; + + protected boolean isSomePopupMenuVisible = false; + private PopupMenu qualityPopupMenu; + protected PopupMenu playbackSpeedPopupMenu; + private PopupMenu captionPopupMenu; + + + /*////////////////////////////////////////////////////////////////////////// + // Gestures + //////////////////////////////////////////////////////////////////////////*/ + + private GestureDetector gestureDetector; + private BasePlayerGestureListener playerGestureListener; + + @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = + new SeekbarPreviewThumbnailHolder(); + + public VideoPlayerUi(@NonNull final Player player, + @NonNull final PlayerBinding playerBinding) { + super(player); + binding = playerBinding; + } + + + /*////////////////////////////////////////////////////////////////////////// + // Setup + //////////////////////////////////////////////////////////////////////////*/ + //region Setup + public void setupFromView() { + initViews(); + initListeners(); + setupPlayerSeekOverlay(); + } + + private void initViews() { + setupSubtitleView(); + + binding.resizeTextView + .setText(PlayerHelper.resizeTypeOf(context, binding.surfaceView.getResizeMode())); + + binding.playbackSeekBar.getThumb() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); + binding.playbackSeekBar.getProgressDrawable() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)); + + final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, + R.style.DarkPopupMenu); + + qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView); + playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed); + captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView); + + binding.progressBarLoadingPanel.getIndeterminateDrawable() + .setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)); + + binding.titleTextView.setSelected(true); + binding.channelTextView.setSelected(true); + + // Prevent hiding of bottom sheet via swipe inside queue + binding.itemsList.setNestedScrollingEnabled(false); + } + + abstract BasePlayerGestureListener buildGestureListener(); + + protected void initListeners() { + binding.qualityTextView.setOnClickListener(this); + binding.playbackSpeed.setOnClickListener(this); + + binding.playbackSeekBar.setOnSeekBarChangeListener(this); + binding.captionTextView.setOnClickListener(this); + binding.resizeTextView.setOnClickListener(this); + binding.playbackLiveSync.setOnClickListener(this); + + playerGestureListener = buildGestureListener(); + gestureDetector = new GestureDetector(context, playerGestureListener); + binding.getRoot().setOnTouchListener(playerGestureListener); + + binding.repeatButton.setOnClickListener(v -> onRepeatClicked()); + binding.shuffleButton.setOnClickListener(v -> onShuffleClicked()); + + binding.playPauseButton.setOnClickListener(this); + binding.playPreviousButton.setOnClickListener(this); + binding.playNextButton.setOnClickListener(this); + + binding.moreOptionsButton.setOnClickListener(this); + binding.moreOptionsButton.setOnLongClickListener(this); + binding.share.setOnClickListener(this); + binding.share.setOnLongClickListener(this); + binding.fullScreenButton.setOnClickListener(this); + binding.screenRotationButton.setOnClickListener(this); + binding.playWithKodi.setOnClickListener(this); + binding.openInBrowser.setOnClickListener(this); + binding.playerCloseButton.setOnClickListener(this); + binding.switchMute.setOnClickListener(this); + + ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, (view, windowInsets) -> { + final Insets cutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()); + if (!cutout.equals(Insets.NONE)) { + view.setPadding(cutout.left, cutout.top, cutout.right, cutout.bottom); + } + return windowInsets; + }); + + // PlaybackControlRoot already consumed window insets but we should pass them to + // player_overlays and fast_seek_overlay too. Without it they will be off-centered. + binding.playbackControlRoot.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + binding.playerOverlays.setPadding( + v.getPaddingLeft(), + v.getPaddingTop(), + v.getPaddingRight(), + v.getPaddingBottom()); + + // If we added padding to the fast seek overlay, too, it would not go under the + // system ui. Instead we apply negative margins equal to the window insets of + // the opposite side, so that the view covers all of the player (overflowing on + // some sides) and its center coincides with the center of other controls. + final RelativeLayout.LayoutParams fastSeekParams = (RelativeLayout.LayoutParams) + binding.fastSeekOverlay.getLayoutParams(); + fastSeekParams.leftMargin = -v.getPaddingRight(); + fastSeekParams.topMargin = -v.getPaddingBottom(); + fastSeekParams.rightMargin = -v.getPaddingLeft(); + fastSeekParams.bottomMargin = -v.getPaddingTop(); + }); + } + + /** + * Initializes the Fast-For/Backward overlay. + */ + private void setupPlayerSeekOverlay() { + binding.fastSeekOverlay + .seekSecondsSupplier(() -> retrieveSeekDurationFromPreferences(player) / 1000) + .performListener(new PlayerFastSeekOverlay.PerformListener() { + + @Override + public void onDoubleTap() { + animate(binding.fastSeekOverlay, true, SEEK_OVERLAY_DURATION); + } + + @Override + public void onDoubleTapEnd() { + animate(binding.fastSeekOverlay, false, SEEK_OVERLAY_DURATION); + } + + @NonNull + @Override + public FastSeekDirection getFastSeekDirection( + @NonNull final DisplayPortion portion + ) { + if (player.exoPlayerIsNull()) { + // Abort seeking + playerGestureListener.endMultiDoubleTap(); + return FastSeekDirection.NONE; + } + if (portion == DisplayPortion.LEFT) { + // Check if it's possible to rewind + // Small puffer to eliminate infinite rewind seeking + if (player.getExoPlayer().getCurrentPosition() < 500L) { + return FastSeekDirection.NONE; + } + return FastSeekDirection.BACKWARD; + } else if (portion == DisplayPortion.RIGHT) { + // Check if it's possible to fast-forward + if (player.getCurrentState() == STATE_COMPLETED + || player.getExoPlayer().getCurrentPosition() + >= player.getExoPlayer().getDuration()) { + return FastSeekDirection.NONE; + } + return FastSeekDirection.FORWARD; + } + /* portion == DisplayPortion.MIDDLE */ + return FastSeekDirection.NONE; + } + + @Override + public void seek(final boolean forward) { + playerGestureListener.keepInDoubleTapMode(); + if (forward) { + player.fastForward(); + } else { + player.fastRewind(); + } + } + }); + playerGestureListener.doubleTapControls(binding.fastSeekOverlay); + } + + @Override + public void setupAfterIntent() { + super.setupAfterIntent(); + setupElementsVisibility(); + setupElementsSize(context.getResources()); + } + + @Override + public void initPlayer() { + super.initPlayer(); + setupVideoSurface(); + setupFromView(); + } + + @Override + public void initPlayback() { + super.initPlayback(); + + // #6825 - Ensure that the shuffle-button is in the correct state on the UI + setShuffleButton(player.getExoPlayer().getShuffleModeEnabled()); + } + + public abstract void removeViewFromParent(); + + @Override + public void destroyPlayer() { + super.destroyPlayer(); + cleanupVideoSurface(); + } + + @Override + public void destroy() { + super.destroy(); + if (binding != null) { + binding.endScreen.setImageBitmap(null); + } + } + + protected void setupElementsVisibility() { + setMuteButton(player.isMuted()); + animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0); + } + + protected abstract void setupElementsSize(Resources resources); + + protected void setupElementsSize(final int buttonsMinWidth, + final int playerTopPad, + final int controlsPad, + final int buttonsPad) { + binding.topControls.setPaddingRelative(controlsPad, playerTopPad, controlsPad, 0); + binding.bottomControls.setPaddingRelative(controlsPad, 0, controlsPad, 0); + binding.qualityTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + binding.playbackSpeed.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + binding.playbackSpeed.setMinimumWidth(buttonsMinWidth); + binding.captionTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Broadcast receiver + //////////////////////////////////////////////////////////////////////////*/ + //region Broadcast receiver + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + // When the orientation changed, the screen height might be smaller. + // If the end screen thumbnail is not re-scaled, + // it can be larger than the current screen height + // and thus enlarging the whole player. + // This causes the seekbar to be ouf the visible area. + updateEndScreenThumbnail(); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Thumbnail + //////////////////////////////////////////////////////////////////////////*/ + //region Thumbnail + /** + * Scale the player audio / end screen thumbnail down if necessary. + *

+ * This is necessary when the thumbnail's height is larger than the device's height + * and thus is enlarging the player's height + * causing the bottom playback controls to be out of the visible screen. + *

+ */ + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + thumbnail = bitmap; + updateEndScreenThumbnail(); + } + + private void updateEndScreenThumbnail() { + if (thumbnail == null) { + // remove end screen thumbnail + binding.endScreen.setImageDrawable(null); + return; + } + + final float endScreenHeight = calculateMaxEndScreenThumbnailHeight(thumbnail); + final Bitmap endScreenBitmap = Bitmap.createScaledBitmap( + thumbnail, + (int) (thumbnail.getWidth() / (thumbnail.getHeight() / endScreenHeight)), + (int) endScreenHeight, + true); + + if (DEBUG) { + Log.d(TAG, "Thumbnail - onThumbnailLoaded() called with: " + + "currentThumbnail = [" + thumbnail + "], " + + thumbnail.getWidth() + "x" + thumbnail.getHeight() + + ", scaled end screen height = " + endScreenHeight + + ", scaled end screen width = " + endScreenBitmap.getWidth()); + } + + binding.endScreen.setImageBitmap(endScreenBitmap); + } + + protected abstract float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap); + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Progress loop and updates + //////////////////////////////////////////////////////////////////////////*/ + //region Progress loop and updates + @Override + public void onUpdateProgress(final int currentProgress, + final int duration, + final int bufferPercent) { + + if (duration != binding.playbackSeekBar.getMax()) { + setVideoDurationToControls(duration); + } + if (player.getCurrentState() != STATE_PAUSED) { + updatePlayBackElementsCurrentDuration(currentProgress); + } + if (player.isLoading() || bufferPercent > 90) { + binding.playbackSeekBar.setSecondaryProgress( + (int) (binding.playbackSeekBar.getMax() * ((float) bufferPercent / 100))); + } + if (DEBUG && bufferPercent % 20 == 0) { //Limit log + Log.d(TAG, "notifyProgressUpdateToListeners() called with: " + + "isVisible = " + isControlsVisible() + ", " + + "currentProgress = [" + currentProgress + "], " + + "duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]"); + } + binding.playbackLiveSync.setClickable(!player.isLiveEdge()); + } + + /** + * Sets the current duration into the corresponding elements. + */ + private void updatePlayBackElementsCurrentDuration(final int currentProgress) { + // Don't set seekbar progress while user is seeking + if (player.getCurrentState() != STATE_PAUSED_SEEK) { + binding.playbackSeekBar.setProgress(currentProgress); + } + binding.playbackCurrentTime.setText(getTimeString(currentProgress)); + } + + /** + * Sets the video duration time into all control components (e.g. seekbar). + */ + private void setVideoDurationToControls(final int duration) { + binding.playbackEndTime.setText(getTimeString(duration)); + + binding.playbackSeekBar.setMax(duration); + // This is important for Android TVs otherwise it would apply the default from + // setMax/Min methods which is (max - min) / 20 + binding.playbackSeekBar.setKeyProgressIncrement( + PlayerHelper.retrieveSeekDurationFromPreferences(player)); + } + + @Override // seekbar listener + public void onProgressChanged(final SeekBar seekBar, final int progress, + final boolean fromUser) { + // Currently we don't need method execution when fromUser is false + if (!fromUser) { + return; + } + if (DEBUG) { + Log.d(TAG, "onProgressChanged() called with: " + + "seekBar = [" + seekBar + "], progress = [" + progress + "]"); + } + + binding.currentDisplaySeek.setText(getTimeString(progress)); + + // Seekbar Preview Thumbnail + SeekbarPreviewThumbnailHelper + .tryResizeAndSetSeekbarPreviewThumbnail( + player.getContext(), + seekbarPreviewThumbnailHolder.getBitmapAt(progress), + binding.currentSeekbarPreviewThumbnail, + binding.subtitleView::getWidth); + + adjustSeekbarPreviewContainer(); + } + + + private void adjustSeekbarPreviewContainer() { + try { + // Should only be required when an error occurred before + // and the layout was positioned in the center + binding.bottomSeekbarPreviewLayout.setGravity(Gravity.NO_GRAVITY); + + // Calculate the current left position of seekbar progress in px + // More info: https://stackoverflow.com/q/20493577 + final int currentSeekbarLeft = + binding.playbackSeekBar.getLeft() + + binding.playbackSeekBar.getPaddingLeft() + + binding.playbackSeekBar.getThumb().getBounds().left; + + // Calculate the (unchecked) left position of the container + final int uncheckedContainerLeft = + currentSeekbarLeft - (binding.seekbarPreviewContainer.getWidth() / 2); + + // Fix the position so it's within the boundaries + final int checkedContainerLeft = + Math.max( + Math.min( + uncheckedContainerLeft, + // Max left + binding.playbackWindowRoot.getWidth() + - binding.seekbarPreviewContainer.getWidth() + ), + 0 // Min left + ); + + // See also: https://stackoverflow.com/a/23249734 + final LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams( + binding.seekbarPreviewContainer.getLayoutParams()); + params.setMarginStart(checkedContainerLeft); + binding.seekbarPreviewContainer.setLayoutParams(params); + } catch (final Exception ex) { + Log.e(TAG, "Failed to adjust seekbarPreviewContainer", ex); + // Fallback - position in the middle + binding.bottomSeekbarPreviewLayout.setGravity(Gravity.CENTER); + } + } + + @Override // seekbar listener + public void onStartTrackingTouch(final SeekBar seekBar) { + if (DEBUG) { + Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]"); + } + if (player.getCurrentState() != STATE_PAUSED_SEEK) { + player.changeState(STATE_PAUSED_SEEK); + } + + player.saveWasPlaying(); + if (player.isPlaying()) { + player.getExoPlayer().pause(); + } + + showControls(0); + animate(binding.currentDisplaySeek, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SCALE_AND_ALPHA); + animate(binding.currentSeekbarPreviewThumbnail, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SCALE_AND_ALPHA); + } + + @Override // seekbar listener + public void onStopTrackingTouch(final SeekBar seekBar) { + if (DEBUG) { + Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]"); + } + + player.seekTo(seekBar.getProgress()); + if (player.wasPlaying() || player.getExoPlayer().getDuration() == seekBar.getProgress()) { + player.getExoPlayer().play(); + } + + binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); + animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); + animate(binding.currentSeekbarPreviewThumbnail, false, 200, AnimationType.SCALE_AND_ALPHA); + + if (player.getCurrentState() == STATE_PAUSED_SEEK) { + player.changeState(STATE_BUFFERING); + } + if (!player.isProgressLoopRunning()) { + player.startProgressLoop(); + } + if (player.wasPlaying()) { + showControlsThenHide(); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Controls showing / hiding + //////////////////////////////////////////////////////////////////////////*/ + //region Controls showing / hiding + + public boolean isControlsVisible() { + return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE; + } + + public void showControlsThenHide() { + if (DEBUG) { + Log.d(TAG, "showControlsThenHide() called"); + } + + showOrHideButtons(); + showSystemUIPartially(); + + final long hideTime = binding.playbackControlRoot.isInTouchMode() + ? DEFAULT_CONTROLS_HIDE_TIME + : DPAD_CONTROLS_HIDE_TIME; + + showHideShadow(true, DEFAULT_CONTROLS_DURATION); + animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, + AnimationType.ALPHA, 0, () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime)); + } + + public void showControls(final long duration) { + if (DEBUG) { + Log.d(TAG, "showControls() called"); + } + showOrHideButtons(); + showSystemUIPartially(); + controlsVisibilityHandler.removeCallbacksAndMessages(null); + showHideShadow(true, duration); + animate(binding.playbackControlRoot, true, duration); + } + + public void hideControls(final long duration, final long delay) { + if (DEBUG) { + Log.d(TAG, "hideControls() called with: duration = [" + duration + + "], delay = [" + delay + "]"); + } + + showOrHideButtons(); + + controlsVisibilityHandler.removeCallbacksAndMessages(null); + controlsVisibilityHandler.postDelayed(() -> { + showHideShadow(false, duration); + animate(binding.playbackControlRoot, false, duration, AnimationType.ALPHA, + 0, this::hideSystemUIIfNeeded); + }, delay); + } + + public void showHideShadow(final boolean show, final long duration) { + animate(binding.playbackControlsShadow, show, duration, AnimationType.ALPHA, 0, null); + animate(binding.playerTopShadow, show, duration, AnimationType.ALPHA, 0, null); + animate(binding.playerBottomShadow, show, duration, AnimationType.ALPHA, 0, null); + } + + protected void showOrHideButtons() { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + final boolean showPrev = playQueue.getIndex() != 0; + final boolean showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size(); + + binding.playPreviousButton.setVisibility(showPrev ? View.VISIBLE : View.INVISIBLE); + binding.playPreviousButton.setAlpha(showPrev ? 1.0f : 0.0f); + binding.playNextButton.setVisibility(showNext ? View.VISIBLE : View.INVISIBLE); + binding.playNextButton.setAlpha(showNext ? 1.0f : 0.0f); + } + + protected void showSystemUIPartially() { + // system UI is really changed only by MainPlayerUi, so overridden there + } + + protected void hideSystemUIIfNeeded() { + // system UI is really changed only by MainPlayerUi, so overridden there + } + + protected boolean isAnyListViewOpen() { + // only MainPlayerUi has list views for the queue and for segments, so overridden there + return false; + } + + public boolean isFullscreen() { + // only MainPlayerUi can be in fullscreen, so overridden there + return false; + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Playback states + //////////////////////////////////////////////////////////////////////////*/ + //region Playback states + @Override + public void onPrepared() { + super.onPrepared(); + setVideoDurationToControls((int) player.getExoPlayer().getDuration()); + binding.playbackSpeed.setText(formatSpeed(player.getPlaybackSpeed())); + } + + @Override + public void onBlocked() { + super.onBlocked(); + + // if we are e.g. switching players, hide controls + hideControls(DEFAULT_CONTROLS_DURATION, 0); + + binding.playbackSeekBar.setEnabled(false); + binding.playbackSeekBar.getThumb() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); + + binding.loadingPanel.setBackgroundColor(Color.BLACK); + animate(binding.loadingPanel, true, 0); + animate(binding.surfaceForeground, true, 100); + + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); + animatePlayButtons(false, 100); + binding.getRoot().setKeepScreenOn(false); + } + + @Override + public void onPlaying() { + super.onPlaying(); + + updateStreamRelatedViews(); + + binding.playbackSeekBar.setEnabled(true); + binding.playbackSeekBar.getThumb() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); + + binding.loadingPanel.setVisibility(View.GONE); + + animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); + + animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, + () -> { + binding.playPauseButton.setImageResource(R.drawable.ic_pause); + animatePlayButtons(true, 200); + if (!isAnyListViewOpen()) { + binding.playPauseButton.requestFocus(); + } + }); + + binding.getRoot().setKeepScreenOn(true); + } + + @Override + public void onBuffering() { + super.onBuffering(); + binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT); + binding.loadingPanel.setVisibility(View.VISIBLE); + binding.getRoot().setKeepScreenOn(true); + } + + @Override + public void onPaused() { + super.onPaused(); + + // Don't let UI elements popup during double tap seeking. This state is entered sometimes + // during seeking/loading. This if-else check ensures that the controls aren't popping up. + if (!playerGestureListener.isDoubleTapping()) { + showControls(400); + binding.loadingPanel.setVisibility(View.GONE); + + animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, + () -> { + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); + animatePlayButtons(true, 200); + if (!isAnyListViewOpen()) { + binding.playPauseButton.requestFocus(); + } + }); + } + + binding.getRoot().setKeepScreenOn(false); + } + + @Override + public void onPausedSeek() { + super.onPausedSeek(); + animatePlayButtons(false, 100); + binding.getRoot().setKeepScreenOn(true); + } + + @Override + public void onCompleted() { + super.onCompleted(); + + animate(binding.playPauseButton, false, 0, AnimationType.SCALE_AND_ALPHA, 0, + () -> { + binding.playPauseButton.setImageResource(R.drawable.ic_replay); + animatePlayButtons(true, DEFAULT_CONTROLS_DURATION); + }); + + binding.getRoot().setKeepScreenOn(false); + + // When a (short) video ends the elements have to display the correct values - see #6180 + updatePlayBackElementsCurrentDuration(binding.playbackSeekBar.getMax()); + + showControls(500); + animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); + binding.loadingPanel.setVisibility(View.GONE); + animate(binding.surfaceForeground, true, 100); + } + + private void animatePlayButtons(final boolean show, final long duration) { + animate(binding.playPauseButton, show, duration, AnimationType.SCALE_AND_ALPHA); + + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + if (!show || playQueue.getIndex() > 0) { + animate( + binding.playPreviousButton, + show, + duration, + AnimationType.SCALE_AND_ALPHA); + } + if (!show || playQueue.getIndex() + 1 < playQueue.getStreams().size()) { + animate( + binding.playNextButton, + show, + duration, + AnimationType.SCALE_AND_ALPHA); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Repeat, shuffle, mute + //////////////////////////////////////////////////////////////////////////*/ + //region Repeat and shuffle + public void onRepeatClicked() { + if (DEBUG) { + Log.d(TAG, "onRepeatClicked() called"); + } + player.cycleNextRepeatMode(); + } + + public void onShuffleClicked() { + if (DEBUG) { + Log.d(TAG, "onShuffleClicked() called"); + } + player.toggleShuffleModeEnabled(); + } + + @Override + public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + super.onRepeatModeChanged(repeatMode); + setRepeatModeButton(binding.repeatButton, repeatMode); + } + + @Override + public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { + super.onShuffleModeEnabledChanged(shuffleModeEnabled); + setShuffleButton(shuffleModeEnabled); + } + + @Override + public void onMuteUnmuteChanged(final boolean isMuted) { + super.onMuteUnmuteChanged(isMuted); + setMuteButton(isMuted); + } + + private void setRepeatModeButton(final AppCompatImageButton imageButton, + @RepeatMode final int repeatMode) { + switch (repeatMode) { + case REPEAT_MODE_OFF: + imageButton.setImageResource(R.drawable.exo_controls_repeat_off); + break; + case REPEAT_MODE_ONE: + imageButton.setImageResource(R.drawable.exo_controls_repeat_one); + break; + case REPEAT_MODE_ALL: + imageButton.setImageResource(R.drawable.exo_controls_repeat_all); + break; + } + } + + private void setMuteButton(final boolean isMuted) { + binding.switchMute.setImageDrawable(AppCompatResources.getDrawable(context, isMuted + ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); + } + + private void setShuffleButton(final boolean shuffled) { + binding.shuffleButton.setImageAlpha(shuffled ? 255 : 77); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // ExoPlayer listeners (that didn't fit in other categories) + //////////////////////////////////////////////////////////////////////////*/ + //region ExoPlayer listeners (that didn't fit in other categories) + @Override + public void onTextTracksChanged(@NonNull final Tracks currentTracks) { + super.onTextTracksChanged(currentTracks); + + final boolean trackTypeTextSupported = !currentTracks.containsType(C.TRACK_TYPE_TEXT) + || currentTracks.isTypeSupported(C.TRACK_TYPE_TEXT, false); + if (getPlayer().getTrackSelector().getCurrentMappedTrackInfo() == null + || !trackTypeTextSupported) { + binding.captionTextView.setVisibility(View.GONE); + return; + } + + // Extract all loaded languages + final List textTracks = currentTracks + .getGroups() + .stream() + .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) + .collect(Collectors.toList()); + final List availableLanguages = textTracks.stream() + .map(Tracks.Group::getMediaTrackGroup) + .filter(textTrack -> textTrack.length > 0) + .map(textTrack -> textTrack.getFormat(0).language) + .collect(Collectors.toList()); + + // Find selected text track + final Optional selectedTracks = textTracks.stream() + .filter(Tracks.Group::isSelected) + .filter(info -> info.getMediaTrackGroup().length >= 1) + .map(info -> info.getMediaTrackGroup().getFormat(0)) + .findFirst(); + + // Build UI + buildCaptionMenu(availableLanguages); + //noinspection SimplifyOptionalCallChains + if (player.getTrackSelector().getParameters().getRendererDisabled( + player.getCaptionRendererIndex()) || !selectedTracks.isPresent()) { + binding.captionTextView.setText(R.string.caption_none); + } else { + binding.captionTextView.setText(selectedTracks.get().language); + } + binding.captionTextView.setVisibility( + availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); + } + + @Override + public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { + super.onPlaybackParametersChanged(playbackParameters); + binding.playbackSpeed.setText(formatSpeed(playbackParameters.speed)); + } + + @Override + public void onRenderedFirstFrame() { + super.onRenderedFirstFrame(); + //TODO check if this causes black screen when switching to fullscreen + animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION); + } + + @Override + public void onCues(@NonNull List cues) { + super.onCues(cues); + binding.subtitleView.setCues(cues); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Metadata & stream related views + //////////////////////////////////////////////////////////////////////////*/ + //region Metadata & stream related views + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + + updateStreamRelatedViews(); + + binding.titleTextView.setText(info.getName()); + binding.channelTextView.setText(info.getUploaderName()); + + this.seekbarPreviewThumbnailHolder.resetFrom(player.getContext(), info.getPreviewFrames()); + } + + private void updateStreamRelatedViews() { + //noinspection SimplifyOptionalCallChains + if (!player.getCurrentStreamInfo().isPresent()) { + return; + } + final StreamInfo info = player.getCurrentStreamInfo().get(); + + binding.qualityTextView.setVisibility(View.GONE); + binding.playbackSpeed.setVisibility(View.GONE); + + binding.playbackEndTime.setVisibility(View.GONE); + binding.playbackLiveSync.setVisibility(View.GONE); + + switch (info.getStreamType()) { + case AUDIO_STREAM: + case POST_LIVE_AUDIO_STREAM: + binding.surfaceView.setVisibility(View.GONE); + binding.endScreen.setVisibility(View.VISIBLE); + binding.playbackEndTime.setVisibility(View.VISIBLE); + break; + + case AUDIO_LIVE_STREAM: + binding.surfaceView.setVisibility(View.GONE); + binding.endScreen.setVisibility(View.VISIBLE); + binding.playbackLiveSync.setVisibility(View.VISIBLE); + break; + + case LIVE_STREAM: + binding.surfaceView.setVisibility(View.VISIBLE); + binding.endScreen.setVisibility(View.GONE); + binding.playbackLiveSync.setVisibility(View.VISIBLE); + break; + + case VIDEO_STREAM: + case POST_LIVE_STREAM: + //noinspection SimplifyOptionalCallChains + if (player.getCurrentMetadata() != null + && !player.getCurrentMetadata().getMaybeQuality().isPresent() + || (info.getVideoStreams().isEmpty() + && info.getVideoOnlyStreams().isEmpty())) { + break; + } + + buildQualityMenu(); + + binding.qualityTextView.setVisibility(View.VISIBLE); + binding.surfaceView.setVisibility(View.VISIBLE); + default: + binding.endScreen.setVisibility(View.GONE); + binding.playbackEndTime.setVisibility(View.VISIBLE); + break; + } + + buildPlaybackSpeedMenu(); + binding.playbackSpeed.setVisibility(View.VISIBLE); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Popup menus ("popup" means that they pop up, not that they belong to the popup player) + //////////////////////////////////////////////////////////////////////////*/ + //region Popup menus ("popup" means that they pop up, not that they belong to the popup player) + private void buildQualityMenu() { + if (qualityPopupMenu == null) { + return; + } + qualityPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_QUALITY); + + @Nullable final List availableStreams + = Optional.ofNullable(player.getCurrentMetadata()) + .flatMap(MediaItemTag::getMaybeQuality) + .map(MediaItemTag.Quality::getSortedVideoStreams) + .orElse(null); + if (availableStreams == null) { + return; + } + + for (int i = 0; i < availableStreams.size(); i++) { + final VideoStream videoStream = availableStreams.get(i); + qualityPopupMenu.getMenu().add(POPUP_MENU_ID_QUALITY, i, Menu.NONE, MediaFormat + .getNameById(videoStream.getFormatId()) + " " + videoStream.getResolution()); + } + final VideoStream selectedVideoStream = player.getSelectedVideoStream(); + if (selectedVideoStream != null) { + binding.qualityTextView.setText(selectedVideoStream.getResolution()); + } + qualityPopupMenu.setOnMenuItemClickListener(this); + qualityPopupMenu.setOnDismissListener(this); + } + + private void buildPlaybackSpeedMenu() { + if (playbackSpeedPopupMenu == null) { + return; + } + playbackSpeedPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_PLAYBACK_SPEED); + + for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) { + playbackSpeedPopupMenu.getMenu().add(POPUP_MENU_ID_PLAYBACK_SPEED, i, Menu.NONE, + formatSpeed(PLAYBACK_SPEEDS[i])); + } + binding.playbackSpeed.setText(formatSpeed(player.getPlaybackSpeed())); + playbackSpeedPopupMenu.setOnMenuItemClickListener(this); + playbackSpeedPopupMenu.setOnDismissListener(this); + } + + private void buildCaptionMenu(@NonNull final List availableLanguages) { + if (captionPopupMenu == null) { + return; + } + captionPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_CAPTION); + + captionPopupMenu.setOnDismissListener(this); + + // Add option for turning off caption + final MenuItem captionOffItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, + 0, Menu.NONE, R.string.caption_none); + captionOffItem.setOnMenuItemClickListener(menuItem -> { + final int textRendererIndex = player.getCaptionRendererIndex(); + if (textRendererIndex != RENDERER_UNAVAILABLE) { + player.getTrackSelector().setParameters(player.getTrackSelector() + .buildUponParameters().setRendererDisabled(textRendererIndex, true)); + } + player.getPrefs().edit() + .remove(context.getString(R.string.caption_user_set_key)).apply(); + return true; + }); + + // Add all available captions + for (int i = 0; i < availableLanguages.size(); i++) { + final String captionLanguage = availableLanguages.get(i); + final MenuItem captionItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, + i + 1, Menu.NONE, captionLanguage); + captionItem.setOnMenuItemClickListener(menuItem -> { + final int textRendererIndex = player.getCaptionRendererIndex(); + if (textRendererIndex != RENDERER_UNAVAILABLE) { + // DefaultTrackSelector will select for text tracks in the following order. + // When multiple tracks share the same rank, a random track will be chosen. + // 1. ANY track exactly matching preferred language name + // 2. ANY track exactly matching preferred language stem + // 3. ROLE_FLAG_CAPTION track matching preferred language stem + // 4. ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND track matching preferred language stem + // This means if a caption track of preferred language is not available, + // then an auto-generated track of that language will be chosen automatically. + player.getTrackSelector().setParameters(player.getTrackSelector() + .buildUponParameters() + .setPreferredTextLanguages(captionLanguage, + PlayerHelper.captionLanguageStemOf(captionLanguage)) + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) + .setRendererDisabled(textRendererIndex, false)); + player.getPrefs().edit().putString(context.getString( + R.string.caption_user_set_key), captionLanguage).apply(); + } + return true; + }); + } + captionPopupMenu.setOnDismissListener(this); + + // apply caption language from previous user preference + final int textRendererIndex = player.getCaptionRendererIndex(); + if (textRendererIndex == RENDERER_UNAVAILABLE) { + return; + } + + // If user prefers to show no caption, then disable the renderer. + // Otherwise, DefaultTrackSelector may automatically find an available caption + // and display that. + final String userPreferredLanguage = + player.getPrefs().getString(context.getString(R.string.caption_user_set_key), null); + if (userPreferredLanguage == null) { + player.getTrackSelector().setParameters(player.getTrackSelector().buildUponParameters() + .setRendererDisabled(textRendererIndex, true)); + return; + } + + // Only set preferred language if it does not match the user preference, + // otherwise there might be an infinite cycle at onTextTracksChanged. + final List selectedPreferredLanguages = + player.getTrackSelector().getParameters().preferredTextLanguages; + if (!selectedPreferredLanguages.contains(userPreferredLanguage)) { + player.getTrackSelector().setParameters(player.getTrackSelector().buildUponParameters() + .setPreferredTextLanguages(userPreferredLanguage, + PlayerHelper.captionLanguageStemOf(userPreferredLanguage)) + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) + .setRendererDisabled(textRendererIndex, false)); + } + } + + protected abstract void onPlaybackSpeedClicked(); + + private void onQualityClicked() { + qualityPopupMenu.show(); + isSomePopupMenuVisible = true; + + final VideoStream videoStream = player.getSelectedVideoStream(); + if (videoStream != null) { + //noinspection SetTextI18n + binding.qualityTextView.setText(MediaFormat.getNameById(videoStream.getFormatId()) + + " " + videoStream.getResolution()); + } + + player.saveWasPlaying(); + } + + /** + * Called when an item of the quality selector or the playback speed selector is selected. + */ + @Override + public boolean onMenuItemClick(@NonNull final MenuItem menuItem) { + if (DEBUG) { + Log.d(TAG, "onMenuItemClick() called with: " + + "menuItem = [" + menuItem + "], " + + "menuItem.getItemId = [" + menuItem.getItemId() + "]"); + } + + if (menuItem.getGroupId() == POPUP_MENU_ID_QUALITY) { + final int menuItemIndex = menuItem.getItemId(); + @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); + //noinspection SimplifyOptionalCallChains + if (currentMetadata == null || !currentMetadata.getMaybeQuality().isPresent()) { + return true; + } + + final MediaItemTag.Quality quality = currentMetadata.getMaybeQuality().get(); + final List availableStreams = quality.getSortedVideoStreams(); + final int selectedStreamIndex = quality.getSelectedVideoStreamIndex(); + if (selectedStreamIndex == menuItemIndex|| availableStreams.size() <= menuItemIndex) { + return true; + } + + player.saveStreamProgressState(); //TODO added, check if good + final String newResolution = availableStreams.get(menuItemIndex).getResolution(); + player.setRecovery(); + player.setPlaybackQuality(newResolution); + player.reloadPlayQueueManager(); + + binding.qualityTextView.setText(menuItem.getTitle()); + return true; + } else if (menuItem.getGroupId() == POPUP_MENU_ID_PLAYBACK_SPEED) { + final int speedIndex = menuItem.getItemId(); + final float speed = PLAYBACK_SPEEDS[speedIndex]; + + player.setPlaybackSpeed(speed); + binding.playbackSpeed.setText(formatSpeed(speed)); + } + + return false; + } + + /** + * Called when some popup menu is dismissed. + */ + @Override + public void onDismiss(@Nullable final PopupMenu menu) { + if (DEBUG) { + Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); + } + isSomePopupMenuVisible = false; //TODO check if this works + final VideoStream selectedVideoStream = player.getSelectedVideoStream(); + if (selectedVideoStream != null) { + binding.qualityTextView.setText(selectedVideoStream.getResolution()); + } + if (player.isPlaying()) { + hideControls(DEFAULT_CONTROLS_DURATION, 0); + hideSystemUIIfNeeded(); + } + } + + private void onCaptionClicked() { + if (DEBUG) { + Log.d(TAG, "onCaptionClicked() called"); + } + captionPopupMenu.show(); + isSomePopupMenuVisible = true; + } + + public boolean isSomePopupMenuVisible() { + return isSomePopupMenuVisible; + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Captions (text tracks) + //////////////////////////////////////////////////////////////////////////*/ + //region Captions (text tracks) + private void setupSubtitleView() { + setupSubtitleView(PlayerHelper.getCaptionScale(context)); + final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context); + binding.subtitleView.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT); + binding.subtitleView.setStyle(captionStyle); + } + + protected abstract void setupSubtitleView(final float captionScale); + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Click listeners + //////////////////////////////////////////////////////////////////////////*/ + //region Click listeners + @Override + public void onClick(final View v) { + if (DEBUG) { + Log.d(TAG, "onClick() called with: v = [" + v + "]"); + } + if (v.getId() == binding.resizeTextView.getId()) { + onResizeClicked(); + } else if (v.getId() == binding.captionTextView.getId()) { + onCaptionClicked(); + } else if (v.getId() == binding.playbackLiveSync.getId()) { + player.seekToDefault(); + } else if (v.getId() == binding.playPauseButton.getId()) { + player.playPause(); + } else if (v.getId() == binding.playPreviousButton.getId()) { + player.playPrevious(); + } else if (v.getId() == binding.playNextButton.getId()) { + player.playNext(); + } else if (v.getId() == binding.moreOptionsButton.getId()) { + onMoreOptionsClicked(); + } else if (v.getId() == binding.share.getId()) { + final PlayQueueItem currentItem = player.getCurrentItem(); + if (currentItem != null) { + ShareUtils.shareText(context, currentItem.getTitle(), + player.getVideoUrlAtCurrentTime(), currentItem.getThumbnailUrl()); + } + } else if (v.getId() == binding.playWithKodi.getId()) { + onPlayWithKodiClicked(); + } else if (v.getId() == binding.openInBrowser.getId()) { + onOpenInBrowserClicked(); + } else if (v.getId() == binding.fullScreenButton.getId()) { + player.setRecovery(); + NavigationHelper.playOnMainPlayer(context, player.getPlayQueue(), true); + return; + } else if (v.getId() == binding.switchMute.getId()) { + player.toggleMute(); + } else if (v.getId() == binding.playerCloseButton.getId()) { + context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)); + } else if (v.getId() == binding.playbackSpeed.getId()) { + onPlaybackSpeedClicked(); + } else if (v.getId() == binding.qualityTextView.getId()) { + onQualityClicked(); + } + + manageControlsAfterOnClick(v); + } + + /** + * Manages the controls after a click occurred on the player UI. + * @param v – The view that was clicked + */ + public void manageControlsAfterOnClick(@NonNull final View v) { + if (player.getCurrentState() == STATE_COMPLETED) { + return; + } + + controlsVisibilityHandler.removeCallbacksAndMessages(null); + showHideShadow(true, DEFAULT_CONTROLS_DURATION); + animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, + AnimationType.ALPHA, 0, () -> { + if (player.getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible) { + if (v.getId() == binding.playPauseButton.getId() + // Hide controls in fullscreen immediately + || (v.getId() == binding.screenRotationButton.getId() + && isFullscreen())) { + hideControls(0, 0); + } else { + hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); + } + } + }); + } + + @Override + public boolean onLongClick(final View v) { + if (v.getId() == binding.share.getId()) { + ShareUtils.copyToClipboard(context, player.getVideoUrlAtCurrentTime()); + } + return true; + } + + public boolean onKeyDown(final int keyCode) { + switch (keyCode) { + default: + break; + case KeyEvent.KEYCODE_BACK: + if (DeviceUtils.isTv(context) && isControlsVisible()) { + hideControls(0, 0); + return true; + } + break; + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_CENTER: + if ((binding.getRoot().hasFocus() && !binding.playbackControlRoot.hasFocus()) + || isAnyListViewOpen()) { + // do not interfere with focus in playlist and play queue etc. + return false; + } + + if (player.getCurrentState() == org.schabi.newpipe.player.Player.STATE_BLOCKED) { + return true; + } + + if (isControlsVisible()) { + hideControls(DEFAULT_CONTROLS_DURATION, DPAD_CONTROLS_HIDE_TIME); + } else { + binding.playPauseButton.requestFocus(); + showControlsThenHide(); + showSystemUIPartially(); + return true; + } + break; + } + + return false; + } + + private void onMoreOptionsClicked() { + if (DEBUG) { + Log.d(TAG, "onMoreOptionsClicked() called"); + } + + final boolean isMoreControlsVisible = + binding.secondaryControls.getVisibility() == View.VISIBLE; + + animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, + isMoreControlsVisible ? 0 : 180); + animate(binding.secondaryControls, !isMoreControlsVisible, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA, 0, () -> { + // Fix for a ripple effect on background drawable. + // When view returns from GONE state it takes more milliseconds than returning + // from INVISIBLE state. And the delay makes ripple background end to fast + if (isMoreControlsVisible) { + binding.secondaryControls.setVisibility(View.INVISIBLE); + } + }); + showControls(DEFAULT_CONTROLS_DURATION); + } + + private void onPlayWithKodiClicked() { + if (player.getCurrentMetadata() != null) { + player.pause(); + try { + NavigationHelper.playWithKore(context, Uri.parse(player.getVideoUrl())); + } catch (final Exception e) { + if (DEBUG) { + Log.i(TAG, "Failed to start kore", e); + } + KoreUtils.showInstallKoreDialog(player.getContext()); + } + } + } + + private void onOpenInBrowserClicked() { + player.getCurrentStreamInfo().ifPresent(streamInfo -> + ShareUtils.openUrlInBrowser(player.getContext(), streamInfo.getOriginalUrl())); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Video size, resize, orientation, fullscreen + //////////////////////////////////////////////////////////////////////////*/ + //region Video size, resize, orientation, fullscreen + protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { + binding.surfaceView.setResizeMode(resizeMode); + binding.resizeTextView.setText(PlayerHelper.resizeTypeOf(context, resizeMode)); + } + + void onResizeClicked() { + setResizeMode(nextResizeModeAndSaveToPrefs(player, binding.surfaceView.getResizeMode())); + } + + @Override + public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + super.onVideoSizeChanged(videoSize); + binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // SurfaceHolderCallback helpers + //////////////////////////////////////////////////////////////////////////*/ + //region SurfaceHolderCallback helpers + private void setupVideoSurface() { + // make sure there is nothing left over from previous calls + cleanupVideoSurface(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer()); + binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); + final Surface surface = binding.surfaceView.getHolder().getSurface(); + + // ensure player is using an unreleased surface, which the surfaceView might not be + // when starting playback on background or during player switching + if (surface.isValid()) { + // initially set the surface manually otherwise + // onRenderedFirstFrame() will not be called + player.getExoPlayer().setVideoSurface(surface); + } + + } else { + player.getExoPlayer().setVideoSurfaceView(binding.surfaceView); + } + } + + private void cleanupVideoSurface() { + final Optional exoPlayer = Optional.ofNullable(player.getExoPlayer()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + if (surfaceHolderCallback != null) { + binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); + surfaceHolderCallback.release(); + surfaceHolderCallback = null; + } + exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurface(null)); + } else { + exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurfaceView(null)); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Getters + //////////////////////////////////////////////////////////////////////////*/ + //region Getters + public PlayerBinding getBinding() { + return binding; + } + + public GestureDetector getGestureDetector() { + return gestureDetector; + } + //endregion +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 849574171..0eb58f7a9 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -26,7 +26,7 @@ import androidx.preference.PreferenceViewHolder; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.NotificationConstants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; @@ -61,7 +61,7 @@ public class NotificationActionsPreference extends Preference { public void onDetached() { super.onDetached(); saveChanges(); - getContext().sendBroadcast(new Intent(MainPlayer.ACTION_RECREATE_NOTIFICATION)); + getContext().sendBroadcast(new Intent(PlayerService.ACTION_RECREATE_NOTIFICATION)); } 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 c40b1a430..36b2bd46d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -50,8 +50,8 @@ import org.schabi.newpipe.local.history.StatisticsPlaylistFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment; -import org.schabi.newpipe.player.MainPlayer; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.PlayQueueActivity; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.helper.PlayerHelper; @@ -91,7 +91,7 @@ public final class NavigationHelper { intent.putExtra(Player.PLAY_QUEUE_KEY, cacheKey); } } - intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.VIDEO.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal()); intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback); return intent; @@ -163,8 +163,8 @@ public final class NavigationHelper { Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); - final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.POPUP.ordinal()); + final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); + intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.POPUP.ordinal()); ContextCompat.startForegroundService(context, intent); } @@ -174,8 +174,8 @@ public final class NavigationHelper { Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT) .show(); - final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.AUDIO.ordinal()); + final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); + intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.AUDIO.ordinal()); ContextCompat.startForegroundService(context, intent); } @@ -184,7 +184,7 @@ public final class NavigationHelper { final PlayQueue queue, final PlayerType playerType) { Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show(); - final Intent intent = getPlayerEnqueueIntent(context, MainPlayer.class, queue); + final Intent intent = getPlayerEnqueueIntent(context, PlayerService.class, queue); intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); ContextCompat.startForegroundService(context, intent); @@ -194,7 +194,7 @@ public final class NavigationHelper { PlayerType playerType = PlayerHolder.getInstance().getType(); if (!PlayerHolder.getInstance().isPlayerOpen()) { Log.e(TAG, "Enqueueing but no player is open; defaulting to background player"); - playerType = MainPlayer.PlayerType.AUDIO; + playerType = PlayerService.PlayerType.AUDIO; } enqueueOnPlayer(context, queue, playerType); @@ -205,10 +205,10 @@ public final class NavigationHelper { PlayerType playerType = PlayerHolder.getInstance().getType(); if (!PlayerHolder.getInstance().isPlayerOpen()) { Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player"); - playerType = MainPlayer.PlayerType.AUDIO; + playerType = PlayerService.PlayerType.AUDIO; } Toast.makeText(context, R.string.enqueued_next, Toast.LENGTH_SHORT).show(); - final Intent intent = getPlayerEnqueueNextIntent(context, MainPlayer.class, queue); + final Intent intent = getPlayerEnqueueNextIntent(context, PlayerService.class, queue); intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); ContextCompat.startForegroundService(context, intent); @@ -414,14 +414,14 @@ public final class NavigationHelper { final boolean switchingPlayers) { final boolean autoPlay; - @Nullable final MainPlayer.PlayerType playerType = PlayerHolder.getInstance().getType(); + @Nullable final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); if (!PlayerHolder.getInstance().isPlayerOpen()) { // no player open autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else if (switchingPlayers) { // switching player to main player autoPlay = PlayerHolder.getInstance().isPlaying(); // keep play/pause state - } else if (playerType == MainPlayer.PlayerType.VIDEO) { + } else if (playerType == PlayerService.PlayerType.MAIN) { // opening new stream while already playing in main player autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else { @@ -436,7 +436,7 @@ public final class NavigationHelper { // Situation when user switches from players to main player. All needed data is // here, we can start watching (assuming newQueue equals playQueue). // Starting directly in fullscreen if the previous player type was popup. - detailFragment.openVideoPlayer(playerType == MainPlayer.PlayerType.POPUP + detailFragment.openVideoPlayer(playerType == PlayerService.PlayerType.POPUP || PlayerHelper.isStartMainPlayerFullscreenEnabled(context)); } else { detailFragment.selectAndLoadVideo(serviceId, url, title, playQueue); diff --git a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt index 649b60494..cbba0a75b 100644 --- a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt +++ b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt @@ -12,8 +12,8 @@ import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.START import androidx.constraintlayout.widget.ConstraintSet import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R -import org.schabi.newpipe.player.event.DisplayPortion -import org.schabi.newpipe.player.event.DoubleTapListener +import org.schabi.newpipe.player.gesture.DisplayPortion +import org.schabi.newpipe.player.gesture.DoubleTapListener class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs), DoubleTapListener { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 97ccd199e..01d842812 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -25,7 +25,7 @@ android:layout_gravity="center_horizontal" app:behavior_hideable="true" app:behavior_peekHeight="0dp" - app:layout_behavior="org.schabi.newpipe.player.event.CustomBottomSheetBehavior" /> + app:layout_behavior="org.schabi.newpipe.player.gesture.CustomBottomSheetBehavior" /> From b3f99645a39005ddfad19f61b98b272c81414470 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Apr 2022 10:48:34 +0200 Subject: [PATCH 029/152] Fix some crashes / issues after player refactor --- .../fragments/detail/VideoDetailFragment.java | 28 ++-- .../org/schabi/newpipe/player/Player.java | 8 +- .../gesture/MainPlayerGestureListener.kt | 15 +-- .../newpipe/player/ui/MainPlayerUi.java | 95 +++++++++----- .../schabi/newpipe/player/ui/PlayerUi.java | 1 - .../newpipe/player/ui/PopupPlayerUi.java | 35 +++-- .../newpipe/player/ui/VideoPlayerUi.java | 123 +++++++++++++----- .../views/player/PlayerFastSeekOverlay.kt | 6 +- 8 files changed, 192 insertions(+), 119 deletions(-) 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 5ecc35034..cb8f0961f 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 @@ -240,10 +240,6 @@ public final class VideoDetailFragment playerUi.ifPresent(MainPlayerUi::toggleFullscreen); } - if (playerIsNotStopped() && player.videoPlayerSelected()) { - addVideoPlayerView(); - } - //noinspection SimplifyOptionalCallChains if (playAfterConnect || (currentInfo != null @@ -335,6 +331,9 @@ public final class VideoDetailFragment @Override public void onResume() { super.onResume(); + if (DEBUG) { + Log.d(TAG, "onResume() called"); + } activity.sendBroadcast(new Intent(ACTION_VIDEO_FRAGMENT_RESUMED)); @@ -1310,22 +1309,14 @@ public final class VideoDetailFragment if (!isPlayerAvailable()) { return; } - - final Optional root = player.UIs().get(VideoPlayerUi.class) - .map(VideoPlayerUi::getBinding) - .map(ViewBinding::getRoot); - - // Check if viewHolder already contains a child TODO TODO whaat - /*if (playerService != null - && root.map(View::getParent).orElse(null) != binding.playerPlaceholder) { - playerService.removeViewFromParent(); - }*/ setHeightThumbnail(); // Prevent from re-adding a view multiple times - if (root.isPresent() && root.get().getParent() == null) { - binding.playerPlaceholder.addView(root.get()); - } + new Handler().post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + })); } private void removeVideoPlayerView() { @@ -1793,9 +1784,6 @@ public final class VideoDetailFragment @Override public void onViewCreated() { - // Video view can have elements visible from popup, - // We hide it here but once it ready the view will be shown in handleIntent() - getRoot().ifPresent(view -> view.setVisibility(View.GONE)); addVideoPlayerView(); } 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 284ab74d8..78e93970c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -485,6 +485,10 @@ public final class Player implements PlaybackListener, Listener { // make sure UIs know whether a service is connected or not UIs.call(PlayerUi::onFragmentListenerSet); } + if (!exoPlayerIsNull()) { + UIs.call(PlayerUi::initPlayer); + UIs.call(PlayerUi::initPlayback); + } } private void initPlayback(@NonNull final PlayQueue queue, @@ -599,7 +603,7 @@ public final class Player implements PlaybackListener, Listener { progressUpdateDisposable.set(null); PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading - UIs.call(PlayerUi::destroy); + UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object } public void setRecovery() { @@ -737,7 +741,7 @@ public final class Player implements PlaybackListener, Listener { case Intent.ACTION_CONFIGURATION_CHANGED: assureCorrectAppLanguage(service); if (DEBUG) { - Log.d(TAG, "onConfigurationChanged() called"); + Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received"); } break; case Intent.ACTION_HEADSET_PLUG: //FIXME diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index 17205fb9a..81e216006 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -1,12 +1,12 @@ package org.schabi.newpipe.player.gesture -import android.app.Activity import android.content.Context import android.util.Log import android.view.MotionEvent import android.view.View import android.view.View.OnTouchListener import android.widget.ProgressBar +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R @@ -29,8 +29,6 @@ import kotlin.math.min class MainPlayerGestureListener( private val playerUi: MainPlayerUi ) : BasePlayerGestureListener(playerUi), OnTouchListener { - private val maxVolume: Int = player.audioReactor.maxVolume - private var isMoving = false override fun onTouch(v: View, event: MotionEvent): Boolean { @@ -41,11 +39,11 @@ class MainPlayerGestureListener( } return when (event.action) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { - v.parent.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) + v.parent?.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) true } MotionEvent.ACTION_UP -> { - v.parent.requestDisallowInterceptTouchEvent(false) + v.parent?.requestDisallowInterceptTouchEvent(false) false } else -> true @@ -68,14 +66,15 @@ class MainPlayerGestureListener( private fun onScrollVolume(distanceY: Float) { // If we just started sliding, change the progress bar to match the system volume if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { - val volumePercent: Float = player.audioReactor.volume / maxVolume.toFloat() + val volumePercent: Float = + player.audioReactor.volume / player.audioReactor.maxVolume.toFloat() binding.volumeProgressBar.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt() } binding.volumeProgressBar.incrementProgressBy(distanceY.toInt()) val currentProgressPercent: Float = binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH - val currentVolume = (maxVolume * currentProgressPercent).toInt() + val currentVolume = (player.audioReactor.maxVolume * currentProgressPercent).toInt() player.audioReactor.volume = currentVolume if (DEBUG) { Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume") @@ -102,7 +101,7 @@ class MainPlayerGestureListener( } private fun onScrollBrightness(distanceY: Float) { - val parent: Activity = playerUi.parentActivity + val parent: AppCompatActivity = playerUi.parentActivity.orElse(null) ?: return val window = parent.window val layoutParams = window.attributes val bar: ProgressBar = binding.brightnessProgressBar diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 10ed424ba..7c60671dd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -13,12 +13,13 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAct import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; +import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Color; -import android.os.Build; import android.os.Handler; import android.provider.Settings; import android.util.DisplayMetrics; @@ -28,7 +29,6 @@ import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; -import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.LinearLayout; @@ -37,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.content.res.AppCompatResources; +import androidx.fragment.app.FragmentActivity; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; @@ -68,8 +69,9 @@ import org.schabi.newpipe.util.external_communication.KoreUtils; import java.util.List; import java.util.Objects; +import java.util.Optional; -public final class MainPlayerUi extends VideoPlayerUi { +public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutChangeListener { private static final String TAG = MainPlayerUi.class.getSimpleName(); private boolean isFullscreen = false; @@ -113,7 +115,6 @@ public final class MainPlayerUi extends VideoPlayerUi { super.setupAfterIntent(); - binding.getRoot().setVisibility(View.VISIBLE); initVideoPlayer(); // Android TV: without it focus will frame the whole player binding.playPauseButton.requestFocus(); @@ -139,7 +140,8 @@ public final class MainPlayerUi extends VideoPlayerUi { binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked()); binding.addToPlaylistButton.setOnClickListener(v -> - player.onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager())); + getParentActivity().map(FragmentActivity::getSupportFragmentManager) + .ifPresent(player::onAddToPlaylistClicked)); settingsContentObserver = new ContentObserver(new Handler()) { @Override @@ -151,7 +153,20 @@ public final class MainPlayerUi extends VideoPlayerUi { Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, settingsContentObserver); - binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); + binding.getRoot().addOnLayoutChangeListener(this); + } + + @Override + protected void deinitListeners() { + super.deinitListeners(); + + binding.queueButton.setOnClickListener(null); + binding.segmentsButton.setOnClickListener(null); + binding.addToPlaylistButton.setOnClickListener(null); + + context.getContentResolver().unregisterContentObserver(settingsContentObserver); + + binding.getRoot().removeOnLayoutChangeListener(this); } @Override @@ -178,7 +193,6 @@ public final class MainPlayerUi extends VideoPlayerUi { @Override public void destroy() { super.destroy(); - context.getContentResolver().unregisterContentObserver(settingsContentObserver); // Exit from fullscreen when user closes the player via notification if (isFullscreen) { @@ -324,9 +338,10 @@ public final class MainPlayerUi extends VideoPlayerUi { player.useVideoSource(false); break; case MINIMIZE_ON_EXIT_MODE_POPUP: - player.setRecovery(); - NavigationHelper.playOnPopupPlayer(getParentActivity(), - player.getPlayQueue(), true); + getParentActivity().ifPresent(activity -> { + player.setRecovery(); + NavigationHelper.playOnPopupPlayer(activity, player.getPlayQueue(), true); + }); break; case MINIMIZE_ON_EXIT_MODE_NONE: default: player.pause(); @@ -385,14 +400,15 @@ public final class MainPlayerUi extends VideoPlayerUi { @Override public void showSystemUIPartially() { if (isFullscreen) { - final Window window = getParentActivity().getWindow(); - window.setStatusBarColor(Color.TRANSPARENT); - window.setNavigationBarColor(Color.TRANSPARENT); - final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - window.getDecorView().setSystemUiVisibility(visibility); - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getParentActivity().map(Activity::getWindow).ifPresent(window -> { + window.setStatusBarColor(Color.TRANSPARENT); + window.setNavigationBarColor(Color.TRANSPARENT); + final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + window.getDecorView().setSystemUiVisibility(visibility); + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + }); } } @@ -476,8 +492,9 @@ public final class MainPlayerUi extends VideoPlayerUi { //region Gestures @SuppressWarnings("checkstyle:ParameterNumber") - private void onLayoutChange(final View view, final int l, final int t, final int r, final int b, - final int ol, final int ot, final int or, final int ob) { + @Override + public void onLayoutChange(final View view, final int l, final int t, final int r, final int b, + final int ol, final int ot, final int or, final int ob) { if (l != ol || t != ot || r != or || b != ob) { // Use smaller value to be consistent between screen orientations // (and to make usage easier) @@ -501,9 +518,8 @@ public final class MainPlayerUi extends VideoPlayerUi { private void setInitialGestureValues() { if (player.getAudioReactor() != null) { - final float currentVolumeNormalized = - (float) player.getAudioReactor().getVolume() - / player.getAudioReactor().getMaxVolume(); + final float currentVolumeNormalized = (float) player.getAudioReactor().getVolume() + / player.getAudioReactor().getMaxVolume(); binding.volumeProgressBar.setProgress( (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized)); } @@ -714,7 +730,7 @@ public final class MainPlayerUi extends VideoPlayerUi { @Override public void held(final PlayQueueItem item, final View view) { @Nullable final PlayQueue playQueue = player.getPlayQueue(); - @Nullable final AppCompatActivity parentActivity = getParentActivity(); + @Nullable final AppCompatActivity parentActivity = getParentActivity().orElse(null); if (playQueue != null && parentActivity != null && playQueue.indexOf(item) != -1) { openPopupMenu(player.getPlayQueue(), item, view, true, parentActivity.getSupportFragmentManager(), context); @@ -801,10 +817,15 @@ public final class MainPlayerUi extends VideoPlayerUi { @Override protected void onPlaybackSpeedClicked() { + final AppCompatActivity activity = getParentActivity().orElse(null); + if (activity == null) { + return; + } + PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), player.getPlaybackSkipSilence(), (speed, pitch, skipSilence) -> player.setPlaybackParameters(speed, pitch, skipSilence)) - .show(getParentActivity().getSupportFragmentManager(), null); + .show(activity.getSupportFragmentManager(), null); } @Override @@ -876,15 +897,15 @@ public final class MainPlayerUi extends VideoPlayerUi { } isFullscreen = !isFullscreen; - if (!isFullscreen) { - // Apply window insets because Android will not do it when orientation changes - // from landscape to portrait (open vertical video to reproduce) - binding.playbackControlRoot.setPadding(0, 0, 0, 0); - } else { + if (isFullscreen) { // Android needs tens milliseconds to send new insets but a user is able to see // how controls changes it's position from `0` to `nav bar height` padding. // So just hide the controls to hide this visual inconsistency hideControls(0, 0); + } else { + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait (open vertical video to reproduce) + binding.playbackControlRoot.setPadding(0, 0, 0, 0); } fragmentListener.onFullscreenStateChanged(isFullscreen); @@ -924,14 +945,22 @@ public final class MainPlayerUi extends VideoPlayerUi { return binding; } - public AppCompatActivity getParentActivity() { - return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext(); + public Optional getParentActivity() { + final ViewParent rootParent = binding.getRoot().getParent(); + if (rootParent instanceof ViewGroup) { + final Context activity = ((ViewGroup) rootParent).getContext(); + if (activity instanceof AppCompatActivity) { + return Optional.of((AppCompatActivity) activity); + } + } + return Optional.empty(); } public boolean isLandscape() { // DisplayMetrics from activity context knows about MultiWindow feature // while DisplayMetrics from app context doesn't - return DeviceUtils.isLandscape(getParentActivity()); + return DeviceUtils.isLandscape( + getParentActivity().map(Context.class::cast).orElse(player.getService())); } //endregion } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index fd63790d6..15b468fb7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -19,7 +19,6 @@ import org.schabi.newpipe.player.Player; import java.util.List; public abstract class PlayerUi { - private static final String TAG = PlayerUi.class.getSimpleName(); @NonNull protected Context context; @NonNull protected Player player; 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 b8a26a233..7df9102b7 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 @@ -69,11 +69,9 @@ public final class PopupPlayerUi extends VideoPlayerUi { @Override public void setupAfterIntent() { - setupElementsVisibility(); - binding.getRoot().setVisibility(View.VISIBLE); + super.setupAfterIntent(); initPopup(); initPopupCloseOverlay(); - binding.playPauseButton.requestFocus(); } @Override @@ -103,6 +101,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { binding.loadingPanel.setMinimumHeight(popupLayoutParams.height); windowManager.addView(binding.getRoot(), popupLayoutParams); + setupVideoSurfaceIfNeeded(); // now there is a parent, we can setup video surface // Popup doesn't have aspectRatio selector, using FIT automatically setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); @@ -304,25 +303,23 @@ public final class PopupPlayerUi extends VideoPlayerUi { } public void removePopupFromView() { - if (windowManager != null) { - // wrap in try-catch since it could sometimes generate errors randomly - try { - if (popupHasParent()) { - windowManager.removeView(binding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup from window manager", e); + // wrap in try-catch since it could sometimes generate errors randomly + try { + if (popupHasParent()) { + windowManager.removeView(binding.getRoot()); } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup from window manager", e); + } - try { - final boolean closeOverlayHasParent = closeOverlayBinding != null - && closeOverlayBinding.getRoot().getParent() != null; - if (closeOverlayHasParent) { - windowManager.removeView(closeOverlayBinding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup overlay from window manager", e); + try { + final boolean closeOverlayHasParent = closeOverlayBinding != null + && closeOverlayBinding.getRoot().getParent() != null; + if (closeOverlayHasParent) { + windowManager.removeView(closeOverlayBinding.getRoot()); } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup overlay from window manager", e); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 99ecb5540..24cdb8908 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -32,7 +32,6 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; -import android.view.Surface; import android.view.View; import android.widget.LinearLayout; import android.widget.RelativeLayout; @@ -107,6 +106,7 @@ public abstract class VideoPlayerUi extends PlayerUi protected PlayerBinding binding; private final Handler controlsVisibilityHandler = new Handler(); @Nullable private SurfaceHolderCallback surfaceHolderCallback; + boolean surfaceIsSetup = false; @Nullable private Bitmap thumbnail = null; @@ -130,6 +130,7 @@ public abstract class VideoPlayerUi extends PlayerUi private GestureDetector gestureDetector; private BasePlayerGestureListener playerGestureListener; + @Nullable private View.OnLayoutChangeListener onLayoutChangeListener = null; @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = new SeekbarPreviewThumbnailHolder(); @@ -138,6 +139,7 @@ public abstract class VideoPlayerUi extends PlayerUi @NonNull final PlayerBinding playerBinding) { super(player); binding = playerBinding; + setupFromView(); } @@ -222,8 +224,8 @@ public abstract class VideoPlayerUi extends PlayerUi // PlaybackControlRoot already consumed window insets but we should pass them to // player_overlays and fast_seek_overlay too. Without it they will be off-centered. - binding.playbackControlRoot.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + onLayoutChangeListener + = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { binding.playerOverlays.setPadding( v.getPaddingLeft(), v.getPaddingTop(), @@ -240,7 +242,43 @@ public abstract class VideoPlayerUi extends PlayerUi fastSeekParams.topMargin = -v.getPaddingBottom(); fastSeekParams.rightMargin = -v.getPaddingLeft(); fastSeekParams.bottomMargin = -v.getPaddingTop(); - }); + }; + binding.playbackControlRoot.addOnLayoutChangeListener(onLayoutChangeListener); + } + + protected void deinitListeners() { + binding.qualityTextView.setOnClickListener(null); + binding.playbackSpeed.setOnClickListener(null); + binding.playbackSeekBar.setOnSeekBarChangeListener(null); + binding.captionTextView.setOnClickListener(null); + binding.resizeTextView.setOnClickListener(null); + binding.playbackLiveSync.setOnClickListener(null); + + binding.getRoot().setOnTouchListener(null); + playerGestureListener = null; + gestureDetector = null; + + binding.repeatButton.setOnClickListener(null); + binding.shuffleButton.setOnClickListener(null); + + binding.playPauseButton.setOnClickListener(null); + binding.playPreviousButton.setOnClickListener(null); + binding.playNextButton.setOnClickListener(null); + + binding.moreOptionsButton.setOnClickListener(null); + binding.moreOptionsButton.setOnLongClickListener(null); + binding.share.setOnClickListener(null); + binding.share.setOnLongClickListener(null); + binding.fullScreenButton.setOnClickListener(null); + binding.screenRotationButton.setOnClickListener(null); + binding.playWithKodi.setOnClickListener(null); + binding.openInBrowser.setOnClickListener(null); + binding.playerCloseButton.setOnClickListener(null); + binding.switchMute.setOnClickListener(null); + + ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, null); + + binding.playbackControlRoot.removeOnLayoutChangeListener(onLayoutChangeListener); } /** @@ -304,18 +342,25 @@ public abstract class VideoPlayerUi extends PlayerUi playerGestureListener.doubleTapControls(binding.fastSeekOverlay); } + public void deinitPlayerSeekOverlay() { + binding.fastSeekOverlay + .seekSecondsSupplier(null) + .performListener(null); + } + @Override public void setupAfterIntent() { super.setupAfterIntent(); setupElementsVisibility(); setupElementsSize(context.getResources()); + binding.getRoot().setVisibility(View.VISIBLE); + binding.playPauseButton.requestFocus(); } @Override public void initPlayer() { super.initPlayer(); - setupVideoSurface(); - setupFromView(); + setupVideoSurfaceIfNeeded(); } @Override @@ -331,7 +376,7 @@ public abstract class VideoPlayerUi extends PlayerUi @Override public void destroyPlayer() { super.destroyPlayer(); - cleanupVideoSurface(); + clearVideoSurface(); } @Override @@ -340,6 +385,8 @@ public abstract class VideoPlayerUi extends PlayerUi if (binding != null) { binding.endScreen.setImageBitmap(null); } + deinitPlayerSeekOverlay(); + deinitListeners(); } protected void setupElementsVisibility() { @@ -1470,40 +1517,50 @@ public abstract class VideoPlayerUi extends PlayerUi // SurfaceHolderCallback helpers //////////////////////////////////////////////////////////////////////////*/ //region SurfaceHolderCallback helpers - private void setupVideoSurface() { - // make sure there is nothing left over from previous calls - cleanupVideoSurface(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 - surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer()); - binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); - final Surface surface = binding.surfaceView.getHolder().getSurface(); + /** + * Connects the video surface to the exo player. This can be called anytime without the risk for + * issues to occur, since the player will run just fine when no surface is connected. Therefore + * the video surface will be setup only when all of these conditions are true: it is not already + * setup (this just prevents wasting resources to setup the surface again), there is an exo + * player, the root view is attached to a parent and the surface view is valid/unreleased (the + * latter two conditions prevent "The surface has been released" errors). So this function can + * be called many times and even while the UI is in unready states. + */ + public void setupVideoSurfaceIfNeeded() { + if (!surfaceIsSetup && player.getExoPlayer() != null + && binding.getRoot().getParent() != null) { + // make sure there is nothing left over from previous calls + clearVideoSurface(); - // ensure player is using an unreleased surface, which the surfaceView might not be - // when starting playback on background or during player switching - if (surface.isValid()) { - // initially set the surface manually otherwise - // onRenderedFirstFrame() will not be called - player.getExoPlayer().setVideoSurface(surface); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer()); + binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); + + // ensure player is using an unreleased surface, which the surfaceView might not be + // when starting playback on background or during player switching + if (binding.surfaceView.getHolder().getSurface().isValid()) { + // initially set the surface manually otherwise + // onRenderedFirstFrame() will not be called + player.getExoPlayer().setVideoSurfaceHolder(binding.surfaceView.getHolder()); + } + } else { + player.getExoPlayer().setVideoSurfaceView(binding.surfaceView); } - } else { - player.getExoPlayer().setVideoSurfaceView(binding.surfaceView); + surfaceIsSetup = true; } } - private void cleanupVideoSurface() { - final Optional exoPlayer = Optional.ofNullable(player.getExoPlayer()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 - if (surfaceHolderCallback != null) { - binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); - surfaceHolderCallback.release(); - surfaceHolderCallback = null; - } - exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurface(null)); - } else { - exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurfaceView(null)); + private void clearVideoSurface() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M // >=API23 + && surfaceHolderCallback != null) { + binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); + surfaceHolderCallback.release(); + surfaceHolderCallback = null; } + Optional.ofNullable(player.getExoPlayer()).ifPresent(ExoPlayer::clearVideoSurface); + surfaceIsSetup = false; } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt index cbba0a75b..d0782e1a1 100644 --- a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt +++ b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt @@ -38,14 +38,14 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : private var performListener: PerformListener? = null - fun performListener(listener: PerformListener) = apply { + fun performListener(listener: PerformListener?) = apply { performListener = listener } private var seekSecondsSupplier: () -> Int = { 0 } - fun seekSecondsSupplier(supplier: () -> Int) = apply { - seekSecondsSupplier = supplier + fun seekSecondsSupplier(supplier: (() -> Int)?) = apply { + seekSecondsSupplier = supplier ?: { 0 } } // Indicates whether this (double) tap is the first of a series From 0bba1d95dee627843bc97224e6e04260931fa9a8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:14:28 +0200 Subject: [PATCH 030/152] Move all notification-related calls to NotificationPlayerUi --- .../newpipe/player/NotificationUtil.java | 93 +++++++-------- .../org/schabi/newpipe/player/Player.java | 60 ++-------- .../schabi/newpipe/player/PlayerService.java | 4 - .../player/ui/NotificationPlayerUi.java | 109 +++++++++++++++++- 4 files changed, 155 insertions(+), 111 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java index f5caf2c79..e88defe7f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java @@ -45,22 +45,16 @@ public final class NotificationUtil { private static final boolean DEBUG = Player.DEBUG; private static final int NOTIFICATION_ID = 123789; - @Nullable private static NotificationUtil instance = null; - @NotificationConstants.Action private final int[] notificationSlots = NotificationConstants.SLOT_DEFAULTS.clone(); private NotificationManagerCompat notificationManager; private NotificationCompat.Builder notificationBuilder; - private NotificationUtil() { - } + private Player player; - public static NotificationUtil getInstance() { - if (instance == null) { - instance = new NotificationUtil(); - } - return instance; + public NotificationUtil(final Player player) { + this.player = player; } @@ -71,20 +65,18 @@ public final class NotificationUtil { /** * Creates the notification if it does not exist already and recreates it if forceRecreate is * true. Updates the notification with the data in the player. - * @param player the player currently open, to take data from * @param forceRecreate whether to force the recreation of the notification even if it already * exists */ - synchronized void createNotificationIfNeededAndUpdate(final Player player, - final boolean forceRecreate) { + public synchronized void createNotificationIfNeededAndUpdate(final boolean forceRecreate) { if (forceRecreate || notificationBuilder == null) { - notificationBuilder = createNotification(player); + notificationBuilder = createNotification(); } - updateNotification(player); + updateNotification(); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); } - private synchronized NotificationCompat.Builder createNotification(final Player player) { + private synchronized NotificationCompat.Builder createNotification() { if (DEBUG) { Log.d(TAG, "createNotification()"); } @@ -93,7 +85,7 @@ public final class NotificationUtil { new NotificationCompat.Builder(player.getContext(), player.getContext().getString(R.string.notification_channel_id)); - initializeNotificationSlots(player); + initializeNotificationSlots(); // count the number of real slots, to make sure compact slots indices are not out of bound int nonNothingSlotCount = 5; @@ -132,9 +124,8 @@ public final class NotificationUtil { /** * Updates the notification builder and the button icons depending on the playback state. - * @param player the player currently open, to take data from */ - private synchronized void updateNotification(final Player player) { + private synchronized void updateNotification() { if (DEBUG) { Log.d(TAG, "updateNotification()"); } @@ -145,17 +136,17 @@ public final class NotificationUtil { notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); - updateActions(notificationBuilder, player); + updateActions(notificationBuilder); final boolean showThumbnail = player.getPrefs().getBoolean( player.getContext().getString(R.string.show_thumbnail_key), true); if (showThumbnail) { - setLargeIcon(notificationBuilder, player); + setLargeIcon(notificationBuilder); } } @SuppressLint("RestrictedApi") - boolean shouldUpdateBufferingSlot() { + public boolean shouldUpdateBufferingSlot() { if (notificationBuilder == null) { // if there is no notification active, there is no point in updating it return false; @@ -173,22 +164,22 @@ public final class NotificationUtil { } - public void createNotificationAndStartForeground(final Player player, final Service service) { + public void createNotificationAndStartForeground() { if (notificationBuilder == null) { - notificationBuilder = createNotification(player); + notificationBuilder = createNotification(); } - updateNotification(player); + updateNotification(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - service.startForeground(NOTIFICATION_ID, notificationBuilder.build(), + player.getService().startForeground(NOTIFICATION_ID, notificationBuilder.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK); } else { - service.startForeground(NOTIFICATION_ID, notificationBuilder.build()); + player.getService().startForeground(NOTIFICATION_ID, notificationBuilder.build()); } } - void cancelNotificationAndStopForeground(final Service service) { - ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE); + public void cancelNotificationAndStopForeground() { + ServiceCompat.stopForeground(player.getService(), ServiceCompat.STOP_FOREGROUND_REMOVE); if (notificationManager != null) { notificationManager.cancel(NOTIFICATION_ID); @@ -202,7 +193,7 @@ public final class NotificationUtil { // ACTIONS ///////////////////////////////////////////////////// - private void initializeNotificationSlots(final Player player) { + private void initializeNotificationSlots() { for (int i = 0; i < 5; ++i) { notificationSlots[i] = player.getPrefs().getInt( player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]), @@ -211,7 +202,7 @@ public final class NotificationUtil { } @SuppressLint("RestrictedApi") - private void updateActions(final NotificationCompat.Builder builder, final Player player) { + private void updateActions(final NotificationCompat.Builder builder) { builder.mActions.clear(); for (int i = 0; i < 5; ++i) { addAction(builder, player, notificationSlots[i]); @@ -221,7 +212,7 @@ public final class NotificationUtil { private void addAction(final NotificationCompat.Builder builder, final Player player, @NotificationConstants.Action final int slot) { - final NotificationCompat.Action action = getAction(player, slot); + final NotificationCompat.Action action = getAction(slot); if (action != null) { builder.addAction(action); } @@ -229,41 +220,40 @@ public final class NotificationUtil { @Nullable private NotificationCompat.Action getAction( - final Player player, @NotificationConstants.Action final int selectedAction) { final int baseActionIcon = NotificationConstants.ACTION_ICONS[selectedAction]; switch (selectedAction) { case NotificationConstants.PREVIOUS: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS); case NotificationConstants.NEXT: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_next_description, ACTION_PLAY_NEXT); case NotificationConstants.REWIND: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_rewind_description, ACTION_FAST_REWIND); case NotificationConstants.FORWARD: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD); case NotificationConstants.SMART_REWIND_PREVIOUS: if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) { - return getAction(player, R.drawable.exo_notification_previous, + return getAction(R.drawable.exo_notification_previous, R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS); } else { - return getAction(player, R.drawable.exo_controls_rewind, + return getAction(R.drawable.exo_controls_rewind, R.string.exo_controls_rewind_description, ACTION_FAST_REWIND); } case NotificationConstants.SMART_FORWARD_NEXT: if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) { - return getAction(player, R.drawable.exo_notification_next, + return getAction(R.drawable.exo_notification_next, R.string.exo_controls_next_description, ACTION_PLAY_NEXT); } else { - return getAction(player, R.drawable.exo_controls_fastforward, + return getAction(R.drawable.exo_controls_fastforward, R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD); } @@ -279,42 +269,42 @@ public final class NotificationUtil { case NotificationConstants.PLAY_PAUSE: if (player.getCurrentState() == Player.STATE_COMPLETED) { - return getAction(player, R.drawable.ic_replay, + return getAction(R.drawable.ic_replay, R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE); } else if (player.isPlaying() || player.getCurrentState() == Player.STATE_PREFLIGHT || player.getCurrentState() == Player.STATE_BLOCKED || player.getCurrentState() == Player.STATE_BUFFERING) { - return getAction(player, R.drawable.exo_notification_pause, + return getAction(R.drawable.exo_notification_pause, R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE); } else { - return getAction(player, R.drawable.exo_notification_play, + return getAction(R.drawable.exo_notification_play, R.string.exo_controls_play_description, ACTION_PLAY_PAUSE); } case NotificationConstants.REPEAT: if (player.getRepeatMode() == REPEAT_MODE_ALL) { - return getAction(player, R.drawable.exo_media_action_repeat_all, + return getAction(R.drawable.exo_media_action_repeat_all, R.string.exo_controls_repeat_all_description, ACTION_REPEAT); } else if (player.getRepeatMode() == REPEAT_MODE_ONE) { - return getAction(player, R.drawable.exo_media_action_repeat_one, + return getAction(R.drawable.exo_media_action_repeat_one, R.string.exo_controls_repeat_one_description, ACTION_REPEAT); } else /* player.getRepeatMode() == REPEAT_MODE_OFF */ { - return getAction(player, R.drawable.exo_media_action_repeat_off, + return getAction(R.drawable.exo_media_action_repeat_off, R.string.exo_controls_repeat_off_description, ACTION_REPEAT); } case NotificationConstants.SHUFFLE: if (player.getPlayQueue() != null && player.getPlayQueue().isShuffled()) { - return getAction(player, R.drawable.exo_controls_shuffle_on, + return getAction(R.drawable.exo_controls_shuffle_on, R.string.exo_controls_shuffle_on_description, ACTION_SHUFFLE); } else { - return getAction(player, R.drawable.exo_controls_shuffle_off, + return getAction(R.drawable.exo_controls_shuffle_off, R.string.exo_controls_shuffle_off_description, ACTION_SHUFFLE); } case NotificationConstants.CLOSE: - return getAction(player, R.drawable.ic_close, + return getAction(R.drawable.ic_close, R.string.close, ACTION_CLOSE); case NotificationConstants.NOTHING: @@ -324,8 +314,7 @@ public final class NotificationUtil { } } - private NotificationCompat.Action getAction(final Player player, - @DrawableRes final int drawable, + private NotificationCompat.Action getAction(@DrawableRes final int drawable, @StringRes final int title, final String intentAction) { return new NotificationCompat.Action(drawable, player.getContext().getString(title), @@ -353,7 +342,7 @@ public final class NotificationUtil { // BITMAP ///////////////////////////////////////////////////// - private void setLargeIcon(final NotificationCompat.Builder builder, final Player player) { + private void setLargeIcon(final NotificationCompat.Builder builder) { final boolean scaleImageToSquareAspectRatio = player.getPrefs().getBoolean( player.getContext().getString(R.string.scale_to_square_image_in_notifications_key), false); 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 78e93970c..e2732f4d0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -38,7 +38,6 @@ import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; @@ -620,7 +619,7 @@ public final class Player implements PlaybackListener, Listener { } private void setRecovery(final int queuePos, final long windowPos) { - if (playQueue.size() <= queuePos) { + if (playQueue == null || playQueue.size() <= queuePos) { return; } @@ -735,9 +734,6 @@ public final class Player implements PlaybackListener, Listener { case ACTION_SHUFFLE: toggleShuffleModeEnabled(); break; - case ACTION_RECREATE_NOTIFICATION: - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); - break; case Intent.ACTION_CONFIGURATION_CHANGED: assureCorrectAppLanguage(service); if (DEBUG) { @@ -797,8 +793,6 @@ public final class Player implements PlaybackListener, Listener { } currentThumbnail = bitmap; - NotificationUtil.getInstance() - .createNotificationIfNeededAndUpdate(Player.this, false); // there is a new thumbnail, so changed the end screen thumbnail, too. UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); } @@ -807,8 +801,7 @@ public final class Player implements PlaybackListener, Listener { public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); currentThumbnail = null; - NotificationUtil.getInstance() - .createNotificationIfNeededAndUpdate(Player.this, false); + UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); } @Override @@ -1082,8 +1075,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onBlocked); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onPlaying() { @@ -1095,8 +1086,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onPlaying); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onBuffering() { @@ -1105,10 +1094,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onBuffering); - - if (NotificationUtil.getInstance().shouldUpdateBufferingSlot()) { - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } } private void onPaused() { @@ -1121,24 +1106,13 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onPaused); - - // Remove running notification when user does not want minimization to background or popup - if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE - && videoPlayerSelected()) { - NotificationUtil.getInstance().cancelNotificationAndStopForeground(service); - } else { - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } } private void onPausedSeek() { if (DEBUG) { Log.d(TAG, "onPausedSeek() called"); } - UIs.call(PlayerUi::onPausedSeek); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onCompleted() { @@ -1150,7 +1124,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onCompleted); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); if (playQueue.getIndex() < playQueue.size() - 1) { playQueue.offsetIndex(+1); @@ -1190,7 +1163,7 @@ public final class Player implements PlaybackListener, Listener { + "repeatMode = [" + repeatMode + "]"); } UIs.call(playerUi -> playerUi.onRepeatModeChanged(repeatMode)); - onShuffleOrRepeatModeChanged(); + notifyPlaybackUpdateToListeners(); } @Override @@ -1209,7 +1182,7 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(playerUi -> playerUi.onShuffleModeEnabledChanged(shuffleModeEnabled)); - onShuffleOrRepeatModeChanged(); + notifyPlaybackUpdateToListeners(); } public void toggleShuffleModeEnabled() { @@ -1217,11 +1190,6 @@ public final class Player implements PlaybackListener, Listener { simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); } } - - private void onShuffleOrRepeatModeChanged() { - notifyPlaybackUpdateToListeners(); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } //endregion @@ -1806,12 +1774,15 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Metadata - private void onMetadataChanged(@NonNull final StreamInfo info) { + private void updateMetadataWith(@NonNull final StreamInfo info) { if (DEBUG) { Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName()); } + if (exoPlayerIsNull()) { + return; + } - UIs.call(playerUi -> playerUi.onMetadataChanged(info)); + maybeAutoQueueNextStream(info); initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); @@ -1826,17 +1797,7 @@ public final class Player implements PlaybackListener, Listener { ); notifyMetadataUpdateToListeners(); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } - - private void updateMetadataWith(@NonNull final StreamInfo streamInfo) { - if (exoPlayerIsNull()) { - return; - } - - maybeAutoQueueNextStream(streamInfo); - onMetadataChanged(streamInfo); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); + UIs.call(playerUi -> playerUi.onMetadataChanged(info)); } @NonNull @@ -1925,7 +1886,6 @@ public final class Player implements PlaybackListener, Listener { public void onPlayQueueEdited() { notifyPlaybackUpdateToListeners(); UIs.call(PlayerUi::onPlayQueueEdited); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @Override // own playback listener 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 cf83dc5c2..7bf918c73 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -88,9 +88,6 @@ public final class PlayerService extends Service { ThemeHelper.setTheme(this); player = new Player(this); - /*final MainPlayerUi mainPlayerUi = new MainPlayerUi(player, - PlayerBinding.inflate(LayoutInflater.from(this))); - player.UIs().add(mainPlayerUi);*/ } @Override @@ -159,7 +156,6 @@ public final class PlayerService extends Service { } public void stopService() { - NotificationUtil.getInstance().cancelNotificationAndStopForeground(this); cleanup(); stopSelf(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java index 40c83c6c7..5736eca3b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java @@ -1,26 +1,125 @@ package org.schabi.newpipe.player.ui; -import androidx.annotation.NonNull; +import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; +import android.content.Intent; +import android.graphics.Bitmap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.Player.RepeatMode; + +import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.NotificationUtil; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.helper.PlayerHelper; public final class NotificationPlayerUi extends PlayerUi { - boolean foregroundNotificationAlreadyCreated = false; + private boolean foregroundNotificationAlreadyCreated = false; + private final NotificationUtil notificationUtil; public NotificationPlayerUi(@NonNull final Player player) { super(player); + notificationUtil = new NotificationUtil(player); } @Override public void initPlayer() { super.initPlayer(); if (!foregroundNotificationAlreadyCreated) { - NotificationUtil.getInstance() - .createNotificationAndStartForeground(player, player.getService()); + notificationUtil.createNotificationAndStartForeground(); foregroundNotificationAlreadyCreated = true; } } - // TODO TODO on destroy remove foreground + @Override + public void destroy() { + super.destroy(); + notificationUtil.cancelNotificationAndStopForeground(); + } + + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBlocked() { + super.onBlocked(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onPlaying() { + super.onPlaying(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBuffering() { + super.onBuffering(); + if (notificationUtil.shouldUpdateBufferingSlot()) { + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + } + + @Override + public void onPaused() { + super.onPaused(); + + // Remove running notification when user does not want minimization to background or popup + if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE + && player.videoPlayerSelected()) { + notificationUtil.cancelNotificationAndStopForeground(); + } else { + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + } + + @Override + public void onPausedSeek() { + super.onPausedSeek(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onCompleted() { + super.onCompleted(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + super.onRepeatModeChanged(repeatMode); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { + super.onShuffleModeEnabledChanged(shuffleModeEnabled); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (ACTION_RECREATE_NOTIFICATION.equals(intent.getAction())) { + notificationUtil.createNotificationIfNeededAndUpdate(true); + } + } + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + notificationUtil.createNotificationIfNeededAndUpdate(true); + } + + @Override + public void onPlayQueueEdited() { + super.onPlayQueueEdited(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } } From 90a89f8ca555433b1e8f9f9d2713d0f7667060be Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:40:55 +0200 Subject: [PATCH 031/152] Move player-notification files into their package --- .../org/schabi/newpipe/player/Player.java | 20 ++++++------ .../schabi/newpipe/player/PlayerService.java | 25 --------------- .../NotificationConstants.java | 32 +++++++++++++++++-- .../NotificationPlayerUi.java | 6 ++-- .../{ => notification}/NotificationUtil.java | 25 +++++++-------- .../newpipe/player/ui/MainPlayerUi.java | 2 +- .../custom/NotificationActionsPreference.java | 6 ++-- 7 files changed, 60 insertions(+), 56 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{ => notification}/NotificationConstants.java (82%) rename app/src/main/java/org/schabi/newpipe/player/{ui => notification}/NotificationPlayerUi.java (94%) rename app/src/main/java/org/schabi/newpipe/player/{ => notification}/NotificationUtil.java (94%) 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 e2732f4d0..55600b956 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -29,21 +29,21 @@ import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static com.google.android.exoplayer2.Player.RepeatMode; import static org.schabi.newpipe.extractor.ServiceList.YouTube; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; -import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; -import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; -import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_REPEAT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE; import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; @@ -125,7 +125,7 @@ import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; import org.schabi.newpipe.player.ui.MainPlayerUi; -import org.schabi.newpipe.player.ui.NotificationPlayerUi; +import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.player.ui.PlayerUiList; import org.schabi.newpipe.player.ui.PopupPlayerUi; 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 7bf918c73..b5014eeed 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -28,8 +28,6 @@ import android.os.Binder; import android.os.IBinder; import android.util.Log; -import org.schabi.newpipe.App; -import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.ThemeHelper; @@ -52,29 +50,6 @@ public final class PlayerService extends Service { POPUP } - /*////////////////////////////////////////////////////////////////////////// - // Notification - //////////////////////////////////////////////////////////////////////////*/ - - static final String ACTION_CLOSE - = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; - public static final String ACTION_PLAY_PAUSE - = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; - static final String ACTION_REPEAT - = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; - static final String ACTION_PLAY_NEXT - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT"; - static final String ACTION_PLAY_PREVIOUS - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; - static final String ACTION_FAST_REWIND - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND"; - static final String ACTION_FAST_FORWARD - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD"; - static final String ACTION_SHUFFLE - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE"; - public static final String ACTION_RECREATE_NOTIFICATION - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; - /*////////////////////////////////////////////////////////////////////////// // Service's LifeCycle //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java similarity index 82% rename from app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java rename to app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index 6c9858d1b..53ef752bd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player; +package org.schabi.newpipe.player.notification; import android.content.Context; import android.content.SharedPreferences; @@ -7,6 +7,7 @@ import androidx.annotation.DrawableRes; import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.util.Localization; @@ -20,7 +21,34 @@ import java.util.TreeSet; public final class NotificationConstants { - private NotificationConstants() { } + private NotificationConstants() { + } + + + + /*////////////////////////////////////////////////////////////////////////// + // Intent actions + //////////////////////////////////////////////////////////////////////////*/ + + public static final String ACTION_CLOSE + = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; + public static final String ACTION_PLAY_PAUSE + = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; + public static final String ACTION_REPEAT + = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; + public static final String ACTION_PLAY_NEXT + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT"; + public static final String ACTION_PLAY_PREVIOUS + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; + public static final String ACTION_FAST_REWIND + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND"; + public static final String ACTION_FAST_FORWARD + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD"; + public static final String ACTION_SHUFFLE + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE"; + public static final String ACTION_RECREATE_NOTIFICATION + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; + public static final int NOTHING = 0; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java similarity index 94% rename from app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java rename to app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java index 5736eca3b..ed678a18c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java @@ -1,7 +1,7 @@ -package org.schabi.newpipe.player.ui; +package org.schabi.newpipe.player.notification; -import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION; import android.content.Intent; import android.graphics.Bitmap; @@ -12,9 +12,9 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.Player.RepeatMode; import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.player.NotificationUtil; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.helper.PlayerHelper; +import org.schabi.newpipe.player.ui.PlayerUi; public final class NotificationPlayerUi extends PlayerUi { private boolean foregroundNotificationAlreadyCreated = false; diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java similarity index 94% rename from app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java rename to app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index e88defe7f..5f0052453 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -1,8 +1,7 @@ -package org.schabi.newpipe.player; +package org.schabi.newpipe.player.notification; import android.annotation.SuppressLint; import android.app.PendingIntent; -import android.app.Service; import android.content.Intent; import android.content.pm.ServiceInfo; import android.graphics.Bitmap; @@ -19,6 +18,7 @@ import androidx.core.content.ContextCompat; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.NavigationHelper; import java.util.List; @@ -26,14 +26,14 @@ import java.util.List; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; -import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_REPEAT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE; /** * This is a utility class for player notifications. @@ -51,7 +51,7 @@ public final class NotificationUtil { private NotificationManagerCompat notificationManager; private NotificationCompat.Builder notificationBuilder; - private Player player; + private final Player player; public NotificationUtil(final Player player) { this.player = player; @@ -205,12 +205,11 @@ public final class NotificationUtil { private void updateActions(final NotificationCompat.Builder builder) { builder.mActions.clear(); for (int i = 0; i < 5; ++i) { - addAction(builder, player, notificationSlots[i]); + addAction(builder, notificationSlots[i]); } } private void addAction(final NotificationCompat.Builder builder, - final Player player, @NotificationConstants.Action final int slot) { final NotificationCompat.Action action = getAction(slot); if (action != null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 7c60671dd..80230d0f7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -5,13 +5,13 @@ import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.player.Player.STATE_COMPLETED; import static org.schabi.newpipe.player.Player.STATE_PAUSED; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE; import android.app.Activity; import android.content.Context; diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 0eb58f7a9..dfcf2e597 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.settings.custom; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION; + import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -27,7 +29,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; import org.schabi.newpipe.player.PlayerService; -import org.schabi.newpipe.player.NotificationConstants; +import org.schabi.newpipe.player.notification.NotificationConstants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; @@ -61,7 +63,7 @@ public class NotificationActionsPreference extends Preference { public void onDetached() { super.onDetached(); saveChanges(); - getContext().sendBroadcast(new Intent(PlayerService.ACTION_RECREATE_NOTIFICATION)); + getContext().sendBroadcast(new Intent(ACTION_RECREATE_NOTIFICATION)); } From 8c26403e91de46631fffceeb1216bb20c630033f Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:42:22 +0200 Subject: [PATCH 032/152] Remove unused PlayerState --- .../schabi/newpipe/player/PlayerState.java | 79 ------------------- 1 file changed, 79 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/PlayerState.java diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerState.java b/app/src/main/java/org/schabi/newpipe/player/PlayerState.java deleted file mode 100644 index af875a32b..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerState.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.schabi.newpipe.player; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.schabi.newpipe.player.playqueue.PlayQueue; - -import java.io.Serializable; - -public class PlayerState implements Serializable { - - @NonNull - private final PlayQueue playQueue; - private final int repeatMode; - private final float playbackSpeed; - private final float playbackPitch; - @Nullable - private final String playbackQuality; - private final boolean playbackSkipSilence; - private final boolean wasPlaying; - - PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode, - final float playbackSpeed, final float playbackPitch, - final boolean playbackSkipSilence, final boolean wasPlaying) { - this(playQueue, repeatMode, playbackSpeed, playbackPitch, null, - playbackSkipSilence, wasPlaying); - } - - PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode, - final float playbackSpeed, final float playbackPitch, - @Nullable final String playbackQuality, final boolean playbackSkipSilence, - final boolean wasPlaying) { - this.playQueue = playQueue; - this.repeatMode = repeatMode; - this.playbackSpeed = playbackSpeed; - this.playbackPitch = playbackPitch; - this.playbackQuality = playbackQuality; - this.playbackSkipSilence = playbackSkipSilence; - this.wasPlaying = wasPlaying; - } - - /*////////////////////////////////////////////////////////////////////////// - // Serdes - //////////////////////////////////////////////////////////////////////////*/ - - /*////////////////////////////////////////////////////////////////////////// - // Getters - //////////////////////////////////////////////////////////////////////////*/ - - @NonNull - public PlayQueue getPlayQueue() { - return playQueue; - } - - public int getRepeatMode() { - return repeatMode; - } - - public float getPlaybackSpeed() { - return playbackSpeed; - } - - public float getPlaybackPitch() { - return playbackPitch; - } - - @Nullable - public String getPlaybackQuality() { - return playbackQuality; - } - - public boolean isPlaybackSkipSilence() { - return playbackSkipSilence; - } - - public boolean wasPlaying() { - return wasPlaying; - } -} From 6fb02569978c8126cb24376419980a8321843b33 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:43:54 +0200 Subject: [PATCH 033/152] Remove unused PlayerServiceBinder --- .../newpipe/player/PlayQueueActivity.java | 4 +--- .../newpipe/player/PlayerServiceBinder.java | 17 ----------------- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java 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 d00e6265e..cdba900f9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -207,9 +207,7 @@ public final class PlayQueueActivity extends AppCompatActivity public void onServiceConnected(final ComponentName name, final IBinder service) { Log.d(TAG, "Player service is connected"); - if (service instanceof PlayerServiceBinder) { - player = ((PlayerServiceBinder) service).getPlayerInstance(); - } else if (service instanceof PlayerService.LocalBinder) { + if (service instanceof PlayerService.LocalBinder) { player = ((PlayerService.LocalBinder) service).getPlayer(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java b/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java deleted file mode 100644 index 5c28c6c7b..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.schabi.newpipe.player; - -import android.os.Binder; - -import androidx.annotation.NonNull; - -class PlayerServiceBinder extends Binder { - private final Player player; - - PlayerServiceBinder(@NonNull final Player player) { - this.player = player; - } - - Player getPlayerInstance() { - return player; - } -} From fa25ecf52143a3cb8b695db7c25799de320b1e5f Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:55:23 +0200 Subject: [PATCH 034/152] Add comment about broadcast receiver --- app/src/main/java/org/schabi/newpipe/player/Player.java | 6 ++++++ .../main/java/org/schabi/newpipe/player/ui/PlayerUi.java | 4 ++++ 2 files changed, 10 insertions(+) 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 55600b956..b0fed3d7d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -663,6 +663,12 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + /** + * This function prepares the broadcast receiver and is called only in the constructor. + * Therefore if you want any PlayerUi to receive a broadcast action, you should add it here, + * even if that player ui might never be added to the player. In that case the received + * broadcast would not do anything. + */ private void setupBroadcastReceiver() { if (DEBUG) { Log.d(TAG, "setupBroadcastReceiver() called"); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 15b468fb7..81e93ca23 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -55,6 +55,10 @@ public abstract class PlayerUi { public void onFragmentListenerSet() { } + /** + * If you want to register new broadcast actions to receive here, add them to + * {@link Player#setupBroadcastReceiver()}. + */ public void onBroadcastReceived(final Intent intent) { } From 6559416bd8e0857a32b00c097638be9dc2eec88b Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 23:07:29 +0200 Subject: [PATCH 035/152] Improve //region comments in player UIs --- .../newpipe/player/ui/MainPlayerUi.java | 73 ++++++---- .../newpipe/player/ui/PopupPlayerUi.java | 80 +++++++---- .../newpipe/player/ui/VideoPlayerUi.java | 131 ++++++++++-------- 3 files changed, 173 insertions(+), 111 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 80230d0f7..c62382782 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -88,6 +88,12 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh // fullscreen player private ItemTouchHelper itemTouchHelper; + + /*////////////////////////////////////////////////////////////////////////// + // Constructor, setup, destroy + //////////////////////////////////////////////////////////////////////////*/ + //region Constructor, setup, destroy + public MainPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player, playerBinding); @@ -272,12 +278,14 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh resources.getDimensionPixelSize(R.dimen.player_main_buttons_padding) ); } + //endregion /*////////////////////////////////////////////////////////////////////////// // Broadcast receiver //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + @Override public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); @@ -313,6 +321,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh // Fragment binding //////////////////////////////////////////////////////////////////////////*/ //region Fragment binding + @Override public void onFragmentListenerSet() { super.onFragmentListenerSet(); @@ -351,13 +360,11 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } //endregion - private void showHideKodiButton() { - // show kodi button if it supports the current service and it is enabled in settings - @Nullable final PlayQueue playQueue = player.getPlayQueue(); - binding.playWithKodi.setVisibility(playQueue != null && playQueue.getItem() != null - && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) - ? View.VISIBLE : View.GONE); - } + + /*////////////////////////////////////////////////////////////////////////// + // Playback states + //////////////////////////////////////////////////////////////////////////*/ + //region Playback states @Override public void onUpdateProgress(final int currentProgress, @@ -373,6 +380,22 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } } + @Override + public void onPlaying() { + super.onPlaying(); + checkLandscape(); + } + + @Override + public void onCompleted() { + super.onCompleted(); + if (isFullscreen) { + toggleFullscreen(); + } + } + //endregion + + /*////////////////////////////////////////////////////////////////////////// // Controls showing / hiding //////////////////////////////////////////////////////////////////////////*/ @@ -457,22 +480,21 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh return Math.min(bitmap.getHeight(), screenHeight); } } + + private void showHideKodiButton() { + // show kodi button if it supports the current service and it is enabled in settings + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + binding.playWithKodi.setVisibility(playQueue != null && playQueue.getItem() != null + && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) + ? View.VISIBLE : View.GONE); + } //endregion - @Override - public void onPlaying() { - super.onPlaying(); - checkLandscape(); - } - - @Override - public void onCompleted() { - super.onCompleted(); - if (isFullscreen) { - toggleFullscreen(); - } - } + /*////////////////////////////////////////////////////////////////////////// + // Captions (text tracks) + //////////////////////////////////////////////////////////////////////////*/ + //region Captions (text tracks) @Override protected void setupSubtitleView(float captionScale) { @@ -482,8 +504,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.subtitleView.setFixedTextSize( TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse); } - - + //endregion /*////////////////////////////////////////////////////////////////////////// @@ -798,6 +819,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh // Click listeners //////////////////////////////////////////////////////////////////////////*/ //region Click listeners + @Override public void onClick(final View v) { if (v.getId() == binding.screenRotationButton.getId()) { @@ -855,9 +877,9 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh /*////////////////////////////////////////////////////////////////////////// - // Video size, resize, orientation, fullscreen + // Video size, orientation, fullscreen //////////////////////////////////////////////////////////////////////////*/ - //region Video size, resize, orientation, fullscreen + //region Video size, orientation, fullscreen private void setupScreenRotationButton() { binding.screenRotationButton.setVisibility(globalScreenOrientationLocked(context) @@ -941,9 +963,6 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh // Getters //////////////////////////////////////////////////////////////////////////*/ //region Getters - public PlayerBinding getBinding() { - return binding; - } public Optional getParentActivity() { final ViewParent rootParent = binding.getRoot().getParent(); 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 7df9102b7..43440b873 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 @@ -8,7 +8,6 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutP import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; -import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; @@ -61,6 +60,12 @@ public final class PopupPlayerUi extends VideoPlayerUi { private WindowManager.LayoutParams popupLayoutParams; // null if player is not popup private final WindowManager windowManager; + + /*////////////////////////////////////////////////////////////////////////// + // Constructor, setup, destroy + //////////////////////////////////////////////////////////////////////////*/ + //region Constructor, setup, destroy + public PopupPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player, playerBinding); @@ -173,11 +178,14 @@ public final class PopupPlayerUi extends VideoPlayerUi { super.destroy(); removePopupFromView(); } + //endregion + /*////////////////////////////////////////////////////////////////////////// // Broadcast receiver //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + @Override public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); @@ -200,6 +208,11 @@ public final class PopupPlayerUi extends VideoPlayerUi { //endregion + /*////////////////////////////////////////////////////////////////////////// + // Popup position and size + //////////////////////////////////////////////////////////////////////////*/ + //region Popup position and size + /** * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary * that goes from (0, 0) to (screenWidth, screenHeight). @@ -272,16 +285,19 @@ public final class PopupPlayerUi extends VideoPlayerUi { windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); } - private void changePopupWindowFlags(final int flags) { - if (DEBUG) { - Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); - } - - if (!anyPopupViewIsNull()) { - popupLayoutParams.flags = flags; - windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); - } + @Override + protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { + // no need for the end screen thumbnail to be resized on popup player: it's only needed + // for the main player so that it is enlarged correctly inside the fragment + return bitmap.getHeight(); } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Popup closing + //////////////////////////////////////////////////////////////////////////*/ + //region Popup closing public void closePopup() { if (DEBUG) { @@ -351,23 +367,22 @@ public final class PopupPlayerUi extends VideoPlayerUi { } }).start(); } + //endregion - @Override - protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { - // no need for the end screen thumbnail to be resized on popup player: it's only needed - // for the main player so that it is enlarged correctly inside the fragment - return bitmap.getHeight(); - } + /*////////////////////////////////////////////////////////////////////////// + // Playback states + //////////////////////////////////////////////////////////////////////////*/ + //region Playback states - private boolean popupHasParent() { - return binding != null - && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams - && binding.getRoot().getParent() != null; - } + private void changePopupWindowFlags(final int flags) { + if (DEBUG) { + Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); + } - private boolean anyPopupViewIsNull() { - return popupLayoutParams == null || windowManager == null - || binding.getRoot().getParent() == null; + if (!anyPopupViewIsNull()) { + popupLayoutParams.flags = flags; + windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); + } } @Override @@ -400,11 +415,14 @@ public final class PopupPlayerUi extends VideoPlayerUi { playbackSpeedPopupMenu.show(); isSomePopupMenuVisible = true; } + //endregion + /*////////////////////////////////////////////////////////////////////////// // Gestures //////////////////////////////////////////////////////////////////////////*/ //region Gestures + private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) { final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft() + closeOverlayBinding.closeButton.getWidth() / 2; @@ -433,7 +451,19 @@ public final class PopupPlayerUi extends VideoPlayerUi { /*////////////////////////////////////////////////////////////////////////// // Getters //////////////////////////////////////////////////////////////////////////*/ - //region Gestures + //region Getters + + private boolean popupHasParent() { + return binding != null + && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams + && binding.getRoot().getParent() != null; + } + + private boolean anyPopupViewIsNull() { + return popupLayoutParams == null || windowManager == null + || binding.getRoot().getParent() == null; + } + public PlayerPopupCloseOverlayBinding getCloseOverlayBinding() { return closeOverlayBinding; } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 24cdb8908..f4ebc3304 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -135,6 +135,12 @@ public abstract class VideoPlayerUi extends PlayerUi @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = new SeekbarPreviewThumbnailHolder(); + + /*////////////////////////////////////////////////////////////////////////// + // Constructor, setup, destroy + //////////////////////////////////////////////////////////////////////////*/ + //region Constructor, setup, destroy + public VideoPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player); @@ -142,11 +148,6 @@ public abstract class VideoPlayerUi extends PlayerUi setupFromView(); } - - /*////////////////////////////////////////////////////////////////////////// - // Setup - //////////////////////////////////////////////////////////////////////////*/ - //region Setup public void setupFromView() { initViews(); initListeners(); @@ -414,6 +415,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Broadcast receiver //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + @Override public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); @@ -433,6 +435,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Thumbnail //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail + /** * Scale the player audio / end screen thumbnail down if necessary. *

@@ -481,6 +484,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Progress loop and updates //////////////////////////////////////////////////////////////////////////*/ //region Progress loop and updates + @Override public void onUpdateProgress(final int currentProgress, final int duration, @@ -744,6 +748,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Playback states //////////////////////////////////////////////////////////////////////////*/ //region Playback states + @Override public void onPrepared() { super.onPrepared(); @@ -885,7 +890,8 @@ public abstract class VideoPlayerUi extends PlayerUi /*////////////////////////////////////////////////////////////////////////// // Repeat, shuffle, mute //////////////////////////////////////////////////////////////////////////*/ - //region Repeat and shuffle + //region Repeat, shuffle, mute + public void onRepeatClicked() { if (DEBUG) { Log.d(TAG, "onRepeatClicked() called"); @@ -945,52 +951,9 @@ public abstract class VideoPlayerUi extends PlayerUi /*////////////////////////////////////////////////////////////////////////// - // ExoPlayer listeners (that didn't fit in other categories) + // Other player listeners //////////////////////////////////////////////////////////////////////////*/ - //region ExoPlayer listeners (that didn't fit in other categories) - @Override - public void onTextTracksChanged(@NonNull final Tracks currentTracks) { - super.onTextTracksChanged(currentTracks); - - final boolean trackTypeTextSupported = !currentTracks.containsType(C.TRACK_TYPE_TEXT) - || currentTracks.isTypeSupported(C.TRACK_TYPE_TEXT, false); - if (getPlayer().getTrackSelector().getCurrentMappedTrackInfo() == null - || !trackTypeTextSupported) { - binding.captionTextView.setVisibility(View.GONE); - return; - } - - // Extract all loaded languages - final List textTracks = currentTracks - .getGroups() - .stream() - .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) - .collect(Collectors.toList()); - final List availableLanguages = textTracks.stream() - .map(Tracks.Group::getMediaTrackGroup) - .filter(textTrack -> textTrack.length > 0) - .map(textTrack -> textTrack.getFormat(0).language) - .collect(Collectors.toList()); - - // Find selected text track - final Optional selectedTracks = textTracks.stream() - .filter(Tracks.Group::isSelected) - .filter(info -> info.getMediaTrackGroup().length >= 1) - .map(info -> info.getMediaTrackGroup().getFormat(0)) - .findFirst(); - - // Build UI - buildCaptionMenu(availableLanguages); - //noinspection SimplifyOptionalCallChains - if (player.getTrackSelector().getParameters().getRendererDisabled( - player.getCaptionRendererIndex()) || !selectedTracks.isPresent()) { - binding.captionTextView.setText(R.string.caption_none); - } else { - binding.captionTextView.setText(selectedTracks.get().language); - } - binding.captionTextView.setVisibility( - availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); - } + //region Other player listeners @Override public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { @@ -1004,12 +967,6 @@ public abstract class VideoPlayerUi extends PlayerUi //TODO check if this causes black screen when switching to fullscreen animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION); } - - @Override - public void onCues(@NonNull List cues) { - super.onCues(cues); - binding.subtitleView.setCues(cues); - } //endregion @@ -1017,6 +974,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Metadata & stream related views //////////////////////////////////////////////////////////////////////////*/ //region Metadata & stream related views + @Override public void onMetadataChanged(@NonNull final StreamInfo info) { super.onMetadataChanged(info); @@ -1092,6 +1050,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Popup menus ("popup" means that they pop up, not that they belong to the popup player) //////////////////////////////////////////////////////////////////////////*/ //region Popup menus ("popup" means that they pop up, not that they belong to the popup player) + private void buildQualityMenu() { if (qualityPopupMenu == null) { return; @@ -1315,6 +1274,57 @@ public abstract class VideoPlayerUi extends PlayerUi // Captions (text tracks) //////////////////////////////////////////////////////////////////////////*/ //region Captions (text tracks) + + @Override + public void onTextTracksChanged(@NonNull final Tracks currentTracks) { + super.onTextTracksChanged(currentTracks); + + final boolean trackTypeTextSupported = !currentTracks.containsType(C.TRACK_TYPE_TEXT) + || currentTracks.isTypeSupported(C.TRACK_TYPE_TEXT, false); + if (getPlayer().getTrackSelector().getCurrentMappedTrackInfo() == null + || !trackTypeTextSupported) { + binding.captionTextView.setVisibility(View.GONE); + return; + } + + // Extract all loaded languages + final List textTracks = currentTracks + .getGroups() + .stream() + .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) + .collect(Collectors.toList()); + final List availableLanguages = textTracks.stream() + .map(Tracks.Group::getMediaTrackGroup) + .filter(textTrack -> textTrack.length > 0) + .map(textTrack -> textTrack.getFormat(0).language) + .collect(Collectors.toList()); + + // Find selected text track + final Optional selectedTracks = textTracks.stream() + .filter(Tracks.Group::isSelected) + .filter(info -> info.getMediaTrackGroup().length >= 1) + .map(info -> info.getMediaTrackGroup().getFormat(0)) + .findFirst(); + + // Build UI + buildCaptionMenu(availableLanguages); + //noinspection SimplifyOptionalCallChains + if (player.getTrackSelector().getParameters().getRendererDisabled( + player.getCaptionRendererIndex()) || !selectedTracks.isPresent()) { + binding.captionTextView.setText(R.string.caption_none); + } else { + binding.captionTextView.setText(selectedTracks.get().language); + } + binding.captionTextView.setVisibility( + availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); + } + + @Override + public void onCues(@NonNull List cues) { + super.onCues(cues); + binding.subtitleView.setCues(cues); + } + private void setupSubtitleView() { setupSubtitleView(PlayerHelper.getCaptionScale(context)); final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context); @@ -1330,6 +1340,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Click listeners //////////////////////////////////////////////////////////////////////////*/ //region Click listeners + @Override public void onClick(final View v) { if (DEBUG) { @@ -1493,9 +1504,10 @@ public abstract class VideoPlayerUi extends PlayerUi /*////////////////////////////////////////////////////////////////////////// - // Video size, resize, orientation, fullscreen + // Video size //////////////////////////////////////////////////////////////////////////*/ - //region Video size, resize, orientation, fullscreen + //region Video size + protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { binding.surfaceView.setResizeMode(resizeMode); binding.resizeTextView.setText(PlayerHelper.resizeTypeOf(context, resizeMode)); @@ -1569,6 +1581,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Getters //////////////////////////////////////////////////////////////////////////*/ //region Getters + public PlayerBinding getBinding() { return binding; } From 1b39b5376f518ee570258da82fbe4fb09d11c231 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 15 Apr 2022 00:01:59 +0200 Subject: [PATCH 036/152] Add some javadocs; move preparing player uis to PlayerUiList --- .../org/schabi/newpipe/player/Player.java | 25 ++--- .../schabi/newpipe/player/ui/PlayerUi.java | 92 ++++++++++++++++++- .../newpipe/player/ui/PlayerUiList.java | 43 ++++++++- 3 files changed, 138 insertions(+), 22 deletions(-) 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 b0fed3d7d..f8ea7bc90 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -447,7 +447,7 @@ public final class Player implements PlaybackListener, Listener { private void initUIsForCurrentPlayerType() { //noinspection SimplifyOptionalCallChains if (!UIs.get(NotificationPlayerUi.class).isPresent()) { - UIs.add(new NotificationPlayerUi(this)); + UIs.addAndPrepare(new NotificationPlayerUi(this)); } if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) @@ -469,24 +469,15 @@ public final class Player implements PlaybackListener, Listener { switch (playerType) { case MAIN: UIs.destroyAll(PopupPlayerUi.class); - UIs.add(new MainPlayerUi(this, binding)); + UIs.addAndPrepare(new MainPlayerUi(this, binding)); + break; + case POPUP: + UIs.destroyAll(MainPlayerUi.class); + UIs.addAndPrepare(new PopupPlayerUi(this, binding)); break; case AUDIO: UIs.destroyAll(VideoPlayerUi.class); break; - case POPUP: - UIs.destroyAll(MainPlayerUi.class); - UIs.add(new PopupPlayerUi(this, binding)); - break; - } - - if (fragmentListener != null) { - // make sure UIs know whether a service is connected or not - UIs.call(PlayerUi::onFragmentListenerSet); - } - if (!exoPlayerIsNull()) { - UIs.call(PlayerUi::initPlayer); - UIs.call(PlayerUi::initPlayback); } } @@ -1968,9 +1959,9 @@ public final class Player implements PlaybackListener, Listener { /*////////////////////////////////////////////////////////////////////////// - // Video size, resize, orientation, fullscreen + // Video size //////////////////////////////////////////////////////////////////////////*/ - //region Video size, resize, orientation, fullscreen + //region Video size @Override // exoplayer listener public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { if (DEBUG) { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 81e93ca23..c4db1f334 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -18,50 +18,105 @@ import org.schabi.newpipe.player.Player; import java.util.List; +/** + * A player UI is a component that can seamlessly connect and disconnect from the {@link Player} and + * provide a user interface of some sort. Try to extend this class instead of adding more code to + * {@link Player}! + */ public abstract class PlayerUi { - @NonNull protected Context context; - @NonNull protected Player player; + @NonNull protected final Context context; + @NonNull protected final Player player; + /** + * @param player the player instance that will be usable throughout the lifetime of this UI + */ public PlayerUi(@NonNull final Player player) { this.context = player.getContext(); this.player = player; } + /** + * @return the player instance this UI was constructed with + */ @NonNull public Player getPlayer() { return player; } + /** + * Called after the player received an intent and processed it + */ public void setupAfterIntent() { } + /** + * Called right after the exoplayer instance is constructed, or right after this UI is + * constructed if the exoplayer is already available then. Note that the exoplayer instance + * could be built and destroyed multiple times during the lifetime of the player, so this method + * might be called multiple times. + */ public void initPlayer() { } + /** + * Called when playback in the exoplayer is about to start, or right after this UI is + * constructed if the exoplayer and the play queue are already available then. The play queue + * will therefore always be not null. + */ public void initPlayback() { } + /** + * Called when the exoplayer instance is about to be destroyed. Note that the exoplayer instance + * could be built and destroyed multiple times during the lifetime of the player, so this method + * might be called multiple times. Be sure to unset any video surface view or play queue + * listeners! This will also be called when this UI is being discarded, just before {@link + * #destroy()}. + */ public void destroyPlayer() { } + /** + * Called when this UI is being discarded, either because the player is switching to a different + * UI or because the player is shutting down completely + */ public void destroy() { } + /** + * Called when the player is smooth-stopping, that is, transitioning smoothly to a new play + * queue after the user tapped on a new video stream while a stream was playing in the video + * detail fragment + */ public void smoothStopForImmediateReusing() { } + /** + * Called when the video detail fragment listener is connected with the player, or right after + * this UI is constructed if the listener is already connected then + */ public void onFragmentListenerSet() { } /** - * If you want to register new broadcast actions to receive here, add them to - * {@link Player#setupBroadcastReceiver()}. + * Broadcasts that the player receives will also be notified to UIs here. If you want to + * register new broadcast actions to receive here, add them to {@link + * Player#setupBroadcastReceiver()}. */ public void onBroadcastReceived(final Intent intent) { } + /** + * Called when stream progress (i.e. the current time in the seekbar) or stream duration change. + * Will surely be called every {@link Player#PROGRESS_LOOP_INTERVAL_MILLIS} while a stream is + * playing. + * @param currentProgress the current progress in milliseconds + * @param duration the duration of the stream being played + * @param bufferPercent the percentage of stream already buffered, see {@link + * com.google.android.exoplayer2.BasePlayer#getBufferedPercentage()} + */ public void onUpdateProgress(final int currentProgress, final int duration, final int bufferPercent) { @@ -97,27 +152,56 @@ public abstract class PlayerUi { public void onMuteUnmuteChanged(final boolean isMuted) { } + /** + * @see com.google.android.exoplayer2.Player.Listener#onTracksChanged(Tracks) + */ public void onTextTracksChanged(@NonNull final Tracks currentTracks) { } + /** + * @see com.google.android.exoplayer2.Player.Listener#onPlaybackParametersChanged + */ public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { } + /** + * @see com.google.android.exoplayer2.Player.Listener#onRenderedFirstFrame + */ public void onRenderedFirstFrame() { } + /** + * @see com.google.android.exoplayer2.text.TextOutput#onCues + */ public void onCues(@NonNull final List cues) { } + /** + * Called when the stream being played changes + * @param info the {@link StreamInfo} metadata object, along with data about the selected and + * available video streams (to be used to build the resolution menus, for example) + */ public void onMetadataChanged(@NonNull final StreamInfo info) { } + /** + * Called when the thumbnail for the current metadata was loaded + * @param bitmap the thumbnail to process, or null if there is no thumbnail or there was an + * error when loading the thumbnail + */ public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { } + /** + * Called when the play queue was edited: a stream was appended, moved or removed. + */ public void onPlayQueueEdited() { } + /** + * @param videoSize the new video size, useful to set the surface aspect ratio + * @see com.google.android.exoplayer2.Player.Listener#onVideoSizeChanged + */ public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java index 8c5c0dbfa..749cda02c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -8,10 +8,39 @@ import java.util.function.Consumer; public final class PlayerUiList { final List playerUis = new ArrayList<>(); - public void add(final PlayerUi playerUi) { + /** + * Adds the provided player ui to the list and calls on it the initialization functions that + * apply based on the current player state. The preparation step needs to be done since when UIs + * are removed and re-added, the player will not call e.g. initPlayer again since the exoplayer + * is already initialized, but we need to notify the newly built UI that the player is ready + * nonetheless. + * @param playerUi the player ui to prepare and add to the list; its {@link + * PlayerUi#getPlayer()} will be used to query information about the player + * state + */ + public void addAndPrepare(final PlayerUi playerUi) { + if (playerUi.getPlayer().getFragmentListener().isPresent()) { + // make sure UIs know whether a service is connected or not + playerUi.onFragmentListenerSet(); + } + + if (!playerUi.getPlayer().exoPlayerIsNull()) { + playerUi.initPlayer(); + if (playerUi.getPlayer().getPlayQueue() != null) { + playerUi.initPlayback(); + } + } + playerUis.add(playerUi); } + /** + * Destroys all matching player UIs and removes them from the list + * @param playerUiType the class of the player UI to destroy; the {@link + * Class#isInstance(Object)} method will be used, so even subclasses will be + * destroyed and removed + * @param the class type parameter + */ public void destroyAll(final Class playerUiType) { playerUis.stream() .filter(playerUiType::isInstance) @@ -22,6 +51,14 @@ public final class PlayerUiList { playerUis.removeIf(playerUiType::isInstance); } + /** + * @param playerUiType the class of the player UI to return; the {@link + * Class#isInstance(Object)} method will be used, so even subclasses could + * be returned + * @param the class type parameter + * @return the first player UI of the required type found in the list, or an empty {@link + * Optional} otherwise + */ public Optional get(final Class playerUiType) { return playerUis.stream() .filter(playerUiType::isInstance) @@ -29,6 +66,10 @@ public final class PlayerUiList { .findFirst(); } + /** + * Calls the provided consumer on all player UIs in the list + * @param consumer the consumer to call with player UIs + */ public void call(final Consumer consumer) { //noinspection SimplifyStreamApiCallChains playerUis.stream().forEach(consumer); From a19073ec011e7c314ccab2e9d84d466d235fd24a Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 12:03:59 +0200 Subject: [PATCH 037/152] Restore checkstyle and solve its errors --- app/build.gradle | 2 +- .../fragments/detail/VideoDetailFragment.java | 25 +++++++++---------- .../org/schabi/newpipe/player/Player.java | 6 +++-- .../player/notification/NotificationUtil.java | 4 +-- .../player/playback/PlayerMediaSession.java | 2 -- .../newpipe/player/ui/MainPlayerUi.java | 2 +- .../schabi/newpipe/player/ui/PlayerUi.java | 16 +++++++----- .../newpipe/player/ui/PlayerUiList.java | 4 +-- .../newpipe/player/ui/VideoPlayerUi.java | 15 ++++++----- .../custom/NotificationActionsPreference.java | 1 - 10 files changed, 41 insertions(+), 36 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 46eee8d00..9867037e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -166,7 +166,7 @@ afterEvaluate { if (!System.properties.containsKey('skipFormatKtlint')) { preDebugBuild.dependsOn formatKtlint } - //preDebugBuild.dependsOn runCheckstyle, runKtlint + preDebugBuild.dependsOn runCheckstyle, runKtlint } sonarqube { 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 cb8f0961f..8ffff2f9e 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 @@ -1,5 +1,16 @@ package org.schabi.newpipe.fragments.detail; +import static android.text.TextUtils.isEmpty; +import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; +import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; +import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; +import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired; +import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET; +import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; +import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams; + import android.animation.ValueAnimator; import android.app.Activity; import android.content.BroadcastReceiver; @@ -43,7 +54,6 @@ import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; -import androidx.viewbinding.ViewBinding; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; @@ -78,9 +88,9 @@ import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.PlayerService.PlayerType; -import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.helper.PlayerHelper; @@ -118,17 +128,6 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; -import static android.text.TextUtils.isEmpty; -import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; -import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; -import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; -import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired; -import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET; -import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; -import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams; - public final class VideoDetailFragment extends BaseStateFragment implements BackPressable, 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 f8ea7bc90..0755f9b4d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -239,6 +239,7 @@ public final class Player implements PlaybackListener, Listener { // UIs, listeners and disposables //////////////////////////////////////////////////////////////////////////*/ + @SuppressWarnings("MemberName") // keep the unusual member name private final PlayerUiList UIs = new PlayerUiList(); private BroadcastReceiver broadcastReceiver; @@ -1148,7 +1149,7 @@ public final class Player implements PlaybackListener, Listener { simpleExoPlayer.setRepeatMode(repeatMode); } } - + public void cycleNextRepeatMode() { setRepeatMode(nextRepeatMode(getRepeatMode())); } @@ -1181,7 +1182,7 @@ public final class Player implements PlaybackListener, Listener { UIs.call(playerUi -> playerUi.onShuffleModeEnabledChanged(shuffleModeEnabled)); notifyPlaybackUpdateToListeners(); } - + public void toggleShuffleModeEnabled() { if (!exoPlayerIsNull()) { simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); @@ -2301,6 +2302,7 @@ public final class Player implements PlaybackListener, Listener { /** * @return the user interfaces connected with the player */ + @SuppressWarnings("MethodName") // keep the unusual method name public PlayerUiList UIs() { return UIs; } diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 5f0052453..28c3b3655 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -132,7 +132,7 @@ public final class NotificationUtil { // also update content intent, in case the user switched players notificationBuilder.setContentIntent(PendingIntent.getActivity(player.getContext(), - NOTIFICATION_ID, getIntentForNotification(player), FLAG_UPDATE_CURRENT)); + NOTIFICATION_ID, getIntentForNotification(), FLAG_UPDATE_CURRENT)); notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); @@ -321,7 +321,7 @@ public final class NotificationUtil { new Intent(intentAction), FLAG_UPDATE_CURRENT)); } - private Intent getIntentForNotification(final Player player) { + private Intent getIntentForNotification() { if (player.audioPlayerSelected() || player.popupPlayerSelected()) { // Means we play in popup or audio only. Let's show the play queue return NavigationHelper.getPlayQueueActivityIntent(player.getContext()); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java index 2f261a0fa..3be9b6173 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java @@ -10,8 +10,6 @@ import org.schabi.newpipe.player.mediasession.MediaSessionCallback; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.ui.VideoPlayerUi; -import java.util.Optional; - public class PlayerMediaSession implements MediaSessionCallback { private final Player player; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index c62382782..3bdda0029 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -497,7 +497,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh //region Captions (text tracks) @Override - protected void setupSubtitleView(float captionScale) { + protected void setupSubtitleView(final float captionScale) { final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); final float captionRatioInverse = 20f + 4f * (1.0f - captionScale); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index c4db1f334..499800690 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -46,7 +46,7 @@ public abstract class PlayerUi { /** - * Called after the player received an intent and processed it + * Called after the player received an intent and processed it. */ public void setupAfterIntent() { } @@ -80,7 +80,7 @@ public abstract class PlayerUi { /** * Called when this UI is being discarded, either because the player is switching to a different - * UI or because the player is shutting down completely + * UI or because the player is shutting down completely. */ public void destroy() { } @@ -88,14 +88,14 @@ public abstract class PlayerUi { /** * Called when the player is smooth-stopping, that is, transitioning smoothly to a new play * queue after the user tapped on a new video stream while a stream was playing in the video - * detail fragment + * detail fragment. */ public void smoothStopForImmediateReusing() { } /** * Called when the video detail fragment listener is connected with the player, or right after - * this UI is constructed if the listener is already connected then + * this UI is constructed if the listener is already connected then. */ public void onFragmentListenerSet() { } @@ -104,6 +104,7 @@ public abstract class PlayerUi { * Broadcasts that the player receives will also be notified to UIs here. If you want to * register new broadcast actions to receive here, add them to {@link * Player#setupBroadcastReceiver()}. + * @param intent the broadcast intent received by the player */ public void onBroadcastReceived(final Intent intent) { } @@ -154,12 +155,14 @@ public abstract class PlayerUi { /** * @see com.google.android.exoplayer2.Player.Listener#onTracksChanged(Tracks) + * @param currentTracks the available tracks information */ public void onTextTracksChanged(@NonNull final Tracks currentTracks) { } /** * @see com.google.android.exoplayer2.Player.Listener#onPlaybackParametersChanged + * @param playbackParameters the new playback parameters */ public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { } @@ -172,12 +175,13 @@ public abstract class PlayerUi { /** * @see com.google.android.exoplayer2.text.TextOutput#onCues + * @param cues the cues to pass to the subtitle view */ public void onCues(@NonNull final List cues) { } /** - * Called when the stream being played changes + * Called when the stream being played changes. * @param info the {@link StreamInfo} metadata object, along with data about the selected and * available video streams (to be used to build the resolution menus, for example) */ @@ -185,7 +189,7 @@ public abstract class PlayerUi { } /** - * Called when the thumbnail for the current metadata was loaded + * Called when the thumbnail for the current metadata was loaded. * @param bitmap the thumbnail to process, or null if there is no thumbnail or there was an * error when loading the thumbnail */ diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java index 749cda02c..05c0ed5b3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -35,7 +35,7 @@ public final class PlayerUiList { } /** - * Destroys all matching player UIs and removes them from the list + * Destroys all matching player UIs and removes them from the list. * @param playerUiType the class of the player UI to destroy; the {@link * Class#isInstance(Object)} method will be used, so even subclasses will be * destroyed and removed @@ -67,7 +67,7 @@ public final class PlayerUiList { } /** - * Calls the provided consumer on all player UIs in the list + * Calls the provided consumer on all player UIs in the list. * @param consumer the consumer to call with player UIs */ public void call(final Consumer consumer) { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index f4ebc3304..393bf141b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -86,7 +86,8 @@ import java.util.Optional; import java.util.stream.Collectors; public abstract class VideoPlayerUi extends PlayerUi - implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { + implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, View.OnLongClickListener, + PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { private static final String TAG = VideoPlayerUi.class.getSimpleName(); // time constants @@ -476,7 +477,7 @@ public abstract class VideoPlayerUi extends PlayerUi binding.endScreen.setImageBitmap(endScreenBitmap); } - protected abstract float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap); + protected abstract float calculateMaxEndScreenThumbnailHeight(@NonNull Bitmap bitmap); //endregion @@ -511,6 +512,7 @@ public abstract class VideoPlayerUi extends PlayerUi /** * Sets the current duration into the corresponding elements. + * @param currentProgress the current progress, in milliseconds */ private void updatePlayBackElementsCurrentDuration(final int currentProgress) { // Don't set seekbar progress while user is seeking @@ -522,6 +524,7 @@ public abstract class VideoPlayerUi extends PlayerUi /** * Sets the video duration time into all control components (e.g. seekbar). + * @param duration the video duration, in milliseconds */ private void setVideoDurationToControls(final int duration) { binding.playbackEndTime.setText(getTimeString(duration)); @@ -1214,7 +1217,7 @@ public abstract class VideoPlayerUi extends PlayerUi final MediaItemTag.Quality quality = currentMetadata.getMaybeQuality().get(); final List availableStreams = quality.getSortedVideoStreams(); final int selectedStreamIndex = quality.getSelectedVideoStreamIndex(); - if (selectedStreamIndex == menuItemIndex|| availableStreams.size() <= menuItemIndex) { + if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) { return true; } @@ -1320,7 +1323,7 @@ public abstract class VideoPlayerUi extends PlayerUi } @Override - public void onCues(@NonNull List cues) { + public void onCues(@NonNull final List cues) { super.onCues(cues); binding.subtitleView.setCues(cues); } @@ -1332,7 +1335,7 @@ public abstract class VideoPlayerUi extends PlayerUi binding.subtitleView.setStyle(captionStyle); } - protected abstract void setupSubtitleView(final float captionScale); + protected abstract void setupSubtitleView(float captionScale); //endregion @@ -1518,7 +1521,7 @@ public abstract class VideoPlayerUi extends PlayerUi } @Override - public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { super.onVideoSizeChanged(videoSize); binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index dfcf2e597..b4f6d598a 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -28,7 +28,6 @@ import androidx.preference.PreferenceViewHolder; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; -import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.notification.NotificationConstants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; From 4979f84e4116d114dca851a31d706bec90a93450 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 16:01:23 +0200 Subject: [PATCH 038/152] Solve some Sonarlint warnings --- .../newpipe/local/dialog/PlaylistDialog.java | 37 ++++++++++++++-- .../newpipe/player/PlayQueueActivity.java | 8 ++-- .../org/schabi/newpipe/player/Player.java | 42 +------------------ .../schabi/newpipe/player/PlayerService.java | 17 ++++---- .../gesture/BasePlayerGestureListener.kt | 5 ++- .../player/notification/NotificationUtil.java | 1 + .../newpipe/player/ui/MainPlayerUi.java | 13 +++--- .../schabi/newpipe/player/ui/PlayerUi.java | 2 +- .../newpipe/player/ui/PopupPlayerUi.java | 16 ++++--- .../newpipe/player/ui/VideoPlayerUi.java | 40 +++++++++--------- 10 files changed, 86 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java index f568ef81a..dec8b05b2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java @@ -9,15 +9,20 @@ import android.view.Window; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.StateSaver; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.Disposable; @@ -131,13 +136,13 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave * @param context context used for accessing the database * @param streamEntities used for crating the dialog * @param onExec execution that should occur after a dialog got created, e.g. showing it - * @return Disposable + * @return the disposable that was created */ public static Disposable createCorrespondingDialog( final Context context, final List streamEntities, - final Consumer onExec - ) { + final Consumer onExec) { + return new LocalPlaylistManager(NewPipeDatabase.getInstance(context)) .hasPlaylists() .observeOn(AndroidSchedulers.mainThread()) @@ -147,4 +152,30 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave : PlaylistCreationDialog.newInstance(streamEntities)) ); } + + /** + * Creates a {@link PlaylistAppendDialog} when playlists exists, + * otherwise a {@link PlaylistCreationDialog}. If the player's play queue is null or empty, no + * dialog will be created. + * + * @param player the player from which to extract the context and the play queue + * @param fragmentManager the fragment manager to use to show the dialog + * @return the disposable that was created + */ + public static Disposable showForPlayQueue( + final Player player, + @NonNull final FragmentManager fragmentManager) { + + final List streamEntities = Stream.of(player.getPlayQueue()) + .filter(Objects::nonNull) + .flatMap(playQueue -> Objects.requireNonNull(playQueue).getStreams().stream()) + .map(StreamEntity::new) + .collect(Collectors.toList()); + if (streamEntities.isEmpty()) { + return Disposable.empty(); + } + + return PlaylistDialog.createCorrespondingDialog(player.getContext(), streamEntities, + dialog -> dialog.show(fragmentManager, "PlaylistDialog")); + } } 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 cdba900f9..c18a7f487 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -29,6 +29,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; +import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.helper.PlaybackParameterDialog; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -53,8 +54,6 @@ public final class PlayQueueActivity extends AppCompatActivity private Player player; - private PlayQueueAdapter adapter = null; - private boolean serviceBound; private ServiceConnection serviceConnection; @@ -128,7 +127,7 @@ public final class PlayQueueActivity extends AppCompatActivity NavigationHelper.openSettings(this); return true; case R.id.action_append_playlist: - player.onAddToPlaylistClicked(getSupportFragmentManager()); + PlaylistDialog.showForPlayQueue(player, getSupportFragmentManager()); return true; case R.id.action_playback_speed: openPlaybackParameterDialog(); @@ -441,10 +440,9 @@ public final class PlayQueueActivity extends AppCompatActivity @Override public void onQueueUpdate(@Nullable final PlayQueue queue) { if (queue == null) { - adapter = null; queueControlBinding.playQueue.setAdapter(null); } else { - adapter = new PlayQueueAdapter(this, queue); + final PlayQueueAdapter adapter = new PlayQueueAdapter(this, queue); adapter.setSelectedListener(getOnSelectedListener()); queueControlBinding.playQueue.setAdapter(adapter); } 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 0755f9b4d..2d44c6449 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -63,16 +63,6 @@ import android.view.LayoutInflater; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatImageButton; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.content.ContextCompat; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.C; @@ -96,7 +86,6 @@ import com.squareup.picasso.Target; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; @@ -105,7 +94,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; -import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.event.PlayerEventListener; @@ -116,6 +104,7 @@ import org.schabi.newpipe.player.helper.MediaSessionManager; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.mediaitem.MediaItemTag; +import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlayerMediaSession; @@ -125,7 +114,6 @@ import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; import org.schabi.newpipe.player.ui.MainPlayerUi; -import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.player.ui.PlayerUiList; import org.schabi.newpipe.player.ui.PopupPlayerUi; @@ -137,10 +125,8 @@ import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.StreamTypeUtil; -import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import java.util.stream.IntStream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -1192,32 +1178,6 @@ public final class Player implements PlaybackListener, Listener { - /*////////////////////////////////////////////////////////////////////////// - // Playlist append TODO this does not make sense here - //////////////////////////////////////////////////////////////////////////*/ - //region Playlist append - - public void onAddToPlaylistClicked(@NonNull final FragmentManager fragmentManager) { - if (DEBUG) { - Log.d(TAG, "onAddToPlaylistClicked() called"); - } - - if (getPlayQueue() != null) { - PlaylistDialog.createCorrespondingDialog( - getContext(), - getPlayQueue() - .getStreams() - .stream() - .map(StreamEntity::new) - .collect(Collectors.toList()), - dialog -> dialog.show(fragmentManager, TAG) - ); - } - } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Mute / Unmute //////////////////////////////////////////////////////////////////////////*/ 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 b5014eeed..326b01590 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -71,16 +71,17 @@ public final class PlayerService extends Service { Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]"); } - if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) - && player.getPlayQueue() == null) { - // Player is not working, no need to process media button's action - return START_NOT_STICKY; + + if (!Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) + || player.getPlayQueue() != null) { + // ^ no need to process media button's action if player is not working + + player.handleIntent(intent); + if (player.getMediaSessionManager() != null) { + player.getMediaSessionManager().handleMediaButtonIntent(intent); + } } - player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); - } return START_NOT_STICKY; } diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt index bd5d6f1c5..b006e73aa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -92,7 +92,10 @@ abstract class BasePlayerGestureListener( return true } - return if (onDownNotDoubleTapping(e)) super.onDown(e) else true + if (onDownNotDoubleTapping(e)) { + return super.onDown(e) + } + return true } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 28c3b3655..2ba754500 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -266,6 +266,7 @@ public final class NotificationUtil { null); } + // fallthrough case NotificationConstants.PLAY_PAUSE: if (player.getCurrentState() == Player.STATE_COMPLETED) { return getAction(R.drawable.ic_replay, diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 3bdda0029..eebcc81c4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -51,6 +51,7 @@ import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.info_list.StreamSegmentAdapter; import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; @@ -147,7 +148,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.addToPlaylistButton.setOnClickListener(v -> getParentActivity().map(FragmentActivity::getSupportFragmentManager) - .ifPresent(player::onAddToPlaylistClicked)); + .ifPresent(fragmentManager -> + PlaylistDialog.showForPlayQueue(player, fragmentManager))); settingsContentObserver = new ContentObserver(new Handler()) { @Override @@ -401,6 +403,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh //////////////////////////////////////////////////////////////////////////*/ //region Controls showing / hiding + @Override protected void showOrHideButtons() { super.showOrHideButtons(); @Nullable final PlayQueue playQueue = player.getPlayQueue(); @@ -667,12 +670,11 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } animate(binding.itemsListPanel, false, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA, 0, () -> { + AnimationType.SLIDE_AND_ALPHA, 0, () -> // Even when queueLayout is GONE it receives touch events // and ruins normal behavior of the app. This line fixes it binding.itemsListPanel.setTranslationY( - -binding.itemsListPanel.getHeight() * 5); - }); + -binding.itemsListPanel.getHeight() * 5.0f)); // clear focus, otherwise a white rectangle remains on top of the player binding.itemsListClose.clearFocus(); @@ -845,8 +847,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), - player.getPlaybackSkipSilence(), (speed, pitch, skipSilence) - -> player.setPlaybackParameters(speed, pitch, skipSilence)) + player.getPlaybackSkipSilence(), player::setPlaybackParameters) .show(activity.getSupportFragmentManager(), null); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 499800690..9ce04bfd5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -31,7 +31,7 @@ public abstract class PlayerUi { /** * @param player the player instance that will be usable throughout the lifetime of this UI */ - public PlayerUi(@NonNull final Player player) { + protected PlayerUi(@NonNull final Player player) { this.context = player.getContext(); this.player = player; } 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 43440b873..8283437f8 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 @@ -1,5 +1,6 @@ package org.schabi.newpipe.player.ui; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; @@ -140,8 +141,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { binding.segmentsButton.setVisibility(View.GONE); binding.moreOptionsButton.setVisibility(View.GONE); binding.topControls.setOrientation(LinearLayout.HORIZONTAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.WRAP_CONTENT; + binding.primaryControls.getLayoutParams().width = WRAP_CONTENT; binding.secondaryControls.setAlpha(1.0f); binding.secondaryControls.setVisibility(View.VISIBLE); binding.secondaryControls.setTranslationY(0); @@ -193,14 +193,12 @@ public final class PopupPlayerUi extends VideoPlayerUi { updateScreenSize(); changePopupSize(popupLayoutParams.width); checkPopupPositionBounds(); - } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { - // Use only audio source when screen turns off while popup player is playing - if (player.isPlaying() || player.isLoading()) { + } else if (player.isPlaying() || player.isLoading()) { + if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + // Use only audio source when screen turns off while popup player is playing player.useVideoSource(false); - } - } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { - // Restore video source when screen turns on and user is watching video in popup player - if (player.isPlaying() || player.isLoading()) { + } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + // Restore video source when screen turns on and user was watching video in popup player.useVideoSource(true); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 393bf141b..5b0be6f64 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -41,7 +41,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatImageButton; import androidx.appcompat.widget.PopupMenu; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; @@ -142,7 +141,7 @@ public abstract class VideoPlayerUi extends PlayerUi //////////////////////////////////////////////////////////////////////////*/ //region Constructor, setup, destroy - public VideoPlayerUi(@NonNull final Player player, + protected VideoPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player); binding = playerBinding; @@ -912,7 +911,20 @@ public abstract class VideoPlayerUi extends PlayerUi @Override public void onRepeatModeChanged(@RepeatMode final int repeatMode) { super.onRepeatModeChanged(repeatMode); - setRepeatModeButton(binding.repeatButton, repeatMode); + + switch (repeatMode) { + case REPEAT_MODE_OFF: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_off); + break; + case REPEAT_MODE_ONE: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_one); + break; + case REPEAT_MODE_ALL: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_all); + break; + default: + break; // unreachable + } } @Override @@ -927,21 +939,6 @@ public abstract class VideoPlayerUi extends PlayerUi setMuteButton(isMuted); } - private void setRepeatModeButton(final AppCompatImageButton imageButton, - @RepeatMode final int repeatMode) { - switch (repeatMode) { - case REPEAT_MODE_OFF: - imageButton.setImageResource(R.drawable.exo_controls_repeat_off); - break; - case REPEAT_MODE_ONE: - imageButton.setImageResource(R.drawable.exo_controls_repeat_one); - break; - case REPEAT_MODE_ALL: - imageButton.setImageResource(R.drawable.exo_controls_repeat_all); - break; - } - } - private void setMuteButton(final boolean isMuted) { binding.switchMute.setImageDrawable(AppCompatResources.getDrawable(context, isMuted ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); @@ -1037,6 +1034,7 @@ public abstract class VideoPlayerUi extends PlayerUi binding.qualityTextView.setVisibility(View.VISIBLE); binding.surfaceView.setVisibility(View.VISIBLE); + // fallthrough default: binding.endScreen.setVisibility(View.GONE); binding.playbackEndTime.setVisibility(View.VISIBLE); @@ -1426,8 +1424,6 @@ public abstract class VideoPlayerUi extends PlayerUi public boolean onKeyDown(final int keyCode) { switch (keyCode) { - default: - break; case KeyEvent.KEYCODE_BACK: if (DeviceUtils.isTv(context) && isControlsVisible()) { hideControls(0, 0); @@ -1442,7 +1438,7 @@ public abstract class VideoPlayerUi extends PlayerUi if ((binding.getRoot().hasFocus() && !binding.playbackControlRoot.hasFocus()) || isAnyListViewOpen()) { // do not interfere with focus in playlist and play queue etc. - return false; + break; } if (player.getCurrentState() == org.schabi.newpipe.player.Player.STATE_BLOCKED) { @@ -1458,6 +1454,8 @@ public abstract class VideoPlayerUi extends PlayerUi return true; } break; + default: + break; // ignore other keys } return false; From 1cf746f7216c173194e17b52abb7bac85763cb23 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 7 Jul 2022 11:09:07 +0200 Subject: [PATCH 039/152] Fix volume gestures not working anymore --- .../gesture/MainPlayerGestureListener.kt | 55 ++++++++++++------- .../newpipe/player/ui/MainPlayerUi.java | 7 ++- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index 81e216006..fd7b4ecf0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -8,11 +8,13 @@ import android.view.View.OnTouchListener import android.widget.ProgressBar import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources +import androidx.core.view.isVisible import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate import org.schabi.newpipe.player.Player +import org.schabi.newpipe.player.helper.AudioReactor import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.MainPlayerUi import kotlin.math.abs @@ -64,22 +66,27 @@ class MainPlayerGestureListener( } private fun onScrollVolume(distanceY: Float) { + val bar: ProgressBar = binding.volumeProgressBar + val audioReactor: AudioReactor = player.audioReactor + // If we just started sliding, change the progress bar to match the system volume - if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { - val volumePercent: Float = - player.audioReactor.volume / player.audioReactor.maxVolume.toFloat() - binding.volumeProgressBar.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt() + if (!binding.volumeRelativeLayout.isVisible) { + val volumePercent: Float = audioReactor.volume / audioReactor.maxVolume.toFloat() + bar.progress = (volumePercent * bar.max).toInt() } + // Update progress bar binding.volumeProgressBar.incrementProgressBy(distanceY.toInt()) - val currentProgressPercent: Float = - binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH - val currentVolume = (player.audioReactor.maxVolume * currentProgressPercent).toInt() - player.audioReactor.volume = currentVolume + + // Update volume + val currentProgressPercent: Float = bar.progress / bar.max.toFloat() + val currentVolume = (audioReactor.maxVolume * currentProgressPercent).toInt() + audioReactor.volume = currentVolume if (DEBUG) { Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume") } + // Update player center image binding.volumeImageView.setImageDrawable( AppCompatResources.getDrawable( player.context, @@ -92,12 +99,11 @@ class MainPlayerGestureListener( ) ) - if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { + // Make sure the correct layout is visible + if (!binding.volumeRelativeLayout.isVisible) { binding.volumeRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) } - if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { - binding.volumeRelativeLayout.visibility = View.GONE - } + binding.brightnessRelativeLayout.isVisible = false } private fun onScrollBrightness(distanceY: Float) { @@ -105,9 +111,13 @@ class MainPlayerGestureListener( val window = parent.window val layoutParams = window.attributes val bar: ProgressBar = binding.brightnessProgressBar + + // Update progress bar val oldBrightness = layoutParams.screenBrightness bar.progress = (bar.max * max(0f, min(1f, oldBrightness))).toInt() bar.incrementProgressBy(distanceY.toInt()) + + // Update brightness val currentProgressPercent = bar.progress.toFloat() / bar.max layoutParams.screenBrightness = currentProgressPercent window.attributes = layoutParams @@ -121,26 +131,32 @@ class MainPlayerGestureListener( "currentBrightness = " + currentProgressPercent ) } + + // Update player center image binding.brightnessImageView.setImageDrawable( AppCompatResources.getDrawable( player.context, - if (currentProgressPercent < 0.25) R.drawable.ic_brightness_low else if (currentProgressPercent < 0.75) R.drawable.ic_brightness_medium else R.drawable.ic_brightness_high + when { + currentProgressPercent < 0.25 -> R.drawable.ic_brightness_low + currentProgressPercent < 0.75 -> R.drawable.ic_brightness_medium + else -> R.drawable.ic_brightness_high + } ) ) - if (binding.brightnessRelativeLayout.visibility != View.VISIBLE) { + + // Make sure the correct layout is visible + if (!binding.brightnessRelativeLayout.isVisible) { binding.brightnessRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) } - if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { - binding.volumeRelativeLayout.visibility = View.GONE - } + binding.volumeRelativeLayout.isVisible = false } override fun onScrollEnd(event: MotionEvent) { super.onScrollEnd(event) - if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { + if (binding.volumeRelativeLayout.isVisible) { binding.volumeRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) } - if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { + if (binding.brightnessRelativeLayout.isVisible) { binding.brightnessRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) } } @@ -210,7 +226,6 @@ class MainPlayerGestureListener( private val TAG = MainPlayerGestureListener::class.java.simpleName private val DEBUG = MainActivity.DEBUG private const val MOVEMENT_THRESHOLD = 40 - const val MAX_GESTURE_LENGTH = 0.75f private fun getNavigationBarHeight(context: Context): Int { val resId = context.resources diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index eebcc81c4..d9f5ea7f4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -520,12 +520,13 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh public void onLayoutChange(final View view, final int l, final int t, final int r, final int b, final int ol, final int ot, final int or, final int ob) { if (l != ol || t != ot || r != or || b != ob) { - // Use smaller value to be consistent between screen orientations - // (and to make usage easier) + // Use a smaller value to be consistent across screen orientations, and to make usage + // easier. Multiply by 3/4 to ensure the user does not need to move the finger up to the + // screen border, in order to reach the maximum volume/brightness. final int width = r - l; final int height = b - t; final int min = Math.min(width, height); - final int maxGestureLength = (int) (min * MainPlayerGestureListener.MAX_GESTURE_LENGTH); + final int maxGestureLength = (int) (min * 0.75); if (DEBUG) { Log.d(TAG, "maxGestureLength = " + maxGestureLength); From 9c51fc3adeaac4670da0bead156315b286f8b5c3 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 7 Jul 2022 11:59:00 +0200 Subject: [PATCH 040/152] Move functions to get Android dimen to ThemeHelper --- .../gesture/MainPlayerGestureListener.kt | 28 ++++++------------- .../org/schabi/newpipe/util/ThemeHelper.java | 16 +++++++++++ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index fd7b4ecf0..095b3ccdb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -1,6 +1,5 @@ package org.schabi.newpipe.player.gesture -import android.content.Context import android.util.Log import android.view.MotionEvent import android.view.View @@ -17,6 +16,7 @@ import org.schabi.newpipe.player.Player import org.schabi.newpipe.player.helper.AudioReactor import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.MainPlayerUi +import org.schabi.newpipe.util.ThemeHelper.getAndroidDimenPx import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -172,9 +172,13 @@ class MainPlayerGestureListener( return false } - val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(player.context) - val isTouchingNavigationBar: Boolean = - initialEvent.y > (binding.root.height - getNavigationBarHeight(player.context)) + // Calculate heights of status and navigation bars + val statusBarHeight = getAndroidDimenPx(player.context, "status_bar_height") + val navigationBarHeight = getAndroidDimenPx(player.context, "navigation_bar_height") + + // Do not handle this event if initially it started from status or navigation bars + val isTouchingStatusBar = initialEvent.y < statusBarHeight + val isTouchingNavigationBar = initialEvent.y > (binding.root.height - navigationBarHeight) if (isTouchingStatusBar || isTouchingNavigationBar) { return false } @@ -226,21 +230,5 @@ class MainPlayerGestureListener( private val TAG = MainPlayerGestureListener::class.java.simpleName private val DEBUG = MainActivity.DEBUG private const val MOVEMENT_THRESHOLD = 40 - - private fun getNavigationBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("navigation_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } - - private fun getStatusBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("status_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index b8e3a86ed..389af80ee 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -244,6 +244,22 @@ public final class ThemeHelper { return AppCompatResources.getDrawable(context, typedValue.resourceId); } + /** + * Gets a runtime dimen from the {@code android} package. Should be used for dimens for which + * normal accessing with {@code R.dimen.} is not available. + * + * @param context context + * @param name dimen resource name (e.g. navigation_bar_height) + * @return the obtained dimension, in pixels, or 0 if the resource could not be resolved + */ + public static int getAndroidDimenPx(@NonNull final Context context, final String name) { + final int resId = context.getResources().getIdentifier(name, "dimen", "android"); + if (resId <= 0) { + return 0; + } + return context.getResources().getDimensionPixelSize(resId); + } + private static String getSelectedThemeKey(final Context context) { final String themeKey = context.getString(R.string.theme_key); final String defaultTheme = context.getResources().getString(R.string.default_theme_value); From 3692858a3d10fb33d0386149d630264ca93eca23 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 8 Jul 2022 22:33:35 +0200 Subject: [PATCH 041/152] Move popup layout param to PopupPlayerUi --- .../gesture/PopupPlayerGestureListener.kt | 71 +++++------ .../newpipe/player/helper/PlayerHelper.java | 103 +--------------- .../newpipe/player/ui/PopupPlayerUi.java | 110 +++++++++++++++++- 3 files changed, 144 insertions(+), 140 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt index b8c1bc54c..bda6ee8d1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -7,7 +7,6 @@ import android.view.ViewConfiguration import org.schabi.newpipe.MainActivity import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate -import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.PopupPlayerUi import kotlin.math.abs import kotlin.math.hypot @@ -87,7 +86,7 @@ class PopupPlayerGestureListener( player.changeState(player.currentState) } if (!playerUi.isPopupClosing) { - PlayerHelper.savePopupPositionAndSizeToPrefs(playerUi) + playerUi.savePopupPositionAndSizeToPrefs() } } @@ -106,40 +105,42 @@ class PopupPlayerGestureListener( } private fun handleMultiDrag(event: MotionEvent): Boolean { - if (initPointerDistance != -1.0 && event.pointerCount == 2) { - // get the movements of the fingers - val firstPointerMove = hypot( - event.getX(0) - initFirstPointerX.toDouble(), - event.getY(0) - initFirstPointerY.toDouble() - ) - val secPointerMove = hypot( - event.getX(1) - initSecPointerX.toDouble(), - event.getY(1) - initSecPointerY.toDouble() - ) - - // minimum threshold beyond which pinch gesture will work - val minimumMove = ViewConfiguration.get(player.context).scaledTouchSlop - - if (max(firstPointerMove, secPointerMove) > minimumMove) { - // calculate current distance between the pointers - val currentPointerDistance = hypot( - event.getX(0) - event.getX(1).toDouble(), - event.getY(0) - event.getY(1).toDouble() - ) - - val popupWidth = playerUi.popupLayoutParams.width.toDouble() - // change co-ordinates of popup so the center stays at the same position - val newWidth = popupWidth * currentPointerDistance / initPointerDistance - initPointerDistance = currentPointerDistance - playerUi.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt() - - playerUi.checkPopupPositionBounds() - playerUi.updateScreenSize() - playerUi.changePopupSize(min(playerUi.screenWidth.toDouble(), newWidth).toInt()) - return true - } + if (initPointerDistance == -1.0 || event.pointerCount != 2) { + return false } - return false + + // get the movements of the fingers + val firstPointerMove = hypot( + event.getX(0) - initFirstPointerX.toDouble(), + event.getY(0) - initFirstPointerY.toDouble() + ) + val secPointerMove = hypot( + event.getX(1) - initSecPointerX.toDouble(), + event.getY(1) - initSecPointerY.toDouble() + ) + + // minimum threshold beyond which pinch gesture will work + val minimumMove = ViewConfiguration.get(player.context).scaledTouchSlop + if (max(firstPointerMove, secPointerMove) <= minimumMove) { + return false + } + + // calculate current distance between the pointers + val currentPointerDistance = hypot( + event.getX(0) - event.getX(1).toDouble(), + event.getY(0) - event.getY(1).toDouble() + ) + + val popupWidth = playerUi.popupLayoutParams.width.toDouble() + // change co-ordinates of popup so the center stays at the same position + val newWidth = popupWidth * currentPointerDistance / initPointerDistance + initPointerDistance = currentPointerDistance + playerUi.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt() + + playerUi.checkPopupPositionBounds() + playerUi.updateScreenSize() + playerUi.changePopupSize(min(playerUi.screenWidth.toDouble(), newWidth).toInt()) + return true } private fun onPopupResizingStart() { diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index ec4cf8602..d1d29dd71 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -10,19 +10,13 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLA import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; -import static org.schabi.newpipe.player.ui.PopupPlayerUi.IDLE_WINDOW_FLAGS; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.graphics.PixelFormat; -import android.os.Build; import android.provider.Settings; -import android.view.Gravity; -import android.view.ViewGroup; -import android.view.WindowManager; import android.view.accessibility.CaptioningManager; import androidx.annotation.IntDef; @@ -49,12 +43,11 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.utils.Utils; -import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; -import org.schabi.newpipe.player.ui.PopupPlayerUi; import org.schabi.newpipe.util.ListHelper; import java.lang.annotation.Retention; @@ -77,20 +70,6 @@ public final class PlayerHelper { private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x"); private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%"); - /** - * Maximum opacity allowed for Android 12 and higher to allow touches on other apps when using - * NewPipe's popup player. - * - *

- * This value is hardcoded instead of being get dynamically with the method linked of the - * constant documentation below, because it is not static and popup player layout parameters - * are generated with static methods. - *

- * - * @see WindowManager.LayoutParams#FLAG_NOT_TOUCHABLE - */ - private static final float MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER = 0.8f; - @Retention(SOURCE) @IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI, AUTOPLAY_TYPE_NEVER}) @@ -525,90 +504,10 @@ public final class PlayerHelper { .apply(); } - /** - * @param playerUi {@code screenWidth} and {@code screenHeight} must have been initialized - * @return the popup starting layout params - */ - @SuppressLint("RtlHardcoded") - public static WindowManager.LayoutParams retrievePopupLayoutParamsFromPrefs( - final PopupPlayerUi playerUi) { - final SharedPreferences prefs = playerUi.getPlayer().getPrefs(); - final Context context = playerUi.getPlayer().getContext(); - - final boolean popupRememberSizeAndPos = prefs.getBoolean( - context.getString(R.string.popup_remember_size_pos_key), true); - final float defaultSize = context.getResources().getDimension(R.dimen.popup_default_width); - final float popupWidth = popupRememberSizeAndPos - ? prefs.getFloat(context.getString(R.string.popup_saved_width_key), defaultSize) - : defaultSize; - final float popupHeight = getMinimumVideoHeight(popupWidth); - - final WindowManager.LayoutParams popupLayoutParams = new WindowManager.LayoutParams( - (int) popupWidth, (int) popupHeight, - popupLayoutParamType(), - IDLE_WINDOW_FLAGS, - PixelFormat.TRANSLUCENT); - popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; - popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - - final int centerX = (int) (playerUi.getScreenWidth() / 2f - popupWidth / 2f); - final int centerY = (int) (playerUi.getScreenHeight() / 2f - popupHeight / 2f); - popupLayoutParams.x = popupRememberSizeAndPos - ? prefs.getInt(context.getString(R.string.popup_saved_x_key), centerX) : centerX; - popupLayoutParams.y = popupRememberSizeAndPos - ? prefs.getInt(context.getString(R.string.popup_saved_y_key), centerY) : centerY; - - return popupLayoutParams; - } - - public static void savePopupPositionAndSizeToPrefs(final PopupPlayerUi playerUi) { - if (playerUi.getPopupLayoutParams() != null) { - final Context context = playerUi.getPlayer().getContext(); - playerUi.getPlayer().getPrefs().edit() - .putFloat(context.getString(R.string.popup_saved_width_key), - playerUi.getPopupLayoutParams().width) - .putInt(context.getString(R.string.popup_saved_x_key), - playerUi.getPopupLayoutParams().x) - .putInt(context.getString(R.string.popup_saved_y_key), - playerUi.getPopupLayoutParams().y) - .apply(); - } - } - public static float getMinimumVideoHeight(final float width) { return width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have } - @SuppressLint("RtlHardcoded") - public static WindowManager.LayoutParams buildCloseOverlayLayoutParams() { - final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - - final WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, - popupLayoutParamType(), - flags, - PixelFormat.TRANSLUCENT); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // Setting maximum opacity allowed for touch events to other apps for Android 12 and - // higher to prevent non interaction when using other apps with the popup player - closeOverlayLayoutParams.alpha = MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER; - } - - closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; - closeOverlayLayoutParams.softInputMode = - WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - return closeOverlayLayoutParams; - } - - public static int popupLayoutParamType() { - return Build.VERSION.SDK_INT < Build.VERSION_CODES.O - ? WindowManager.LayoutParams.TYPE_PHONE - : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - } - public static int retrieveSeekDurationFromPreferences(final Player player) { return Integer.parseInt(Objects.requireNonNull(player.getPrefs().getString( player.getContext().getString(R.string.seek_duration_key), 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 8283437f8..46396a840 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 @@ -2,21 +2,25 @@ package org.schabi.newpipe.player.ui; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static org.schabi.newpipe.MainActivity.DEBUG; -import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.PixelFormat; +import android.os.Build; import android.util.DisplayMetrics; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AnticipateInterpolator; import android.widget.LinearLayout; @@ -38,6 +42,20 @@ import org.schabi.newpipe.player.helper.PlayerHelper; public final class PopupPlayerUi extends VideoPlayerUi { private static final String TAG = PopupPlayerUi.class.getSimpleName(); + /** + * Maximum opacity allowed for Android 12 and higher to allow touches on other apps when using + * NewPipe's popup player. + * + *

+ * This value is hardcoded instead of being get dynamically with the method linked of the + * constant documentation below, because it is not static and popup player layout parameters + * are generated with static methods. + *

+ * + * @see WindowManager.LayoutParams#FLAG_NOT_TOUCHABLE + */ + private static final float MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER = 0.8f; + /*////////////////////////////////////////////////////////////////////////// // Popup player //////////////////////////////////////////////////////////////////////////*/ @@ -98,7 +116,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { updateScreenSize(); - popupLayoutParams = retrievePopupLayoutParamsFromPrefs(this); + popupLayoutParams = retrievePopupLayoutParamsFromPrefs(); binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); checkPopupPositionBounds(); @@ -446,6 +464,92 @@ public final class PopupPlayerUi extends VideoPlayerUi { //endregion + /*////////////////////////////////////////////////////////////////////////// + // Popup & closing overlay layout params + saving popup position and size + //////////////////////////////////////////////////////////////////////////*/ + //region Popup & closing overlay layout params + saving popup position and size + + /** + * {@code screenWidth} and {@code screenHeight} must have been initialized. + * @return the popup starting layout params + */ + @SuppressLint("RtlHardcoded") + public WindowManager.LayoutParams retrievePopupLayoutParamsFromPrefs() { + final SharedPreferences prefs = getPlayer().getPrefs(); + final Context context = getPlayer().getContext(); + + final boolean popupRememberSizeAndPos = prefs.getBoolean( + context.getString(R.string.popup_remember_size_pos_key), true); + final float defaultSize = context.getResources().getDimension(R.dimen.popup_default_width); + final float popupWidth = popupRememberSizeAndPos + ? prefs.getFloat(context.getString(R.string.popup_saved_width_key), defaultSize) + : defaultSize; + final float popupHeight = getMinimumVideoHeight(popupWidth); + + final WindowManager.LayoutParams params = new WindowManager.LayoutParams( + (int) popupWidth, (int) popupHeight, + popupLayoutParamType(), + IDLE_WINDOW_FLAGS, + PixelFormat.TRANSLUCENT); + params.gravity = Gravity.LEFT | Gravity.TOP; + params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + + final int centerX = (int) (screenWidth / 2f - popupWidth / 2f); + final int centerY = (int) (screenHeight / 2f - popupHeight / 2f); + params.x = popupRememberSizeAndPos + ? prefs.getInt(context.getString(R.string.popup_saved_x_key), centerX) : centerX; + params.y = popupRememberSizeAndPos + ? prefs.getInt(context.getString(R.string.popup_saved_y_key), centerY) : centerY; + + return params; + } + + public void savePopupPositionAndSizeToPrefs() { + if (getPopupLayoutParams() != null) { + final Context context = getPlayer().getContext(); + getPlayer().getPrefs().edit() + .putFloat(context.getString(R.string.popup_saved_width_key), + popupLayoutParams.width) + .putInt(context.getString(R.string.popup_saved_x_key), + popupLayoutParams.x) + .putInt(context.getString(R.string.popup_saved_y_key), + popupLayoutParams.y) + .apply(); + } + } + + @SuppressLint("RtlHardcoded") + public static WindowManager.LayoutParams buildCloseOverlayLayoutParams() { + final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + + final WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, + popupLayoutParamType(), + flags, + PixelFormat.TRANSLUCENT); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // Setting maximum opacity allowed for touch events to other apps for Android 12 and + // higher to prevent non interaction when using other apps with the popup player + closeOverlayLayoutParams.alpha = MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER; + } + + closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; + closeOverlayLayoutParams.softInputMode = + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + return closeOverlayLayoutParams; + } + + public static int popupLayoutParamType() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.O + ? WindowManager.LayoutParams.TYPE_PHONE + : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + } + //endregion + + /*////////////////////////////////////////////////////////////////////////// // Getters //////////////////////////////////////////////////////////////////////////*/ From 61c1da144e4b0a824d4816d9ecad6a59b53d9cf3 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Jul 2022 17:17:30 +0200 Subject: [PATCH 042/152] Some refactorings after review comments --- .../fragments/detail/VideoDetailFragment.java | 11 +++-- .../newpipe/local/dialog/PlaylistDialog.java | 2 +- .../schabi/newpipe/player/PlayerService.java | 23 ++++----- .../newpipe/player/ui/MainPlayerUi.java | 47 ++++++++----------- .../newpipe/player/ui/VideoPlayerUi.java | 19 +++----- 5 files changed, 42 insertions(+), 60 deletions(-) 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 8ffff2f9e..5dc6bb436 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 @@ -1311,11 +1311,12 @@ public final class VideoDetailFragment setHeightThumbnail(); // Prevent from re-adding a view multiple times - new Handler().post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { - playerUi.removeViewFromParent(); - binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); - playerUi.setupVideoSurfaceIfNeeded(); - })); + new Handler(Looper.getMainLooper()).post(() -> + player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + })); } private void removeVideoPlayerView() { diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java index dec8b05b2..612c38181 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java @@ -168,7 +168,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave final List streamEntities = Stream.of(player.getPlayQueue()) .filter(Objects::nonNull) - .flatMap(playQueue -> Objects.requireNonNull(playQueue).getStreams().stream()) + .flatMap(playQueue -> playQueue.getStreams().stream()) .map(StreamEntity::new) .collect(Collectors.toList()); if (streamEntities.isEmpty()) { 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 326b01590..14e8262d6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -33,8 +33,6 @@ import org.schabi.newpipe.util.ThemeHelper; /** * One service for all players. - * - * @author mauriciocolli */ public final class PlayerService extends Service { private static final String TAG = PlayerService.class.getSimpleName(); @@ -72,14 +70,16 @@ public final class PlayerService extends Service { + "], flags = [" + flags + "], startId = [" + startId + "]"); } - if (!Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) - || player.getPlayQueue() != null) { - // ^ no need to process media button's action if player is not working + if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) + && player.getPlayQueue() == null) { + // No need to process media button's actions if the player is not working, otherwise the + // player service would strangely start with nothing to play + return START_NOT_STICKY; + } - player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); - } + player.handleIntent(intent); + if (player.getMediaSessionManager() != null) { + player.getMediaSessionManager().handleMediaButtonIntent(intent); } return START_NOT_STICKY; @@ -97,11 +97,6 @@ public final class PlayerService extends Service { // We can't just pause the player here because it will make transition // from one stream to a new stream not smooth player.smoothStopForImmediateReusing(); - - // Notification shows information about old stream but if a user selects - // a stream from backStack it's not actual anymore - // So we should hide the notification at all. - // When autoplay enabled such notification flashing is annoying so skip this case } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index d9f5ea7f4..278e4f1ff 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -75,6 +75,11 @@ import java.util.Optional; public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutChangeListener { private static final String TAG = MainPlayerUi.class.getSimpleName(); + // see the Javadoc of calculateMaxEndScreenThumbnailHeight for information + private static final int DETAIL_ROOT_MINIMUM_HEIGHT = 85; // dp + private static final int DETAIL_TITLE_TEXT_SIZE_TV = 16; // sp + private static final int DETAIL_TITLE_TEXT_SIZE_TABLET = 15; // sp + private boolean isFullscreen = false; private boolean isVerticalVideo = false; private boolean fragmentIsVisible = false; @@ -262,13 +267,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.topControls.setClickable(true); binding.topControls.setFocusable(true); - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - } + binding.titleTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); + binding.channelTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); } @Override @@ -450,13 +450,12 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh * The calculating follows these rules: *
    *
  • - * Show at least stream title and content creator on TVs and tablets - * when in landscape (always the case for TVs) and not in fullscreen mode. - * This requires to have at least 85dp free space for {@link R.id.detail_root} - * and additional space for the stream title text size - * ({@link R.id.detail_title_root_layout}). - * The text size is 15sp on tablets and 16sp on TVs, - * see {@link R.id.titleTextView}. + * Show at least stream title and content creator on TVs and tablets when in landscape + * (always the case for TVs) and not in fullscreen mode. This requires to have at least + * {@link #DETAIL_ROOT_MINIMUM_HEIGHT} free space for {@link R.id.detail_root} and + * additional space for the stream title text size ({@link R.id.detail_title_root_layout}). + * The text size is {@link #DETAIL_TITLE_TEXT_SIZE_TABLET} on tablets and + * {@link #DETAIL_TITLE_TEXT_SIZE_TV} on TVs, see {@link R.id.titleTextView}. *
  • *
  • * Otherwise, the max thumbnail height is the screen height. @@ -472,12 +471,12 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh final int screenHeight = context.getResources().getDisplayMetrics().heightPixels; if (DeviceUtils.isTv(context) && !isFullscreen()) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context); + final int videoInfoHeight = DeviceUtils.dpToPx(DETAIL_ROOT_MINIMUM_HEIGHT, context) + + DeviceUtils.spToPx(DETAIL_TITLE_TEXT_SIZE_TV, context); return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); } else if (DeviceUtils.isTablet(context) && isLandscape() && !isFullscreen()) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context); + final int videoInfoHeight = DeviceUtils.dpToPx(DETAIL_ROOT_MINIMUM_HEIGHT, context) + + DeviceUtils.spToPx(DETAIL_TITLE_TEXT_SIZE_TABLET, context); return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); } else { // fullscreen player: max height is the device height return Math.min(bitmap.getHeight(), screenHeight); @@ -933,15 +932,9 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } fragmentListener.onFullscreenStateChanged(isFullscreen); - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - binding.playerCloseButton.setVisibility(View.GONE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - binding.playerCloseButton.setVisibility(View.VISIBLE); - } + binding.titleTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); + binding.channelTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); + binding.playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE); setupScreenRotationButton(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 5b0be6f64..4d1065112 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.player.ui; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.ktx.ViewUtils.animate; @@ -912,18 +911,12 @@ public abstract class VideoPlayerUi extends PlayerUi public void onRepeatModeChanged(@RepeatMode final int repeatMode) { super.onRepeatModeChanged(repeatMode); - switch (repeatMode) { - case REPEAT_MODE_OFF: - binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_off); - break; - case REPEAT_MODE_ONE: - binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_one); - break; - case REPEAT_MODE_ALL: - binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_all); - break; - default: - break; // unreachable + if (repeatMode == REPEAT_MODE_ALL) { + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_all); + } else if (repeatMode == REPEAT_MODE_ONE) { + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_one); + } else /* repeatMode == REPEAT_MODE_OFF */ { + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_off); } } From c03eac1dc99e9e61b863b5984444407dc8ee13a2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Jul 2022 17:50:12 +0200 Subject: [PATCH 043/152] Some SonarLint refactors --- .../player/gesture/BasePlayerGestureListener.kt | 3 ++- .../newpipe/player/gesture/DoubleTapListener.kt | 6 +++--- .../org/schabi/newpipe/player/ui/MainPlayerUi.java | 11 +++++------ .../org/schabi/newpipe/player/ui/PopupPlayerUi.java | 3 +-- .../org/schabi/newpipe/player/ui/VideoPlayerUi.java | 3 ++- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt index b006e73aa..555c34f96 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -1,6 +1,7 @@ package org.schabi.newpipe.player.gesture import android.os.Handler +import android.os.Looper import android.util.Log import android.view.GestureDetector import android.view.MotionEvent @@ -130,7 +131,7 @@ abstract class BasePlayerGestureListener( } private var doubleTapDelay = DOUBLE_TAP_DELAY - private val doubleTapHandler: Handler = Handler() + private val doubleTapHandler: Handler = Handler(Looper.getMainLooper()) private val doubleTapRunnable = Runnable { if (DEBUG) Log.d(TAG, "doubleTapRunnable called") diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt index 1a0b141e6..fc026abd9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt @@ -1,7 +1,7 @@ package org.schabi.newpipe.player.gesture interface DoubleTapListener { - fun onDoubleTapStarted(portion: DisplayPortion) {} - fun onDoubleTapProgressDown(portion: DisplayPortion) {} - fun onDoubleTapFinished() {} + fun onDoubleTapStarted(portion: DisplayPortion) + fun onDoubleTapProgressDown(portion: DisplayPortion) + fun onDoubleTapFinished() } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 278e4f1ff..52a486add 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.player.ui; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; import static org.schabi.newpipe.ktx.ViewUtils.animate; @@ -21,6 +22,7 @@ import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Color; import android.os.Handler; +import android.os.Looper; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; @@ -156,7 +158,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh .ifPresent(fragmentManager -> PlaylistDialog.showForPlayQueue(player, fragmentManager))); - settingsContentObserver = new ContentObserver(new Handler()) { + settingsContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { @Override public void onChange(final boolean selfChange) { setupScreenRotationButton(); @@ -237,8 +239,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh private void initVideoPlayer() { // restore last resize mode setResizeMode(PlayerHelper.retrieveResizeModeFromPrefs(player)); - binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } @Override @@ -253,8 +254,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); binding.moreOptionsButton.setVisibility(View.VISIBLE); binding.topControls.setOrientation(LinearLayout.VERTICAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.MATCH_PARENT; + binding.primaryControls.getLayoutParams().width = MATCH_PARENT; binding.secondaryControls.setVisibility(View.INVISIBLE); binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_expand_more)); @@ -459,7 +459,6 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh *
  • *
  • * Otherwise, the max thumbnail height is the screen height. - * TODO investigate why this is done on popup player, too *
  • *
* 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 46396a840..bb810f86b 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 @@ -287,8 +287,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { } final float minimumWidth = context.getResources().getDimension(R.dimen.popup_minimum_width); - final int actualWidth = (int) (width > screenWidth ? screenWidth - : (width < minimumWidth ? minimumWidth : width)); + final int actualWidth = Math.min((int) Math.max(width, minimumWidth), screenWidth); final int actualHeight = (int) getMinimumVideoHeight(width); if (DEBUG) { Log.d(TAG, "updatePopupSize() updated values:" diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 4d1065112..bdb327df1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -25,6 +25,7 @@ import android.graphics.PorterDuffColorFilter; import android.net.Uri; import android.os.Build; import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; @@ -103,7 +104,7 @@ public abstract class VideoPlayerUi extends PlayerUi //////////////////////////////////////////////////////////////////////////*/ protected PlayerBinding binding; - private final Handler controlsVisibilityHandler = new Handler(); + private final Handler controlsVisibilityHandler = new Handler(Looper.getMainLooper()); @Nullable private SurfaceHolderCallback surfaceHolderCallback; boolean surfaceIsSetup = false; @Nullable private Bitmap thumbnail = null; From 4443c908cb203e97df5e2624c34681aea2cfe129 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Jul 2022 17:58:03 +0200 Subject: [PATCH 044/152] Fix SonarLint java:S5320, restrict broadcasts to app package --- .../java/org/schabi/newpipe/player/ui/VideoPlayerUi.java | 5 ++++- .../settings/custom/NotificationActionsPreference.java | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index bdb327df1..d38c8cfe4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -57,6 +57,7 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.CaptionStyleCompat; import com.google.android.exoplayer2.video.VideoSize; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.extractor.MediaFormat; @@ -1372,7 +1373,9 @@ public abstract class VideoPlayerUi extends PlayerUi } else if (v.getId() == binding.switchMute.getId()) { player.toggleMute(); } else if (v.getId() == binding.playerCloseButton.getId()) { - context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)); + // set package to this app's package to prevent the intent from being seen outside + context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER) + .setPackage(App.PACKAGE_NAME)); } else if (v.getId() == binding.playbackSpeed.getId()) { onPlaybackSpeedClicked(); } else if (v.getId() == binding.qualityTextView.getId()) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index b4f6d598a..03b5a5a95 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -25,6 +25,7 @@ import androidx.core.graphics.drawable.DrawableCompat; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; @@ -62,7 +63,9 @@ public class NotificationActionsPreference extends Preference { public void onDetached() { super.onDetached(); saveChanges(); - getContext().sendBroadcast(new Intent(ACTION_RECREATE_NOTIFICATION)); + // set package to this app's package to prevent the intent from being seen outside + getContext().sendBroadcast(new Intent(ACTION_RECREATE_NOTIFICATION) + .setPackage(App.PACKAGE_NAME)); } From 8187a3bc04704e8b1cc380ccbf0c0274a4514494 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 10 Jul 2022 23:05:37 +0200 Subject: [PATCH 045/152] Move PlayerType into its own class and add documentation Also replace some `isPlayerOpen` with direct `playerType == null` checks. --- .../org/schabi/newpipe/RouterActivity.java | 6 ++-- .../fragments/detail/VideoDetailFragment.java | 2 +- .../list/channel/ChannelFragment.java | 2 +- .../list/playlist/PlaylistFragment.java | 2 +- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../org/schabi/newpipe/player/Player.java | 4 +-- .../schabi/newpipe/player/PlayerService.java | 5 --- .../org/schabi/newpipe/player/PlayerType.java | 32 +++++++++++++++++++ .../newpipe/player/helper/PlayerHelper.java | 9 ------ .../newpipe/player/helper/PlayerHolder.java | 7 ++-- .../schabi/newpipe/util/NavigationHelper.java | 28 ++++++++-------- 11 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/PlayerType.java diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 1194b4068..d055da1e8 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -60,7 +60,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.dialog.PlaylistDialog; -import org.schabi.newpipe.player.PlayerService; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; @@ -630,8 +630,8 @@ public class RouterActivity extends AppCompatActivity { } // ...the player is not running or in normal Video-mode/type - final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); - return playerType == null || playerType == PlayerService.PlayerType.MAIN; + final PlayerType playerType = PlayerHolder.getInstance().getType(); + return playerType == null || playerType == PlayerType.MAIN; } private void openAddToPlaylistDialog() { 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 5dc6bb436..92e7e4f16 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 @@ -90,7 +90,7 @@ import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.PlayerService; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.helper.PlayerHelper; 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 aabd64744..e44048473 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 @@ -43,7 +43,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.local.feed.notifications.NotificationHelper; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ExtractorHelper; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 65fd8ada1..e3caeb522 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -43,7 +43,7 @@ import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.util.ExtractorHelper; 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 3bec07dcc..7fc72e064 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 @@ -43,7 +43,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.util.Localization; 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 2d44c6449..17b5a1985 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -32,7 +32,6 @@ import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; @@ -95,7 +94,6 @@ import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; @@ -308,7 +306,7 @@ public final class Player implements PlaybackListener, Listener { } final PlayerType oldPlayerType = playerType; - playerType = retrievePlayerTypeFromIntent(intent); + playerType = PlayerType.retrieveFromIntent(intent); initUIsForCurrentPlayerType(); // We need to setup audioOnly before super(), see "sourceOf" isAudioOnly = audioPlayerSelected(); 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 14e8262d6..8d982617a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -42,11 +42,6 @@ public final class PlayerService extends Service { private final IBinder mBinder = new PlayerService.LocalBinder(); - public enum PlayerType { - MAIN, - AUDIO, - POPUP - } /*////////////////////////////////////////////////////////////////////////// // Service's LifeCycle diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerType.java b/app/src/main/java/org/schabi/newpipe/player/PlayerType.java new file mode 100644 index 000000000..171a70395 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerType.java @@ -0,0 +1,32 @@ +package org.schabi.newpipe.player; + +import static org.schabi.newpipe.player.Player.PLAYER_TYPE; + +import android.content.Intent; + +public enum PlayerType { + MAIN, + AUDIO, + POPUP; + + /** + * @return an integer representing this {@link PlayerType}, to be used to save it in intents + * @see #retrieveFromIntent(Intent) Use retrieveFromIntent() to retrieve and convert player type + * integers from an intent + */ + public int valueForIntent() { + return ordinal(); + } + + /** + * @param intent the intent to retrieve a player type from + * @return the player type integer retrieved from the intent, converted back into a {@link + * PlayerType}, or {@link PlayerType#MAIN} if there is no player type extra in the + * intent + * @throws ArrayIndexOutOfBoundsException if the intent contains an invalid player type integer + * @see #valueForIntent() Use valueForIntent() to obtain valid player type integers + */ + public static PlayerType retrieveFromIntent(final Intent intent) { + return values()[intent.getIntExtra(PLAYER_TYPE, MAIN.valueForIntent())]; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index d1d29dd71..fb346f5ba 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.player.helper; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.Player.PLAYER_TYPE; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI; @@ -14,7 +13,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.SuppressLint; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; import android.provider.Settings; import android.view.accessibility.CaptioningManager; @@ -44,7 +42,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.utils.Utils; import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; @@ -428,12 +425,6 @@ public final class PlayerHelper { // Utils used by player //////////////////////////////////////////////////////////////////////////// - public static PlayerService.PlayerType retrievePlayerTypeFromIntent(final Intent intent) { - // If you want to open popup from the app just include Constants.POPUP_ONLY into an extra - return PlayerService.PlayerType.values()[ - intent.getIntExtra(PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal())]; - } - public static boolean isPlaybackResumeEnabled(final Player player) { return player.getPrefs().getBoolean( player.getContext().getString(R.string.enable_watch_history_key), true) 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 cb613f854..5eaecd48d 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 @@ -18,6 +18,7 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -46,13 +47,13 @@ public final class PlayerHolder { @Nullable private Player player; /** - * Returns the current {@link PlayerService.PlayerType} of the {@link PlayerService} service, - * otherwise `null` if no service running. + * Returns the current {@link PlayerType} of the {@link PlayerService} service, + * otherwise `null` if no service is running. * * @return Current PlayerType */ @Nullable - public PlayerService.PlayerType getType() { + public PlayerType getType() { if (player == null) { return null; } 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 36b2bd46d..3b2c52691 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -51,9 +51,9 @@ import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment; import org.schabi.newpipe.player.PlayerService; -import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.PlayQueueActivity; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -91,7 +91,7 @@ public final class NavigationHelper { intent.putExtra(Player.PLAY_QUEUE_KEY, cacheKey); } } - intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN.valueForIntent()); intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback); return intent; @@ -164,7 +164,7 @@ public final class NavigationHelper { Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.POPUP.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerType.POPUP.valueForIntent()); ContextCompat.startForegroundService(context, intent); } @@ -175,7 +175,7 @@ public final class NavigationHelper { .show(); final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.AUDIO.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerType.AUDIO.valueForIntent()); ContextCompat.startForegroundService(context, intent); } @@ -186,15 +186,15 @@ public final class NavigationHelper { Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show(); final Intent intent = getPlayerEnqueueIntent(context, PlayerService.class, queue); - intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent()); ContextCompat.startForegroundService(context, intent); } public static void enqueueOnPlayer(final Context context, final PlayQueue queue) { PlayerType playerType = PlayerHolder.getInstance().getType(); - if (!PlayerHolder.getInstance().isPlayerOpen()) { + if (playerType == null) { Log.e(TAG, "Enqueueing but no player is open; defaulting to background player"); - playerType = PlayerService.PlayerType.AUDIO; + playerType = PlayerType.AUDIO; } enqueueOnPlayer(context, queue, playerType); @@ -203,14 +203,14 @@ public final class NavigationHelper { /* ENQUEUE NEXT */ public static void enqueueNextOnPlayer(final Context context, final PlayQueue queue) { PlayerType playerType = PlayerHolder.getInstance().getType(); - if (!PlayerHolder.getInstance().isPlayerOpen()) { + if (playerType == null) { Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player"); - playerType = PlayerService.PlayerType.AUDIO; + playerType = PlayerType.AUDIO; } Toast.makeText(context, R.string.enqueued_next, Toast.LENGTH_SHORT).show(); final Intent intent = getPlayerEnqueueNextIntent(context, PlayerService.class, queue); - intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent()); ContextCompat.startForegroundService(context, intent); } @@ -414,14 +414,14 @@ public final class NavigationHelper { final boolean switchingPlayers) { final boolean autoPlay; - @Nullable final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); - if (!PlayerHolder.getInstance().isPlayerOpen()) { + @Nullable final PlayerType playerType = PlayerHolder.getInstance().getType(); + if (playerType == null) { // no player open autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else if (switchingPlayers) { // switching player to main player autoPlay = PlayerHolder.getInstance().isPlaying(); // keep play/pause state - } else if (playerType == PlayerService.PlayerType.MAIN) { + } else if (playerType == PlayerType.MAIN) { // opening new stream while already playing in main player autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else { @@ -436,7 +436,7 @@ public final class NavigationHelper { // Situation when user switches from players to main player. All needed data is // here, we can start watching (assuming newQueue equals playQueue). // Starting directly in fullscreen if the previous player type was popup. - detailFragment.openVideoPlayer(playerType == PlayerService.PlayerType.POPUP + detailFragment.openVideoPlayer(playerType == PlayerType.POPUP || PlayerHelper.isStartMainPlayerFullscreenEnabled(context)); } else { detailFragment.selectAndLoadVideo(serviceId, url, title, playQueue); From 4536e8b55b59502471cd0b409f8b68e8cff600d2 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Jul 2022 01:48:52 -0400 Subject: [PATCH 046/152] Update some miscellaneous libraries --- app/build.gradle | 18 +++++++++--------- build.gradle | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9867037e6..787dd7833 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,7 +95,7 @@ android { } ext { - checkstyleVersion = '10.0' + checkstyleVersion = '10.3.1' androidxLifecycleVersion = '2.3.1' androidxRoomVersion = '2.4.2' @@ -110,7 +110,7 @@ ext { leakCanaryVersion = '2.5' stethoVersion = '1.6.0' mockitoVersion = '4.0.0' - assertJVersion = '3.22.0' + assertJVersion = '3.23.1' } configurations { @@ -179,7 +179,7 @@ sonarqube { dependencies { /** Desugaring **/ - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6' /** NewPipe libraries **/ // You can use a local version by uncommenting a few lines in settings.gradle @@ -191,7 +191,7 @@ dependencies { /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" - ktlint 'com.pinterest:ktlint:0.44.0' + ktlint 'com.pinterest:ktlint:0.45.2' /** Kotlin **/ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" @@ -199,14 +199,14 @@ dependencies { /** AndroidX **/ implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation 'androidx.core:core-ktx:1.6.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.fragment:fragment-ktx:1.3.6' implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' - implementation 'androidx.media:media:1.5.0' + implementation 'androidx.media:media:1.6.0' implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "androidx.room:room-runtime:${androidxRoomVersion}" @@ -226,7 +226,7 @@ dependencies { kapt "frankiesardo:icepick-processor:${icepickVersion}" // HTML parser - implementation "org.jsoup:jsoup:1.14.3" + implementation "org.jsoup:jsoup:1.15.2" // HTTP client //noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users @@ -274,7 +274,7 @@ dependencies { implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0" // Date and time formatting - implementation "org.ocpsoft.prettytime:prettytime:5.0.2.Final" + implementation "org.ocpsoft.prettytime:prettytime:5.0.3.Final" /** Debugging **/ // Memory leak detection diff --git a/build.gradle b/build.gradle index bea444fab..322a47a6f 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' + classpath 'com.android.tools.build:gradle:7.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong From c1e78cf55bf6e4178ac3ef3b4f7bae52c7f992c0 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Jul 2022 03:23:45 -0400 Subject: [PATCH 047/152] Update OkHttp to 4.x --- app/build.gradle | 3 +-- app/src/main/java/org/schabi/newpipe/DownloaderImpl.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9867037e6..b5e26e8fc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -229,8 +229,7 @@ dependencies { implementation "org.jsoup:jsoup:1.14.3" // HTTP client - //noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users - implementation "com.squareup.okhttp3:okhttp:3.12.13" + implementation "com.squareup.okhttp3:okhttp:4.10.0" // Media player implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}" diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index f2803dc2f..79d2ad7b7 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -135,7 +135,7 @@ public final class DownloaderImpl extends Downloader { RequestBody requestBody = null; if (dataToSend != null) { - requestBody = RequestBody.create(null, dataToSend); + requestBody = RequestBody.create(dataToSend); } final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder() From cc7a8fb1a611299bab6b574b75b602b38a23b2d9 Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Tue, 21 Jun 2022 20:22:39 +0300 Subject: [PATCH 048/152] Improve image placeholders - Show placeholders until the image is loaded because timeout can be very long and missing profile pictures and video thumbnails make app inconvenient to use - Adapt profile picture and video thumbnail placeholders to light theme - Replace profile picture and video thumbnail placeholders with vector graphics --- .../fragments/detail/VideoDetailFragment.java | 4 +- .../schabi/newpipe/util/PicassoHelper.java | 17 +++- app/src/main/res/drawable-nodpi/buddy.png | Bin 1168 -> 0 bytes .../res/drawable-nodpi/buddy_channel_item.png | Bin 1051 -> 0 bytes .../res/drawable-nodpi/dummy_thumbnail.png | Bin 956 -> 0 bytes .../drawable-nodpi/dummy_thumbnail_dark.png | Bin 105 -> 134 bytes .../dummy_thumbnail_playlist.png | Bin 1225 -> 0 bytes app/src/main/res/drawable/buddy.xml | 31 ++++++++ app/src/main/res/drawable/dummy_thumbnail.xml | 22 ++++++ .../res/drawable/dummy_thumbnail_playlist.xml | 30 +++++++ .../res/layout/list_channel_grid_item.xml | 2 +- .../res/layout/list_channel_mini_item.xml | 2 +- .../res/layout/list_comments_mini_item.xml | 2 +- .../res/layout/picker_subscription_item.xml | 2 +- app/src/main/res/values-night/colors.xml | 3 + app/src/main/res/values/colors.xml | 3 + assets/buddy_channel_item.svg | 73 ------------------ assets/dummy_thumbnail.svg | 7 ++ assets/dummy_thumbnail_playlist.svg | 8 ++ 19 files changed, 125 insertions(+), 81 deletions(-) delete mode 100644 app/src/main/res/drawable-nodpi/buddy.png delete mode 100644 app/src/main/res/drawable-nodpi/buddy_channel_item.png delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail.png delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_playlist.png create mode 100644 app/src/main/res/drawable/buddy.xml create mode 100644 app/src/main/res/drawable/dummy_thumbnail.xml create mode 100644 app/src/main/res/drawable/dummy_thumbnail_playlist.xml delete mode 100644 assets/buddy_channel_item.svg create mode 100644 assets/dummy_thumbnail.svg create mode 100644 assets/dummy_thumbnail_playlist.svg 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 278d472d4..78f3772e9 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 @@ -714,7 +714,7 @@ public final class VideoDetailFragment } private void initThumbnailViews(@NonNull final StreamInfo info) { - PicassoHelper.loadThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) + PicassoHelper.loadDetailsThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) .into(binding.detailThumbnailImageView, new Callback() { @Override public void onSuccess() { @@ -2361,7 +2361,7 @@ public final class VideoDetailFragment binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle); binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader); binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark); - PicassoHelper.loadThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) + PicassoHelper.loadDetailsThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) .into(binding.overlayThumbnail); } diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index aabc459d0..223e8ac9c 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -104,6 +104,10 @@ public final class PicassoHelper { return loadImageDefault(url, R.drawable.dummy_thumbnail); } + public static RequestCreator loadDetailsThumbnail(final String url) { + return loadImageDefault(url, R.drawable.dummy_thumbnail, false); + } + public static RequestCreator loadBanner(final String url) { return loadImageDefault(url, R.drawable.channel_banner); } @@ -189,15 +193,24 @@ public final class PicassoHelper { private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { + return loadImageDefault(url, placeholderResId, true); + } + + private static RequestCreator loadImageDefault(final String url, final int placeholderResId, + final boolean showPlaceholderWhileLoading) { if (!shouldLoadImages || isBlank(url)) { return picassoInstance .load((String) null) .placeholder(placeholderResId) // show placeholder when no image should load .error(placeholderResId); } else { - return picassoInstance + final RequestCreator requestCreator = picassoInstance .load(url) - .error(placeholderResId); // don't show placeholder while loading, only on error + .error(placeholderResId); + if (showPlaceholderWhileLoading) { + requestCreator.placeholder(placeholderResId); + } + return requestCreator; } } } diff --git a/app/src/main/res/drawable-nodpi/buddy.png b/app/src/main/res/drawable-nodpi/buddy.png deleted file mode 100644 index 8713ee02bfa8cd492bf79ed4730ca4060ceeb631..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1168 zcmV;B1aJF^P)b~j1!&(iVkGA*yRLsUHxctK)V zbrwUKLgK~AqOlZmKgKJ|YYQQFIo?ozuk3;}eY~-(u?X@o$J-OLt0fRsC3shIYX#(- zNuCL1%5yBL~_YZH(P`V$pD zqy|Nz=7uyeXe13Uq?I5_ww#bl3bN7*A4KIKEK_Wd1xQxb3@jDYkVb%~h8CjwU@E7D)L^S7h2&r>r-a;rv3o+u0E`Xk zAPrb+$RGt+E2toy1Zz?eLH1y7PXrl(xiJmoD!`n$l0ZtZSCT*`U~fzTQ3=?K6#?V~ z12qJY9SpP~fJ|VZ2?3-B1NHPn6pYkqhqN%#Njs#1k@nglYZz&x9WsTHrrIF`7-^^- z(ua{U?T{2kO0`2Ij3l)~1dJ5>0ucdX86@`ppFO<%W-wA;J7j3tN0jiPaRVc*wL|tW z(m^}q2qU%LA3lWRpvN%`H1XxgbV~qfVW4BUR(0a6bI$P>d_(L(AznCe$_5LNcT*v+jXfvEir znCq2FKcw!%K>b=XJ&yxYb}t60>+zM-w&x0SWt3!Cq&I* zNwe-fkfsM~>K)$>DFtk5^F~NFgJn&-w?NJrtSh^`9@3_;u;ln!NFBq<;`()vQozzg z`5MShz}m$2<&X`mZuwHk3YNEi8DtCV+q?vFD`0)1d>(Ru3yJzU$T5Z!#m&=@OA1Fy zFHb?b49=9RCm;nJYW{yn1*fY12RX-Zs`&IDNDjx!)juH{IM?Q1kXFFCMEf^H4d7q{ z^%vw8PPYFO(j{=RwEF|Hgrlu~hg@SgT73NtGKaG*euZ2GoK0MRfvn(g>z|NL!r^51 z1G0nDl|LYTI9>J~a)jfx-ym~1-r_4nC2+jtE94%|*L;C2;C#z3kRF_`_Zf1A2VFiv zDtJ)!2~xm==AR%p@Sy%`|M z$uorG8CUd71_tH>o-U3d6}R4;Zxmo);9%JJhx3$K|NgTH#ezx%!PgEjI8j$!K5O|Z zzLvy2;(yc}-&{ENO`)%0bIaX(=gaM!R6~nC-9PwPV)Ci;kG~avnkTi?=a=2jTl4N{ zx83>Fc`SZLVO{lFf$g#n=dZlDu5RW8EBOcZD);-Ra}@48_}^gXWceW8kKC7nDm?av z{7}t$snUMO=#NqLMCqU`n}bt=oWDu=2UgEil(xxLHhi++!=AZ&CIl^H4fW#QnYATp z-6p3wuHM%=xgr;BUAkm~*Fw`!POgnF5?&l>dGbPKx=@gGNm7=gqISuY(;O>>OQu)| zDq5Fp6BaabN>AogG;e*E$K+Hb5W&qcv*ExL`y-M927R_qIE2){P2ly&x#?`@_^8`9 zg-Iy*n*g`aG+Pzc&MCG>8e9&^Epl)=I^oA9p|5Gv|9_O;FYs6>^>zA1iRm9dwuc(C zHD=zanXpxM$-BblK7*9o({(RbU6}0qM(zE{jZVSpOW18czUVz*{WmegW?q`K#E&;8 zIozMM@LrYpw@^xI>NEM`BpI9A=8v3S9eR{`I96BZiOuzlqdMoBoK|%@RYxi+A9<^5 zwW<2Je9uCyzi!yp4gH%GgXh|nsmIJ$}Rs%;$ZVd<&qPs zt>P~&b376sblSD>L>`vk$|pWayFmy*8bpJ zDRAea_X(MUx-~5p%@2A$3j{SdK2%I$V>9Mg0=K=XbS{$IP4O z{n?Q9EWx0EHvgt=%|hblFXA#zo8%>#r^fe*&f;-Q{rKYTc1xbzcb$J8XBILuTkltv z5oR&^XcH35FX-~?gUqFvqkWdPs{=l)e{oXdf!djZXKw!r)ap6^?$Eit+B>((C@Ahr z%8CD%wr7ifRPy0|&a>oA>)UI$``IM7zB>BV)^x8?{0f`8h6Y=%9gnIuywI@r*jZu| znvoyGwTrDTQesQewD3gn&#@o>{EqW2>-&>r*LXZ_(Z|zYQa)$A*kiinPNr$lkNR(` zm;6sR%zC#cI!)?-n#K9myA;1)`|)ty({I&=+su4<5++@__3=@4bX@G?s;5ox STg?c}*bJVoelF{r5}E*aKG6LD diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail.png b/app/src/main/res/drawable-nodpi/dummy_thumbnail.png deleted file mode 100644 index 86f454186e1f39cf9ca3ac28dd95502fdbce4655..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 956 zcmeAS@N?(olHy`uVBq!ia0y~yU|a=cf8YQT3?*&#{6LDa*vT`5;~7`oJ0;^v zbkO&@i;A40S_@}sggCW&t!Uwz8UW%7X@xkchOB7uS`olGH2}z62;_nkpmA}jU-kOz z?QT77ynJ~dP~_Fwoytz3TYq1XT)67y-s}+O@=*J%Z%?i;Mux_3 zttx%jux8czTeWX`0zzf=t(8Met1B)GE?gC5W4~Bx?de}%la}pR;k0Z=9mDFTWjn$` zFNj=Ub!z6LsL;URn2^Ojg@LhG){9#l-MR`adqqS%^n}QD{)96?Wl{-U-LH!OR=otO zRE(Y`e_)B`y3nto-Qsg60IAvE!BqKrE0CQfuM(X^i>I|_E!nISGXK@})Aa@JqL$g8 zm}h)>kQeHDc2>Ysn;nZ+MeTdl5Wos#o!Z^fmi4Rp@(R0a^3&@*dbEBXlnuRl__lyn z$kvL&a|?hT{wv|os`YerO&ch7mWLgHnG*aK9*E?oBd!%lXN&;yEws~T?4U2%!8 zGZ1W|y^5RZ-?x4t?_ckKX>j0(Z0M6x$B_N^avJup4{*IV!SU;B-T3be3%P7WzHRvT zT2`;>NC4{}%cpy^(mWcq62G*+x*om%eZ6_e{GzzjHa))zrV$y6Uoi3%n%#7VP>pe=l=;*57T*`9qKWzO`aqa+~mqO>CQ2 z)THPwm^~vpdBvu@-i2HhD|K63m`>kT3RDwn0E0NDPwL>%ybA~*haoIVPItuU|?H)Z&(<0e9y%;t!Ks7D}w%g6j_y9 zd%x&~zu$seOMqsVMa~vk_4YR(N9eKgi2-l3#XkMwT{Y!;egH2oIOXC?*T@N(n0)X> Ym3dv9k!XF~Gf=klboFyt=akR{0K-zPssI20 diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.png b/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.png index 02f698918fff753ac9d5bfe6091ac8349435243b..0e73416edeb6371b992c6c150b42d46a1c22f156 100644 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}K#X;^)4C~IxyaaMs(j9#r85lP9 zbN@+X1@buyJR*yMny-N{YhX3vTXZ8bmyq+$OArj%q2?+^5_!$^k V7#Ru_Qn`T=44$rjF6*2UngBC(9*6({ literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^?m(=;2qYLEakt!I&(;+6**`aRLM5QNh-iK2A&*Qo$V9E1zaSW-r_2yzD4^RoyhCjTU&MyAT<3;rZUGZhj37hA-&Z3Ve?Q;Lp*yG@)pezZZRsB|P*hnYQrnlAje`Hk3S|LIAbp7KR{)xjUW9eGv}CnXjKosifj&vP{) z{F(dzylTf|5>uUyZFrlof^F{pzEkUxCV%AMNDSI2zNq~zhh%cdvoxM%iL(y|@LKVM zj8;tU@ls6AYRP4AHcj8~jpa7OykiS~Yq zIX!PnLS(|NYz7z!bj^~ zZ=+f1Q{rrYJ0Hu6Q=7bT>b}GJ{6=YkH`g|%JekfenO*pyt7mzUT|+Nz(U+W(+CO@x_M8ooTwdrYDaq@nHhE&a9MCF8Dyo_L9U1(RTviPS^Ur$ZX;p6|W zncGadSpV0}?OJ}ynuom&F)JPwd-F;Dy`G0IbQBJ(j>R>r@XP{JfC-?%^>7C`(3r<)-(T2l)gh!>>E3+i$JRmUbd(d z&UHSqL)-N>gOT2n`cUKPZ@Ls7e+b?3S0Q<$p6hKEBfb6QnRhuZ9yV*~X%@c-B)fw^ zl8=Knyfu5660>x{f0bKp6AJ4Da_vvtun-5DwxZ_gUcb8=?kYU4`lJSw`}nit+wpHN v9Drp2gTsXVUlpc1f%7!MjLuFbP0l+XkK#Ah!m diff --git a/app/src/main/res/drawable/buddy.xml b/app/src/main/res/drawable/buddy.xml new file mode 100644 index 000000000..b7d23c4b1 --- /dev/null +++ b/app/src/main/res/drawable/buddy.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml new file mode 100644 index 000000000..5114fda32 --- /dev/null +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -0,0 +1,22 @@ + + + + diff --git a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml new file mode 100644 index 000000000..683b814c9 --- /dev/null +++ b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/app/src/main/res/layout/list_channel_grid_item.xml b/app/src/main/res/layout/list_channel_grid_item.xml index d9084bbe9..9f92c35a7 100644 --- a/app/src/main/res/layout/list_channel_grid_item.xml +++ b/app/src/main/res/layout/list_channel_grid_item.xml @@ -18,7 +18,7 @@ android:layout_centerHorizontal="true" android:layout_margin="2dp" android:contentDescription="@string/detail_uploader_thumbnail_view_description" - android:src="@drawable/buddy_channel_item" + android:src="@drawable/buddy" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index b66e07a12..e03f7c2fa 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy_channel_item" + android:src="@drawable/buddy" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml index 6bd363311..ba984e0ff 100644 --- a/app/src/main/res/layout/list_comments_mini_item.xml +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy_channel_item" + android:src="@drawable/buddy" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/picker_subscription_item.xml b/app/src/main/res/layout/picker_subscription_item.xml index c858ccc4e..f6e5f3587 100644 --- a/app/src/main/res/layout/picker_subscription_item.xml +++ b/app/src/main/res/layout/picker_subscription_item.xml @@ -22,7 +22,7 @@ android:layout_width="48dp" android:layout_height="48dp" app:shapeAppearance="@style/CircularImageView" - tools:src="@drawable/buddy_channel_item" /> + tools:src="@drawable/buddy" /> @color/white @color/white + + #6C6C6C + #999999
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 281fd9ce7..bbcc5d4bb 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,6 +6,9 @@ #CD201F + #F6F6F6 + #6E6E6E + #EEEEEE #EEEEEE diff --git a/assets/buddy_channel_item.svg b/assets/buddy_channel_item.svg deleted file mode 100644 index 4dec41f9d..000000000 --- a/assets/buddy_channel_item.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/assets/dummy_thumbnail.svg b/assets/dummy_thumbnail.svg new file mode 100644 index 000000000..bdea80b55 --- /dev/null +++ b/assets/dummy_thumbnail.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/dummy_thumbnail_playlist.svg b/assets/dummy_thumbnail_playlist.svg new file mode 100644 index 000000000..bd4b190aa --- /dev/null +++ b/assets/dummy_thumbnail_playlist.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 7b41acb781a994943318adb513fd9ccc9bc0b9f0 Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Thu, 7 Jul 2022 22:56:40 +0300 Subject: [PATCH 049/152] Use corresponding material icon in user profile thumbnail --- app/src/main/res/drawable/buddy.xml | 43 +++++++++++------------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/app/src/main/res/drawable/buddy.xml b/app/src/main/res/drawable/buddy.xml index b7d23c4b1..b1d8be868 100644 --- a/app/src/main/res/drawable/buddy.xml +++ b/app/src/main/res/drawable/buddy.xml @@ -1,31 +1,20 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> - - + android:pathData="M0,0 L24,0 L24,24 L0,24 z" + android:fillColor="@color/placeholder_foreground" /> + + + + + From 429f2536af73c1ac94626e5e32425e9036135bcf Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Thu, 7 Jul 2022 23:07:09 +0300 Subject: [PATCH 050/152] Optimize thumbnail placeholder drawables --- app/src/main/res/drawable/dummy_thumbnail.xml | 26 +++++--------- .../res/drawable/dummy_thumbnail_playlist.xml | 35 ++++++------------- 2 files changed, 18 insertions(+), 43 deletions(-) diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml index 5114fda32..bfa2a59cc 100644 --- a/app/src/main/res/drawable/dummy_thumbnail.xml +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -1,22 +1,12 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M0,0h24v24h-24z" + android:fillColor="@color/placeholder_background" /> + android:pathData="M15,11.9023L12.2188,13.3828L9.0898,15L9.0273,12.1641L9,9L11.8477,10.3555ZM15,11.9023" + android:fillColor="@color/placeholder_foreground" /> diff --git a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml index 683b814c9..2e74c16e2 100644 --- a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml +++ b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml @@ -1,30 +1,15 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M0,0h24v24h-24z" + android:fillColor="@color/placeholder_background" /> + android:pathData="M13.8008,11.9023L11.0156,13.3828L7.8906,15L7.8281,12.1641L7.8008,9L10.6484,10.3555ZM13.8008,11.9023" + android:fillColor="@color/placeholder_foreground" /> + android:pathData="M16.8008,11.9023L14.0156,13.3828L10.8906,15L10.8281,12.1641L10.8008,9L13.6484,10.3555ZM16.8008,11.9023" + android:fillColor="@color/placeholder_foreground" /> From 35eeccd45a374caf3de21e90fd874151cb7f5cfe Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Wed, 13 Jul 2022 23:13:17 +0300 Subject: [PATCH 051/152] Rename buddy.xml to dummy_person.xml --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 3 ++- app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java | 2 +- app/src/main/res/drawable/{buddy.xml => dummy_person.xml} | 0 app/src/main/res/layout-large-land/fragment_video_detail.xml | 4 ++-- app/src/main/res/layout/channel_header.xml | 4 ++-- app/src/main/res/layout/fragment_video_detail.xml | 4 ++-- app/src/main/res/layout/list_channel_grid_item.xml | 2 +- app/src/main/res/layout/list_channel_item.xml | 2 +- app/src/main/res/layout/list_channel_mini_item.xml | 2 +- app/src/main/res/layout/list_comments_item.xml | 2 +- app/src/main/res/layout/list_comments_mini_item.xml | 2 +- app/src/main/res/layout/picker_subscription_item.xml | 2 +- app/src/main/res/layout/playlist_header.xml | 2 +- app/src/main/res/layout/select_channel_item.xml | 2 +- 14 files changed, 17 insertions(+), 16 deletions(-) rename app/src/main/res/drawable/{buddy.xml => dummy_person.xml} (100%) 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 78f3772e9..56e8f6d92 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 @@ -1551,7 +1551,8 @@ public final class VideoDetailFragment binding.detailUploaderThumbnailView.setVisibility(View.GONE); } - final Drawable buddyDrawable = AppCompatResources.getDrawable(activity, R.drawable.buddy); + final Drawable buddyDrawable + = AppCompatResources.getDrawable(activity, R.drawable.dummy_person); binding.detailSubChannelThumbnailView.setImageDrawable(buddyDrawable); binding.detailUploaderThumbnailView.setImageDrawable(buddyDrawable); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 223e8ac9c..04135702b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -97,7 +97,7 @@ public final class PicassoHelper { public static RequestCreator loadAvatar(final String url) { - return loadImageDefault(url, R.drawable.buddy); + return loadImageDefault(url, R.drawable.dummy_person); } public static RequestCreator loadThumbnail(final String url) { diff --git a/app/src/main/res/drawable/buddy.xml b/app/src/main/res/drawable/dummy_person.xml similarity index 100% rename from app/src/main/res/drawable/buddy.xml rename to app/src/main/res/drawable/dummy_person.xml diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index d8e52b354..1fecf93a5 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -271,7 +271,7 @@ android:layout_width="@dimen/video_item_detail_uploader_image_size" android:layout_height="@dimen/video_item_detail_uploader_image_size" android:contentDescription="@string/detail_uploader_thumbnail_view_description" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" /> diff --git a/app/src/main/res/layout/channel_header.xml b/app/src/main/res/layout/channel_header.xml index 3365e66e2..c2a70af02 100644 --- a/app/src/main/res/layout/channel_header.xml +++ b/app/src/main/res/layout/channel_header.xml @@ -33,7 +33,7 @@ android:layout_width="@dimen/channel_avatar_size" android:layout_height="@dimen/channel_avatar_size" android:padding="1dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" app:strokeWidth="2dp" /> @@ -44,7 +44,7 @@ android:layout_height="@dimen/sub_channel_avatar_size" android:layout_gravity="bottom|right" android:padding="1dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" android:visibility="gone" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 18655283e..49b6a807c 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -258,7 +258,7 @@ android:layout_width="@dimen/video_item_detail_uploader_image_size" android:layout_height="@dimen/video_item_detail_uploader_image_size" android:contentDescription="@string/detail_uploader_thumbnail_view_description" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" /> diff --git a/app/src/main/res/layout/list_channel_item.xml b/app/src/main/res/layout/list_channel_item.xml index c09c17ead..67bdac5f7 100644 --- a/app/src/main/res/layout/list_channel_item.xml +++ b/app/src/main/res/layout/list_channel_item.xml @@ -64,7 +64,7 @@ android:layout_height="@dimen/video_item_search_avatar_image_height" android:layout_marginLeft="@dimen/video_item_search_avatar_left_margin" android:layout_marginRight="@dimen/video_item_search_avatar_right_margin" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/itemTitleView" app:layout_constraintHorizontal_bias="0.5" diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index e03f7c2fa..747d22f52 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index 4358c438f..7cd7e066f 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -20,7 +20,7 @@ android:layout_marginLeft="3dp" android:layout_marginRight="@dimen/comment_item_avatar_right_margin" android:focusable="false" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml index ba984e0ff..e000b55c1 100644 --- a/app/src/main/res/layout/list_comments_mini_item.xml +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/picker_subscription_item.xml b/app/src/main/res/layout/picker_subscription_item.xml index f6e5f3587..237ed107a 100644 --- a/app/src/main/res/layout/picker_subscription_item.xml +++ b/app/src/main/res/layout/picker_subscription_item.xml @@ -22,7 +22,7 @@ android:layout_width="48dp" android:layout_height="48dp" app:shapeAppearance="@style/CircularImageView" - tools:src="@drawable/buddy" /> + tools:src="@drawable/dummy_person" /> diff --git a/app/src/main/res/layout/select_channel_item.xml b/app/src/main/res/layout/select_channel_item.xml index cfaaf2760..c77ff64fd 100644 --- a/app/src/main/res/layout/select_channel_item.xml +++ b/app/src/main/res/layout/select_channel_item.xml @@ -19,7 +19,7 @@ android:layout_alignParentTop="true" android:layout_marginStart="3dp" android:layout_marginRight="8dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> From dff1adb1adf7c8b82f657eab189ef49f447d23b2 Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Wed, 13 Jul 2022 23:44:21 +0300 Subject: [PATCH 052/152] Fix swapped colors in video and playlist thumbnails --- .../main/res/drawable-night/dummy_person.xml | 20 ++++++++++++++++++ .../drawable-nodpi/dummy_thumbnail_dark.bmp | Bin 0 -> 62 bytes .../drawable-nodpi/dummy_thumbnail_dark.png | Bin 134 -> 0 bytes app/src/main/res/drawable/dummy_person.xml | 4 ++-- app/src/main/res/drawable/dummy_thumbnail.xml | 4 ++-- app/src/main/res/values/colors.xml | 4 ++-- 6 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 app/src/main/res/drawable-night/dummy_person.xml create mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.png diff --git a/app/src/main/res/drawable-night/dummy_person.xml b/app/src/main/res/drawable-night/dummy_person.xml new file mode 100644 index 000000000..b1d8be868 --- /dev/null +++ b/app/src/main/res/drawable-night/dummy_person.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp b/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f16e2a2bfe14d493e27d6c0686248d9a5223c1e0 GIT binary patch literal 62 jcmZ?rwPSz)DYhX3vTXZ8bmyq+$OArj%q2?+^5_!$^k V7#Ru_Qn`T=44$rjF6*2UngBC(9*6({ diff --git a/app/src/main/res/drawable/dummy_person.xml b/app/src/main/res/drawable/dummy_person.xml index b1d8be868..2b3229e8f 100644 --- a/app/src/main/res/drawable/dummy_person.xml +++ b/app/src/main/res/drawable/dummy_person.xml @@ -6,7 +6,7 @@ + android:fillColor="@color/placeholder_background" /> + android:fillColor="@color/placeholder_foreground" /> diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml index bfa2a59cc..6a04fc53e 100644 --- a/app/src/main/res/drawable/dummy_thumbnail.xml +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> + android:fillColor="@color/placeholder_foreground" /> + android:fillColor="@color/placeholder_background" /> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index bbcc5d4bb..25dd0fc86 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,8 +6,8 @@ #CD201F - #F6F6F6 - #6E6E6E + #6E6E6E + #F6F6F6 #EEEEEE From 7b9b9218dc72e9d29beb5bdd4cb08bca3545d36d Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 10:55:26 +0200 Subject: [PATCH 053/152] Remove bottom-sheet-thumbnail placeholder, clear the image instead --- .../fragments/detail/VideoDetailFragment.java | 2 +- .../res/drawable-nodpi/dummy_thumbnail_dark.bmp | Bin 62 -> 0 bytes app/src/main/res/layout/fragment_video_detail.xml | 3 +-- 3 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp 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 56e8f6d92..4e81101db 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 @@ -2361,7 +2361,7 @@ public final class VideoDetailFragment @Nullable final String thumbnailUrl) { binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle); binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader); - binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark); + binding.overlayThumbnail.setImageDrawable(null); PicassoHelper.loadDetailsThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) .into(binding.overlayThumbnail); } diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp b/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp deleted file mode 100644 index f16e2a2bfe14d493e27d6c0686248d9a5223c1e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62 jcmZ?rwPSz)D + android:scaleType="fitCenter" /> Date: Thu, 14 Jul 2022 10:56:28 +0200 Subject: [PATCH 054/152] Improve placeholder thumbnail SVGs and remove theme customization Theme customization does not seem to work well with Picasso: square/picasso#1275 --- .../main/res/drawable-night/dummy_person.xml | 20 ------------------- app/src/main/res/drawable/dummy_thumbnail.xml | 6 +++--- .../res/drawable/dummy_thumbnail_playlist.xml | 4 ++-- app/src/main/res/values-night/colors.xml | 3 --- app/src/main/res/values/colors.xml | 4 ++-- 5 files changed, 7 insertions(+), 30 deletions(-) delete mode 100644 app/src/main/res/drawable-night/dummy_person.xml diff --git a/app/src/main/res/drawable-night/dummy_person.xml b/app/src/main/res/drawable-night/dummy_person.xml deleted file mode 100644 index b1d8be868..000000000 --- a/app/src/main/res/drawable-night/dummy_person.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml index 6a04fc53e..aa81e03c9 100644 --- a/app/src/main/res/drawable/dummy_thumbnail.xml +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml index 2e74c16e2..c56d558b8 100644 --- a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml +++ b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml @@ -7,9 +7,9 @@ android:pathData="M0,0h24v24h-24z" android:fillColor="@color/placeholder_background" /> diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index a0a0e891b..99026e70b 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -2,7 +2,4 @@ @color/white @color/white - - #6C6C6C - #999999 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 25dd0fc86..73e8a0cb1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,8 +6,8 @@ #CD201F - #6E6E6E - #F6F6F6 + #999999 + #6C6C6C #EEEEEE From 6ea85e6380f35a0123e8b64c847c0eb3b355c454 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 10:58:48 +0200 Subject: [PATCH 055/152] Rename dummy_* and more to placeholder_* --- .../fragments/detail/VideoDetailFragment.java | 2 +- .../newpipe/local/dialog/PlaylistAppendDialog.java | 3 ++- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../main/java/org/schabi/newpipe/player/Player.java | 2 +- .../java/org/schabi/newpipe/util/PicassoHelper.java | 10 +++++----- ...el_banner.png => placeholder_channel_banner.png} | Bin .../{dummy_person.xml => placeholder_person.xml} | 0 ...ylist.xml => placeholder_thumbnail_playlist.xml} | 0 ...humbnail.xml => placeholder_thumbnail_video.xml} | 0 .../res/layout-large-land/fragment_video_detail.xml | 8 ++++---- app/src/main/res/layout/channel_header.xml | 6 +++--- app/src/main/res/layout/fragment_video_detail.xml | 6 +++--- app/src/main/res/layout/item_stream_segment.xml | 2 +- app/src/main/res/layout/list_channel_grid_item.xml | 2 +- app/src/main/res/layout/list_channel_item.xml | 2 +- app/src/main/res/layout/list_channel_mini_item.xml | 2 +- app/src/main/res/layout/list_comments_item.xml | 2 +- app/src/main/res/layout/list_comments_mini_item.xml | 2 +- app/src/main/res/layout/list_playlist_grid_item.xml | 2 +- app/src/main/res/layout/list_playlist_item.xml | 2 +- app/src/main/res/layout/list_playlist_mini_item.xml | 2 +- app/src/main/res/layout/list_stream_grid_item.xml | 2 +- app/src/main/res/layout/list_stream_item.xml | 2 +- app/src/main/res/layout/list_stream_mini_item.xml | 2 +- .../res/layout/list_stream_playlist_grid_item.xml | 2 +- .../main/res/layout/list_stream_playlist_item.xml | 2 +- .../main/res/layout/picker_subscription_item.xml | 2 +- app/src/main/res/layout/play_queue_item.xml | 2 +- app/src/main/res/layout/player.xml | 2 +- app/src/main/res/layout/playlist_header.xml | 2 +- app/src/main/res/layout/select_channel_item.xml | 2 +- 31 files changed, 39 insertions(+), 38 deletions(-) rename app/src/main/res/drawable-nodpi/{channel_banner.png => placeholder_channel_banner.png} (100%) rename app/src/main/res/drawable/{dummy_person.xml => placeholder_person.xml} (100%) rename app/src/main/res/drawable/{dummy_thumbnail_playlist.xml => placeholder_thumbnail_playlist.xml} (100%) rename app/src/main/res/drawable/{dummy_thumbnail.xml => placeholder_thumbnail_video.xml} (100%) 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 4e81101db..c8ae0781e 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 @@ -1552,7 +1552,7 @@ public final class VideoDetailFragment } final Drawable buddyDrawable - = AppCompatResources.getDrawable(activity, R.drawable.dummy_person); + = AppCompatResources.getDrawable(activity, R.drawable.placeholder_person); binding.detailSubChannelThumbnailView.setImageDrawable(buddyDrawable); binding.detailUploaderThumbnailView.setImageDrawable(buddyDrawable); diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java index a874cdd62..a97eb0c18 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java @@ -145,7 +145,8 @@ public final class PlaylistAppendDialog extends PlaylistDialog { final Toast successToast = Toast.makeText(getContext(), R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); - if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) { + if (playlist.thumbnailUrl + .equals("drawable://" + R.drawable.placeholder_thumbnail_playlist)) { playlistDisposables.add(manager .changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl()) .observeOn(AndroidSchedulers.mainThread()) 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 4bffc0983..b4af73d08 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 @@ -613,7 +613,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment + tools:src="@drawable/placeholder_thumbnail_video" /> @@ -645,7 +645,7 @@ android:paddingLeft="@dimen/video_item_search_padding" android:paddingRight="@dimen/video_item_search_padding" android:scaleType="fitCenter" - android:src="@drawable/dummy_thumbnail" /> + android:src="@drawable/placeholder_thumbnail_video" /> @@ -44,7 +44,7 @@ android:layout_height="@dimen/sub_channel_avatar_size" android:layout_gravity="bottom|right" android:padding="1dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" android:visibility="gone" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 137dafee2..c794c5a55 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -47,7 +47,7 @@ android:scaleType="fitCenter" tools:ignore="RtlHardcoded" tools:layout_height="200dp" - tools:src="@drawable/dummy_thumbnail" /> + tools:src="@drawable/placeholder_thumbnail_video" /> diff --git a/app/src/main/res/layout/list_channel_item.xml b/app/src/main/res/layout/list_channel_item.xml index 67bdac5f7..cf4685e50 100644 --- a/app/src/main/res/layout/list_channel_item.xml +++ b/app/src/main/res/layout/list_channel_item.xml @@ -64,7 +64,7 @@ android:layout_height="@dimen/video_item_search_avatar_image_height" android:layout_marginLeft="@dimen/video_item_search_avatar_left_margin" android:layout_marginRight="@dimen/video_item_search_avatar_right_margin" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/itemTitleView" app:layout_constraintHorizontal_bias="0.5" diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index 747d22f52..473709bbd 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index 7cd7e066f..ad73c5ff4 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -20,7 +20,7 @@ android:layout_marginLeft="3dp" android:layout_marginRight="@dimen/comment_item_avatar_right_margin" android:focusable="false" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml index e000b55c1..606a237c5 100644 --- a/app/src/main/res/layout/list_comments_mini_item.xml +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_playlist_grid_item.xml b/app/src/main/res/layout/list_playlist_grid_item.xml index e052e9ac0..3ab69448d 100644 --- a/app/src/main/res/layout/list_playlist_grid_item.xml +++ b/app/src/main/res/layout/list_playlist_grid_item.xml @@ -17,7 +17,7 @@ android:layout_centerHorizontal="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:scaleType="fitStart" - android:src="@drawable/dummy_thumbnail_playlist" + android:src="@drawable/placeholder_thumbnail_playlist" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_stream_item.xml b/app/src/main/res/layout/list_stream_item.xml index 1117a028e..793942568 100644 --- a/app/src/main/res/layout/list_stream_item.xml +++ b/app/src/main/res/layout/list_stream_item.xml @@ -15,7 +15,7 @@ android:layout_width="@dimen/video_item_search_thumbnail_image_width" android:layout_height="@dimen/video_item_search_thumbnail_image_height" android:scaleType="fitCenter" - android:src="@drawable/dummy_thumbnail" + android:src="@drawable/placeholder_thumbnail_video" app:layout_constraintBottom_toTopOf="@+id/itemProgressView" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/list_stream_mini_item.xml b/app/src/main/res/layout/list_stream_mini_item.xml index d33c9d4ba..2eab66eaf 100644 --- a/app/src/main/res/layout/list_stream_mini_item.xml +++ b/app/src/main/res/layout/list_stream_mini_item.xml @@ -18,7 +18,7 @@ android:layout_alignParentTop="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:scaleType="fitCenter" - android:src="@drawable/dummy_thumbnail" + android:src="@drawable/placeholder_thumbnail_video" tools:ignore="RtlHardcoded" /> + tools:src="@drawable/placeholder_person" /> diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index b8b60b3f8..60cbcf7c4 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -397,7 +397,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="2dp" - android:src="@drawable/dummy_thumbnail" + android:src="@drawable/placeholder_thumbnail_video" android:visibility="gone" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/playlist_header.xml b/app/src/main/res/layout/playlist_header.xml index 39eca2fda..9c038db3a 100644 --- a/app/src/main/res/layout/playlist_header.xml +++ b/app/src/main/res/layout/playlist_header.xml @@ -45,7 +45,7 @@ android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:padding="0.7dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" app:strokeWidth="1dp" /> diff --git a/app/src/main/res/layout/select_channel_item.xml b/app/src/main/res/layout/select_channel_item.xml index c77ff64fd..c5fd51bb8 100644 --- a/app/src/main/res/layout/select_channel_item.xml +++ b/app/src/main/res/layout/select_channel_item.xml @@ -19,7 +19,7 @@ android:layout_alignParentTop="true" android:layout_marginStart="3dp" android:layout_marginRight="8dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> From 9f993e0c49f2c0cf1f48205b584096621779eece Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 14:47:54 +0200 Subject: [PATCH 056/152] Make video and playlist placeholder thumbnails 16:9 After making the playlist and video thumbnails' scaleType fitCenter, the 24dp*24dp thumbnails would appear as a square, which would be strange, since the image view is 16:9. --- .../main/res/drawable/placeholder_thumbnail_playlist.xml | 8 ++++---- app/src/main/res/drawable/placeholder_thumbnail_video.xml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml b/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml index c56d558b8..de286d860 100644 --- a/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml +++ b/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml @@ -1,15 +1,15 @@ diff --git a/app/src/main/res/drawable/placeholder_thumbnail_video.xml b/app/src/main/res/drawable/placeholder_thumbnail_video.xml index aa81e03c9..0b262f923 100644 --- a/app/src/main/res/drawable/placeholder_thumbnail_video.xml +++ b/app/src/main/res/drawable/placeholder_thumbnail_video.xml @@ -1,12 +1,12 @@ From 0e8cc72b134bf5755f59cbbfd6e01180509e7c57 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 22:14:03 +0200 Subject: [PATCH 057/152] Fix random NullPointerException when adding video player view --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 92e7e4f16..0b32a5e29 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 @@ -1305,7 +1305,7 @@ public final class VideoDetailFragment } private void addVideoPlayerView() { - if (!isPlayerAvailable()) { + if (!isPlayerAvailable() || getView() == null) { return; } setHeightThumbnail(); From 25a43b57b20ec686b6a5a9c4eb44241a6b2ab864 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 10 Jul 2022 14:19:37 +0200 Subject: [PATCH 058/152] Updated checkstyle So that the assign operators are used on the same branch --- checkstyle/checkstyle.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/checkstyle/checkstyle.xml b/checkstyle/checkstyle.xml index 282358f6a..ce378a65f 100644 --- a/checkstyle/checkstyle.xml +++ b/checkstyle/checkstyle.xml @@ -116,6 +116,10 @@ + + + + From 8b209df253fe8e11377fed82b2e45ba87f4c4a02 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 10 Jul 2022 14:19:58 +0200 Subject: [PATCH 059/152] Changed the code accordingly + Removed some unused code --- .../material/appbar/FlingBehavior.java | 4 +- .../org/schabi/newpipe/DownloaderImpl.java | 10 +- .../java/org/schabi/newpipe/MainActivity.java | 8 +- .../newpipe/download/DownloadDialog.java | 12 +- .../schabi/newpipe/error/ErrorActivity.java | 8 +- .../fragments/detail/DescriptionFragment.java | 16 +- .../newpipe/info_list/InfoItemBuilder.java | 4 +- .../dialog/StreamDialogDefaultEntry.java | 4 +- .../holder/CommentsMiniInfoItemHolder.java | 9 +- .../holder/StreamMiniInfoItemHolder.java | 7 +- .../local/bookmark/BookmarkFragment.java | 4 +- .../local/dialog/PlaylistCreationDialog.java | 4 +- .../local/playlist/LocalPlaylistFragment.java | 10 +- .../services/SubscriptionsExportService.java | 8 +- .../newpipe/player/helper/CacheFactory.java | 4 +- .../player/helper/PlayerDataSource.java | 4 +- .../newpipe/player/helper/PlayerHelper.java | 4 +- .../player/playback/PlayerMediaSession.java | 9 +- .../settings/ContentSettingsFragment.java | 12 +- .../settings/DebugSettingsFragment.java | 30 +- .../settings/DownloadSettingsFragment.java | 8 +- .../PeertubeInstanceListFragment.java | 4 +- .../settings/UpdateSettingsFragment.java | 12 +- .../schabi/newpipe/streams/DataReader.java | 4 +- .../newpipe/streams/Mp4FromDashWriter.java | 4 +- .../org/schabi/newpipe/util/ListHelper.java | 29 +- .../schabi/newpipe/util/PeertubeHelper.java | 4 +- .../schabi/newpipe/util/PermissionHelper.java | 4 +- .../schabi/newpipe/util/PicassoHelper.java | 4 +- .../org/schabi/newpipe/util/StateSaver.java | 8 +- .../InternalUrlsHandler.java | 8 +- .../util/urlfinder/PatternsCompat.java | 274 +----------------- .../newpipe/views/FocusAwareDrawerLayout.java | 8 +- .../services/ImportExportJsonHelperTest.java | 10 +- .../settings/tabs/TabsJsonHelperTest.java | 14 +- 35 files changed, 153 insertions(+), 413 deletions(-) diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index 3e5f408f7..47c8deb83 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -132,8 +132,8 @@ public final class FlingBehavior extends AppBarLayout.Behavior { try { final Class headerBehaviorType = this.getClass().getSuperclass().getSuperclass(); if (headerBehaviorType != null) { - final Field field - = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef"); + final Field field = + headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef"); field.setAccessible(true); return field; } diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index f2803dc2f..3579a0bad 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -1,5 +1,7 @@ package org.schabi.newpipe; +import static org.schabi.newpipe.MainActivity.DEBUG; + import android.content.Context; import androidx.annotation.NonNull; @@ -26,10 +28,10 @@ import okhttp3.RequestBody; import okhttp3.ResponseBody; public final class DownloaderImpl extends Downloader { - public static final String USER_AGENT - = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; - public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY - = "youtube_restricted_mode_key"; + public static final String USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; + public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY = + "youtube_restricted_mode_key"; public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000"; public static final String YOUTUBE_DOMAIN = "youtube.com"; diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index dd59eeb25..4a982874c 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -473,8 +473,8 @@ public class MainActivity extends AppCompatActivity { ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e); } - final SharedPreferences sharedPreferences - = PreferenceManager.getDefaultSharedPreferences(this); + final SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(this); if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) { if (DEBUG) { Log.d(TAG, "Theme has changed, recreating activity..."); @@ -646,8 +646,8 @@ public class MainActivity extends AppCompatActivity { } super.onCreateOptionsMenu(menu); - final Fragment fragment - = getSupportFragmentManager().findFragmentById(R.id.fragment_holder); + final Fragment fragment = + getSupportFragmentManager().findFragmentById(R.id.fragment_holder); if (!(fragment instanceof SearchFragment)) { toolbarLayoutBinding.toolbarSearchContainer.getRoot().setVisibility(View.GONE); } diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index e4adddc2a..0e64e8b48 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.download; +import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP; +import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery; +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; + import android.app.Activity; import android.content.ComponentName; import android.content.Context; @@ -82,10 +86,6 @@ import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder; import us.shandian.giga.service.MissionState; -import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP; -import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { private static final String TAG = "DialogFragment"; @@ -205,8 +205,8 @@ public class DownloadDialog extends DialogFragment setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context)); Icepick.restoreInstanceState(this, savedInstanceState); - final SparseArray> secondaryStreams - = new SparseArray<>(4); + final SparseArray> secondaryStreams = + new SparseArray<>(4); final List videoStreams = wrappedVideoStreams.getStreamsList(); for (int i = 0; i < videoStreams.size(); i++) { diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index bd8430296..8b2ac37dc 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -65,11 +65,11 @@ public class ErrorActivity extends AppCompatActivity { public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org"; public static final String ERROR_EMAIL_SUBJECT = "Exception in "; - public static final String ERROR_GITHUB_ISSUE_URL - = "https://github.com/TeamNewPipe/NewPipe/issues"; + public static final String ERROR_GITHUB_ISSUE_URL = + "https://github.com/TeamNewPipe/NewPipe/issues"; - public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER - = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); private ErrorInfo errorInfo; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index d57ddb02d..33fbe5db1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.fragments.detail; +import static android.text.TextUtils.isEmpty; +import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; +import static org.schabi.newpipe.extractor.utils.Utils.isBlank; + import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -33,10 +37,6 @@ import java.util.List; import icepick.State; import io.reactivex.rxjava3.disposables.CompositeDisposable; -import static android.text.TextUtils.isEmpty; -import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; -import static org.schabi.newpipe.extractor.utils.Utils.isBlank; - public class DescriptionFragment extends BaseFragment { @State @@ -185,8 +185,8 @@ public class DescriptionFragment extends BaseFragment { return; } - final ItemMetadataBinding itemBinding - = ItemMetadataBinding.inflate(inflater, layout, false); + final ItemMetadataBinding itemBinding = + ItemMetadataBinding.inflate(inflater, layout, false); itemBinding.metadataTypeView.setText(type); itemBinding.metadataTypeView.setOnLongClickListener(v -> { @@ -206,8 +206,8 @@ public class DescriptionFragment extends BaseFragment { private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { if (streamInfo.getTags() != null && !streamInfo.getTags().isEmpty()) { - final ItemMetadataTagsBinding itemBinding - = ItemMetadataTagsBinding.inflate(inflater, layout, false); + final ItemMetadataTagsBinding itemBinding = + ItemMetadataTagsBinding.inflate(inflater, layout, false); final List tags = new ArrayList<>(streamInfo.getTags()); Collections.sort(tags); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index d78bf1076..68f19ee97 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -67,8 +67,8 @@ public class InfoItemBuilder { public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem, final HistoryRecordManager historyRecordManager, final boolean useMiniVariant) { - final InfoItemHolder holder - = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); + final InfoItemHolder holder = + holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); holder.updateFromItem(infoItem, historyRecordManager); return holder.itemView; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java index d03546593..2b3f73926 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java @@ -115,8 +115,8 @@ public enum StreamDialogDefaultEntry { DOWNLOAD(R.string.download, (fragment, item) -> fetchStreamInfoAndSaveToDatabase(fragment.requireContext(), item.getServiceId(), item.getUrl(), info -> { - final DownloadDialog downloadDialog - = new DownloadDialog(fragment.requireContext(), info); + final DownloadDialog downloadDialog = + new DownloadDialog(fragment.requireContext(), info); downloadDialog.show(fragment.getChildFragmentManager(), "downloadDialog"); }) ), diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index 6e4773c09..b900750a8 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -23,9 +23,9 @@ import org.schabi.newpipe.util.CommentTextOnTouchListener; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.external_communication.TimestampExtractor; -import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.PicassoHelper; +import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.external_communication.TimestampExtractor; import java.util.regex.Matcher; @@ -204,8 +204,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { boolean hasEllipsis = false; if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) { - final int endOfLastLine - = itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1); + final int endOfLastLine = itemContentView + .getLayout() + .getLineEnd(COMMENT_DEFAULT_LINES - 1); int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2); if (end == -1) { end = Math.max(endOfLastLine - 2, 0); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 54d31ca57..8d17017d2 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -14,8 +14,8 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.StreamTypeUtil; import org.schabi.newpipe.views.AnimatedProgressBar; @@ -111,8 +111,9 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { final HistoryRecordManager historyRecordManager) { final StreamInfoItem item = (StreamInfoItem) infoItem; - final StreamStateEntity state - = historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; + final StreamStateEntity state = historyRecordManager + .loadStreamState(infoItem) + .blockingGet()[0]; if (state != null && item.getDuration() > 0 && !StreamTypeUtil.isLiveStream(item.getStreamType())) { itemProgressView.setMax((int) item.getDuration()); diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index f272a8831..ac11d007f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -256,8 +256,8 @@ public final class BookmarkFragment extends BaseLocalListFragment playlistIter = playlist.iterator(); // History data - final HistoryRecordManager recordManager - = new HistoryRecordManager(getContext()); + final HistoryRecordManager recordManager = + new HistoryRecordManager(getContext()); final Iterator historyIter = recordManager .getStreamHistorySortedById().blockingFirst().iterator(); @@ -544,8 +544,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { - final List result - = new ArrayList<>(subscriptionEntities.size()); + final List result = + new ArrayList<>(subscriptionEntities.size()); for (final SubscriptionEntity entity : subscriptionEntities) { result.add(new SubscriptionItem(entity.getServiceId(), entity.getUrl(), entity.getName())); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java index d189616d1..41fcc823a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java @@ -39,8 +39,8 @@ final class CacheFactory implements DataSource.Factory { .createDataSource(); final FileDataSource fileSource = new FileDataSource(); - final CacheDataSink dataSink - = new CacheDataSink(cache, PlayerHelper.getPreferredFileSize()); + final CacheDataSink dataSink = + new CacheDataSink(cache, PlayerHelper.getPreferredFileSize()); return new CacheDataSource(cache, dataSource, fileSource, dataSink, CACHE_FLAGS, null); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java index 88f25e194..0530d56e9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java @@ -208,8 +208,8 @@ public class PlayerDataSource { Log.w(TAG, "instantiateCacheIfNeeded: could not create cache dir"); } - final LeastRecentlyUsedCacheEvictor evictor - = new LeastRecentlyUsedCacheEvictor(PlayerHelper.getPreferredCacheSize()); + final LeastRecentlyUsedCacheEvictor evictor = + new LeastRecentlyUsedCacheEvictor(PlayerHelper.getPreferredCacheSize()); cache = new SimpleCache(cacheDir, evictor, new StandaloneDatabaseProvider(context)); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index fb346f5ba..abde7c3d1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -62,8 +62,8 @@ import java.util.concurrent.TimeUnit; public final class PlayerHelper { private static final StringBuilder STRING_BUILDER = new StringBuilder(); - private static final Formatter STRING_FORMATTER - = new Formatter(STRING_BUILDER, Locale.getDefault()); + private static final Formatter STRING_FORMATTER = + new Formatter(STRING_BUILDER, Locale.getDefault()); private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x"); private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%"); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java index 3be9b6173..3c41acc75 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java @@ -61,8 +61,7 @@ public class PlayerMediaSession implements MediaSessionCallback { return null; } - final MediaDescriptionCompat.Builder descriptionBuilder - = new MediaDescriptionCompat.Builder() + final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() .setMediaId(String.valueOf(index)) .setTitle(item.getTitle()) .setSubtitle(item.getUploader()); @@ -76,14 +75,14 @@ public class PlayerMediaSession implements MediaSessionCallback { additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); - descriptionBuilder.setExtras(additionalMetadata); + descBuilder.setExtras(additionalMetadata); final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); if (thumbnailUri != null) { - descriptionBuilder.setIconUri(thumbnailUri); + descBuilder.setIconUri(thumbnailUri); } - return descriptionBuilder.build(); + return descBuilder.build(); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 47458ad3f..37f83057b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -43,8 +43,8 @@ import java.util.Objects; public class ContentSettingsFragment extends BasePreferenceFragment { private static final String ZIP_MIME_TYPE = "application/zip"; - private final SimpleDateFormat exportDateFormat - = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); + private final SimpleDateFormat exportDateFormat = + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); private ContentSettingsManager manager; @@ -160,8 +160,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment { // will be saved only on success final Uri lastExportDataUri = result.getData().getData(); - final StoredFileHelper file - = new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); + final StoredFileHelper file = + new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); exportDatabase(file, lastExportDataUri); } @@ -173,8 +173,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment { // will be saved only on success final Uri lastImportDataUri = result.getData().getData(); - final StoredFileHelper file - = new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); + final StoredFileHelper file = + new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); new AlertDialog.Builder(requireActivity()) .setMessage(R.string.override_current_data) diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java index dd9f5fb1f..0f4c9765e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java @@ -9,8 +9,8 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.local.feed.notifications.NotificationWorker; +import org.schabi.newpipe.util.PicassoHelper; import java.util.Optional; @@ -21,20 +21,20 @@ public class DebugSettingsFragment extends BasePreferenceFragment { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { addPreferencesFromResourceRegistry(); - final Preference allowHeapDumpingPreference - = findPreference(getString(R.string.allow_heap_dumping_key)); - final Preference showMemoryLeaksPreference - = findPreference(getString(R.string.show_memory_leaks_key)); - final Preference showImageIndicatorsPreference - = findPreference(getString(R.string.show_image_indicators_key)); - final Preference checkNewStreamsPreference - = findPreference(getString(R.string.check_new_streams_key)); - final Preference crashTheAppPreference - = findPreference(getString(R.string.crash_the_app_key)); - final Preference showErrorSnackbarPreference - = findPreference(getString(R.string.show_error_snackbar_key)); - final Preference createErrorNotificationPreference - = findPreference(getString(R.string.create_error_notification_key)); + final Preference allowHeapDumpingPreference = + findPreference(getString(R.string.allow_heap_dumping_key)); + final Preference showMemoryLeaksPreference = + findPreference(getString(R.string.show_memory_leaks_key)); + final Preference showImageIndicatorsPreference = + findPreference(getString(R.string.show_image_indicators_key)); + final Preference checkNewStreamsPreference = + findPreference(getString(R.string.check_new_streams_key)); + final Preference crashTheAppPreference = + findPreference(getString(R.string.crash_the_app_key)); + final Preference showErrorSnackbarPreference = + findPreference(getString(R.string.show_error_snackbar_key)); + final Preference createErrorNotificationPreference = + findPreference(getString(R.string.create_error_notification_key)); assert allowHeapDumpingPreference != null; assert showMemoryLeaksPreference != null; diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index 74dc9f63e..5a4300cdd 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.settings; +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; + import android.app.Activity; import android.content.ContentResolver; import android.content.Context; @@ -32,8 +34,6 @@ import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - public class DownloadSettingsFragment extends BasePreferenceFragment { public static final boolean IGNORE_RELEASE_ON_OLD_PATH = true; private String downloadPathVideoPreference; @@ -255,8 +255,8 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { context.grantUriPermission(context.getPackageName(), uri, StoredDirectoryHelper.PERMISSION_FLAGS); - final StoredDirectoryHelper mainStorage - = new StoredDirectoryHelper(context, uri, null); + final StoredDirectoryHelper mainStorage = + new StoredDirectoryHelper(context, uri, null); Log.i(TAG, "Acquiring tree success from " + uri.toString()); if (!mainStorage.canWrite()) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index 1ff7947fd..92b9a0362 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -199,8 +199,8 @@ public class PeertubeInstanceListFragment extends Fragment { } private void showAddItemDialog(final Context c) { - final DialogEditTextBinding dialogBinding - = DialogEditTextBinding.inflate(getLayoutInflater()); + final DialogEditTextBinding dialogBinding = + DialogEditTextBinding.inflate(getLayoutInflater()); dialogBinding.dialogEditText.setInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); dialogBinding.dialogEditText.setHint(R.string.peertube_instance_add_help); diff --git a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java index 1043e88c2..f1f63ffdf 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java @@ -9,19 +9,19 @@ import org.schabi.newpipe.NewVersionWorker; import org.schabi.newpipe.R; public class UpdateSettingsFragment extends BasePreferenceFragment { - private final Preference.OnPreferenceChangeListener updatePreferenceChange - = (preference, checkForUpdates) -> { + private final Preference.OnPreferenceChangeListener updatePreferenceChange = (p, nVal) -> { + final boolean checkForUpdates = (boolean) nVal; defaultPreferences.edit() - .putBoolean(getString(R.string.update_app_key), (boolean) checkForUpdates).apply(); + .putBoolean(getString(R.string.update_app_key), checkForUpdates) + .apply(); - if ((boolean) checkForUpdates) { + if (checkForUpdates) { checkNewVersionNow(); } return true; }; - private final Preference.OnPreferenceClickListener manualUpdateClick - = preference -> { + private final Preference.OnPreferenceClickListener manualUpdateClick = preference -> { Toast.makeText(getContext(), R.string.checking_updates_toast, Toast.LENGTH_SHORT).show(); checkNewVersionNow(); return true; diff --git a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java index dc6e29d7d..68225fbab 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java @@ -82,8 +82,8 @@ public class DataReader { public long readLong() throws IOException { primitiveRead(LONG_SIZE); - final long high - = primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; + final long high = + primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; final long low = primitive[4] << 24 | primitive[5] << 16 | primitive[6] << 8 | primitive[7]; return high << 32 | low; } diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java index 889cc85e6..807f190b4 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java @@ -307,8 +307,8 @@ public class Mp4FromDashWriter { outWrite(makeMdat(totalSampleSize, is64)); final int[] sampleIndex = new int[readers.length]; - final int[] sizes - = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK]; + final int[] sizes = + new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK]; final int[] sync = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK]; int written = readers.length; diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index eabac8330..b0b4bae74 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -176,8 +176,8 @@ public final class ListHelper { @Nullable final List videoOnlyStreams, final boolean ascendingOrder, final boolean preferVideoOnlyStreams) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); final boolean showHigherResolutions = preferences.getBoolean( context.getString(R.string.show_higher_resolutions_key), false); @@ -214,8 +214,8 @@ public final class ListHelper { private static String computeDefaultResolution(final Context context, final int key, final int value) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); // Load the preferred resolution otherwise the best available String resolution = preferences != null @@ -254,8 +254,8 @@ public final class ListHelper { return 0; } - final int defaultStreamIndex - = getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams); + final int defaultStreamIndex = + getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams); // this is actually an error, // but maybe there is really no stream fitting to the default value. @@ -446,8 +446,9 @@ public final class ListHelper { final String targetResolutionNoRefresh = targetResolution.replaceAll("p\\d+$", "p"); for (int idx = 0; idx < videoStreams.size(); idx++) { - final MediaFormat format - = targetFormat == null ? null : videoStreams.get(idx).getFormat(); + final MediaFormat format = targetFormat == null + ? null + : videoStreams.get(idx).getFormat(); final String resolution = videoStreams.get(idx).getResolution(); final String resolutionNoRefresh = resolution.replaceAll("p\\d+$", "p"); @@ -510,8 +511,8 @@ public final class ListHelper { private static MediaFormat getDefaultFormat(@NonNull final Context context, @StringRes final int defaultFormatKey, @StringRes final int defaultFormatValueKey) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); final String defaultFormat = context.getString(defaultFormatValueKey); final String defaultFormatString = preferences.getString( @@ -617,8 +618,8 @@ public final class ListHelper { private static String getResolutionLimit(@NonNull final Context context) { String resolutionLimit = null; if (isMeteredNetwork(context)) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); final String defValue = context.getString(R.string.limit_data_usage_none_key); final String value = preferences.getString( context.getString(R.string.limit_mobile_data_usage_key), defValue); @@ -634,8 +635,8 @@ public final class ListHelper { * @return {@code true} if connected to a metered network */ public static boolean isMeteredNetwork(@NonNull final Context context) { - final ConnectivityManager manager - = ContextCompat.getSystemService(context, ConnectivityManager.class); + final ConnectivityManager manager = + ContextCompat.getSystemService(context, ConnectivityManager.class); if (manager == null || manager.getActiveNetworkInfo() == null) { return false; } diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java index dcc39eccf..97e6e7563 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java @@ -54,8 +54,8 @@ public final class PeertubeHelper { final Context context) { final SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(context); - final String selectedInstanceKey - = context.getString(R.string.peertube_selected_instance_key); + final String selectedInstanceKey = + context.getString(R.string.peertube_selected_instance_key); final JsonStringWriter jsonWriter = JsonWriter.string().object(); jsonWriter.value("name", instance.getName()); jsonWriter.value("url", instance.getUrl()); diff --git a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java index 1e3061374..f3151ec8b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java @@ -122,8 +122,8 @@ public final class PermissionHelper { } public static void showPopupEnablementToast(final Context context) { - final Toast toast - = Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG); + final Toast toast = + Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG); final TextView messageView = toast.getView().findViewById(android.R.id.message); if (messageView != null) { messageView.setGravity(Gravity.CENTER); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index a104660f4..54140b0fb 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -28,8 +28,8 @@ import okhttp3.OkHttpClient; public final class PicassoHelper { public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; - private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY - = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; + private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = + "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; private PicassoHelper() { } diff --git a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java index 6ebdaee02..3c901aacb 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java +++ b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java @@ -46,8 +46,8 @@ import java.util.concurrent.ConcurrentHashMap; */ public final class StateSaver { public static final String KEY_SAVED_STATE = "key_saved_state"; - private static final ConcurrentHashMap> STATE_OBJECTS_HOLDER - = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap> STATE_OBJECTS_HOLDER = + new ConcurrentHashMap<>(); private static final String TAG = "StateSaver"; private static final String CACHE_DIR_NAME = "state_cache"; private static String cacheDirPath; @@ -107,8 +107,8 @@ public final class StateSaver { } try { - Queue savedObjects - = STATE_OBJECTS_HOLDER.remove(savedState.getPrefixFileSaved()); + Queue savedObjects = + STATE_OBJECTS_HOLDER.remove(savedState.getPrefixFileSaved()); if (savedObjects != null) { writeRead.readFrom(savedObjects); if (MainActivity.DEBUG) { diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java index 240341ab0..c46e6636d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java +++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java @@ -153,13 +153,13 @@ public final class InternalUrlsHandler { return false; } - final Single single - = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false); + final Single single = + ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false); disposables.add(single.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(info -> { - final PlayQueue playQueue - = new SinglePlayQueue(info, seconds * 1000L); + final PlayQueue playQueue = + new SinglePlayQueue(info, seconds * 1000L); NavigationHelper.playOnPopupPlayer(context, playQueue, false); }, throwable -> { if (DEBUG) { diff --git a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java index 5a0dbb003..5fededbcb 100644 --- a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java +++ b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java @@ -18,159 +18,13 @@ package org.schabi.newpipe.util.urlfinder; -import androidx.annotation.RestrictTo; - import java.util.regex.Pattern; -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; - /** * Commonly used regular expression patterns. */ public final class PatternsCompat { - /** - * Regular expression to match all IANA top-level domains. - * - * List accurate as of 2015/11/24. List taken from: - * http://data.iana.org/TLD/tlds-alpha-by-domain.txt - * This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py - */ - static final String IANA_TOP_LEVEL_DOMAINS = "(?:" - + "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active" - + "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica" - + "|amsterdam|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia" - + "|associates|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])" - + "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva" - + "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz" - + "|black|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots" - + "|boutique|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build" - + "|builders|business|buzz|bzh|b[abdefghijmnorstvwyz])" - + "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards" - + "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center" - + "|ceo|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani" - + "|cisco|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed" - + "|coach|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec" - + "|condos|construction|consulting|contractors|cooking|cool|coop|corsica|country" - + "|coupons|courses|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc" - + "|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])" - + "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta" - + "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory" - + "|discount|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])" - + "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises" - + "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert" - + "|exposed|express|e[cegrstu])" - + "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm" - + "|fashion|feedback|ferrero|film|final|finance|financial|firmdale|fish|fishing|fit" - + "|fitness|flights|florist|flowers|flsmidth|fly|foo|football|forex|forsale|forum" - + "|foundation|frl|frogans|fund|furniture|futbol|fyi|f[ijkmor])" - + "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving" - + "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov" - + "|grainger|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru" - + "|g[abdefghilmnpqrstuwy])" - + "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey" - + "|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house" - + "|how|hsbc|hyundai|h[kmnrtu])" - + "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink" - + "|institute|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau" - + "|iwc|i[delmnoqrst])" - + "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])" - + "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto" - + "|k[eghimnprwyz])" - + "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease" - + "|leclerc|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde" - + "|link|live|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury" - + "|l[abcikrstuvy])" - + "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba" - + "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi" - + "|moda|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov" - + "|movie|movistar|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])" - + "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk" - + "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])" - + "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka" - + "|otsuka|ovh|om)" - + "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography" - + "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation" - + "|plumbing|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties" - + "|property|protection|pub|p[aefghklmnrstwy])" - + "|(?:qpon|quebec|qa)" - + "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent" - + "|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip" - + "|rocher|rocks|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])" - + "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo" - + "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat" - + "|security|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles" - + "|site|ski|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space" - + "|spiegel|spreadbetting|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study" - + "|style|sucks|supplies|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems" - + "|s[abcdeghijklmnortuvxyz])" - + "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica" - + "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo" - + "|tools|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust" - + "|tui|t[cdfghjklmnortvwz])" - + "|(?:ubs|university|uno|uol|u[agksyz])" - + "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin" - + "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])" - + "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki" - + "|williamhill|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])" - + "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c" - + "|\u043c\u043a\u0434|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430" - + "|\u043e\u043d\u043b\u0430\u0439\u043d|\u043e\u0440\u0433|\u0440\u0443\u0441" - + "|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431|\u0443\u043a\u0440" - + "|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd" - + "|\u0627\u0631\u0627\u0645\u0643\u0648|\u0627\u0644\u0627\u0631\u062f\u0646" - + "|\u0627\u0644\u062c\u0632\u0627\u0626\u0631" - + "|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629" - + "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a" - + "|\u0627\u06cc\u0631\u0627\u0646|\u0628\u0627\u0632\u0627\u0631" - + "|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633" - + "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629" - + "|\u0634\u0628\u0643\u0629|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646" - + "|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0643\u0648\u0645" - + "|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627|\u0645\u0648\u0642\u0639" - + "|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924" - + "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24" - + "|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe" - + "|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8" - + "|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd" - + "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21" - + "|\u0e44\u0e17\u0e22|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb" - + "|\u30b3\u30e0|\u4e16\u754c|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51" - + "|\u4f01\u4e1a|\u4f5b\u5c71|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8" - + "|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063|\u5546\u57ce|\u5546\u5e97|\u5546\u6807" - + "|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c|\u5e7f\u4e1c|\u6148\u5584" - + "|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c|\u65b0\u52a0\u5761" - + "|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f|\u70b9\u770b" - + "|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc" - + "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137" - + "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox|xerox|xin|xn\\-\\-11b4c3d" - + "|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g|xn\\-\\-3e0b707e" - + "|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim" - + "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks" - + "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais" - + "|xn\\-\\-9dbq2a|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g" - + "|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-czr694b|xn\\-\\-czrs0t" - + "|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h|xn\\-\\-estv75g" - + "|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s" - + "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c" - + "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i" - + "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d" - + "|xn\\-\\-kpry57d|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf" - + "|xn\\-\\-mgba3a3ejt|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd" - + "|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar" - + "|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m" - + "|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema|xn\\-\\-nyqy26a" - + "|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh" - + "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g" - + "|xn\\-\\-s9brj9c|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y" - + "|xn\\-\\-vermgensberater\\-ctb|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv" - + "|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a" - + "|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx" - + "|xn\\-\\-zfr164b|xperia|xxx|xyz)" - + "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])" - + "|(?:zara|zip|zone|zuerich|z[amw]))"; - - public static final Pattern IP_ADDRESS - = Pattern.compile( + public static final Pattern IP_ADDRESS = Pattern.compile( "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" @@ -204,28 +58,11 @@ public final class PatternsCompat { */ private static final String LABEL_CHAR = "a-zA-Z0-9" + UCS_CHAR; - /** - * Valid characters for IRI TLD defined in RFC 3987. - */ - private static final String TLD_CHAR = "a-zA-Z" + UCS_CHAR; - /** * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets. */ - private static final String IRI_LABEL - = "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}"; - - /** - * RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters. - */ - private static final String PUNYCODE_TLD = "xn\\-\\-[\\w\\-]{0,58}\\w"; - - private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" + ")"; - - private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD; - - public static final Pattern DOMAIN_NAME - = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")"); + private static final String IRI_LABEL = + "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}"; //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // CHANGED: Removed rtsp from supported protocols // @@ -245,59 +82,11 @@ public final class PatternsCompat { + ";/\\?:@&=#~" // plus optional query params + "\\-\\.\\+!\\*'\\(\\),_\\$])|(?:%[a-fA-F0-9]{2}))*"; - /** - * Regular expression pattern to match most part of RFC 3987 - * Internationalized URLs, aka IRIs. - */ - public static final Pattern WEB_URL = Pattern.compile("(" - + "(" - + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")?" - + "(?:" + DOMAIN_NAME + ")" - + "(?:" + PORT_NUMBER + ")?" - + ")" - + "(" + PATH_AND_QUERY + ")?" - + WORD_BOUNDARY - + ")"); - - /** - * Regular expression that matches known TLDs and punycode TLDs. - */ - private static final String STRICT_TLD = "(?:" - + IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")"; - - /** - * Regular expression that matches host names using {@link #STRICT_TLD}. - */ - private static final String STRICT_HOST_NAME = "(?:(?:" + IRI_LABEL + "\\.)+" - + STRICT_TLD + ")"; - - /** - * Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or - * {@link #IP_ADDRESS}. - */ - private static final Pattern STRICT_DOMAIN_NAME - = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")"); - /** * Regular expression that matches domain names without a TLD. */ - private static final String RELAXED_DOMAIN_NAME - = "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" + "?)+" + "|" + IP_ADDRESS + ")"; - - /** - * Regular expression to match strings that do not start with a supported protocol. The TLDs - * are expected to be one of the known TLDs. - */ - private static final String WEB_URL_WITHOUT_PROTOCOL = "(" - + WORD_BOUNDARY - + "(? Date: Fri, 15 Jul 2022 19:49:41 +0200 Subject: [PATCH 060/152] Added note that explains that unused code was removed. --- .../org/schabi/newpipe/util/urlfinder/PatternsCompat.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java index 5fededbcb..49be86ae0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java +++ b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java @@ -24,6 +24,10 @@ import java.util.regex.Pattern; * Commonly used regular expression patterns. */ public final class PatternsCompat { + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // CHANGED: Removed unused code // + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + public static final Pattern IP_ADDRESS = Pattern.compile( "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" From 3ba04f179fb3d6b5ad9505ed663d1e09b0579cd3 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 15 Jul 2022 19:53:48 +0200 Subject: [PATCH 061/152] Fixed conflicts/build --- .../org/schabi/newpipe/DownloaderImpl.java | 2 - .../fragments/detail/VideoDetailFragment.java | 5 ++- .../notification/NotificationConstants.java | 39 ++++++++++--------- .../newpipe/player/ui/MainPlayerUi.java | 18 +++++---- .../newpipe/player/ui/VideoPlayerUi.java | 7 ++-- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 3579a0bad..74ca39e12 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -1,7 +1,5 @@ package org.schabi.newpipe; -import static org.schabi.newpipe.MainActivity.DEBUG; - import android.content.Context; import androidx.annotation.NonNull; 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 a24f667a7..55a6f5d5b 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 @@ -1555,8 +1555,8 @@ public final class VideoDetailFragment binding.detailUploaderThumbnailView.setVisibility(View.GONE); } - final Drawable buddyDrawable - = AppCompatResources.getDrawable(activity, R.drawable.placeholder_person); + final Drawable buddyDrawable = + AppCompatResources.getDrawable(activity, R.drawable.placeholder_person); binding.detailSubChannelThumbnailView.setImageDrawable(buddyDrawable); binding.detailUploaderThumbnailView.setImageDrawable(buddyDrawable); @@ -2030,6 +2030,7 @@ public final class VideoDetailFragment } // Listener implementation + @Override public void hideSystemUiIfNeeded() { if (isFullscreen() && bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index 53ef752bd..b8e39e564 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -30,25 +30,26 @@ public final class NotificationConstants { // Intent actions //////////////////////////////////////////////////////////////////////////*/ - public static final String ACTION_CLOSE - = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; - public static final String ACTION_PLAY_PAUSE - = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; - public static final String ACTION_REPEAT - = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; - public static final String ACTION_PLAY_NEXT - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT"; - public static final String ACTION_PLAY_PREVIOUS - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; - public static final String ACTION_FAST_REWIND - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND"; - public static final String ACTION_FAST_FORWARD - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD"; - public static final String ACTION_SHUFFLE - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE"; - public static final String ACTION_RECREATE_NOTIFICATION - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; - + private static final String BASE_ACTION = + App.PACKAGE_NAME + ".player.MainPlayer."; + public static final String ACTION_CLOSE = + BASE_ACTION + "CLOSE"; + public static final String ACTION_PLAY_PAUSE = + BASE_ACTION + ".player.MainPlayer.PLAY_PAUSE"; + public static final String ACTION_REPEAT = + BASE_ACTION + ".player.MainPlayer.REPEAT"; + public static final String ACTION_PLAY_NEXT = + BASE_ACTION + ".player.MainPlayer.ACTION_PLAY_NEXT"; + public static final String ACTION_PLAY_PREVIOUS = + BASE_ACTION + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; + public static final String ACTION_FAST_REWIND = + BASE_ACTION + ".player.MainPlayer.ACTION_FAST_REWIND"; + public static final String ACTION_FAST_FORWARD = + BASE_ACTION + ".player.MainPlayer.ACTION_FAST_FORWARD"; + public static final String ACTION_SHUFFLE = + BASE_ACTION + ".player.MainPlayer.ACTION_SHUFFLE"; + public static final String ACTION_RECREATE_NOTIFICATION = + BASE_ACTION + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; public static final int NOTHING = 0; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 52a486add..81dc954d1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -534,8 +534,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.brightnessProgressBar.setMax(maxGestureLength); setInitialGestureValues(); - binding.itemsListPanel.getLayoutParams().height - = height - binding.itemsListPanel.getTop(); + binding.itemsListPanel.getLayoutParams().height = + height - binding.itemsListPanel.getTop(); } } @@ -710,8 +710,9 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } int nearestPosition = 0; - final List segments - = player.getCurrentStreamInfo().get().getStreamSegments(); + final List segments = player.getCurrentStreamInfo() + .get() + .getStreamSegments(); for (int i = 0; i < segments.size(); i++) { if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) { @@ -912,8 +913,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh if (DEBUG) { Log.d(TAG, "toggleFullscreen() called"); } - final PlayerServiceEventListener fragmentListener - = player.getFragmentListener().orElse(null); + final PlayerServiceEventListener fragmentListener = player.getFragmentListener() + .orElse(null); if (fragmentListener == null || player.exoPlayerIsNull()) { return; } @@ -939,8 +940,9 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh public void checkLandscape() { // check if landscape is correct - final boolean videoInLandscapeButNotInFullscreen - = isLandscape() && !isFullscreen && !player.isAudioOnly(); + final boolean videoInLandscapeButNotInFullscreen = isLandscape() + && !isFullscreen + && !player.isAudioOnly(); final boolean notPaused = player.getCurrentState() != STATE_COMPLETED && player.getCurrentState() != STATE_PAUSED; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index d38c8cfe4..217dd38b3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -226,8 +226,8 @@ public abstract class VideoPlayerUi extends PlayerUi // PlaybackControlRoot already consumed window insets but we should pass them to // player_overlays and fast_seek_overlay too. Without it they will be off-centered. - onLayoutChangeListener - = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + onLayoutChangeListener = + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { binding.playerOverlays.setPadding( v.getPaddingLeft(), v.getPaddingTop(), @@ -1053,8 +1053,7 @@ public abstract class VideoPlayerUi extends PlayerUi } qualityPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_QUALITY); - @Nullable final List availableStreams - = Optional.ofNullable(player.getCurrentMetadata()) + final List availableStreams = Optional.ofNullable(player.getCurrentMetadata()) .flatMap(MediaItemTag::getMaybeQuality) .map(MediaItemTag.Quality::getSortedVideoStreams) .orElse(null); From 5da8d5fc7324be346ea9f431aeb74e7ad76b8702 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 16 Jul 2022 05:47:57 +0530 Subject: [PATCH 062/152] Use ViewCompat.setBackgroundTintList(). --- app/src/main/java/org/schabi/newpipe/ktx/View.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 ea680dd60..56c9d825a 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -12,6 +12,7 @@ import android.view.View import androidx.annotation.ColorInt import androidx.annotation.FloatRange import androidx.core.animation.addListener +import androidx.core.view.ViewCompat import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -101,11 +102,11 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo viewPropertyAnimator.interpolator = FastOutSlowInInterpolator() viewPropertyAnimator.duration = duration viewPropertyAnimator.addUpdateListener { animation: ValueAnimator -> - backgroundTintList = ColorStateList(empty, intArrayOf(animation.animatedValue as Int)) + ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(animation.animatedValue as Int))) } viewPropertyAnimator.addListener( - onCancel = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) }, - onEnd = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) } + onCancel = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) }, + onEnd = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) } ) viewPropertyAnimator.start() } From f1de353b74f10e6834bf3e2ea33cd784b31a0654 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 16 Jul 2022 08:34:04 +0530 Subject: [PATCH 063/152] Use stream sorting. --- .../database/playlist/PlaylistLocalItem.java | 17 ++++++----------- .../fragments/detail/DescriptionFragment.java | 7 +++---- 2 files changed, 9 insertions(+), 15 deletions(-) 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 43dbd89ea..695f9ec5a 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 @@ -3,10 +3,10 @@ package org.schabi.newpipe.database.playlist; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; -import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; public interface PlaylistLocalItem extends LocalItem { String getOrderingName(); @@ -14,14 +14,9 @@ public interface PlaylistLocalItem extends LocalItem { static List merge( final List localPlaylists, final List remotePlaylists) { - final List items = new ArrayList<>( - localPlaylists.size() + remotePlaylists.size()); - items.addAll(localPlaylists); - items.addAll(remotePlaylists); - - Collections.sort(items, Comparator.comparing(PlaylistLocalItem::getOrderingName, - Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER))); - - return items; + return Stream.concat(localPlaylists.stream(), remotePlaylists.stream()) + .sorted(Comparator.comparing(PlaylistLocalItem::getOrderingName, + Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER))) + .collect(Collectors.toList()); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index d57ddb02d..362388314 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -26,9 +26,8 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.TextLinkifier; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import icepick.State; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -209,8 +208,8 @@ public class DescriptionFragment extends BaseFragment { final ItemMetadataTagsBinding itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - final List tags = new ArrayList<>(streamInfo.getTags()); - Collections.sort(tags); + final List tags = streamInfo.getTags().stream().sorted() + .collect(Collectors.toList()); for (final String tag : tags) { final Chip chip = (Chip) inflater.inflate(R.layout.chip, itemBinding.metadataTagsChips, false); From a1c6f0073e196a9c742a2addedddb9f79ea7e7f4 Mon Sep 17 00:00:00 2001 From: Mohammed Anas Date: Sun, 17 Jul 2022 20:37:15 +0300 Subject: [PATCH 064/152] Rename "waiting-for-author" label to "waiting for author" (#8642) --- .github/workflows/no-response.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index 6a4a8a61a..b3495135f 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -21,4 +21,4 @@ jobs: with: token: ${{ github.token }} daysUntilClose: 14 - responseRequiredLabel: waiting-for-author + responseRequiredLabel: waiting for author From e8669d4ab50e323c2353c908fd1c9660d8cf47d6 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 13 Jul 2022 11:37:46 +0200 Subject: [PATCH 065/152] Deduplicate SQL queries to get feed streams --- .../newpipe/database/feed/dao/FeedDAO.kt | 118 +++++------------- .../newpipe/local/feed/FeedDatabaseManager.kt | 20 ++- .../newpipe/local/feed/FeedViewModel.kt | 3 +- 3 files changed, 37 insertions(+), 104 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt index d573788a6..b2b3d18a6 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt @@ -9,6 +9,7 @@ import androidx.room.Update import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import org.schabi.newpipe.database.feed.model.FeedEntity +import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity import org.schabi.newpipe.database.stream.StreamWithState import org.schabi.newpipe.database.stream.model.StreamStateEntity @@ -21,56 +22,16 @@ abstract class FeedDAO { @Query("DELETE FROM feed") abstract fun deleteAll(): Int - @Query( - """ - SELECT s.*, sst.progress_time - FROM streams s - - LEFT JOIN stream_state sst - ON s.uid = sst.stream_id - - LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - - INNER JOIN feed f - ON s.uid = f.stream_id - - ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC - LIMIT 500 - """ - ) - abstract fun getAllStreams(): Maybe> - - @Query( - """ - SELECT s.*, sst.progress_time - FROM streams s - - LEFT JOIN stream_state sst - ON s.uid = sst.stream_id - - LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - - INNER JOIN feed f - ON s.uid = f.stream_id - - INNER JOIN feed_group_subscription_join fgs - ON fgs.subscription_id = f.subscription_id - - WHERE fgs.group_id = :groupId - - ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC - LIMIT 500 - """ - ) - abstract fun getAllStreamsForGroup(groupId: Long): Maybe> - /** + * @param groupId the group id to get feed streams of; use + * [FeedGroupEntity.GROUP_ALL_ID] to not filter by group + * @param includePlayed if false, only return all of the live, never-played or non-finished + * feed streams (see `@see` items); if true no filter is applied + * @param uploadDateBefore get only streams uploaded before this date (useful to filter out + * future streams); use null to not filter by upload date + * @return the feed streams filtered according to the conditions provided in the parameters * @see StreamStateEntity.isFinished() * @see StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS - * @return all of the non-live, never-played and non-finished streams in the feed - * (all of the cited conditions must hold for a stream to be in the returned list) */ @Query( """ @@ -79,67 +40,44 @@ abstract class FeedDAO { LEFT JOIN stream_state sst ON s.uid = sst.stream_id - + LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - + ON s.uid = sh.stream_id + INNER JOIN feed f ON s.uid = f.stream_id + LEFT JOIN feed_group_subscription_join fgs + ON fgs.subscription_id = f.subscription_id + WHERE ( - sh.stream_id IS NULL - OR sst.stream_id IS NULL - OR sst.progress_time < s.duration * 1000 - ${StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS} - OR sst.progress_time < s.duration * 1000 * 3 / 4 - OR s.stream_type = 'LIVE_STREAM' - OR s.stream_type = 'AUDIO_LIVE_STREAM' + :groupId = ${FeedGroupEntity.GROUP_ALL_ID} + OR fgs.group_id = :groupId ) - - ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC - LIMIT 500 - """ - ) - abstract fun getLiveOrNotPlayedStreams(): Maybe> - - /** - * @see StreamStateEntity.isFinished() - * @see StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS - * @param groupId the group id to get streams of - * @return all of the non-live, never-played and non-finished streams for the given feed group - * (all of the cited conditions must hold for a stream to be in the returned list) - */ - @Query( - """ - SELECT s.*, sst.progress_time - FROM streams s - - LEFT JOIN stream_state sst - ON s.uid = sst.stream_id - - LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - - INNER JOIN feed f - ON s.uid = f.stream_id - - INNER JOIN feed_group_subscription_join fgs - ON fgs.subscription_id = f.subscription_id - - WHERE fgs.group_id = :groupId AND ( - sh.stream_id IS NULL + :includePlayed + OR sh.stream_id IS NULL OR sst.stream_id IS NULL OR sst.progress_time < s.duration * 1000 - ${StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS} OR sst.progress_time < s.duration * 1000 * 3 / 4 OR s.stream_type = 'LIVE_STREAM' OR s.stream_type = 'AUDIO_LIVE_STREAM' ) + AND ( + :uploadDateBefore IS NULL + OR s.upload_date IS NULL + OR s.upload_date < :uploadDateBefore + ) ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC LIMIT 500 """ ) - abstract fun getLiveOrNotPlayedStreamsForGroup(groupId: Long): Maybe> + abstract fun getStreams( + groupId: Long, + includePlayed: Boolean, + uploadDateBefore: OffsetDateTime? + ): Maybe> @Query( """ diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt index 7a8723ceb..07edb0499 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt @@ -41,19 +41,15 @@ class FeedDatabaseManager(context: Context) { fun database() = database fun getStreams( - groupId: Long = FeedGroupEntity.GROUP_ALL_ID, - getPlayedStreams: Boolean = true + groupId: Long, + includePlayedStreams: Boolean, + includeFutureStreams: Boolean ): Maybe> { - return when (groupId) { - FeedGroupEntity.GROUP_ALL_ID -> { - if (getPlayedStreams) feedTable.getAllStreams() - else feedTable.getLiveOrNotPlayedStreams() - } - else -> { - if (getPlayedStreams) feedTable.getAllStreamsForGroup(groupId) - else feedTable.getLiveOrNotPlayedStreamsForGroup(groupId) - } - } + return feedTable.getStreams( + groupId, + includePlayedStreams, + if (includeFutureStreams) null else OffsetDateTime.now() + ) } fun outdatedSubscriptions(outdatedThreshold: OffsetDateTime) = feedTable.getAllOutdated(outdatedThreshold) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index 87409ddae..7f5ef4301 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -65,9 +65,8 @@ class FeedViewModel( .map { (event, showPlayedItems, showFutureItems, notLoadedCount, oldestUpdate) -> val streamItems = if (event is SuccessResultEvent || event is IdleEvent) feedDatabaseManager - .getStreams(groupId, showPlayedItems) + .getStreams(groupId, showPlayedItems, showFutureItems) .blockingGet(arrayListOf()) - .filter { s -> showFutureItems || s.stream.uploadDate?.isBefore(OffsetDateTime.now()) ?: true } else arrayListOf() From e77224444051323fe506b350ebd4df5546ca4de2 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne <31027858+Isira-Seneviratne@users.noreply.github.com> Date: Tue, 19 Jul 2022 05:10:51 +0530 Subject: [PATCH 066/152] Update app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java Co-authored-by: Stypox --- .../newpipe/fragments/detail/DescriptionFragment.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index 362388314..ab4883768 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -26,9 +26,6 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.TextLinkifier; -import java.util.List; -import java.util.stream.Collectors; - import icepick.State; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -208,16 +205,14 @@ public class DescriptionFragment extends BaseFragment { final ItemMetadataTagsBinding itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - final List tags = streamInfo.getTags().stream().sorted() - .collect(Collectors.toList()); - for (final String tag : tags) { + streamInfo.getTags().stream().sorted().forEach(tag -> { final Chip chip = (Chip) inflater.inflate(R.layout.chip, itemBinding.metadataTagsChips, false); chip.setText(tag); chip.setOnClickListener(this::onTagClick); chip.setOnLongClickListener(this::onTagLongClick); itemBinding.metadataTagsChips.addView(chip); - } + }); layout.addView(itemBinding.getRoot()); } From c53143ef4fe86b4ca43f8b8828c3b4eacae35acd Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 15 Jul 2022 07:40:25 +0530 Subject: [PATCH 067/152] Use Set.of(). --- app/src/main/java/org/schabi/newpipe/util/CookieUtils.java | 3 +-- app/src/main/java/org/schabi/newpipe/util/ListHelper.java | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java index d970ec472..b906c1c4f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java @@ -2,7 +2,6 @@ package org.schabi.newpipe.util; import android.text.TextUtils; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -20,6 +19,6 @@ public final class CookieUtils { } public static Set splitCookies(final String cookies) { - return new HashSet<>(Arrays.asList(cookies.split("; *"))); + return Set.of(cookies.split("; *")); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index b0b4bae74..9707956ed 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -22,7 +22,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -40,9 +39,8 @@ public final class ListHelper { // Audio format in order of efficiency. 0=most efficient, n=least efficient private static final List AUDIO_FORMAT_EFFICIENCY_RANKING = Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); - // Use a HashSet for better performance - private static final Set HIGH_RESOLUTION_LIST = new HashSet<>( - Arrays.asList("1440p", "2160p")); + // Use a Set for better performance + private static final Set HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p"); private ListHelper() { } From 4eddd2c3d1e246d92e8da81e335c3dc10fc1e68c Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 19 Jul 2022 20:01:46 +0200 Subject: [PATCH 068/152] Fix random NullPointerException when adding video player view --- .../newpipe/fragments/detail/VideoDetailFragment.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 55a6f5d5b..cc8007596 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 @@ -1313,9 +1313,11 @@ public final class VideoDetailFragment // Prevent from re-adding a view multiple times new Handler(Looper.getMainLooper()).post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { - playerUi.removeViewFromParent(); - binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); - playerUi.setupVideoSurfaceIfNeeded(); + if (binding != null) { + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + } })); } From ca26fcb0ebf906d10cdc9551e1043ee074fc88c1 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 15 Jul 2022 07:40:29 +0530 Subject: [PATCH 069/152] Use List.of(). --- .../material/appbar/FlingBehavior.java | 5 +- app/src/main/java/org/schabi/newpipe/App.java | 65 +++++++++---------- .../org/schabi/newpipe/QueueItemMenuUtil.java | 4 +- .../org/schabi/newpipe/RouterActivity.java | 3 +- .../fragments/detail/VideoDetailFragment.java | 3 +- .../dialog/StreamDialogDefaultEntry.java | 4 +- .../gesture/CustomBottomSheetBehavior.java | 5 +- .../player/mediasource/FailedMediaSource.java | 6 +- .../newpipe/player/playqueue/PlayQueue.java | 3 +- .../player/playqueue/SinglePlayQueue.java | 7 +- .../PreferenceSearchConfiguration.java | 8 +-- .../PreferenceSearchItem.java | 7 +- .../newpipe/settings/tabs/TabsJsonHelper.java | 10 +-- .../org/schabi/newpipe/util/ListHelper.java | 6 +- .../schabi/newpipe/util/PeertubeHelper.java | 6 +- .../player/playqueue/PlayQueueTest.java | 2 +- .../schabi/newpipe/util/ListHelperTest.java | 33 +++++----- 17 files changed, 75 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index 47c8deb83..52754e8fa 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -14,7 +14,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout; import org.schabi.newpipe.R; import java.lang.reflect.Field; -import java.util.Arrays; import java.util.List; // See https://stackoverflow.com/questions/56849221#57997489 @@ -27,7 +26,7 @@ public final class FlingBehavior extends AppBarLayout.Behavior { private boolean allowScroll = true; private final Rect globalRect = new Rect(); - private final List skipInterceptionOfElements = Arrays.asList( + private final List skipInterceptionOfElements = List.of( R.id.itemsListPanel, R.id.playbackSeekBar, R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton); @@ -67,7 +66,7 @@ public final class FlingBehavior extends AppBarLayout.Behavior { public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent, @NonNull final AppBarLayout child, @NonNull final MotionEvent ev) { - for (final Integer element : skipInterceptionOfElements) { + for (final int element : skipInterceptionOfElements) { final View view = child.findViewById(element); if (view != null) { final boolean visible = view.getGlobalVisibleRect(globalRect); diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index c63f07891..f4410a31b 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -27,9 +27,8 @@ import org.schabi.newpipe.util.StateSaver; import java.io.IOException; import java.io.InterruptedIOException; import java.net.SocketException; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Objects; import io.reactivex.rxjava3.exceptions.CompositeException; import io.reactivex.rxjava3.exceptions.MissingBackpressureException; @@ -140,7 +139,7 @@ public class App extends Application { if (throwable instanceof UndeliverableException) { // As UndeliverableException is a wrapper, // get the cause of it to get the "real" exception - actualThrowable = throwable.getCause(); + actualThrowable = Objects.requireNonNull(throwable.getCause()); } else { actualThrowable = throwable; } @@ -149,7 +148,7 @@ public class App extends Application { if (actualThrowable instanceof CompositeException) { errors = ((CompositeException) actualThrowable).getExceptions(); } else { - errors = Collections.singletonList(actualThrowable); + errors = List.of(actualThrowable); } for (final Throwable error : errors) { @@ -213,41 +212,37 @@ public class App extends Application { private void initNotificationChannels() { // Keep the importance below DEFAULT to avoid making noise on every notification update for // the main and update channels - final List notificationChannelCompats = new ArrayList<>(); - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.notification_channel_id), + final List notificationChannelCompats = List.of( + new NotificationChannelCompat.Builder(getString(R.string.notification_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) - .setName(getString(R.string.notification_channel_name)) - .setDescription(getString(R.string.notification_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.app_update_notification_channel_id), + .setName(getString(R.string.notification_channel_name)) + .setDescription(getString(R.string.notification_channel_description)) + .build(), + new NotificationChannelCompat + .Builder(getString(R.string.app_update_notification_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) - .setName(getString(R.string.app_update_notification_channel_name)) - .setDescription(getString(R.string.app_update_notification_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.hash_channel_id), + .setName(getString(R.string.app_update_notification_channel_name)) + .setDescription( + getString(R.string.app_update_notification_channel_description)) + .build(), + new NotificationChannelCompat.Builder(getString(R.string.hash_channel_id), NotificationManagerCompat.IMPORTANCE_HIGH) - .setName(getString(R.string.hash_channel_name)) - .setDescription(getString(R.string.hash_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.error_report_channel_id), + .setName(getString(R.string.hash_channel_name)) + .setDescription(getString(R.string.hash_channel_description)) + .build(), + new NotificationChannelCompat.Builder(getString(R.string.error_report_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) - .setName(getString(R.string.error_report_channel_name)) - .setDescription(getString(R.string.error_report_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.streams_notification_channel_id), - NotificationManagerCompat.IMPORTANCE_DEFAULT) - .setName(getString(R.string.streams_notification_channel_name)) - .setDescription(getString(R.string.streams_notification_channel_description)) - .build()); + .setName(getString(R.string.error_report_channel_name)) + .setDescription(getString(R.string.error_report_channel_description)) + .build(), + new NotificationChannelCompat + .Builder(getString(R.string.streams_notification_channel_id), + NotificationManagerCompat.IMPORTANCE_DEFAULT) + .setName(getString(R.string.streams_notification_channel_name)) + .setDescription( + getString(R.string.streams_notification_channel_description)) + .build() + ); final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.createNotificationChannelsCompat(notificationChannelCompats); diff --git a/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java b/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java index c7604e512..7c646d0e4 100644 --- a/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java +++ b/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java @@ -16,7 +16,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.SparseItemUtil; -import java.util.Collections; +import java.util.List; public final class QueueItemMenuUtil { private QueueItemMenuUtil() { @@ -53,7 +53,7 @@ public final class QueueItemMenuUtil { case R.id.menu_item_append_playlist: PlaylistDialog.createCorrespondingDialog( context, - Collections.singletonList(new StreamEntity(item)), + List.of(new StreamEntity(item)), dialog -> dialog.show( fragmentManager, "QueueItemMenuUtil@append_playlist" diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index d055da1e8..522f25df8 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -81,7 +81,6 @@ import org.schabi.newpipe.views.FocusOverlayView; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import icepick.Icepick; @@ -649,7 +648,7 @@ public class RouterActivity extends AppCompatActivity { .subscribe( info -> PlaylistDialog.createCorrespondingDialog( getThemeWrapperContext(), - Collections.singletonList(new StreamEntity(info)), + List.of(new StreamEntity(info)), playlistDialog -> { playlistDialog.setOnDismissListener(dialog -> finish()); 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 55a6f5d5b..f4838482b 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 @@ -114,7 +114,6 @@ import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -453,7 +452,7 @@ public final class VideoDetailFragment disposables.add( PlaylistDialog.createCorrespondingDialog( getContext(), - Collections.singletonList(new StreamEntity(currentInfo)), + List.of(new StreamEntity(currentInfo)), dialog -> dialog.show(getFM(), TAG) ) ); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java index 2b3f73926..1265e9767 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java @@ -20,7 +20,7 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; -import java.util.Collections; +import java.util.List; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -89,7 +89,7 @@ public enum StreamDialogDefaultEntry { APPEND_PLAYLIST(R.string.add_to_playlist, (fragment, item) -> PlaylistDialog.createCorrespondingDialog( fragment.getContext(), - Collections.singletonList(new StreamEntity(item)), + List.of(new StreamEntity(item)), dialog -> dialog.show( fragment.getParentFragmentManager(), "StreamDialogEntry@" diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java index 240009105..41046784f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java @@ -14,7 +14,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior; import org.schabi.newpipe.R; -import java.util.Arrays; import java.util.List; public class CustomBottomSheetBehavior extends BottomSheetBehavior { @@ -25,7 +24,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior Rect globalRect = new Rect(); private boolean skippingInterception = false; - private final List skipInterceptionOfElements = Arrays.asList( + private final List skipInterceptionOfElements = List.of( R.id.detail_content_root_layout, R.id.relatedItemsLayout, R.id.itemsListPanel, R.id.view_pager, R.id.tab_layout, R.id.bottomControls, R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton); @@ -57,7 +56,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior if (getState() == BottomSheetBehavior.STATE_EXPANDED && event.getAction() == MotionEvent.ACTION_DOWN) { // Without overriding scrolling will not work when user touches these elements - for (final Integer element : skipInterceptionOfElements) { + for (final int element : skipInterceptionOfElements) { final View view = child.findViewById(element); if (view != null) { final boolean visible = view.getGlobalVisibleRect(globalRect); diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java index 8aad356d0..b9ca90d89 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java @@ -16,7 +16,7 @@ import org.schabi.newpipe.player.mediaitem.ExceptionTag; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import java.io.IOException; -import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; import androidx.annotation.NonNull; @@ -56,9 +56,7 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo this.playQueueItem = playQueueItem; this.error = error; this.retryTimestamp = retryTimestamp; - this.mediaItem = ExceptionTag - .of(playQueueItem, Collections.singletonList(error)) - .withExtras(this) + this.mediaItem = ExceptionTag.of(playQueueItem, List.of(error)).withExtras(this) .asMediaItem(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index f46c9d72f..d54fed248 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -16,7 +16,6 @@ import org.schabi.newpipe.player.playqueue.events.SelectEvent; import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -264,7 +263,7 @@ public abstract class PlayQueue implements Serializable { * @param items {@link PlayQueueItem}s to append */ public synchronized void append(@NonNull final PlayQueueItem... items) { - append(Arrays.asList(items)); + append(List.of(items)); } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java index 527e80470..e51173214 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java @@ -4,20 +4,19 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public final class SinglePlayQueue extends PlayQueue { public SinglePlayQueue(final StreamInfoItem item) { - super(0, Collections.singletonList(new PlayQueueItem(item))); + super(0, List.of(new PlayQueueItem(item))); } public SinglePlayQueue(final StreamInfo info) { - super(0, Collections.singletonList(new PlayQueueItem(info))); + super(0, List.of(new PlayQueueItem(info))); } public SinglePlayQueue(final StreamInfo info, final long startPosition) { - super(0, Collections.singletonList(new PlayQueueItem(info))); + super(0, List.of(new PlayQueueItem(info))); getItem().setRecoveryPosition(startPosition); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java index a445ea309..88b0b7735 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java @@ -3,8 +3,6 @@ package org.schabi.newpipe.settings.preferencesearch; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Stream; @@ -12,9 +10,9 @@ import java.util.stream.Stream; public class PreferenceSearchConfiguration { private PreferenceSearchFunction searcher = new PreferenceFuzzySearchFunction(); - private final List parserIgnoreElements = Collections.singletonList( - PreferenceCategory.class.getSimpleName()); - private final List parserContainerElements = Arrays.asList( + private final List parserIgnoreElements = List.of(PreferenceCategory.class + .getSimpleName()); + private final List parserContainerElements = List.of( PreferenceCategory.class.getSimpleName(), PreferenceScreen.class.getSimpleName()); diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java index 98d2a5d84..33856326c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.settings.preferencesearch; import androidx.annotation.NonNull; import androidx.annotation.XmlRes; -import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -92,11 +91,7 @@ public class PreferenceSearchItem { } public List getAllRelevantSearchFields() { - return Arrays.asList( - getTitle(), - getSummary(), - getEntries(), - getBreadcrumbs()); + return List.of(getTitle(), getSummary(), getEntries(), getBreadcrumbs()); } @NonNull diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java index 057ca50f0..40dacc964 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java @@ -10,8 +10,6 @@ import com.grack.nanojson.JsonStringWriter; import com.grack.nanojson.JsonWriter; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; /** @@ -20,11 +18,9 @@ import java.util.List; public final class TabsJsonHelper { private static final String JSON_TABS_ARRAY_KEY = "tabs"; - private static final List FALLBACK_INITIAL_TABS_LIST = Collections.unmodifiableList( - Arrays.asList( - Tab.Type.DEFAULT_KIOSK.getTab(), - Tab.Type.SUBSCRIPTIONS.getTab(), - Tab.Type.BOOKMARKS.getTab())); + private static final List FALLBACK_INITIAL_TABS_LIST = List.of( + Tab.Type.DEFAULT_KIOSK.getTab(), Tab.Type.SUBSCRIPTIONS.getTab(), + Tab.Type.BOOKMARKS.getTab()); private TabsJsonHelper() { } diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 9707956ed..1b3cb1651 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -31,14 +31,14 @@ import java.util.stream.Collectors; public final class ListHelper { // Video format in order of quality. 0=lowest quality, n=highest quality private static final List VIDEO_FORMAT_QUALITY_RANKING = - Arrays.asList(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4); + List.of(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4); // Audio format in order of quality. 0=lowest quality, n=highest quality private static final List AUDIO_FORMAT_QUALITY_RANKING = - Arrays.asList(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A); + List.of(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A); // Audio format in order of efficiency. 0=most efficient, n=least efficient private static final List AUDIO_FORMAT_EFFICIENCY_RANKING = - Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); + List.of(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); // Use a Set for better performance private static final Set HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p"); diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java index 97e6e7563..34f99d262 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java @@ -17,7 +17,6 @@ import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public final class PeertubeHelper { @@ -29,7 +28,7 @@ public final class PeertubeHelper { final String savedInstanceListKey = context.getString(R.string.peertube_instance_list_key); final String savedJson = sharedPreferences.getString(savedInstanceListKey, null); if (null == savedJson) { - return Collections.singletonList(getCurrentInstance()); + return List.of(getCurrentInstance()); } try { @@ -45,9 +44,8 @@ public final class PeertubeHelper { } return result; } catch (final JsonParserException e) { - return Collections.singletonList(getCurrentInstance()); + return List.of(getCurrentInstance()); } - } public static PeertubeInstance selectInstance(final PeertubeInstance instance, diff --git a/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java b/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java index f92863eab..a130359a3 100644 --- a/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java +++ b/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java @@ -153,7 +153,7 @@ public class PlayQueueTest { @Test public void itemsAreNotCloned() { final PlayQueueItem item = makeItemWithUrl("A url"); - final PlayQueue playQueue = makePlayQueue(0, Collections.singletonList(item)); + final PlayQueue playQueue = makePlayQueue(0, List.of(item)); // make sure that items are not cloned when added to the queue assertSame(playQueue.getItem(), item); diff --git a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java index c9d570c7d..4f9b0b497 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -6,7 +6,6 @@ import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.VideoStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; @@ -18,7 +17,7 @@ import androidx.annotation.Nullable; public class ListHelperTest { private static final String BEST_RESOLUTION_KEY = "best_resolution"; - private static final List AUDIO_STREAMS_TEST_LIST = Arrays.asList( + private static final List AUDIO_STREAMS_TEST_LIST = List.of( generateAudioStream("m4a-128-1", MediaFormat.M4A, 128), generateAudioStream("webma-192", MediaFormat.WEBMA, 192), generateAudioStream("mp3-64", MediaFormat.MP3, 64), @@ -30,7 +29,7 @@ public class ListHelperTest { generateAudioStream("mp3-192", MediaFormat.MP3, 192), generateAudioStream("webma-320", MediaFormat.WEBMA, 320)); - private static final List VIDEO_STREAMS_TEST_LIST = Arrays.asList( + private static final List VIDEO_STREAMS_TEST_LIST = List.of( generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false), generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false), @@ -38,7 +37,7 @@ public class ListHelperTest { generateVideoStream("mpeg_4-360", MediaFormat.MPEG_4, "360p", false), generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false)); - private static final List VIDEO_ONLY_STREAMS_TEST_LIST = Arrays.asList( + private static final List VIDEO_ONLY_STREAMS_TEST_LIST = List.of( generateVideoStream("mpeg_4-720-1", MediaFormat.MPEG_4, "720p", true), generateVideoStream("mpeg_4-720-2", MediaFormat.MPEG_4, "720p", true), generateVideoStream("mpeg_4-2160", MediaFormat.MPEG_4, "2160p", true), @@ -54,7 +53,7 @@ public class ListHelperTest { List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, true, false); - List expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60", + List expected = List.of("144p", "240p", "360p", "480p", "720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); assertEquals(expected.size(), result.size()); @@ -69,7 +68,7 @@ public class ListHelperTest { result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false, false); - expected = Arrays.asList("2160p60", "2160p", "1440p60", "1080p60", "1080p", "720p60", + expected = List.of("2160p60", "2160p", "1440p60", "1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -83,7 +82,7 @@ public class ListHelperTest { null, VIDEO_ONLY_STREAMS_TEST_LIST, true, true); List expected = - Arrays.asList("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); + List.of("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -97,7 +96,7 @@ public class ListHelperTest { result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, null, false, true); - expected = Arrays.asList("720p", "480p", "360p", "240p", "144p"); + expected = List.of("720p", "480p", "360p", "240p", "144p"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { assertEquals(expected.get(i), result.get(i).getResolution()); @@ -110,10 +109,10 @@ public class ListHelperTest { result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, true, true); - expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60", + expected = List.of("144p", "240p", "360p", "480p", "720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); final List expectedVideoOnly = - Arrays.asList("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); + List.of("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -131,7 +130,7 @@ public class ListHelperTest { final List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, false, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false, false); - final List expected = Arrays.asList( + final List expected = List.of( "1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -141,7 +140,7 @@ public class ListHelperTest { @Test public void getDefaultResolutionTest() { - final List testList = Arrays.asList( + final List testList = List.of( generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false), generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false), @@ -223,7 +222,7 @@ public class ListHelperTest { // Doesn't contain the preferred format // //////////////////////////////////////// - List testList = Arrays.asList( + List testList = List.of( generateAudioStream("m4a-128", MediaFormat.M4A, 128), generateAudioStream("webma-192", MediaFormat.WEBMA, 192)); // List doesn't contains this format @@ -237,7 +236,7 @@ public class ListHelperTest { // Multiple not-preferred-formats and equal bitrates // ////////////////////////////////////////////////////// - testList = new ArrayList<>(Arrays.asList( + testList = new ArrayList<>(List.of( generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192), generateAudioStream("m4a-192-1", MediaFormat.M4A, 192), generateAudioStream("webma-192-2", MediaFormat.WEBMA, 192), @@ -290,7 +289,7 @@ public class ListHelperTest { // Doesn't contain the preferred format // //////////////////////////////////////// - List testList = new ArrayList<>(Arrays.asList( + List testList = new ArrayList<>(List.of( generateAudioStream("m4a-128", MediaFormat.M4A, 128), generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192))); // List doesn't contains this format @@ -310,7 +309,7 @@ public class ListHelperTest { // Multiple not-preferred-formats and equal bitrates // ////////////////////////////////////////////////////// - testList = new ArrayList<>(Arrays.asList( + testList = new ArrayList<>(List.of( generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192), generateAudioStream("m4a-192-1", MediaFormat.M4A, 192), generateAudioStream("webma-256", MediaFormat.WEBMA, 256), @@ -337,7 +336,7 @@ public class ListHelperTest { @Test public void getVideoDefaultStreamIndexCombinations() { - final List testList = Arrays.asList( + final List testList = List.of( generateVideoStream("mpeg_4-1080", MediaFormat.MPEG_4, "1080p", false), generateVideoStream("mpeg_4-720_60", MediaFormat.MPEG_4, "720p60", false), generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), From 55a995c4cd5990eb0a6b0058b58e7fa2e16a1c00 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 15 Jul 2022 08:13:08 +0530 Subject: [PATCH 070/152] Replace LinkedHashMap with List.of(). --- .../detail/VideoDetailPlayerCrasher.java | 74 +++++++------------ 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java index 55336a42f..c816723ff 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java @@ -6,6 +6,7 @@ import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_UNSPECI import android.content.Context; import android.util.Log; +import android.util.Pair; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -28,9 +29,7 @@ import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.ThemeHelper; import java.io.IOException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.List; import java.util.function.Supplier; /** @@ -43,50 +42,34 @@ public final class VideoDetailPlayerCrasher { // https://stackoverflow.com/a/54744028 private static final String TAG = "VideoDetPlayerCrasher"; - private static final Map> AVAILABLE_EXCEPTION_TYPES = - getExceptionTypes(); + private static final String DEFAULT_MSG = "Dummy"; + + private static final List>> + AVAILABLE_EXCEPTION_TYPES = List.of( + new Pair<>("Source", () -> ExoPlaybackException.createForSource( + new IOException(DEFAULT_MSG), + ERROR_CODE_BEHIND_LIVE_WINDOW + )), + new Pair<>("Renderer", () -> ExoPlaybackException.createForRenderer( + new Exception(DEFAULT_MSG), + "Dummy renderer", + 0, + null, + C.FORMAT_HANDLED, + /*isRecoverable=*/false, + ERROR_CODE_DECODING_FAILED + )), + new Pair<>("Unexpected", () -> ExoPlaybackException.createForUnexpected( + new RuntimeException(DEFAULT_MSG), + ERROR_CODE_UNSPECIFIED + )), + new Pair<>("Remote", () -> ExoPlaybackException.createForRemote(DEFAULT_MSG)) + ); private VideoDetailPlayerCrasher() { // No impls } - private static Map> getExceptionTypes() { - final String defaultMsg = "Dummy"; - final Map> exceptionTypes = new LinkedHashMap<>(); - exceptionTypes.put( - "Source", - () -> ExoPlaybackException.createForSource( - new IOException(defaultMsg), - ERROR_CODE_BEHIND_LIVE_WINDOW - ) - ); - exceptionTypes.put( - "Renderer", - () -> ExoPlaybackException.createForRenderer( - new Exception(defaultMsg), - "Dummy renderer", - 0, - null, - C.FORMAT_HANDLED, - /*isRecoverable=*/false, - ERROR_CODE_DECODING_FAILED - ) - ); - exceptionTypes.put( - "Unexpected", - () -> ExoPlaybackException.createForUnexpected( - new RuntimeException(defaultMsg), - ERROR_CODE_UNSPECIFIED - ) - ); - exceptionTypes.put( - "Remote", - () -> ExoPlaybackException.createForRemote(defaultMsg) - ); - - return Collections.unmodifiableMap(exceptionTypes); - } - private static Context getThemeWrapperContext(final Context context) { return new ContextThemeWrapper( context, @@ -121,10 +104,9 @@ public final class VideoDetailPlayerCrasher { .setNegativeButton(R.string.cancel, null) .create(); - for (final Map.Entry> entry - : AVAILABLE_EXCEPTION_TYPES.entrySet()) { + for (final Pair> entry : AVAILABLE_EXCEPTION_TYPES) { final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot(); - radioButton.setText(entry.getKey()); + radioButton.setText(entry.first); radioButton.setChecked(false); radioButton.setLayoutParams( new RadioGroup.LayoutParams( @@ -133,7 +115,7 @@ public final class VideoDetailPlayerCrasher { ) ); radioButton.setOnClickListener(v -> { - tryCrashPlayerWith(player, entry.getValue().get()); + tryCrashPlayerWith(player, entry.second.get()); alertDialog.cancel(); }); binding.list.addView(radioButton); From a6cc13845a70dd80aaa31efa291078c3534cca48 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Tue, 19 Jul 2022 08:52:16 +0530 Subject: [PATCH 071/152] Use Map.of(). --- .../helper/PlaybackParameterDialog.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 8a5a4f8d2..d542e1c53 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -30,7 +30,6 @@ import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; -import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -334,10 +333,8 @@ public class PlaybackParameterDialog extends DialogFragment { } private Map getPitchControlModeComponentMappings() { - final Map mappings = new HashMap<>(); - mappings.put(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent); - mappings.put(PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone); - return mappings; + return Map.of(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent, + PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone); } private void changePitchControlMode(final boolean semitones) { @@ -407,13 +404,11 @@ public class PlaybackParameterDialog extends DialogFragment { } private Map getStepSizeComponentMappings() { - final Map mappings = new HashMap<>(); - mappings.put(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent); - mappings.put(STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent); - mappings.put(STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent); - mappings.put(STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent); - mappings.put(STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent); - return mappings; + return Map.of(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent, + STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent, + STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent, + STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent, + STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent); } private void setStepSizeToUI(final double newStepSize) { From d62cdc659f2933a2f522e7054838f3d86162bde6 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 09:00:11 +0530 Subject: [PATCH 072/152] Use MathUtils.clamp(). Co-authored-by: Stypox --- .../org/schabi/newpipe/RouterActivity.java | 3 ++- .../org/schabi/newpipe/player/Player.java | 13 ++++--------- .../helper/PlaybackParameterDialog.java | 10 +++------- .../player/helper/PlayerSemitoneHelper.java | 4 +++- .../playqueue/PlayQueueItemTouchCallback.java | 15 +++++++++------ .../SeekbarPreviewThumbnailHelper.java | 19 ++++++------------- .../newpipe/player/ui/PopupPlayerUi.java | 16 +++++----------- .../newpipe/player/ui/VideoPlayerUi.java | 14 ++++---------- .../org/schabi/newpipe/util/Localization.java | 4 ++-- 9 files changed, 38 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index d055da1e8..9daceda08 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -30,6 +30,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.content.res.AppCompatResources; import androidx.core.app.NotificationCompat; import androidx.core.app.ServiceCompat; +import androidx.core.math.MathUtils; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; @@ -452,7 +453,7 @@ public class RouterActivity extends AppCompatActivity { } } - selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.size() - 1); + selectedRadioPosition = MathUtils.clamp(selectedRadioPosition, -1, choices.size() - 1); if (selectedRadioPosition != -1) { ((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true); } 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 43427ac27..159d361c1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -62,6 +62,7 @@ import android.view.LayoutInflater; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.C; @@ -591,7 +592,7 @@ public final class Player implements PlaybackListener, Listener { final long duration = simpleExoPlayer.getDuration(); // No checks due to https://github.com/TeamNewPipe/NewPipe/pull/7195#issuecomment-962624380 - setRecovery(queuePos, Math.max(0, Math.min(windowPos, duration))); + setRecovery(queuePos, MathUtils.clamp(windowPos, 0, duration)); } private void setRecovery(final int queuePos, final long windowPos) { @@ -1534,14 +1535,8 @@ public final class Player implements PlaybackListener, Listener { } if (!exoPlayerIsNull()) { // prevent invalid positions when fast-forwarding/-rewinding - long normalizedPositionMillis = positionMillis; - if (normalizedPositionMillis < 0) { - normalizedPositionMillis = 0; - } else if (normalizedPositionMillis > simpleExoPlayer.getDuration()) { - normalizedPositionMillis = simpleExoPlayer.getDuration(); - } - - simpleExoPlayer.seekTo(normalizedPositionMillis); + simpleExoPlayer.seekTo(MathUtils.clamp(positionMillis, 0, + simpleExoPlayer.getDuration())); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 8a5a4f8d2..83258bcd2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -21,6 +21,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; +import androidx.core.math.MathUtils; import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; @@ -532,7 +533,7 @@ public class PlaybackParameterDialog extends DialogFragment { } private void setAndUpdateTempo(final double newTempo) { - this.tempo = calcValidTempo(newTempo); + this.tempo = MathUtils.clamp(newTempo, MIN_PITCH_OR_SPEED, MAX_PITCH_OR_SPEED); binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo); @@ -551,13 +552,8 @@ public class PlaybackParameterDialog extends DialogFragment { pitchPercent); } - private double calcValidTempo(final double newTempo) { - return Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newTempo)); - } - private double calcValidPitch(final double newPitch) { - final double calcPitch = - Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newPitch)); + final double calcPitch = MathUtils.clamp(newPitch, MIN_PITCH_OR_SPEED, MAX_PITCH_OR_SPEED); if (!isCurrentPitchControlModeSemitone()) { return calcPitch; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java index f3a71d7cd..f1ba90f8e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.player.helper; +import androidx.core.math.MathUtils; + /** * Converts between percent and 12-tone equal temperament semitones. *
@@ -33,6 +35,6 @@ public final class PlayerSemitoneHelper { } private static int ensureSemitonesInRange(final int semitones) { - return Math.max(-SEMITONE_COUNT, Math.min(SEMITONE_COUNT, semitones)); + return MathUtils.clamp(semitones, -SEMITONE_COUNT, SEMITONE_COUNT); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java index b283e105e..de1359bca 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.player.playqueue; +import androidx.annotation.NonNull; +import androidx.core.math.MathUtils; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; @@ -16,18 +18,19 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC public abstract void onSwiped(int index); @Override - public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView, final int viewSize, - final int viewSizeOutOfBounds, final int totalSize, - final long msSinceStartScroll) { + public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView, + final int viewSize, final int viewSizeOutOfBounds, + final int totalSize, final long msSinceStartScroll) { final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); - final int clampedAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY, - Math.min(Math.abs(standardSpeed), MAXIMUM_INITIAL_DRAG_VELOCITY)); + final int clampedAbsVelocity = MathUtils.clamp(Math.abs(standardSpeed), + MINIMUM_INITIAL_DRAG_VELOCITY, MAXIMUM_INITIAL_DRAG_VELOCITY); return clampedAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); } @Override - public boolean onMove(final RecyclerView recyclerView, final RecyclerView.ViewHolder source, + public boolean onMove(@NonNull final RecyclerView recyclerView, + final RecyclerView.ViewHolder source, final RecyclerView.ViewHolder target) { if (source.getItemViewType() != target.getItemViewType()) { return false; diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java index 54d11da83..dba28a69a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java @@ -8,13 +8,13 @@ import android.widget.ImageView; import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.util.DeviceUtils; import java.lang.annotation.Retention; -import java.util.Objects; import java.util.Optional; import java.util.function.IntSupplier; @@ -79,19 +79,12 @@ public final class SeekbarPreviewThumbnailHelper { // Resize original bitmap try { - Objects.requireNonNull(srcBitmap); - final int srcWidth = srcBitmap.getWidth() > 0 ? srcBitmap.getWidth() : 1; - final int newWidth = Math.max( - Math.min( - // Use 1/4 of the width for the preview - Math.round(baseViewWidthSupplier.getAsInt() / 4f), - // Scaling more than that factor looks really pixelated -> max - Math.round(srcWidth * 2.5f) - ), - // Min width = 10dp - DeviceUtils.dpToPx(10, context) - ); + // Use 1/4 of the width for the preview + final int newWidth = MathUtils.clamp(Math.round(baseViewWidthSupplier.getAsInt() / 4f), + DeviceUtils.dpToPx(10, context), + // Scaling more than that factor looks really pixelated -> max + Math.round(srcWidth * 2.5f)); final float scaleFactor = (float) newWidth / srcWidth; final int newHeight = (int) (srcBitmap.getHeight() * scaleFactor); 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 bb810f86b..74adf281c 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 @@ -27,6 +27,7 @@ import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; +import androidx.core.math.MathUtils; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.SubtitleView; @@ -247,17 +248,10 @@ public final class PopupPlayerUi extends VideoPlayerUi { return; } - if (popupLayoutParams.x < 0) { - popupLayoutParams.x = 0; - } else if (popupLayoutParams.x > screenWidth - popupLayoutParams.width) { - popupLayoutParams.x = screenWidth - popupLayoutParams.width; - } - - if (popupLayoutParams.y < 0) { - popupLayoutParams.y = 0; - } else if (popupLayoutParams.y > screenHeight - popupLayoutParams.height) { - popupLayoutParams.y = screenHeight - popupLayoutParams.height; - } + popupLayoutParams.x = MathUtils.clamp(popupLayoutParams.x, 0, screenWidth + - popupLayoutParams.width); + popupLayoutParams.y = MathUtils.clamp(popupLayoutParams.y, 0, screenHeight + - popupLayoutParams.height); } public void updateScreenSize() { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 217dd38b3..a972d2f71 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -43,6 +43,7 @@ import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.view.ContextThemeWrapper; import androidx.appcompat.widget.PopupMenu; import androidx.core.graphics.Insets; +import androidx.core.math.MathUtils; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; @@ -580,16 +581,9 @@ public abstract class VideoPlayerUi extends PlayerUi currentSeekbarLeft - (binding.seekbarPreviewContainer.getWidth() / 2); // Fix the position so it's within the boundaries - final int checkedContainerLeft = - Math.max( - Math.min( - uncheckedContainerLeft, - // Max left - binding.playbackWindowRoot.getWidth() - - binding.seekbarPreviewContainer.getWidth() - ), - 0 // Min left - ); + final int checkedContainerLeft = MathUtils.clamp(uncheckedContainerLeft, + 0, binding.playbackWindowRoot.getWidth() + - binding.seekbarPreviewContainer.getWidth()); // See also: https://stackoverflow.com/a/23249734 final LinearLayout.LayoutParams params = 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 b222f6abf..5ac24503d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -13,6 +13,7 @@ import android.util.DisplayMetrics; import androidx.annotation.NonNull; import androidx.annotation.PluralsRes; import androidx.annotation.StringRes; +import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; import org.ocpsoft.prettytime.PrettyTime; @@ -247,8 +248,7 @@ public final class Localization { // is not the responsibility of this method handle long numbers // (it probably will fall in the "other" category, // or some language have some specific rule... then we have to change it) - final int safeCount = count > Integer.MAX_VALUE ? Integer.MAX_VALUE - : count < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) count; + final int safeCount = (int) MathUtils.clamp(count, Integer.MIN_VALUE, Integer.MAX_VALUE); return context.getResources().getQuantityString(pluralId, safeCount, formattedCount); } From 394eb92e714e4c2f7375aea4bf22afd5a84ba660 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 09:09:18 +0530 Subject: [PATCH 073/152] Use coerceIn(). --- .../gesture/PopupPlayerGestureListener.kt | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt index bda6ee8d1..01b15f30a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -233,22 +233,14 @@ class PopupPlayerGestureListener( isMoving = true - val diffX: Float = (movingEvent.rawX - initialEvent.rawX) - var posX: Float = (initialPopupX + diffX) - val diffY: Float = (movingEvent.rawY - initialEvent.rawY) - var posY: Float = (initialPopupY + diffY) - - if (posX > playerUi.screenWidth - playerUi.popupLayoutParams.width) { - posX = (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() - } else if (posX < 0) { - posX = 0f - } - - if (posY > playerUi.screenHeight - playerUi.popupLayoutParams.height) { - posY = (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() - } else if (posY < 0) { - posY = 0f - } + val diffX = (movingEvent.rawX - initialEvent.rawX) + val posX = (initialPopupX + diffX).coerceIn( + 0f, (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() + ) + val diffY = (movingEvent.rawY - initialEvent.rawY) + val posY = (initialPopupY + diffY).coerceIn( + 0f, (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() + ) playerUi.popupLayoutParams.x = posX.toInt() playerUi.popupLayoutParams.y = posY.toInt() From c5b970cca3e6ba2f8071e9483167697c591925e1 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 20 Jul 2022 14:50:23 +0200 Subject: [PATCH 074/152] Improve code style in List.of() --- .../preferencesearch/PreferenceSearchConfiguration.java | 4 ++-- .../java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java index 88b0b7735..1ded181c8 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java @@ -10,8 +10,8 @@ import java.util.stream.Stream; public class PreferenceSearchConfiguration { private PreferenceSearchFunction searcher = new PreferenceFuzzySearchFunction(); - private final List parserIgnoreElements = List.of(PreferenceCategory.class - .getSimpleName()); + private final List parserIgnoreElements = List.of( + PreferenceCategory.class.getSimpleName()); private final List parserContainerElements = List.of( PreferenceCategory.class.getSimpleName(), PreferenceScreen.class.getSimpleName()); diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java index 40dacc964..32f25ccbd 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java @@ -19,7 +19,8 @@ public final class TabsJsonHelper { private static final String JSON_TABS_ARRAY_KEY = "tabs"; private static final List FALLBACK_INITIAL_TABS_LIST = List.of( - Tab.Type.DEFAULT_KIOSK.getTab(), Tab.Type.SUBSCRIPTIONS.getTab(), + Tab.Type.DEFAULT_KIOSK.getTab(), + Tab.Type.SUBSCRIPTIONS.getTab(), Tab.Type.BOOKMARKS.getTab()); private TabsJsonHelper() { } From 373ee53143fd4de2cfb6cd5cbf2ce8f27f04ac99 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 20 Jul 2022 15:05:25 +0200 Subject: [PATCH 075/152] Improve code style --- .../player/playqueue/PlayQueueItemTouchCallback.java | 6 ++++-- .../seekbarpreview/SeekbarPreviewThumbnailHelper.java | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java index de1359bca..6e2792d4f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java @@ -19,8 +19,10 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC @Override public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView, - final int viewSize, final int viewSizeOutOfBounds, - final int totalSize, final long msSinceStartScroll) { + final int viewSize, + final int viewSizeOutOfBounds, + final int totalSize, + final long msSinceStartScroll) { final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); final int clampedAbsVelocity = MathUtils.clamp(Math.abs(standardSpeed), diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java index dba28a69a..43d89055c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java @@ -80,10 +80,12 @@ public final class SeekbarPreviewThumbnailHelper { // Resize original bitmap try { final int srcWidth = srcBitmap.getWidth() > 0 ? srcBitmap.getWidth() : 1; - // Use 1/4 of the width for the preview - final int newWidth = MathUtils.clamp(Math.round(baseViewWidthSupplier.getAsInt() / 4f), + final int newWidth = MathUtils.clamp( + // Use 1/4 of the width for the preview + Math.round(baseViewWidthSupplier.getAsInt() / 4f), + // But have a min width of 10dp DeviceUtils.dpToPx(10, context), - // Scaling more than that factor looks really pixelated -> max + // And scaling more than that factor looks really pixelated -> max Math.round(srcWidth * 2.5f)); final float scaleFactor = (float) newWidth / srcWidth; From b845645b80c07b77b97fc464fdc451c8eb289be2 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 06:21:51 +0530 Subject: [PATCH 076/152] Use IO extensions. Co-authored-by: Stypox --- .../newpipe/about/LicenseFragmentHelper.kt | 32 +++---------------- .../settings/ContentSettingsManager.kt | 9 ++---- .../schabi/newpipe/util/ReleaseVersionUtil.kt | 6 +--- 3 files changed, 8 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index c1dd38389..3acb50cb9 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -12,10 +12,7 @@ import org.schabi.newpipe.R import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.external_communication.ShareUtils -import java.io.BufferedReader import java.io.IOException -import java.io.InputStreamReader -import java.nio.charset.StandardCharsets object LicenseFragmentHelper { /** @@ -25,32 +22,13 @@ object LicenseFragmentHelper { * styled according to the context's theme */ private fun getFormattedLicense(context: Context, license: License): String { - val licenseContent = StringBuilder() - val webViewData: String try { - BufferedReader( - InputStreamReader( - context.assets.open(license.filename), - StandardCharsets.UTF_8 - ) - ).use { `in` -> - var str: String? - while (`in`.readLine().also { str = it } != null) { - licenseContent.append(str) - } - + return context.assets.open(license.filename).bufferedReader().use { it.readText() } // split the HTML file and insert the stylesheet into the HEAD of the file - webViewData = "$licenseContent".replace( - "", - "" - ) - } + .replace("", "") } catch (e: IOException) { - throw IllegalArgumentException( - "Could not get license file: " + license.filename, e - ) + throw IllegalArgumentException("Could not get license file: ${license.filename}", e) } - return webViewData } /** @@ -118,9 +96,7 @@ object LicenseFragmentHelper { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { formattedLicense -> - val webViewData = Base64.encodeToString( - formattedLicense.toByteArray(StandardCharsets.UTF_8), Base64.NO_PADDING - ) + val webViewData = Base64.encodeToString(formattedLicense.toByteArray(), Base64.NO_PADDING) val webView = WebView(context) webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt index 3ac275695..8adf6a649 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt @@ -5,9 +5,6 @@ import android.util.Log import org.schabi.newpipe.streams.io.SharpOutputStream import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.ZipHelper -import java.io.BufferedOutputStream -import java.io.FileInputStream -import java.io.FileOutputStream import java.io.IOException import java.io.ObjectInputStream import java.io.ObjectOutputStream @@ -25,12 +22,12 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) { @Throws(Exception::class) fun exportDatabase(preferences: SharedPreferences, file: StoredFileHelper) { file.create() - ZipOutputStream(BufferedOutputStream(SharpOutputStream(file.stream))) + ZipOutputStream(SharpOutputStream(file.stream).buffered()) .use { outZip -> ZipHelper.addFileToZip(outZip, fileLocator.db.path, "newpipe.db") try { - ObjectOutputStream(FileOutputStream(fileLocator.settings)).use { output -> + ObjectOutputStream(fileLocator.settings.outputStream()).use { output -> output.writeObject(preferences.all) output.flush() } @@ -74,7 +71,7 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) { try { val preferenceEditor = preferences.edit() - ObjectInputStream(FileInputStream(fileLocator.settings)).use { input -> + ObjectInputStream(fileLocator.settings.inputStream()).use { input -> preferenceEditor.clear() @Suppress("UNCHECKED_CAST") val entries = input.readObject() as Map diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt index 21a9059e2..0c66cc6d4 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt @@ -7,8 +7,6 @@ import org.schabi.newpipe.App import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.ErrorUtil.Companion.createNotification import org.schabi.newpipe.error.UserAction -import java.io.ByteArrayInputStream -import java.io.InputStream import java.security.MessageDigest import java.security.NoSuchAlgorithmException import java.security.cert.CertificateEncodingException @@ -47,10 +45,8 @@ object ReleaseVersionUtil { return "" } val x509cert = try { - val cert = signatures[0].toByteArray() - val input: InputStream = ByteArrayInputStream(cert) val cf = CertificateFactory.getInstance("X509") - cf.generateCertificate(input) as X509Certificate + cf.generateCertificate(signatures[0].toByteArray().inputStream()) as X509Certificate } catch (e: CertificateException) { showRequestError(app, e, "Certificate error") return "" From 8b400b48f7ad5ecfa7762854b55a06aa5c828dd0 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 20 Jul 2022 04:36:54 +0530 Subject: [PATCH 077/152] Refactor notifying method in PlayQueue. --- .../newpipe/player/playqueue/AbstractInfoPlayQueue.java | 4 ++-- .../org/schabi/newpipe/player/playqueue/PlayQueue.java | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) 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 df2747c3b..e51ee4720 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 @@ -82,7 +82,7 @@ abstract class AbstractInfoPlayQueue> public void onError(@NonNull final Throwable e) { Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); isComplete = true; - append(); // Notify change + notifyChange(); } }; } @@ -117,7 +117,7 @@ abstract class AbstractInfoPlayQueue> public void onError(@NonNull final Throwable e) { Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); isComplete = true; - append(); // Notify change + notifyChange(); } }; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index d54fed248..edf5a771c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -257,13 +257,10 @@ public abstract class PlayQueue implements Serializable { } /** - * Appends the given {@link PlayQueueItem}s to the current play queue. - * - * @see #append(List items) - * @param items {@link PlayQueueItem}s to append + * Notifies that a change has occurred. */ - public synchronized void append(@NonNull final PlayQueueItem... items) { - append(List.of(items)); + public synchronized void notifyChange() { + broadcast(new AppendEvent(0)); } /** From 6d1c61407d45e54b810fec7ce187afbbfc224b41 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 20 Jul 2022 07:14:40 +0530 Subject: [PATCH 078/152] Remove unnecessary method in ChannelFragment. --- .../newpipe/fragments/list/channel/ChannelFragment.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 e44048473..8ed9389c3 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 @@ -578,17 +578,13 @@ public class ChannelFragment extends BaseListInfoFragment streamItems = infoListAdapter.getItemsList().stream() .filter(StreamInfoItem.class::isInstance) .map(StreamInfoItem.class::cast) .collect(Collectors.toList()); return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(), - currentInfo.getNextPage(), streamItems, index); + currentInfo.getNextPage(), streamItems, 0); } /*////////////////////////////////////////////////////////////////////////// From f1dab11f1fecdca4c8f3fd3c393718e6528d81e3 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Thu, 21 Jul 2022 07:19:23 +0530 Subject: [PATCH 079/152] Remove deprecated method calls in FocusAwareCoordinator. --- .../newpipe/views/FocusAwareCoordinator.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java index 747aed025..d4fafc31a 100644 --- a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java @@ -27,6 +27,7 @@ import android.view.WindowInsets; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.WindowInsetsCompat; import org.schabi.newpipe.R; @@ -83,23 +84,22 @@ public final class FocusAwareCoordinator extends CoordinatorLayout { } } - if (consumed) { - insets.consumeSystemWindowInsets(); - } - return insets; + return consumed ? WindowInsetsCompat.CONSUMED.toWindowInsets() : insets; } /** - * Adjusts player's controls manually because fitsSystemWindows doesn't work when multiple + * Adjusts player's controls manually because onApplyWindowInsets doesn't work when multiple * receivers adjust its bounds. So when two listeners are present (like in profile page) * the player's controls will not receive insets. This method fixes it */ @Override - protected boolean fitSystemWindows(final Rect insets) { + public WindowInsets onApplyWindowInsets(final WindowInsets windowInsets) { + final var windowInsetsCompat = WindowInsetsCompat.toWindowInsetsCompat(windowInsets, this); + final var insets = windowInsetsCompat.getInsets(WindowInsetsCompat.Type.systemBars()); final ViewGroup controls = findViewById(R.id.playbackControlRoot); if (controls != null) { controls.setPadding(insets.left, insets.top, insets.right, insets.bottom); } - return super.fitSystemWindows(insets); + return super.onApplyWindowInsets(windowInsets); } } From 37275e8fe3b07b3ac7360a0e2e85a54188f78304 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 15:10:35 +0200 Subject: [PATCH 080/152] Fix wrong thumbnail used as placeholder for channel --- .../newpipe/info_list/holder/ChannelMiniInfoItemHolder.java | 2 +- .../org/schabi/newpipe/local/subscription/item/ChannelItem.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index aa4f4c9f0..89398a1e5 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -42,7 +42,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { itemTitleView.setText(item.getName()); itemAdditionalDetailView.setText(getDetailLine(item)); - PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); + PicassoHelper.loadAvatar(item.getThumbnailUrl()).into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnChannelSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt index a8c05838f..bee2e910a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt @@ -39,7 +39,7 @@ class ChannelItem( itemChannelDescriptionView.text = infoItem.description } - PicassoHelper.loadThumbnail(infoItem.thumbnailUrl).into(itemThumbnailView) + PicassoHelper.loadAvatar(infoItem.thumbnailUrl).into(itemThumbnailView) gesturesListener?.run { viewHolder.root.setOnClickListener { selected(infoItem) } From f4fe5fcb165d9545b95e050652d54b6e3cc9453d Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 16:04:41 +0200 Subject: [PATCH 081/152] Fix ListHelperTest failure caused by immutable list being used --- .../main/java/org/schabi/newpipe/util/ListHelper.java | 9 +++++---- .../java/org/schabi/newpipe/util/ListHelperTest.java | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 1b3cb1651..fbbe43513 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -230,14 +230,15 @@ public final class ListHelper { } /** - * Return the index of the default stream in the list, based on the parameters - * defaultResolution and defaultFormat. + * Return the index of the default stream in the list, that will be sorted in the process, based + * on the parameters defaultResolution and defaultFormat. * * @param defaultResolution the default resolution to look for * @param bestResolutionKey key of the best resolution * @param defaultFormat the default format to look for - * @param videoStreams list of the video streams to check - * @return index of the default resolution&format + * @param videoStreams a mutable list of the video streams to check (it will be sorted in + * place) + * @return index of the default resolution&format in the sorted videoStreams */ static int getDefaultResolutionIndex(final String defaultResolution, final String bestResolutionKey, diff --git a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java index 4f9b0b497..8a75b1b4e 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -140,7 +140,7 @@ public class ListHelperTest { @Test public void getDefaultResolutionTest() { - final List testList = List.of( + final List testList = new ArrayList<>(List.of( generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false), generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false), @@ -148,7 +148,7 @@ public class ListHelperTest { generateVideoStream("mpeg_4-240", MediaFormat.MPEG_4, "240p", false), generateVideoStream("webm-144", MediaFormat.WEBM, "144p", false), generateVideoStream("mpeg_4-360", MediaFormat.MPEG_4, "360p", false), - generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false)); + generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false))); VideoStream result = testList.get(ListHelper.getDefaultResolutionIndex( "720p", BEST_RESOLUTION_KEY, MediaFormat.MPEG_4, testList)); assertEquals("720p", result.getResolution()); From 51e72d1a0544ed47789cd102e46a92ec82eaea29 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 24 Jul 2022 14:57:23 +0200 Subject: [PATCH 082/152] Removed the "(beta)"-tag from services (#8637) --- app/src/main/java/org/schabi/newpipe/MainActivity.java | 9 ++++----- .../main/java/org/schabi/newpipe/util/ServiceHelper.java | 9 --------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 4a982874c..d4b2305c7 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -374,8 +374,7 @@ public class MainActivity extends AppCompatActivity { private void showServices() { for (final StreamingService s : NewPipe.getServices()) { - final String title = s.getServiceInfo().getName() - + (ServiceHelper.isBeta(s) ? " (beta)" : ""); + final String title = s.getServiceInfo().getName(); final MenuItem menuItem = drawerLayoutBinding.navigation.getMenu() .add(R.id.menu_services_group, s.getServiceId(), ORDER, title) @@ -383,7 +382,7 @@ public class MainActivity extends AppCompatActivity { // peertube specifics if (s.getServiceId() == 3) { - enhancePeertubeMenu(s, menuItem); + enhancePeertubeMenu(menuItem); } } drawerLayoutBinding.navigation.getMenu() @@ -391,9 +390,9 @@ public class MainActivity extends AppCompatActivity { .setChecked(true); } - private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) { + private void enhancePeertubeMenu(final MenuItem menuItem) { final PeertubeInstance currentInstance = PeertubeHelper.getCurrentInstance(); - menuItem.setTitle(currentInstance.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : "")); + menuItem.setTitle(currentInstance.getName()); final Spinner spinner = InstanceSpinnerLayoutBinding.inflate(LayoutInflater.from(this)) .getRoot(); final List instances = PeertubeHelper.getInstanceList(this); diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index b13ae4a97..acd019ba0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -169,15 +169,6 @@ public final class ServiceHelper { } } - public static boolean isBeta(final StreamingService s) { - switch (s.getServiceInfo().getName()) { - case "YouTube": - return false; - default: - return true; - } - } - public static void initService(final Context context, final int serviceId) { if (serviceId == ServiceList.PeerTube.getServiceId()) { final SharedPreferences sharedPreferences = PreferenceManager From 8f5d564f8430ec6d5e582a2d1e9267d84d4c0f4a Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 20 Jul 2022 21:01:17 -0400 Subject: [PATCH 083/152] Migrate NoNonsense-FilePicker to our updated fork --- app/build.gradle | 4 +--- .../org/schabi/newpipe/util/FilePickerActivityHelper.java | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..dfa512d22 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -188,6 +188,7 @@ dependencies { // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' implementation 'com.github.TeamNewPipe:NewPipeExtractor:5219a705bab539cf8c6624d0cec216e76e85f0b1' + implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" @@ -257,9 +258,6 @@ dependencies { implementation "io.noties.markwon:core:${markwonVersion}" implementation "io.noties.markwon:linkify:${markwonVersion}" - // File picker - implementation "com.nononsenseapps:filepicker:4.2.1" - // Crash reporting implementation "ch.acra:acra-core:5.9.3" diff --git a/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java b/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java index 20d8ce30c..d7fb39651 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java @@ -76,7 +76,7 @@ public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.File public static class CustomFilePickerFragment extends FilePickerFragment { @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, + public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { return super.onCreateView(inflater, container, savedInstanceState); } @@ -138,7 +138,7 @@ public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.File } @Override - public void onLoadFinished(final Loader> loader, + public void onLoadFinished(@NonNull final Loader> loader, final SortedList data) { super.onLoadFinished(loader, data); layoutManager.scrollToPosition(0); From baabba1deade7e798b7852871039ed148ce6b1f6 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 20 Jul 2022 21:32:27 -0400 Subject: [PATCH 084/152] Disable Jetifier --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 76b51ef0b..032d70cee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -android.enableJetifier=true +android.enableJetifier=false android.useAndroidX=true org.gradle.jvmargs=-Xmx2048M systemProp.file.encoding=utf-8 From 229422bfa95c42f436d4bd21a947c0e5642b64ea Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 24 Jul 2022 14:11:31 -0400 Subject: [PATCH 085/152] Update ExoPlayer to 2.18.1 --- app/build.gradle | 2 +- .../schabi/newpipe/player/datasource/YoutubeHttpDataSource.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..64378378e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -102,7 +102,7 @@ ext { androidxWorkVersion = '2.7.1' icepickVersion = '3.2.0' - exoPlayerVersion = '2.18.0' + exoPlayerVersion = '2.18.1' googleAutoServiceVersion = '1.0.1' groupieVersion = '2.10.1' markwonVersion = '4.6.2' diff --git a/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java b/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java index 2eecddf36..cf1f03b45 100644 --- a/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java @@ -1,5 +1,5 @@ /* - * Based on ExoPlayer's DefaultHttpDataSource, version 2.18.0. + * Based on ExoPlayer's DefaultHttpDataSource, version 2.18.1. * * Original source code copyright (C) 2016 The Android Open Source Project, licensed under the * Apache License, Version 2.0. From d7a654fc27d72334a01980e1b527007053734b48 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 24 Jul 2022 15:35:33 -0400 Subject: [PATCH 086/152] Update AndroidX Fragment to 1.4.1 --- app/build.gradle | 2 +- .../schabi/newpipe/player/helper/PlaybackParameterDialog.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..7aa0fec28 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -202,7 +202,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.documentfile:documentfile:1.0.1' - implementation 'androidx.fragment:fragment-ktx:1.3.6' + implementation 'androidx.fragment:fragment-ktx:1.4.1' implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index a9b507bd4..796208a04 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -11,7 +11,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.widget.CheckBox; import android.widget.SeekBar; @@ -149,7 +148,7 @@ public class PlaybackParameterDialog extends DialogFragment { assureCorrectAppLanguage(getContext()); Icepick.restoreInstanceState(this, savedInstanceState); - binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext())); + binding = DialogPlaybackParameterBinding.inflate(getLayoutInflater()); initUI(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) From d66997c2ed3afa66b5fe7432dfad6dfc3ab92383 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 24 Jul 2022 16:51:26 -0400 Subject: [PATCH 087/152] Update Google Material to 1.6.1 --- app/build.gradle | 2 +- .../newpipe/player/gesture/CustomBottomSheetBehavior.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..b4f17e41f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -218,7 +218,7 @@ dependencies { implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" - implementation 'com.google.android.material:material:1.5.0' + implementation 'com.google.android.material:material:1.6.1' /** Third-party libraries **/ // Instance state boilerplate elimination diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java index 41046784f..0970dbeb6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java @@ -8,6 +8,7 @@ import android.view.View; import android.widget.FrameLayout; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; import com.google.android.material.bottomsheet.BottomSheetBehavior; @@ -18,7 +19,8 @@ import java.util.List; public class CustomBottomSheetBehavior extends BottomSheetBehavior { - public CustomBottomSheetBehavior(final Context context, final AttributeSet attrs) { + public CustomBottomSheetBehavior(@NonNull final Context context, + @Nullable final AttributeSet attrs) { super(context, attrs); } @@ -32,7 +34,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior @Override public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent, @NonNull final FrameLayout child, - final MotionEvent event) { + @NonNull final MotionEvent event) { // Drop following when action ends if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) { From 81fb44c45c8acf6d235e8afff31f348bccf8e2b7 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Thu, 21 Jul 2022 06:56:51 +0530 Subject: [PATCH 088/152] Remove uses of setBottomSheetCallback(). --- .../fragments/detail/VideoDetailFragment.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) 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 f4838482b..4ad270c50 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 @@ -208,6 +208,8 @@ public final class VideoDetailFragment private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); + private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback; + /*////////////////////////////////////////////////////////////////////////// // Service management //////////////////////////////////////////////////////////////////////////*/ @@ -386,7 +388,7 @@ public final class VideoDetailFragment disposables.clear(); positionSubscriber = null; currentWorker = null; - bottomSheetBehavior.setBottomSheetCallback(null); + bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallback); if (activity.isFinishing()) { playQueue = null; @@ -2289,7 +2291,7 @@ public final class VideoDetailFragment } } - bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull final View bottomSheet, final int newState) { bottomSheetState = newState; @@ -2343,12 +2345,14 @@ public final class VideoDetailFragment } if (isPlayerAvailable()) { player.UIs().get(MainPlayerUi.class).ifPresent(ui -> { - if (ui.isControlsVisible()) { - ui.hideControls(0, 0); - } + if (ui.isControlsVisible()) { + ui.hideControls(0, 0); + } }); } break; + case BottomSheetBehavior.STATE_HALF_EXPANDED: + break; } } @@ -2356,7 +2360,9 @@ public final class VideoDetailFragment public void onSlide(@NonNull final View bottomSheet, final float slideOffset) { setOverlayLook(binding.appBarLayout, behavior, slideOffset); } - }); + }; + + bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback); // User opened a new page and the player will hide itself activity.getSupportFragmentManager().addOnBackStackChangedListener(() -> { From 81c4b822e0e1af82e1ec031457b57e80611ae003 Mon Sep 17 00:00:00 2001 From: Mohammed Anas Date: Tue, 26 Jul 2022 23:29:43 +0300 Subject: [PATCH 089/152] Add "needs triage" label to issue templates (#8643) This label would make it easier for issue triagers to know what they haven't triaged yet. --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/question.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a0a9f9ef5..3abb1fbb1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug report description: Create a bug report to help us improve -labels: [bug] +labels: [bug, needs triage] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 83d6f0299..9fc3c1632 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,6 @@ name: Feature request description: Suggest an idea for this project -labels: [enhancement] +labels: [enhancement, needs triage] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 4c42ab26a..8cf22d8af 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -1,6 +1,6 @@ name: Question description: Ask about anything NewPipe-related -labels: [question] +labels: [question, needs triage] body: - type: markdown attributes: From af9c2bd59db4da18b707c2ae178d5852c168a5a5 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 27 Jul 2022 07:54:49 +0530 Subject: [PATCH 090/152] Use stackTraceToString(). --- .../java/org/schabi/newpipe/error/ErrorInfo.kt | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index f9f9f003a..d87fa3330 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -14,8 +14,6 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException import org.schabi.newpipe.ktx.isNetworkRelated import org.schabi.newpipe.util.ServiceHelper -import java.io.PrintWriter -import java.io.StringWriter @Parcelize class ErrorInfo( @@ -80,19 +78,10 @@ class ErrorInfo( companion object { const val SERVICE_NONE = "none" - private fun getStackTrace(throwable: Throwable): String { - StringWriter().use { stringWriter -> - PrintWriter(stringWriter, true).use { printWriter -> - throwable.printStackTrace(printWriter) - return stringWriter.buffer.toString() - } - } - } + fun throwableToStringList(throwable: Throwable) = arrayOf(throwable.stackTraceToString()) - fun throwableToStringList(throwable: Throwable) = arrayOf(getStackTrace(throwable)) - - fun throwableListToStringList(throwable: List) = - Array(throwable.size) { i -> getStackTrace(throwable[i]) } + fun throwableListToStringList(throwableList: List) = + throwableList.map { it.stackTraceToString() }.toTypedArray() private fun getInfoServiceName(info: Info?) = if (info == null) SERVICE_NONE else ServiceHelper.getNameOfServiceById(info.serviceId) From 69942003f7dcb0adc0564dd1bc7e47fcd9a11965 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 29 Jul 2022 09:21:02 +0530 Subject: [PATCH 091/152] Sort tags case-insensitively. --- .../schabi/newpipe/fragments/detail/DescriptionFragment.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index bbe52f2e9..bf7f8fa5d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -202,10 +202,9 @@ public class DescriptionFragment extends BaseFragment { private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { if (streamInfo.getTags() != null && !streamInfo.getTags().isEmpty()) { - final ItemMetadataTagsBinding itemBinding = - ItemMetadataTagsBinding.inflate(inflater, layout, false); + final var itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - streamInfo.getTags().stream().sorted().forEach(tag -> { + streamInfo.getTags().stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(tag -> { final Chip chip = (Chip) inflater.inflate(R.layout.chip, itemBinding.metadataTagsChips, false); chip.setText(tag); From 630558ed4f55e77bed1509c05d0b4a1ae22c3d45 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 30 Jul 2022 01:39:02 +0530 Subject: [PATCH 092/152] Use nested functions. --- .../main/java/org/schabi/newpipe/ktx/View.kt | 49 ++++++------------- 1 file changed, 14 insertions(+), 35 deletions(-) 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 56c9d825a..5ab8dbdc2 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -90,64 +90,43 @@ fun View.animate( */ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) { if (MainActivity.DEBUG) { - Log.d( - TAG, - "animateBackgroundColor() called with: " + - "view = [" + this + "], duration = [" + duration + "], " + - "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]" + Log.d(TAG, "animateBackgroundColor() called with: view = [$this], duration = [$duration], " + + "colorStart = [$colorStart], colorEnd = [$colorEnd]" ) } - val empty = arrayOf(IntArray(0)) val viewPropertyAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorStart, colorEnd) viewPropertyAnimator.interpolator = FastOutSlowInInterpolator() viewPropertyAnimator.duration = duration - viewPropertyAnimator.addUpdateListener { animation: ValueAnimator -> - ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(animation.animatedValue as Int))) + + fun listenerAction(color: Int) { + ViewCompat.setBackgroundTintList(this, ColorStateList.valueOf(color)) } - viewPropertyAnimator.addListener( - onCancel = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) }, - onEnd = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) } - ) + viewPropertyAnimator.addUpdateListener { listenerAction(it.animatedValue as Int) } + viewPropertyAnimator.addListener(onCancel = { listenerAction(colorEnd) }, onEnd = { listenerAction(colorEnd) }) viewPropertyAnimator.start() } fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator { if (MainActivity.DEBUG) { - Log.d( - TAG, - "animateHeight: duration = [" + duration + "], " + - "from " + height + " to → " + targetHeight + " in: " + this - ) + Log.d(TAG, "animateHeight: duration = [$duration], from $height to → $targetHeight in: $this") } val animator = ValueAnimator.ofFloat(height.toFloat(), targetHeight.toFloat()) animator.interpolator = FastOutSlowInInterpolator() animator.duration = duration - animator.addUpdateListener { animation: ValueAnimator -> - val value = animation.animatedValue as Float - layoutParams.height = value.toInt() + + fun listenerAction(value: Int) { + layoutParams.height = value requestLayout() } - animator.addListener( - onCancel = { - layoutParams.height = targetHeight - requestLayout() - }, - onEnd = { - layoutParams.height = targetHeight - requestLayout() - } - ) + animator.addUpdateListener { listenerAction((it.animatedValue as Float).toInt()) } + animator.addListener(onCancel = { listenerAction(targetHeight) }, onEnd = { listenerAction(targetHeight) }) animator.start() return animator } fun View.animateRotation(duration: Long, targetRotation: Int) { if (MainActivity.DEBUG) { - Log.d( - TAG, - "animateRotation: duration = [" + duration + "], " + - "from " + rotation + " to → " + targetRotation + " in: " + this - ) + Log.d(TAG, "animateRotation: duration = [$duration], from $rotation to → $targetRotation in: $this") } animate().setListener(null).cancel() animate() From 4d7a6fb6deb36125a4f74490ebe11e48b92f0a58 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 08:53:29 +0530 Subject: [PATCH 093/152] Use WindowMetrics API in VideoDetailFragment and PopupPlayerUi. --- .../fragments/detail/VideoDetailFragment.java | 11 ++++------ .../newpipe/player/ui/PopupPlayerUi.java | 20 ++++++++++++++----- .../org/schabi/newpipe/util/DeviceUtils.java | 17 ++++++++++++++++ 3 files changed, 36 insertions(+), 12 deletions(-) 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 f4838482b..807922166 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 @@ -21,7 +21,6 @@ import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.database.ContentObserver; import android.graphics.Color; -import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -1051,15 +1050,13 @@ public final class VideoDetailFragment // call `post()` to be sure `viewPager.getHitRect()` // is up to date and not being currently recomputed binding.tabLayout.post(() -> { - if (getContext() != null) { + final var activity = getActivity(); + if (activity != null) { final Rect pagerHitRect = new Rect(); binding.viewPager.getHitRect(pagerHitRect); - final Point displaySize = new Point(); - Objects.requireNonNull(ContextCompat.getSystemService(getContext(), - WindowManager.class)).getDefaultDisplay().getSize(displaySize); - - final int viewPagerVisibleHeight = displaySize.y - pagerHitRect.top; + final int height = DeviceUtils.getWindowHeight(activity.getWindowManager()); + final int viewPagerVisibleHeight = height - pagerHitRect.top; // see TabLayout.DEFAULT_HEIGHT, which is equal to 48dp final float tabLayoutHeight = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 48, getResources().getDisplayMetrics()); 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 74adf281c..aa36a6a5a 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 @@ -21,6 +21,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.animation.AnticipateInterpolator; import android.widget.LinearLayout; @@ -255,11 +256,20 @@ public final class PopupPlayerUi extends VideoPlayerUi { } public void updateScreenSize() { - final DisplayMetrics metrics = new DisplayMetrics(); - windowManager.getDefaultDisplay().getMetrics(metrics); - - screenWidth = metrics.widthPixels; - screenHeight = metrics.heightPixels; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final var windowMetrics = windowManager.getCurrentWindowMetrics(); + final var bounds = windowMetrics.getBounds(); + final var windowInsets = windowMetrics.getWindowInsets(); + final var insets = windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars() | WindowInsets.Type.displayCutout()); + screenWidth = bounds.width() - (insets.left + insets.right); + screenHeight = bounds.height() - (insets.top + insets.bottom); + } else { + final DisplayMetrics metrics = new DisplayMetrics(); + windowManager.getDefaultDisplay().getMetrics(metrics); + screenWidth = metrics.widthPixels; + screenHeight = metrics.heightPixels; + } if (DEBUG) { Log.d(TAG, "updateScreenSize() called: screenWidth = [" + screenWidth + "], screenHeight = [" + screenHeight + "]"); diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index 7f4b33f44..3c20dc04b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -4,11 +4,14 @@ import android.app.UiModeManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Point; import android.os.BatteryManager; import android.os.Build; import android.provider.Settings; import android.util.TypedValue; import android.view.KeyEvent; +import android.view.WindowInsets; +import android.view.WindowManager; import androidx.annotation.Dimension; import androidx.annotation.NonNull; @@ -151,4 +154,18 @@ public final class DeviceUtils { Settings.Global.ANIMATOR_DURATION_SCALE, 1F) != 0F; } + + public static int getWindowHeight(@NonNull final WindowManager windowManager) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final var windowMetrics = windowManager.getCurrentWindowMetrics(); + final var windowInsets = windowMetrics.getWindowInsets(); + final var insets = windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars() | WindowInsets.Type.displayCutout()); + return windowMetrics.getBounds().height() - (insets.top + insets.bottom); + } else { + final Point point = new Point(); + windowManager.getDefaultDisplay().getSize(point); + return point.y; + } + } } From 5c68c8ece8ab36734db7cabeb722fa05ca7f2739 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 13:52:56 +0530 Subject: [PATCH 094/152] Update Lifecycle to 2.5.1. --- app/build.gradle | 6 +++--- .../java/org/schabi/newpipe/local/feed/FeedViewModel.kt | 2 +- .../local/subscription/dialog/FeedGroupDialogViewModel.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..c4a9aa1ee 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -97,7 +97,7 @@ android { ext { checkstyleVersion = '10.3.1' - androidxLifecycleVersion = '2.3.1' + androidxLifecycleVersion = '2.5.1' androidxRoomVersion = '2.4.2' androidxWorkVersion = '2.7.1' @@ -203,8 +203,8 @@ dependencies { implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.fragment:fragment-ktx:1.3.6' - implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" - implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' implementation 'androidx.media:media:1.6.0' implementation 'androidx.preference:preference:1.2.0' diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index 7f5ef4301..fd8c5f5de 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -146,7 +146,7 @@ class FeedViewModel( private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { + override fun create(modelClass: Class): T { return FeedViewModel( context.applicationContext, groupId, diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt index 54ba1c6dc..dfdb2b47a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt @@ -122,7 +122,7 @@ class FeedGroupDialogViewModel( private val initialShowOnlyUngrouped: Boolean = false ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { + override fun create(modelClass: Class): T { return FeedGroupDialogViewModel( context.applicationContext, groupId, initialQuery, initialShowOnlyUngrouped From 404c13d4c1d5bc887a45733018a87cee26056fc2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 26 Jul 2022 17:31:14 +0200 Subject: [PATCH 095/152] Improve FeedViewModel factory --- .../schabi/newpipe/local/feed/FeedFragment.kt | 4 +-- .../newpipe/local/feed/FeedViewModel.kt | 28 ++++++++----------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index f0ebabd85..899163050 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -135,8 +135,8 @@ class FeedFragment : BaseStateFragment() { _feedBinding = FragmentFeedBinding.bind(rootView) super.onViewCreated(rootView, savedInstanceState) - val factory = FeedViewModel.Factory(requireContext(), groupId) - viewModel = ViewModelProvider(this, factory).get(FeedViewModel::class.java) + val factory = FeedViewModel.getFactory(requireContext(), groupId) + viewModel = ViewModelProvider(this, factory)[FeedViewModel::class.java] showPlayedItems = viewModel.getShowPlayedItemsFromPreferences() showFutureItems = viewModel.getShowFutureItemsFromPreferences() viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(::handleResult) } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index fd8c5f5de..896d815bd 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -5,7 +5,8 @@ import androidx.core.content.edit import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import androidx.preference.PreferenceManager import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable @@ -139,21 +140,16 @@ class FeedViewModel( private fun getShowFutureItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(context.getString(R.string.feed_show_future_items_key), true) - } - - class Factory( - private val context: Context, - private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID - ) : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return FeedViewModel( - context.applicationContext, - groupId, - // Read initial value from preferences - getShowPlayedItemsFromPreferences(context.applicationContext), - getShowFutureItemsFromPreferences(context.applicationContext) - ) as T + fun getFactory(context: Context, groupId: Long) = viewModelFactory { + initializer { + FeedViewModel( + context.applicationContext, + groupId, + // Read initial value from preferences + getShowPlayedItemsFromPreferences(context.applicationContext), + getShowFutureItemsFromPreferences(context.applicationContext) + ) + } } } } From 311d392386696a4185405d80f8f271f17005da7f Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 08:35:50 +0530 Subject: [PATCH 096/152] Use Application instead of Context in FeedViewModel. --- .../newpipe/local/feed/FeedViewModel.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index 896d815bd..76d5e9d63 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -1,5 +1,6 @@ package org.schabi.newpipe.local.feed +import android.app.Application import android.content.Context import androidx.core.content.edit import androidx.lifecycle.LiveData @@ -13,6 +14,7 @@ import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.functions.Function5 import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.schedulers.Schedulers +import org.schabi.newpipe.App import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.stream.StreamWithState @@ -27,12 +29,12 @@ import java.time.OffsetDateTime import java.util.concurrent.TimeUnit class FeedViewModel( - private val applicationContext: Context, + private val application: Application, groupId: Long = FeedGroupEntity.GROUP_ALL_ID, initialShowPlayedItems: Boolean = true, initialShowFutureItems: Boolean = true ) : ViewModel() { - private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext) + private val feedDatabaseManager = FeedDatabaseManager(application) private val toggleShowPlayedItems = BehaviorProcessor.create() private val toggleShowPlayedItemsFlowable = toggleShowPlayedItems @@ -114,24 +116,24 @@ class FeedViewModel( } fun saveShowPlayedItemsToPreferences(showPlayedItems: Boolean) = - PreferenceManager.getDefaultSharedPreferences(applicationContext).edit { - this.putBoolean(applicationContext.getString(R.string.feed_show_played_items_key), showPlayedItems) + PreferenceManager.getDefaultSharedPreferences(application).edit { + this.putBoolean(application.getString(R.string.feed_show_played_items_key), showPlayedItems) this.apply() } - fun getShowPlayedItemsFromPreferences() = getShowPlayedItemsFromPreferences(applicationContext) + fun getShowPlayedItemsFromPreferences() = getShowPlayedItemsFromPreferences(application) fun toggleFutureItems(showFutureItems: Boolean) { toggleShowFutureItems.onNext(showFutureItems) } fun saveShowFutureItemsToPreferences(showFutureItems: Boolean) = - PreferenceManager.getDefaultSharedPreferences(applicationContext).edit { - this.putBoolean(applicationContext.getString(R.string.feed_show_future_items_key), showFutureItems) + PreferenceManager.getDefaultSharedPreferences(application).edit { + this.putBoolean(application.getString(R.string.feed_show_future_items_key), showFutureItems) this.apply() } - fun getShowFutureItemsFromPreferences() = getShowFutureItemsFromPreferences(applicationContext) + fun getShowFutureItemsFromPreferences() = getShowFutureItemsFromPreferences(application) companion object { private fun getShowPlayedItemsFromPreferences(context: Context) = @@ -143,7 +145,7 @@ class FeedViewModel( fun getFactory(context: Context, groupId: Long) = viewModelFactory { initializer { FeedViewModel( - context.applicationContext, + App.getApp(), groupId, // Read initial value from preferences getShowPlayedItemsFromPreferences(context.applicationContext), From 582032f37299cdfdf6854d00718e96646bb07d2b Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 31 Jul 2022 00:14:23 -0400 Subject: [PATCH 097/152] Update AndroidX Room to 2.4.3 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..c77335ad9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -98,7 +98,7 @@ ext { checkstyleVersion = '10.3.1' androidxLifecycleVersion = '2.3.1' - androidxRoomVersion = '2.4.2' + androidxRoomVersion = '2.4.3' androidxWorkVersion = '2.7.1' icepickVersion = '3.2.0' From 47f58040d1fc843ba8f4b7abcc1d40b1f9852cc1 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 13:38:24 +0530 Subject: [PATCH 098/152] Make OnClickGesture an interface. --- .../fragments/list/BaseListFragment.java | 58 ++++++------------- .../local/bookmark/BookmarkFragment.java | 2 +- .../local/dialog/PlaylistAppendDialog.java | 22 ++----- .../history/StatisticsPlaylistFragment.java | 2 +- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../subscription/SubscriptionFragment.kt | 4 +- .../schabi/newpipe/util/OnClickGesture.java | 9 ++- 7 files changed, 32 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 27e5a8571..9e7cb757c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -23,14 +23,11 @@ import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.channel.ChannelInfoItem; -import org.schabi.newpipe.extractor.comments.CommentsInfoItem; -import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; -import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.info_list.InfoListAdapter; +import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.StateSaver; @@ -264,45 +261,28 @@ public abstract class BaseListFragment extends BaseStateFragment } }); - infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<>() { - @Override - public void selected(final ChannelInfoItem selectedItem) { - try { - onItemSelected(selectedItem); - NavigationHelper.openChannelFragment(getFM(), - selectedItem.getServiceId(), - selectedItem.getUrl(), - selectedItem.getName()); - } catch (final Exception e) { - ErrorUtil.showUiErrorSnackbar( - BaseListFragment.this, "Opening channel fragment", e); - } - } - }); - - infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<>() { - @Override - public void selected(final PlaylistInfoItem selectedItem) { - try { - onItemSelected(selectedItem); - NavigationHelper.openPlaylistFragment(getFM(), - selectedItem.getServiceId(), - selectedItem.getUrl(), - selectedItem.getName()); - } catch (final Exception e) { - ErrorUtil.showUiErrorSnackbar(BaseListFragment.this, - "Opening playlist fragment", e); - } - } - }); - - infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<>() { - @Override - public void selected(final CommentsInfoItem selectedItem) { + infoListAdapter.setOnChannelSelectedListener(selectedItem -> { + try { onItemSelected(selectedItem); + NavigationHelper.openChannelFragment(getFM(), selectedItem.getServiceId(), + selectedItem.getUrl(), selectedItem.getName()); + } catch (final Exception e) { + ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e); } }); + infoListAdapter.setOnPlaylistSelectedListener(selectedItem -> { + try { + onItemSelected(selectedItem); + NavigationHelper.openPlaylistFragment(getFM(), selectedItem.getServiceId(), + selectedItem.getUrl(), selectedItem.getName()); + } catch (final Exception e) { + ErrorUtil.showUiErrorSnackbar(this, "Opening playlist fragment", e); + } + }); + + infoListAdapter.setOnCommentsSelectedListener(this::onItemSelected); + // Ensure that there is always a scroll listener (e.g. when rotating the device) useNormalItemListScrollListener(); } diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index ac11d007f..be7414542 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -98,7 +98,7 @@ public final class BookmarkFragment extends BaseLocalListFragment() { + itemListAdapter.setSelectedListener(new OnClickGesture<>() { @Override public void selected(final LocalItem selectedItem) { final FragmentManager fragmentManager = getFM(); diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java index a97eb0c18..3d5d16c39 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java @@ -13,12 +13,10 @@ import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.local.LocalItemListAdapter; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; -import org.schabi.newpipe.util.OnClickGesture; import java.util.List; @@ -63,18 +61,10 @@ public final class PlaylistAppendDialog extends PlaylistDialog { new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext())); playlistAdapter = new LocalItemListAdapter(getActivity()); - playlistAdapter.setSelectedListener(new OnClickGesture() { - @Override - public void selected(final LocalItem selectedItem) { - if (!(selectedItem instanceof PlaylistMetadataEntry) - || getStreamEntities() == null) { - return; - } - onPlaylistSelected( - playlistManager, - (PlaylistMetadataEntry) selectedItem, - getStreamEntities() - ); + playlistAdapter.setSelectedListener(selectedItem -> { + final List entities = getStreamEntities(); + if (selectedItem instanceof PlaylistMetadataEntry && entities != null) { + onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem, entities); } }); @@ -138,10 +128,6 @@ public final class PlaylistAppendDialog extends PlaylistDialog { private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager, @NonNull final PlaylistMetadataEntry playlist, @NonNull final List streams) { - if (getStreamEntities() == null) { - return; - } - final Toast successToast = Toast.makeText(getContext(), R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 01df34292..a20a80ae9 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -135,7 +135,7 @@ public class StatisticsPlaylistFragment protected void initListeners() { super.initListeners(); - itemListAdapter.setSelectedListener(new OnClickGesture() { + itemListAdapter.setSelectedListener(new OnClickGesture<>() { @Override public void selected(final LocalItem selectedItem) { if (selectedItem instanceof StreamStatisticsEntry) { 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 fa789d97d..11d54f1ef 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 @@ -167,7 +167,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment() { + itemListAdapter.setSelectedListener(new OnClickGesture<>() { @Override public void selected(final LocalItem selectedItem) { if (selectedItem instanceof PlaylistStreamEntry) { diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 4295424e6..20f8a01c1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -346,7 +346,7 @@ class SubscriptionFragment : BaseStateFragment() { override fun doInitialLoadLogic() = Unit override fun startLoading(forceLoad: Boolean) = Unit - private val listenerFeedGroups = object : OnClickGesture>() { + private val listenerFeedGroups = object : OnClickGesture> { override fun selected(selectedItem: Item<*>?) { when (selectedItem) { is FeedGroupCardItem -> NavigationHelper.openFeedFragment(fm, selectedItem.groupId, selectedItem.name) @@ -361,7 +361,7 @@ class SubscriptionFragment : BaseStateFragment() { } } - private val listenerChannelItem = object : OnClickGesture() { + private val listenerChannelItem = object : OnClickGesture { override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment( fm, selectedItem.serviceId, selectedItem.url, selectedItem.name diff --git a/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java b/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java index 5f44cab8b..ae8d86af1 100644 --- a/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java +++ b/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java @@ -2,15 +2,14 @@ package org.schabi.newpipe.util; import androidx.recyclerview.widget.RecyclerView; -public abstract class OnClickGesture { +public interface OnClickGesture { + void selected(T selectedItem); - public abstract void selected(T selectedItem); - - public void held(final T selectedItem) { + default void held(final T selectedItem) { // Optional gesture } - public void drag(final T selectedItem, final RecyclerView.ViewHolder viewHolder) { + default void drag(final T selectedItem, final RecyclerView.ViewHolder viewHolder) { // Optional gesture } } From 8a896114c110ba4fb6441465fbb88317e597666f Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 1 Aug 2022 08:25:24 +0530 Subject: [PATCH 099/152] Apply code review change. --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 4ad270c50..9d13cb931 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 @@ -192,6 +192,7 @@ public final class VideoDetailFragment private Disposable positionSubscriber = null; private BottomSheetBehavior bottomSheetBehavior; + private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback; private BroadcastReceiver broadcastReceiver; /*////////////////////////////////////////////////////////////////////////// @@ -208,8 +209,6 @@ public final class VideoDetailFragment private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); - private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback; - /*////////////////////////////////////////////////////////////////////////// // Service management //////////////////////////////////////////////////////////////////////////*/ From 947242d9134d20696c72b98b39cd098b30fc1029 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 16 Jul 2022 06:33:53 +0530 Subject: [PATCH 100/152] Update AppCompat to 1.4.2. --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c4a9aa1ee..d672940a9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" /** AndroidX **/ - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.core:core-ktx:1.8.0' From 013522c3766816db2bb23c144e7fdd36b1562aaa Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 05:20:06 +0530 Subject: [PATCH 101/152] Convert LicenseFragmentHelper methods to top-level declarations. --- .../schabi/newpipe/about/LicenseFragment.kt | 1 - .../newpipe/about/LicenseFragmentHelper.kt | 151 +++++++++--------- 2 files changed, 75 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt index c816d78be..f19ecd74a 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt @@ -8,7 +8,6 @@ import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import io.reactivex.rxjava3.disposables.CompositeDisposable import org.schabi.newpipe.R -import org.schabi.newpipe.about.LicenseFragmentHelper.showLicense import org.schabi.newpipe.databinding.FragmentLicensesBinding import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index 3acb50cb9..34dfe8fb4 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -14,30 +14,29 @@ import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.external_communication.ShareUtils import java.io.IOException -object LicenseFragmentHelper { - /** - * @param context the context to use - * @param license the license - * @return String which contains a HTML formatted license page - * styled according to the context's theme - */ - private fun getFormattedLicense(context: Context, license: License): String { - try { - return context.assets.open(license.filename).bufferedReader().use { it.readText() } - // split the HTML file and insert the stylesheet into the HEAD of the file - .replace("", "") - } catch (e: IOException) { - throw IllegalArgumentException("Could not get license file: ${license.filename}", e) - } +/** + * @param context the context to use + * @param license the license + * @return String which contains a HTML formatted license page + * styled according to the context's theme + */ +private fun getFormattedLicense(context: Context, license: License): String { + try { + return context.assets.open(license.filename).bufferedReader().use { it.readText() } + // split the HTML file and insert the stylesheet into the HEAD of the file + .replace("", "") + } catch (e: IOException) { + throw IllegalArgumentException("Could not get license file: ${license.filename}", e) } +} - /** - * @param context the Android context - * @return String which is a CSS stylesheet according to the context's theme - */ - private fun getLicenseStylesheet(context: Context): String { - val isLightTheme = ThemeHelper.isLightThemeSelected(context) - return ( +/** + * @param context the Android context + * @return String which is a CSS stylesheet according to the context's theme + */ +private fun getLicenseStylesheet(context: Context): String { + val isLightTheme = ThemeHelper.isLightThemeSelected(context) + return ( "body{padding:12px 15px;margin:0;" + "background:#" + getHexRGBColor( context, if (isLightTheme) R.color.light_license_background_color @@ -52,62 +51,62 @@ object LicenseFragmentHelper { else R.color.dark_youtube_primary_color ) + "}" + "pre{white-space:pre-wrap}" ) - } +} - /** - * Cast R.color to a hexadecimal color value. - * - * @param context the context to use - * @param color the color number from R.color - * @return a six characters long String with hexadecimal RGB values - */ - private fun getHexRGBColor(context: Context, color: Int): String { - return context.getString(color).substring(3) - } +/** + * Cast R.color to a hexadecimal color value. + * + * @param context the context to use + * @param color the color number from R.color + * @return a six characters long String with hexadecimal RGB values + */ +private fun getHexRGBColor(context: Context, color: Int): String { + return context.getString(color).substring(3) +} - fun showLicense(context: Context?, license: License): Disposable { - return showLicense(context, license) { alertDialog -> - alertDialog.setPositiveButton(R.string.ok) { dialog, _ -> - dialog.dismiss() - } - } - } - - fun showLicense(context: Context?, component: SoftwareComponent): Disposable { - return showLicense(context, component.license) { alertDialog -> - alertDialog.setPositiveButton(R.string.dismiss) { dialog, _ -> - dialog.dismiss() - } - alertDialog.setNeutralButton(R.string.open_website_license) { _, _ -> - ShareUtils.openUrlInBrowser(context!!, component.link) - } - } - } - - private fun showLicense( - context: Context?, - license: License, - block: (AlertDialog.Builder) -> Unit - ): Disposable { - return if (context == null) { - Disposable.empty() - } else { - Observable.fromCallable { getFormattedLicense(context, license) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { formattedLicense -> - val webViewData = Base64.encodeToString(formattedLicense.toByteArray(), Base64.NO_PADDING) - val webView = WebView(context) - webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") - - AlertDialog.Builder(context).apply { - setTitle(license.name) - setView(webView) - Localization.assureCorrectAppLanguage(context) - block(this) - show() - } - } +fun showLicense(context: Context?, license: License): Disposable { + return showLicense(context, license) { alertDialog -> + alertDialog.setPositiveButton(R.string.ok) { dialog, _ -> + dialog.dismiss() } } } + +fun showLicense(context: Context?, component: SoftwareComponent): Disposable { + return showLicense(context, component.license) { alertDialog -> + alertDialog.setPositiveButton(R.string.dismiss) { dialog, _ -> + dialog.dismiss() + } + alertDialog.setNeutralButton(R.string.open_website_license) { _, _ -> + ShareUtils.openUrlInBrowser(context!!, component.link) + } + } +} + +private fun showLicense( + context: Context?, + license: License, + block: (AlertDialog.Builder) -> Unit +): Disposable { + return if (context == null) { + Disposable.empty() + } else { + Observable.fromCallable { getFormattedLicense(context, license) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { formattedLicense -> + val webViewData = + Base64.encodeToString(formattedLicense.toByteArray(), Base64.NO_PADDING) + val webView = WebView(context) + webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") + + AlertDialog.Builder(context).apply { + setTitle(license.name) + setView(webView) + Localization.assureCorrectAppLanguage(context) + block(this) + show() + } + } + } +} From a9095ca2ad3abcce665d14a609973a3410f39f1d Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 05:38:33 +0530 Subject: [PATCH 102/152] Make block parameter an extension lambda. --- .../newpipe/about/LicenseFragmentHelper.kt | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index 34dfe8fb4..05873c05d 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -64,29 +64,25 @@ private fun getHexRGBColor(context: Context, color: Int): String { return context.getString(color).substring(3) } -fun showLicense(context: Context?, license: License): Disposable { - return showLicense(context, license) { alertDialog -> - alertDialog.setPositiveButton(R.string.ok) { dialog, _ -> - dialog.dismiss() - } - } -} - fun showLicense(context: Context?, component: SoftwareComponent): Disposable { - return showLicense(context, component.license) { alertDialog -> - alertDialog.setPositiveButton(R.string.dismiss) { dialog, _ -> + return showLicense(context, component.license) { + setPositiveButton(R.string.dismiss) { dialog, _ -> dialog.dismiss() } - alertDialog.setNeutralButton(R.string.open_website_license) { _, _ -> + setNeutralButton(R.string.open_website_license) { _, _ -> ShareUtils.openUrlInBrowser(context!!, component.link) } } } +fun showLicense(context: Context?, license: License) = showLicense(context, license) { + setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } +} + private fun showLicense( context: Context?, license: License, - block: (AlertDialog.Builder) -> Unit + block: AlertDialog.Builder.() -> AlertDialog.Builder ): Disposable { return if (context == null) { Disposable.empty() @@ -100,13 +96,12 @@ private fun showLicense( val webView = WebView(context) webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") - AlertDialog.Builder(context).apply { - setTitle(license.name) - setView(webView) - Localization.assureCorrectAppLanguage(context) - block(this) - show() - } + Localization.assureCorrectAppLanguage(context) + AlertDialog.Builder(context) + .setTitle(license.name) + .setView(webView) + .block() + .show() } } } From 4b7de86a92bfe22a20a017b615523a64e1bb9e80 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 06:00:08 +0530 Subject: [PATCH 103/152] Clean up getLicenseStylesheet(). --- .../newpipe/about/LicenseFragmentHelper.kt | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index 05873c05d..6e3aa4be8 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -36,21 +36,17 @@ private fun getFormattedLicense(context: Context, license: License): String { */ private fun getLicenseStylesheet(context: Context): String { val isLightTheme = ThemeHelper.isLightThemeSelected(context) - return ( - "body{padding:12px 15px;margin:0;" + "background:#" + getHexRGBColor( - context, - if (isLightTheme) R.color.light_license_background_color - else R.color.dark_license_background_color - ) + ";" + "color:#" + getHexRGBColor( - context, - if (isLightTheme) R.color.light_license_text_color - else R.color.dark_license_text_color - ) + "}" + "a[href]{color:#" + getHexRGBColor( - context, - if (isLightTheme) R.color.light_youtube_primary_color - else R.color.dark_youtube_primary_color - ) + "}" + "pre{white-space:pre-wrap}" - ) + val licenseBackgroundColor = getHexRGBColor( + context, if (isLightTheme) R.color.light_license_background_color else R.color.dark_license_background_color + ) + val licenseTextColor = getHexRGBColor( + context, if (isLightTheme) R.color.light_license_text_color else R.color.dark_license_text_color + ) + val youtubePrimaryColor = getHexRGBColor( + context, if (isLightTheme) R.color.light_youtube_primary_color else R.color.dark_youtube_primary_color + ) + return "body{padding:12px 15px;margin:0;background:#$licenseBackgroundColor;color:#$licenseTextColor}" + + "a[href]{color:#$youtubePrimaryColor}pre{white-space:pre-wrap}" } /** From 059cfcbad26422bff114cab9e94da03219243d70 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 3 Aug 2022 04:38:47 +0530 Subject: [PATCH 104/152] Use Comparator factory methods in ListHelper. --- .../org/schabi/newpipe/util/ListHelper.java | 62 ++++--------------- 1 file changed, 12 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index fbbe43513..45234706b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -343,7 +343,10 @@ public final class ListHelper { */ private static List sortStreamList(final List videoStreams, final boolean ascendingOrder) { - final Comparator comparator = ListHelper::compareVideoStreamResolution; + // Compares the quality of two video streams. + final Comparator comparator = Comparator.nullsLast(Comparator + .comparing(VideoStream::getResolution, ListHelper::compareVideoStreamResolution) + .thenComparingInt(s -> VIDEO_FORMAT_QUALITY_RANKING.indexOf(s.getFormat()))); Collections.sort(videoStreams, ascendingOrder ? comparator : comparator.reversed()); return videoStreams; } @@ -360,8 +363,7 @@ public final class ListHelper { @Nullable final List audioStreams) { return getAudioIndexByHighestRank(format, audioStreams, // Compares descending (last = highest rank) - (s1, s2) -> compareAudioStreamBitrate(s1, s2, AUDIO_FORMAT_QUALITY_RANKING) - ); + getAudioStreamComparator(AUDIO_FORMAT_QUALITY_RANKING)); } /** @@ -374,11 +376,15 @@ public final class ListHelper { */ static int getMostCompactAudioIndex(@Nullable final MediaFormat format, @Nullable final List audioStreams) { - return getAudioIndexByHighestRank(format, audioStreams, // The "-" is important -> Compares ascending (first = highest rank) - (s1, s2) -> -compareAudioStreamBitrate(s1, s2, AUDIO_FORMAT_EFFICIENCY_RANKING) - ); + getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING).reversed()); + } + + private static Comparator getAudioStreamComparator( + final List formatRanking) { + return Comparator.nullsLast(Comparator.comparingInt(AudioStream::getAverageBitrate)) + .thenComparingInt(stream -> formatRanking.indexOf(stream.getFormat())); } /** @@ -544,28 +550,6 @@ public final class ListHelper { return format; } - // Compares the quality of two audio streams - private static int compareAudioStreamBitrate(final AudioStream streamA, - final AudioStream streamB, - final List formatRanking) { - if (streamA == null) { - return -1; - } - if (streamB == null) { - return 1; - } - if (streamA.getAverageBitrate() < streamB.getAverageBitrate()) { - return -1; - } - if (streamA.getAverageBitrate() > streamB.getAverageBitrate()) { - return 1; - } - - // Same bitrate and format - return formatRanking.indexOf(streamA.getFormat()) - - formatRanking.indexOf(streamB.getFormat()); - } - private static int compareVideoStreamResolution(@NonNull final String r1, @NonNull final String r2) { try { @@ -582,28 +566,6 @@ public final class ListHelper { } } - // Compares the quality of two video streams. - private static int compareVideoStreamResolution(final VideoStream streamA, - final VideoStream streamB) { - if (streamA == null) { - return -1; - } - if (streamB == null) { - return 1; - } - - final int resComp = compareVideoStreamResolution(streamA.getResolution(), - streamB.getResolution()); - if (resComp != 0) { - return resComp; - } - - // Same bitrate and format - return ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamA.getFormat()) - - ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamB.getFormat()); - } - - private static boolean isLimitingDataUsage(final Context context) { return getResolutionLimit(context) != null; } From d1f3f15478bada81a2172b45f6ba4385bc0ff7a7 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 3 Aug 2022 06:01:32 +0530 Subject: [PATCH 105/152] Use Comparator.comparingDouble(). --- .../preferencesearch/PreferenceFuzzySearchFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java index 7c231cafb..ea45c68d2 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java @@ -31,7 +31,7 @@ public class PreferenceFuzzySearchFunction // Specific search - Used for determining order of search results // Calculate a score based on specific search fields .map(item -> new FuzzySearchSpecificDTO(item, keyword)) - .sorted(Comparator.comparing(FuzzySearchSpecificDTO::getScore).reversed()) + .sorted(Comparator.comparingDouble(FuzzySearchSpecificDTO::getScore).reversed()) .map(FuzzySearchSpecificDTO::getItem) // Limit the amount of search results .limit(20); From 8024b437e9dfeeec29be83b7b6d0049f53d5090f Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 30 Jul 2022 01:50:23 +0530 Subject: [PATCH 106/152] Add reusable classes extending AnimatorListenerAdapter. --- .../main/java/org/schabi/newpipe/ktx/View.kt | 99 +++++++------------ 1 file changed, 38 insertions(+), 61 deletions(-) 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 5ab8dbdc2..bf0dcb201 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -90,7 +90,9 @@ fun View.animate( */ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) { if (MainActivity.DEBUG) { - Log.d(TAG, "animateBackgroundColor() called with: view = [$this], duration = [$duration], " + + Log.d( + TAG, + "animateBackgroundColor() called with: view = [$this], duration = [$duration], " + "colorStart = [$colorStart], colorEnd = [$colorEnd]" ) } @@ -147,20 +149,13 @@ private fun View.animateAlpha(enterOrExit: Boolean, duration: Long, delay: Long, if (enterOrExit) { animate().setInterpolator(FastOutSlowInInterpolator()).alpha(1f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { animate().setInterpolator(FastOutSlowInInterpolator()).alpha(0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -172,11 +167,8 @@ private fun View.animateScaleAndAlpha(enterOrExit: Boolean, duration: Long, dela .setInterpolator(FastOutSlowInInterpolator()) .alpha(1f).scaleX(1f).scaleY(1f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { scaleX = 1f scaleY = 1f @@ -184,12 +176,8 @@ private fun View.animateScaleAndAlpha(enterOrExit: Boolean, duration: Long, dela .setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).scaleX(.8f).scaleY(.8f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -202,11 +190,8 @@ private fun View.animateLightScaleAndAlpha(enterOrExit: Boolean, duration: Long, .setInterpolator(FastOutSlowInInterpolator()) .alpha(1f).scaleX(1f).scaleY(1f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { alpha = 1f scaleX = 1f @@ -215,12 +200,8 @@ private fun View.animateLightScaleAndAlpha(enterOrExit: Boolean, duration: Long, .setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).scaleX(.95f).scaleY(.95f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -231,22 +212,15 @@ private fun View.animateSlideAndAlpha(enterOrExit: Boolean, duration: Long, dela animate() .setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { animate() .setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).translationY(-height.toFloat()) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -257,21 +231,14 @@ private fun View.animateLightSlideAndAlpha(enterOrExit: Boolean, duration: Long, animate() .setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { animate().setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).translationY(-height / 2.0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -293,11 +260,7 @@ fun View.slideUp( .setStartDelay(delay) .setDuration(duration) .setInterpolator(FastOutSlowInInterpolator()) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }) + .setListener(ExecOnEndListener(execOnEnd)) .start() } @@ -311,6 +274,20 @@ fun View.animateHideRecyclerViewAllowingScrolling() { animate().alpha(0.0f).setDuration(200).start() } +private open class ExecOnEndListener(private val execOnEnd: Runnable?) : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + execOnEnd?.run() + } +} + +private class HideAndExecOnEndListener(private val view: View, execOnEnd: Runnable?) : + ExecOnEndListener(execOnEnd) { + override fun onAnimationEnd(animation: Animator) { + view.isGone = true + super.onAnimationEnd(animation) + } +} + enum class AnimationType { ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA } From 2eec2e9128924bbe708eeb4958191b0d3fe3ff6c Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 5 Aug 2022 06:19:06 +0530 Subject: [PATCH 107/152] Replace coerceIn() with MathUtils.clamp(). --- .../newpipe/player/gesture/PopupPlayerGestureListener.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt index 01b15f30a..666ea6a46 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -4,6 +4,7 @@ import android.util.Log import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration +import androidx.core.math.MathUtils import org.schabi.newpipe.MainActivity import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate @@ -234,11 +235,13 @@ class PopupPlayerGestureListener( isMoving = true val diffX = (movingEvent.rawX - initialEvent.rawX) - val posX = (initialPopupX + diffX).coerceIn( + val posX = MathUtils.clamp( + initialPopupX + diffX, 0f, (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() ) val diffY = (movingEvent.rawY - initialEvent.rawY) - val posY = (initialPopupY + diffY).coerceIn( + val posY = MathUtils.clamp( + initialPopupY + diffY, 0f, (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() ) From fc46233baf5827250b852d2118f15ea89b7ef132 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 30 Jul 2022 00:51:15 +0530 Subject: [PATCH 108/152] Use toArray() with zero-length arrays. --- .../FragmentStatePagerAdapterMenuWorkaround.java | 6 ++---- .../fragments/detail/VideoDetailFragment.java | 8 ++------ .../fragments/list/search/SearchFragment.java | 5 ++--- .../player/notification/NotificationConstants.java | 5 ++--- .../player/notification/NotificationUtil.java | 5 +---- .../custom/NotificationActionsPreference.java | 13 ++++++------- .../java/org/schabi/newpipe/streams/WebMReader.java | 3 +-- 7 files changed, 16 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java b/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java index 639443377..8d87e90bd 100644 --- a/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java +++ b/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java @@ -282,11 +282,9 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt @Nullable public Parcelable saveState() { Bundle state = null; - if (mSavedState.size() > 0) { + if (!mSavedState.isEmpty()) { state = new Bundle(); - final Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; - mSavedState.toArray(fss); - state.putParcelableArray("states", fss); + state.putParcelableArray("states", mSavedState.toArray(new Fragment.SavedState[0])); } for (int i = 0; i < mFragments.size(); i++) { final Fragment f = mFragments.get(i); 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 f0672cd41..3b1bdaede 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 @@ -2167,12 +2167,8 @@ public final class VideoDetailFragment } else { final int selectedVideoStreamIndexForExternalPlayers = ListHelper.getDefaultResolutionIndex(activity, videoStreamsForExternalPlayers); - final CharSequence[] resolutions = - new CharSequence[videoStreamsForExternalPlayers.size()]; - - for (int i = 0; i < videoStreamsForExternalPlayers.size(); i++) { - resolutions[i] = videoStreamsForExternalPlayers.get(i).getResolution(); - } + final CharSequence[] resolutions = videoStreamsForExternalPlayers.stream() + .map(VideoStream::getResolution).toArray(CharSequence[]::new); builder.setSingleChoiceItems(resolutions, selectedVideoStreamIndexForExternalPlayers, null); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 44f8328a5..008163890 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -919,7 +919,7 @@ public class SearchFragment extends BaseListFragment cannot be bundled without creating some containers - metaInfo = new MetaInfo[result.getMetaInfo().size()]; - metaInfo = result.getMetaInfo().toArray(metaInfo); + metaInfo = result.getMetaInfo().toArray(new MetaInfo[0]); showMetaInfoInTextView(result.getMetaInfo(), searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator, disposables); diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index b8e39e564..7015e0717 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -14,7 +14,6 @@ import org.schabi.newpipe.util.Localization; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; @@ -115,7 +114,7 @@ public final class NotificationConstants { }; - public static final Integer[] SLOT_COMPACT_DEFAULTS = {0, 1, 2}; + public static final List SLOT_COMPACT_DEFAULTS = List.of(0, 1, 2); public static final int[] SLOT_COMPACT_PREF_KEYS = { R.string.notification_slot_compact_0_key, @@ -181,7 +180,7 @@ public final class NotificationConstants { if (compactSlot == Integer.MAX_VALUE) { // settings not yet populated, return default values - return new ArrayList<>(Arrays.asList(SLOT_COMPACT_DEFAULTS)); + return SLOT_COMPACT_DEFAULTS; } // a negative value (-1) is set when the user does not want a particular compact slot diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 2ba754500..ef225b14e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -99,10 +99,7 @@ public final class NotificationUtil { // build the compact slot indices array (need code to convert from Integer... because Java) final List compactSlotList = NotificationConstants.getCompactSlotsFromPreferences( player.getContext(), player.getPrefs(), nonNothingSlotCount); - final int[] compactSlots = new int[compactSlotList.size()]; - for (int i = 0; i < compactSlotList.size(); i++) { - compactSlots[i] = compactSlotList.get(i); - } + final int[] compactSlots = compactSlotList.stream().mapToInt(i -> i).toArray(); builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() .setMediaSession(player.getMediaSessionManager().getSessionToken()) diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 03b5a5a95..1101f410d 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -34,7 +34,9 @@ import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; +import java.util.ArrayList; import java.util.List; +import java.util.stream.IntStream; public class NotificationActionsPreference extends Preference { @@ -74,13 +76,10 @@ public class NotificationActionsPreference extends Preference { //////////////////////////////////////////////////////////////////////////// private void setupActions(@NonNull final View view) { - compactSlots = - NotificationConstants.getCompactSlotsFromPreferences( - getContext(), getSharedPreferences(), 5); - notificationSlots = new NotificationSlot[5]; - for (int i = 0; i < 5; i++) { - notificationSlots[i] = new NotificationSlot(i, view); - } + compactSlots = new ArrayList<>(NotificationConstants + .getCompactSlotsFromPreferences(getContext(), getSharedPreferences(), 5)); + notificationSlots = IntStream.range(0, 5).mapToObj(i -> new NotificationSlot(i, view)) + .toArray(NotificationSlot[]::new); } diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java index 8253ad6af..678974cce 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java @@ -348,8 +348,7 @@ public class WebMReader { ensure(elemTrackEntry); } - final WebMTrack[] entries = new WebMTrack[trackEntries.size()]; - trackEntries.toArray(entries); + final WebMTrack[] entries = trackEntries.toArray(new WebMTrack[0]); for (final WebMTrack entry : entries) { switch (entry.trackType) { From a9af1dfdd28a70b2668f201eedf25265b946adb5 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 5 Aug 2022 06:54:03 +0530 Subject: [PATCH 109/152] Applied code review changes. --- .../player/notification/NotificationConstants.java | 2 +- .../newpipe/player/notification/NotificationUtil.java | 2 +- .../settings/custom/NotificationActionsPreference.java | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index 7015e0717..89bf0b22a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -180,7 +180,7 @@ public final class NotificationConstants { if (compactSlot == Integer.MAX_VALUE) { // settings not yet populated, return default values - return SLOT_COMPACT_DEFAULTS; + return new ArrayList<>(SLOT_COMPACT_DEFAULTS); } // a negative value (-1) is set when the user does not want a particular compact slot diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index ef225b14e..1a91bc66d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -99,7 +99,7 @@ public final class NotificationUtil { // build the compact slot indices array (need code to convert from Integer... because Java) final List compactSlotList = NotificationConstants.getCompactSlotsFromPreferences( player.getContext(), player.getPrefs(), nonNothingSlotCount); - final int[] compactSlots = compactSlotList.stream().mapToInt(i -> i).toArray(); + final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() .setMediaSession(player.getMediaSessionManager().getSessionToken()) diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 1101f410d..1770685e4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -34,7 +34,6 @@ import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; -import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; @@ -76,9 +75,10 @@ public class NotificationActionsPreference extends Preference { //////////////////////////////////////////////////////////////////////////// private void setupActions(@NonNull final View view) { - compactSlots = new ArrayList<>(NotificationConstants - .getCompactSlotsFromPreferences(getContext(), getSharedPreferences(), 5)); - notificationSlots = IntStream.range(0, 5).mapToObj(i -> new NotificationSlot(i, view)) + compactSlots = NotificationConstants.getCompactSlotsFromPreferences(getContext(), + getSharedPreferences(), 5); + notificationSlots = IntStream.range(0, 5) + .mapToObj(i -> new NotificationSlot(i, view)) .toArray(NotificationSlot[]::new); } From ee6a27959647df5a39c57ee429046fb838457357 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 6 Aug 2022 05:09:54 +0530 Subject: [PATCH 110/152] Remove unused methods in HistoryRecordManager. --- .../org/schabi/newpipe/database/BasicDAO.java | 11 +--- .../local/history/HistoryRecordManager.java | 54 ------------------- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java b/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java index 1b8540808..255f5ba8d 100644 --- a/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.database; import androidx.room.Dao; import androidx.room.Delete; import androidx.room.Insert; -import androidx.room.OnConflictStrategy; import androidx.room.Update; import java.util.Collection; @@ -14,13 +13,10 @@ import io.reactivex.rxjava3.core.Flowable; @Dao public interface BasicDAO { /* Inserts */ - @Insert(onConflict = OnConflictStrategy.ABORT) + @Insert long insert(Entity entity); - @Insert(onConflict = OnConflictStrategy.ABORT) - List insertAll(Entity... entities); - - @Insert(onConflict = OnConflictStrategy.ABORT) + @Insert List insertAll(Collection entities); /* Searches */ @@ -32,9 +28,6 @@ public interface BasicDAO { @Delete void delete(Entity entity); - @Delete - int delete(Collection entities); - int deleteAll(); /* Updates */ diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index 19f7afce5..b8d2eae2d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -28,7 +28,6 @@ import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.AppDatabase; import org.schabi.newpipe.database.LocalItem; -import org.schabi.newpipe.database.feed.dao.FeedDAO; import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; import org.schabi.newpipe.database.history.dao.StreamHistoryDAO; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; @@ -51,7 +50,6 @@ import org.schabi.newpipe.util.ExtractorHelper; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import io.reactivex.rxjava3.core.Completable; @@ -89,7 +87,6 @@ public class HistoryRecordManager { * Marks a stream item as watched such that it is hidden from the feed if watched videos are * hidden. Adds a history entry and updates the stream progress to 100%. * - * @see FeedDAO#getLiveOrNotPlayedStreams * @see FeedViewModel#togglePlayedItems * @param info the item to mark as watched * @return a Maybe containing the ID of the item if successful @@ -176,10 +173,6 @@ public class HistoryRecordManager { .subscribeOn(Schedulers.io()); } - public Flowable> getStreamHistory() { - return streamHistoryTable.getHistory().subscribeOn(Schedulers.io()); - } - public Flowable> getStreamHistorySortedById() { return streamHistoryTable.getHistorySortedById().subscribeOn(Schedulers.io()); } @@ -188,24 +181,6 @@ public class HistoryRecordManager { return streamHistoryTable.getStatistics().subscribeOn(Schedulers.io()); } - public Single> insertStreamHistory(final Collection entries) { - final List entities = new ArrayList<>(entries.size()); - for (final StreamHistoryEntry entry : entries) { - entities.add(entry.toStreamHistoryEntity()); - } - return Single.fromCallable(() -> streamHistoryTable.insertAll(entities)) - .subscribeOn(Schedulers.io()); - } - - public Single deleteStreamHistory(final Collection entries) { - final List entities = new ArrayList<>(entries.size()); - for (final StreamHistoryEntry entry : entries) { - entities.add(entry.toStreamHistoryEntity()); - } - return Single.fromCallable(() -> streamHistoryTable.delete(entities)) - .subscribeOn(Schedulers.io()); - } - private boolean isStreamHistoryEnabled() { return sharedPreferences.getBoolean(streamHistoryKey, false); } @@ -259,13 +234,6 @@ public class HistoryRecordManager { // Stream State History /////////////////////////////////////////////////////// - public Maybe getStreamHistory(final StreamInfo info) { - return Maybe.fromCallable(() -> { - final long streamId = streamTable.upsert(new StreamEntity(info)); - return streamHistoryTable.getLatestEntry(streamId); - }).subscribeOn(Schedulers.io()); - } - public Maybe loadStreamState(final PlayQueueItem queueItem) { return queueItem.getStream() .map(info -> streamTable.upsert(new StreamEntity(info))) @@ -311,28 +279,6 @@ public class HistoryRecordManager { }).subscribeOn(Schedulers.io()); } - public Single> loadStreamStateBatch(final List infos) { - return Single.fromCallable(() -> { - final List result = new ArrayList<>(infos.size()); - for (final InfoItem info : infos) { - final List entities = streamTable - .getStream(info.getServiceId(), info.getUrl()).blockingFirst(); - if (entities.isEmpty()) { - result.add(null); - continue; - } - final List states = streamStateTable - .getState(entities.get(0).getUid()).blockingFirst(); - if (states.isEmpty()) { - result.add(null); - } else { - result.add(states.get(0)); - } - } - return result; - }).subscribeOn(Schedulers.io()); - } - public Single> loadLocalStreamStateBatch( final List items) { return Single.fromCallable(() -> { From 7aacaf8c38358ccd3da98fcb26ed9420d6fdd9ee Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 29 Jul 2022 09:05:50 +0530 Subject: [PATCH 111/152] Use Collectors.joining(). --- .../org/schabi/newpipe/DownloaderImpl.java | 26 +++++++++---------- .../schabi/newpipe/error/ErrorActivity.java | 12 +++------ .../preferencesearch/PreferenceParser.java | 10 ++----- .../org/schabi/newpipe/util/CookieUtils.java | 24 ----------------- .../org/schabi/newpipe/util/Localization.java | 23 +++++----------- 5 files changed, 24 insertions(+), 71 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/CookieUtils.java diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 7bb394bf4..9ddbe96df 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -11,15 +11,17 @@ import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.extractor.downloader.Request; import org.schabi.newpipe.extractor.downloader.Response; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; -import org.schabi.newpipe.util.CookieUtils; import org.schabi.newpipe.util.InfoCache; import java.io.IOException; -import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; import okhttp3.OkHttpClient; import okhttp3.RequestBody; @@ -63,19 +65,15 @@ public final class DownloaderImpl extends Downloader { } public String getCookies(final String url) { - final List resultCookies = new ArrayList<>(); - if (url.contains(YOUTUBE_DOMAIN)) { - final String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY); - if (youtubeCookie != null) { - resultCookies.add(youtubeCookie); - } - } + final String youtubeCookie = url.contains(YOUTUBE_DOMAIN) + ? getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY) : null; + // Recaptcha cookie is always added TODO: not sure if this is necessary - final String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY); - if (recaptchaCookie != null) { - resultCookies.add(recaptchaCookie); - } - return CookieUtils.concatCookies(resultCookies); + return Stream.of(youtubeCookie, getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY)) + .filter(Objects::nonNull) + .flatMap(cookies -> Arrays.stream(cookies.split("; *"))) + .distinct() + .collect(Collectors.joining("; ")); } public String getCookie(final String key) { diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index 8b2ac37dc..e1dd929d4 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -31,6 +31,7 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; +import java.util.stream.Collectors; /* * Created by Christian Schabesberger on 24.10.15. @@ -182,14 +183,9 @@ public class ErrorActivity extends AppCompatActivity { } private String formErrorText(final String[] el) { - final StringBuilder text = new StringBuilder(); - if (el != null) { - for (final String e : el) { - text.append("-------------------------------------\n").append(e); - } - } - text.append("-------------------------------------"); - return text.toString(); + final String separator = "-------------------------------------"; + return Arrays.stream(el) + .collect(Collectors.joining(separator + "\n", separator + "\n", separator)); } /** diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java index 1f507c7f1..b925e8b5f 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java @@ -9,13 +9,13 @@ import androidx.annotation.Nullable; import androidx.annotation.XmlRes; import androidx.preference.PreferenceManager; +import org.schabi.newpipe.util.Localization; import org.xmlpull.v1.XmlPullParser; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; /** * Parses the corresponding preference-file(s). @@ -54,7 +54,7 @@ public class PreferenceParser { if (xpp.getEventType() == XmlPullParser.START_TAG) { final PreferenceSearchItem result = parseSearchResult( xpp, - joinBreadcrumbs(breadcrumbs), + Localization.concatenateStrings(" > ", breadcrumbs), resId ); @@ -82,12 +82,6 @@ public class PreferenceParser { return results; } - private String joinBreadcrumbs(final List breadcrumbs) { - return breadcrumbs.stream() - .filter(crumb -> !TextUtils.isEmpty(crumb)) - .collect(Collectors.joining(" > ")); - } - private String getAttribute( final XmlPullParser xpp, @NonNull final String attribute diff --git a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java deleted file mode 100644 index b906c1c4f..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.schabi.newpipe.util; - -import android.text.TextUtils; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -public final class CookieUtils { - private CookieUtils() { - } - - public static String concatCookies(final Collection cookieStrings) { - final Set cookieSet = new HashSet<>(); - for (final String cookies : cookieStrings) { - cookieSet.addAll(splitCookies(cookies)); - } - return TextUtils.join("; ", cookieSet).trim(); - } - - public static Set splitCookies(final String cookies) { - return Set.of(cookies.split("; *")); - } -} 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 5ac24503d..28712bb4d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -32,6 +32,7 @@ import java.time.format.FormatStyle; import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; /* @@ -63,26 +64,14 @@ public final class Localization { @NonNull public static String concatenateStrings(final String... strings) { - return concatenateStrings(Arrays.asList(strings)); + return concatenateStrings(DOT_SEPARATOR, Arrays.asList(strings)); } @NonNull - public static String concatenateStrings(final List strings) { - if (strings.isEmpty()) { - return ""; - } - - final StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(strings.get(0)); - - for (int i = 1; i < strings.size(); i++) { - final String string = strings.get(i); - if (!TextUtils.isEmpty(string)) { - stringBuilder.append(DOT_SEPARATOR).append(strings.get(i)); - } - } - - return stringBuilder.toString(); + public static String concatenateStrings(final String delimiter, final List strings) { + return strings.stream() + .filter(string -> !TextUtils.isEmpty(string)) + .collect(Collectors.joining(delimiter)); } public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization( From 5c7dfd1d69bb7726b0f91cb6d5c5296f51173647 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 29 Jul 2022 09:18:21 +0530 Subject: [PATCH 112/152] Remove unused method. --- .../org/schabi/newpipe/util/Localization.java | 15 --------------- 1 file changed, 15 deletions(-) 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 28712bb4d..e20955a76 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -347,19 +347,4 @@ public final class Localization { private static double round(final double value, final int places) { return new BigDecimal(value).setScale(places, RoundingMode.HALF_UP).doubleValue(); } - - /** - * Workaround to match normalized captions like english to English or deutsch to Deutsch. - * @param list the list to search into - * @param toFind the string to look for - * @return whether the string was found or not - */ - public static boolean containsCaseInsensitive(final List list, final String toFind) { - for (final String i : list) { - if (i.equalsIgnoreCase(toFind)) { - return true; - } - } - return false; - } } From ebd06bdd246403384f6d93795d102bc5d22cfce0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 6 Aug 2022 11:56:00 +0200 Subject: [PATCH 113/152] Improve comment --- app/src/main/java/org/schabi/newpipe/util/ListHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 45234706b..b3b7c1792 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -377,7 +377,7 @@ public final class ListHelper { static int getMostCompactAudioIndex(@Nullable final MediaFormat format, @Nullable final List audioStreams) { return getAudioIndexByHighestRank(format, audioStreams, - // The "-" is important -> Compares ascending (first = highest rank) + // The "reversed()" is important -> Compares ascending (first = highest rank) getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING).reversed()); } From 0b11afaf2fddff26de7927605994dea1c4be5b87 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Mon, 8 Aug 2022 19:32:21 -0400 Subject: [PATCH 114/152] Update Gradle to 7.5.1 --- gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 6 ++++++ gradlew.bat | 14 ++++++++------ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 10197 zcmaKS1ymhDwk=#NxVyW%y9U<)A-Dv)xI0|j{UX8L-JRg>5ZnnKAh;%chM6~S-g^K4 z>eZ{yK4;gd>gwvXs=Id8Jk-J}R4pT911;+{Jp9@aiz6!p1Oz9z&_kGLA%J5%3Ih@0 zQ|U}%$)3u|G`jIfPzMVfcWs?jV2BO^*3+q2><~>3j+Z`^Z%=;19VWg0XndJ zwJ~;f4$;t6pBKaWn}UNO-wLCFHBd^1)^v%$P)fJk1PbK5<;Z1K&>k~MUod6d%@Bq9 z>(44uiaK&sdhwTTxFJvC$JDnl;f}*Q-^01T508(8{+!WyquuyB7R!d!J)8Ni0p!cV6$CHsLLy6}7C zYv_$eD;)@L)tLj0GkGpBoa727hs%wH$>EhfuFy{_8Q8@1HI%ZAjlpX$ob{=%g6`Ox zLzM!d^zy`VV1dT9U9(^}YvlTO9Bf8v^wMK37`4wFNFzW?HWDY(U(k6@tp(crHD)X5>8S-# zW1qgdaZa*Sh6i%60e1+hty}34dD%vKgb?QmQiZ=-j+isA4={V_*R$oGN#j|#ia@n6 zuZx4e2Xx?^lUwYFn2&Tmbx0qA3Z8;y+zKoeQu;~k~FZGy!FU_TFxYd!Ck;5QvMx9gj5fI2@BLNp~Ps@ zf@k<&Q2GS5Ia9?_D?v~$I%_CLA4x~eiKIZ>9w^c#r|vB?wXxZ(vXd*vH(Fd%Me8p( z=_0)k=iRh%8i`FYRF>E97uOFTBfajv{IOz(7CU zv0Gd84+o&ciHlVtY)wn6yhZTQQO*4Mvc#dxa>h}82mEKKy7arOqU$enb9sgh#E=Lq zU;_RVm{)30{bw+|056%jMVcZRGEBSJ+JZ@jH#~DvaDQm92^TyUq=bY*+AkEakpK>8 zB{)CkK48&nE5AzTqT;WysOG|!y}5fshxR8Ek(^H6i>|Fd&wu?c&Q@N9ZrJ=?ABHI! z`*z8D`w=~AJ!P-9M=T}f`;76$qZRllB&8#9WgbuO$P7lVqdX1=g*t=7z6!0AQ^ux_ z9rcfUv^t}o_l-ZE+TqvqFsA*~W<^78!k;~!i8(eS+(+@u8FxK+Q7;mHZ<1}|4m<}vh@p`t%|@eM_J(P% zI>M7C)Ir{l|J;$G_EGGEhbP4?6{sYzMqBv+x95N&YWFH6UcE@b}B?q)G*4<4mR@sy1#vPnLMK51tb#ED(8TA1nE zYfhK7bo1!R5WJF$5Y?zG21)6+_(_5oSX9sGIW;(O&S?Rh(nydNQYzKjjJ54aDJ-1F zrJ=np8LsN?%?Rt7f~3aAX!2E{`fh_pb?2(;HOB3W+I*~A>W%iY+v45+^e$cE10fA} zXPvw9=Bd+(;+!rl)pkYj0HGB}+3Z!Mr;zr%gz~c-hFMv8b2VRE2R$8V=_XE zq$3=|Yg05(fmwrJ)QK2ptB4no`Y8Dg_vK2QDc6-6sXRQ5k78-+cPi-fH}vpgs|Ive zE=m*XNVs?EWgiNI!5AcD*3QMW)R`EqT!f0e1%hERO&?AT7HWnSf5@#AR{OGuXG3Zb zCnVWg7h|61lGV3k+>L<#d>)InG>ETn1DbOHCfztqzQ_fBiaUt@q6VMy={Fe-w#~2- z0?*f|z$zgjI9>+JVICObBaK=pU}AEOd@q(8d?j7zQFD@=6t`|KmolTr2MfBI$;EGh zD%W0cA_d#V6Lb$us5yIG(|d>r-QleC4;%hEu5W9hyY zY#+ESY&v`8(&mC~?*|e5WEhC!YU2>m_}`K+q9)a(d$bsS<=YkyZGp}YA%TXw>@abA zS_poVPoN+?<6?DAuCNt&5SHV(hp56PJ})swwVFZFXM->F zc|0c8<$H_OV%DR|y7e+s$12@Ac8SUClPg8_O9sTUjpv%6Jsn5vsZCg>wL+db4c+{+ zsg<#wOuV4jeOq`veckdi-1`dz;gvL)bZeH|D*x=8UwRU5&8W1@l>3$)8WzET0%;1J zM3(X<7tKK&9~kWRI{&FmwY5Gg!b5f4kI_vSm)H1#>l6M+OiReDXC{kPy!`%Ecq-+3yZTk=<` zm)pE6xum5q0Qkd#iny0Q-S}@I0;mDhxf>sX)Oiv)FdsAMnpx%oe8OQ`m%Xeozdzx!C1rQR>m1c_}+J4x)K}k{G zo68;oGG&Ox7w^-m7{g4a7NJu-B|~M;oIH~~#`RyUNm##feZH;E?pf}nshmoiIY52n z%pc%lnU4Q#C=RUz)RU6}E_j4#)jh<&a%JyJj$Fufc#&COaxFHtl}zJUGNLBu3~_@1 zn9F^JO9);Duxo&i@>X(kbYga1i>6p1fca8FzQ0>((Lb-aPUbC*d~a03V$y;*RBY!R ziEJ2IF^FjrvO}0Uy{cMn%u<+P5U!UO>pm9#ZYL5i6|xSC+np7IH$GfXs&uI;y4as@ z&AzJh>(S2?3PKKgab3Z(`xbx(C#46XIvVcW8eG_DjT~}Yz_8PWZ`uf6^Xr=vkvL_` zqmvfgJL+Zc`;iq~iP?%@G7}~fal-zqxa0yNyHBJJ5M)9bI>7S_cg?Ya&p(I)C5Ef4 zZ>YAF6x|U=?ec?g*|f2g5Tw3PgxaM_bi_5Az9MO$;_Byw(2d}2%-|bg4ShdQ;)Z|M z4K|tFv)qx*kKGKoyh!DQY<{n&UmAChq@DJrQP>EY7g1JF(ih*D8wCVWyQ z5Jj^|-NVFSh5T0vd1>hUvPV6?=`90^_)t(L9)XOW7jeP45NyA2lzOn&QAPTl&d#6P zSv%36uaN(9i9WlpcH#}rmiP#=L0q(dfhdxvFVaOwM;pY;KvNQ9wMyUKs6{d}29DZQ z{H3&Sosr6)9Z+C>Q5)iHSW~gGoWGgK-0;k~&dyr-bA3O|3PCNzgC?UKS_B=^i8Ri^ zd_*_qI4B07Cayq|p4{`U_E_P=K`N_~{F|+-+`sCgcNxs`%X!$=(?l2aAW}0M=~COb zf19oe^iuAUuDEf)4tgv<=WRPpK@IjToNNC*#&Ykw!)aqWU4h#|U@(cG_=Qx+&xt~a zvCz~Ds3F71dsjNLkfM%TqdVNu=RNMOzh7?b+%hICbFlOAPphrYy>7D-e7{%o_kPFn z;T!?ilE-LcKM0P(GKMseEeW57Vs`=FF}(y@^pQl;rL3fHs8icmA+!6YJt&8 ztSF?%Un35qkv>drkks&BNTJv~xK?vD;aBkp7eIkDYqn+G0%;sT4FcwAoO+vke{8CO z0d76sgg$CannW5T#q`z~L4id)9BCKRU0A!Z-{HpXr)QJrd9@iJB+l32Ql)Z}*v(St zE)Vp=BB=DDB4Pr}B(UHNe31<@!6d{U?XDoxJ@S)9QM)2L%SA0x^~^fb=bdsBy!uh& zU?M_^kvnt%FZzm+>~bEH{2o?v&Iogs`1t-b+Ml`J!ZPS(46YQJKxWE81O$HE5w;** z|8zM%bp`M7J8)4;%DqH`wVTmM0V@D}xd%tRE3_6>ioMJxyi5Hkb>85muF81&EY!73ei zA3e<#ug||EZJ=1GLXNJ)A z791&ge#lF;GVX6IU?iw0jX^1bYaU?+x{zPlpyX6zijyn*nEdZ$fxxkl!a-~*P3bkf zPd*pzu~3GBYkR_>ET`5UM^>>zTV>5m>)f=az{d0sg6a8VzUtXy$ZS?h#Gk-CA?7)c zI%Vu9DN6XSDQn6;?n9`>l$q&>s?K)R8*OsmI+$L_m z_~E`}w694Z*`Xk3Ne=497Si~=RWRqCM?6=88smrxle#s*W znwhTRsMRmg?37GLJ-)%nDZA7r$YG849j8mJWir1bWBy& zZPneYojSbooC8U@tkO`bWx4%E5*;p#Q^1^S3lsfy7(6A{jL0`A__0vm?>xC%1y8_m z57FfWr^@YG2I1K7MGYuYd>JC}@sT2n^rkrY3w%~$J$Y~HSoOHn?zpR$ zjLj_bq@Yj8kd~DXHh30KVbz@K)0S;hPKm+S&-o%IG+@x@MEcrxW2KFh;z^4dJDZix zGRGe&lQD$p)0JVF4NRgGYuh0bYLy)BCy~sbS3^b3 zHixT<%-Vwbht|25T{3^Hk;qZ^3s!OOgljHs+EIf~C%=_>R5%vQI4mQR9qOXThMXlU zS|oSH>0PjnCakb*js2{ObN`}%HYsT6=%(xA| znpUtG_TJ08kHgm5l@G|t?4E3tG2fq?wNtIp*Vqrb{9@bo^~Rx7+J&OnayrX`LDcF~ zd@0m0ZJ#Z@=T>4kTa5e2FjI&5c(F7S{gnRPoGpu9eIqrtSvnT_tk$8T)r%YwZw!gK zj*k@cG)V&@t+mtDi37#>LhVGTfRA^p%x0d#_P|Mktz3*KOoLIqFm`~KGoDDD4OOxe z?}ag_c08u%vu=5Vx=~uoS8Q;}+R2~?Uh|m-+`-2kDo$d6T!nD*hc#dB(*R{LXV=zo z`PJP0V=O!@3l-bw+d`X6(=@fq=4O#ETa8M^fOvO4qja9o3e8ANc9$sI=A4$zUut~w z4+JryRkI{9qWxU1CCMM$@Aj=6)P+z?vqa=UCv_4XyVNoBD{Xb~Oi4cjjhm8fRD!*U z2)zaS;AI78^Wq+5mDInKiMz|z#K`2emQfNH*U;{9^{NqSMVoq?RSo43<8YpJM^+W$ zxy!A5>5Zl16Vi#?nAYywu3w_=KWnd3*QetocWt`3pK67>)ZVwnT3h zbPdD&MZkD?q=-N`MpCCwpM74L+Tr1aa)zJ)8G;(Pg51@U&5W>aNu9rA`bh{vgfE={ zdJ>aKc|2Ayw_bop+dK?Y5$q--WM*+$9&3Q9BBiwU8L<-`T6E?ZC`mT0b}%HR*LPK} z!MCd_Azd{36?Y_>yN{U1w5yrN8q`z(Vh^RnEF+;4b|2+~lfAvPT!`*{MPiDioiix8 zY*GdCwJ{S(5(HId*I%8XF=pHFz<9tAe;!D5$Z(iN#jzSql4sqX5!7Y?q4_%$lH zz8ehZuyl0K=E&gYhlfFWabnSiGty$>md|PpU1VfaC5~kskDnZX&Yu}?-h;OSav=8u z=e3Yq=mi$4A|sB-J00;1d{Sd1+!v0NtU((Nz2;PFFlC}V{@p&4wGcVhU&nI($RAS! zwXn7)?8~1J3*4+VccRSg5JS<(bBhBM&{ELMD4C_NTpvzboH!{Zr*%HP;{UqxI#g&7 zOAqPSW5Qus$8-xtTvD%h{Tw<2!XR(lU54LZG{)Cah*LZbpJkA=PMawg!O>X@&%+5XiyeIf91n2E*hl$k-Y(3iW*E}Mz-h~H~7S9I1I zR#-j`|Hk?$MqFhE4C@=n!hN*o5+M%NxRqP+aLxDdt=wS6rAu6ECK*;AB%Nyg0uyAv zO^DnbVZZo*|Ef{nsYN>cjZC$OHzR_*g%T#oF zCky9HJS;NCi=7(07tQXq?V8I&OA&kPlJ_dfSRdL2bRUt;tA3yKZRMHMXH&#W@$l%-{vQd7y@~i*^qnj^`Z{)V$6@l&!qP_y zg2oOd!Wit#)2A~w-eqw3*Mbe)U?N|q6sXw~E~&$!!@QYX4b@%;3=>)@Z#K^`8~Aki z+LYKJu~Y$;F5%_0aF9$MsbGS9Bz2~VUG@i@3Fi2q(hG^+Ia44LrfSfqtg$4{%qBDM z_9-O#3V+2~W$dW0G)R7l_R_vw(KSkC--u&%Rs^Io&*?R=`)6BN64>6>)`TxyT_(Rd zUn+aIl1mPa#Jse9B3`!T=|e!pIp$(8ZOe0ao?nS7o?oKlj zypC-fMj1DHIDrh1unUI1vp=-Fln;I9e7Jvs3wj*^_1&W|X} zZSL|S|Bb@CV*YC_-T&2!Ht3b6?)d`tHOP?rA;;t#zaXa0Sc;vGnV0BLIf8f-r{QHh z*Zp`4_ItlOR7{u(K+!p_oLDmaAkNag*l4#29F2b_A*0oz0T|#-&f*;c#<`^)(W@gm z#k9k=t%u8<+C1fNUA{Fh7~wgPrEZZ#(6aBI%6bR4RO(e1(ZocjoDek4#MTgZD>1NG zy9~yoZfWYfwe&S-(zk4o6q6o?2*~DOrJ(%5wSnEJMVOKCzHd z=Yhm+HLzoDl{P*Ybro7@sk1!Ez3`hE+&qr7Rw^2glw^M(b(NS2!F|Q!mi|l~lF94o z!QiV)Q{Z>GO5;l1y!$O)=)got;^)%@v#B!ZEVQy1(BJApHr5%Zh&W|gweD+%Ky%CO ztr45vR*y(@*Dg_Qw5v~PJtm^@Lyh*zRuT6~(K+^HWEF{;R#L$vL2!_ndBxCtUvZ(_ zauI7Qq}ERUWjr&XW9SwMbU>*@p)(cuWXCxRK&?ZoOy>2VESII53iPDP64S1pl{NsC zD;@EGPxs&}$W1;P6BB9THF%xfoLX|4?S;cu@$)9OdFst-!A7T{(LXtdNQSx!*GUSIS_lyI`da8>!y_tpJb3Zuf0O*;2y?HCfH z5QT6@nL|%l3&u4;F!~XG9E%1YwF*Fgs5V&uFsx52*iag(?6O|gYCBY3R{qhxT-Etb zq(E%V=MgQnuDGEKOGsmBj9T0-nmI%zys8NSO>gfJT4bP>tI>|ol@ zDt(&SUKrg%cz>AmqtJKEMUM;f47FEOFc%Bbmh~|*#E zDd!Tl(wa)ZZIFwe^*)4>{T+zuRykc3^-=P1aI%0Mh}*x7%SP6wD{_? zisraq`Las#y-6{`y@CU3Ta$tOl|@>4qXcB;1bb)oH9kD6 zKym@d$ zv&PZSSAV1Gwwzqrc?^_1+-ZGY+3_7~a(L+`-WdcJMo>EWZN3%z4y6JyF4NR^urk`c z?osO|J#V}k_6*9*n2?j+`F{B<%?9cdTQyVNm8D}H~T}?HOCXt%r7#2hz97Gx#X%62hyaLbU z_ZepP0<`<;eABrHrJAc!_m?kmu#7j}{empH@iUIEk^jk}^EFwO)vd7NZB=&uk6JG^ zC>xad8X$h|eCAOX&MaX<$tA1~r|hW?-0{t4PkVygTc`yh39c;&efwY(-#;$W)+4Xb z$XFsdG&;@^X`aynAMxsq)J#KZXX!sI@g~YiJdHI~r z$4mj_?S29sIa4c$z)19JmJ;Uj?>Kq=0XuH#k#};I&-6zZ_&>)j>UR0XetRO!-sjF< zd_6b1A2vfi++?>cf}s{@#BvTD|a%{9si7G}T+8ZnwuA z1k8c%lgE<-7f~H`cqgF;qZ|$>R-xNPA$25N1WI3#n%gj}4Ix}vj|e=x)B^roGQpB) zO+^#nO2 zjzJ9kHI6nI5ni&V_#5> z!?<7Qd9{|xwIf4b0bRc;zb}V4>snRg6*wl$Xz`hRDN8laL5tg&+@Dv>U^IjGQ}*=XBnXWrwTy;2nX?<1rkvOs#u(#qJ=A zBy>W`N!?%@Ay=upXFI}%LS9bjw?$h)7Dry0%d}=v0YcCSXf9nnp0tBKT1eqZ-4LU` zyiXglKRX)gtT0VbX1}w0f2ce8{$WH?BQm@$`ua%YP8G@<$n13D#*(Yd5-bHfI8!on zf5q4CPdgJLl;BqIo#>CIkX)G;rh|bzGuz1N%rr+5seP${mEg$;uQ3jC$;TsR&{IX< z;}7j3LnV+xNn^$F1;QarDf6rNYj7He+VsjJk6R@0MAkcwrsq4?(~`GKy|mgkfkd1msc2>%B!HpZ~HOzj}kl|ZF(IqB=D6ZTVcKe=I7)LlAI=!XU?J*i#9VXeKeaG zwx_l@Z(w`)5Cclw`6kQKlS<;_Knj)^Dh2pL`hQo!=GPOMR0iqEtx12ORLpN(KBOm5 zontAH5X5!9WHS_=tJfbACz@Dnkuw|^7t=l&x8yb2a~q|aqE_W&0M|tI7@ilGXqE)MONI8p67OiQGqKEQWw;LGga=ZM1;{pSw1jJK_y$vhY6 ztFrV7-xf>lbeKH1U)j3R=?w*>(Yh~NNEPVmeQ8n}0x01$-o z2Jyjn+sXhgOz>AzcZ zAbJZ@f}MBS0lLKR=IE{z;Fav%tcb+`Yi*!`HTDPqSCsFr>;yt^^&SI2mhKJ8f*%ji zz%JkZGvOn{JFn;)5jf^21AvO-9nRzsg0&CPz;OEn07`CfT@gK4abFBT$Mu?8fCcscmRkK+ zbAVJZ~#_a z{|(FFX}~8d3;DW8zuY9?r#Dt>!aD>} zlYw>D7y#eDy+PLZ&XKIY&Df0hsLDDi(Yrq8O==d30RchrUw8a=Eex>Dd?)3+k=}Q> z-b85lun-V$I}86Vg#l1S@1%=$2BQD5_waAZKQfJ${3{b2SZ#w1u+jMr{dJMvI|Og= zpQ9D={XK|ggbe04zTUd}iF{`GO1dV%zWK~?sM9OM(= zVK9&y4F^w1WFW{$qi|xQk0F`@HG8oLI5|5$j~ci9xTMT69v5KS-Yym--raU5kn2#C z<~5q^Bf0rTXVhctG2%&MG(cUGaz(gC(rcG~>qgO$W6>!#NOVQJ;pIYe-lLy(S=HgI zPh;lkL$l+FfMHItHnw_^bj8}CKM19t(C_2vSrhX2$K@-gFlH};#C?1;kk&U1L%4S~ zR^h%h+O1WE7DI$~dly?-_C7>(!E`~#REJ~Xa7lyrB$T!`&qYV5QreAa^aKr%toUJR zPWh)J3iD`(P6BI5k$oE$us#%!4$>`iH2p-88?WV0M$-K)JDibvA4 zpef%_*txN$Ei3=Lt(BBxZ&mhl|mUz-z*OD1=r9nfN zc5vOMFWpi>K=!$6f{eb?5Ru4M3o;t9xLpry|C%j~`@$f)OFB5+xo8XM8g&US@UU-sB|dAoc20y(F@=-2Ggp_`SWjEb#>IG^@j zuQK}e^>So#W2%|-)~K!+)wdU#6l>w5wnZt2pRL5Dz#~N`*UyC9tYechBTc2`@(OI# zNvcE*+zZZjU-H`QOITK^tZwOyLo)ZCLk>>Wm+flMsr5X{A<|m`Y281n?8H_2Fkz5}X?i%Rfm5s+n`J zDB&->=U+LtOIJ|jdYXjQWSQZFEs>Rm{`knop4Sq)(}O_@gk{14y51)iOcGQ5J=b#e z2Yx^6^*F^F7q_m-AGFFgx5uqyw6_4w?yKCJKDGGprWyekr;X(!4CnM5_5?KgN=3qCm03 z##6k%kIU5%g!cCL(+aK>`Wd;dZ4h$h_jb7n?nqx5&o9cUJfr%h#m4+Bh)>HodKcDcsXDXwzJ3jR(sSFqWV(OKHC*cV8;;&bH=ZI0YbW3PgIHwTjiWy z?2MXWO2u0RAEEq(zv9e%Rsz|0(OKB?_3*kkXwHxEuazIZ7=JhaNV*P~hv57q55LoebmJpfHXA@yuS{Esg+ z*C}0V-`x^=0nOa@SPUJek>td~tJ{U1T&m)~`FLp*4DF77S^{|0g%|JIqd-=5)p6a` zpJOsEkKT(FPS@t^80V!I-YJbLE@{5KmVXjEq{QbCnir%}3 zB)-J379=wrBNK6rbUL7Mh^tVmQYn-BJJP=n?P&m-7)P#OZjQoK0{5?}XqJScV6>QX zPR>G{xvU_P;q!;S9Y7*07=Z!=wxIUorMQP(m?te~6&Z0PXQ@I=EYhD*XomZ^z;`Os z4>Uh4)Cg2_##mUa>i1Dxi+R~g#!!i{?SMj%9rfaBPlWj_Yk)lCV--e^&3INB>I?lu z9YXCY5(9U`3o?w2Xa5ErMbl5+pDVpu8v+KJzI9{KFk1H?(1`_W>Cu903Hg81vEX32l{nP2vROa1Fi!Wou0+ZX7Rp`g;B$*Ni3MC-vZ`f zFTi7}c+D)!4hz6NH2e%%t_;tkA0nfkmhLtRW%){TpIqD_ev>}#mVc)<$-1GKO_oK8 zy$CF^aV#x7>F4-J;P@tqWKG0|D1+7h+{ZHU5OVjh>#aa8+V;6BQ)8L5k9t`>)>7zr zfIlv77^`Fvm<)_+^z@ac%D&hnlUAFt8!x=jdaUo{)M9Ar;Tz5Dcd_|~Hl6CaRnK3R zYn${wZe8_BZ0l0c%qbP}>($jsNDay>8+JG@F!uV4F;#zGsBP0f$f3HqEHDz_sCr^q z1;1}7KJ9&`AX2Qdav1(nNzz+GPdEk5K3;hGXe{Hq13{)c zZy%fFEEH#nlJoG{f*M^#8yXuW%!9svN8ry-Vi7AOFnN~r&D`%6d#lvMXBgZkX^vFj z;tkent^62jUr$Cc^@y31Lka6hS>F?1tE8JW$iXO*n9CQMk}D*At3U(-W1E~z>tG?> z5f`5R5LbrhRNR8kv&5d9SL7ke2a*Xr)Qp#75 z6?-p035n2<7hK;sb>t9GAwG4{9v~iEIG>}7B5zcCgZhu$M0-z8?eUO^E?g)md^XT_ z2^~-u$yak>LBy(=*GsTj6p<>b5PO&un@5hGCxpBQlOB3DpsItKZRC*oXq-r{u}Wb; z&ko>#fbnl2Z;o@KqS-d6DTeCG?m1 z&E>p}SEc*)SD&QjZbs!Csjx~0+$@ekuzV_wAalnQvX3a^n~3ui)|rDO+9HW|JPEeBGP4 z)?zcZ<8qv47`EWA*_X~H^vr(lP|f%=%cWFM;u)OFHruKT<~?>5Y8l?56>&;=WdZU# zZEK4-C8s-3zPMA^&y~e*9z)!ZJghr3N^pJa2A$??Xqx-BR*TytGYor&l8Q+^^r%Yq02xay^f#;;wO6K7G!v>wRd6531WnDI~h$PN( z+4#08uX?r&zVKsQ;?5eBX=FxsXaGyH4Gth4a&L|{8LnNCHFr1M{KjJ!BfBS_aiy-E zxtmNcXq3}WTwQ7Dq-9YS5o758sT(5b`Sg-NcH>M9OH1oW6&sZ@|GYk|cJI`vm zO<$~q!3_$&GfWetudRc*mp8)M)q7DEY-#@8w=ItkApfq3sa)*GRqofuL7)dafznKf zLuembr#8gm*lIqKH)KMxSDqbik*B(1bFt%3Vv|ypehXLCa&wc7#u!cJNlUfWs8iQ` z$66(F=1fkxwg745-8_eqV>nWGY3DjB9gE23$R5g&w|C{|xvT@7j*@aZNB199scGchI7pINb5iyqYn)O=yJJX)Ca3&Ca+{n<=1w|(|f0)h<9gs$pVSV<<9Og-V z8ki@nKwE)x)^wmHBMk?mpMT=g{S#^8W|>&rI#Ceh;9za}io0k@0JxiCqi-jHlxbt3 zjJA?RihhRvhk6%G5-D{ePh1jare*fQS<328P-DcVAxPTrw=n6k?C6EV75f}cnBRPT zMYDqqKu(ND&aOtc!QRV`vzJSVxx8i~WB#5Ml{b#eQqNnSi7l-bS-`ITW<^zyYQA(b zbj4SuRK>q9o`_v%+C=S?h>2e4!66Ij(P5{7Uz$3u6YJJC$W%EoBa{-(=tQ|y1vov%ZkXVOV z##_UVg4V^4ne#4~<-1DkJqkKqgT+E_=&4Ue&eQ-JC+gi?7G@d6= zximz{zE)WW{b@QCJ!7l&N5x=dXS?$5RBU-VvN4Uec-GHK&jPa&P2z+qDdLhIB+HU) zu0CW&uLvE^4I5xtK-$+oe|58)7m6*PO%Xt<+-XEA%jG_BEachkF3e@pn?tl!`8lOF zbi2QOuNXX)YT*MCYflILO{VZ*9GiC%R4FO20zMK?p+&aCMm2oeMK7(aW=UDzr=AO0 z$5mJ%=qRsR8rZ>_YsL+vi{3*J_9Kzq(;ZwRj+4_f0-*wbkSMPWahX#Fj_a8BnrhJ6 zo^ZZ?Vah1@&6#r=JkuaYDBdp;J3@ii+CHM&@9*er&#P}$@wI$bfrH)&c!*|nkvhf%^*Y6b%dKz%QBSIo@U z{?V^qEs4`q<8@n+u8YiB^sc@6g>TncG<|GsmC3egwE6aO=EwLr~3-2 zNr`+)`i+-83?|1Xy0^8ps&pb}YT?w1eWVnC9Ps1=KM;Rw)bH6O!7Did1NwpnqVPZc z*%Qo~qkDL>@^<^fmIBtx$WUWQiNtAB2x-LO^BB=|w~-zTnJNEdm1Ou(?8PF&U88X@ z#8rdaTd||)dG^uJw~N_-%!XNbuAyh4`>Shea=pSj0TqP+w4!`nxsmVSv02kb`DBr% zyX=e>5IJ3JYPtdbCHvKMdhXUO_*E9jc_?se7%VJF#&ZaBD;7+eFN3x+hER7!u&`Wz z7zMvBPR4y`*$a250KYjFhAKS%*XG&c;R-kS0wNY1=836wL6q02mqx;IPcH(6ThA@2 zXKQF|9H>6AW$KUF#^A%l6y5{fel77_+cR_zZ0(7=6bmNXABv}R!B-{(E^O6Y?ZS)n zs1QEmh_Fm7p}oRyT3zxUNr4UV8NGs+2b8|4shO$OGFj3D&7_e?#yDi=TTe%$2QbG5 zk<;q7aQ;p!M-Osm{vFdmXZ@!z9uWh!;*%>(vTRggufuUGP9Hols@vhx z73pn$3u2;vzRvnXuT&$Os7J@6y12*j!{ix%3B4YU1466ItmJs0NsU(4ZYRYh7wEA6q{b*Hs6@k~ zi7Yq@Ax!et0cUMTvk7P%ym){MHpcliHEI~e3HP0NV=}7;xFv#IC?a<=`>~j_sk{e> z7vg-tK*p83HZ0=QK@ zRIHo^r{D8&Ms-^WZp+6US_Quqjh$Q66W^1}=Uz&XJ8AQE9&2}P zY|FXZzZ|0IiaBd2qdt6dIjQr(ZMIOU%NG1F&fu6Po9m^?BvLhI6T0R!H2d8;U(&p2 zYA|MFscMqcO(ye~Jp?F;0>Ke+5hzVr?aBNe>GsGgr$XrpS9uajN2kNQ3o$V5rp0T( z0$6TJC;3)26SNG#XcX7l^MKTn$ga?6r4Jzfb%ZgA(Zbwit0$kY=avSnI$@Gk%+^pu zS5mHrcRS8LFPC*uVWH4DDD1pY$H8N>X?KIJZuZ2SvTqc5Nr0GHdD8TCJcd$zIhOdC zZX0ErnsozQh;t^==4zTfrZO421AL?)O)l#GSxU#|LTTg4#&yeK=^w#;q63!Nv~1(@ zs^-RNRuF&qgcr+bIzc@7$h9L;_yjdifE*$j0Q&Np=1AuHL--zdkv@}`1 zo~LlDl_YAq*z?vmr4M`GjDkl9?p|-tl(DtX76oZv25_DtZutLS9Ez!5~p?th@4 zyc_uax4W#<(#)LMkvo)yp|5tKsC2=p#6PyhpH|449T<9Zdk|%CAb5cw?fhvQtBO&7 zpQ9$24yLqPHP;$N&fe2wm%8qdctwIna<3SwGtQA3{C77s%CW%LYxtK(SBGustL0<( zu~U9r0UOkr(c{OJxZS0Ntu3+cJlF7R`7k-Bsa&q?9Ae5{{|o~?cM+T7{lB1^#vT8R z?>c9fNWey`1dKDY%F3d2O*8^qYhjlB8*7HMKE<*=(A`{>=1%s1}Pm&#_t1xy!FkPk@%SMEka2@*= zxDuM|vJJ5s+xgDls{>*o!7eOcs|xuVBPWX&+y5vEiADK%hi`#Dbd>;;Pbk2H4*-X&R?_-6ZEutSd8hC+sSjhIo z;D(j4P;2EVpEj#UF7IjM6PC+X$C5T&=nL`*!*hm9U)#O?>wqOgC>jXKN3Slk_yaQX zLf|4D8T4k|wHW`;#ZQVocNF|3izi0sOqXzi7@KlYC3CXBG`94wD;tMI1bj|8Vm zY}9`VI9!plSfhAal$M_HlaYOVNU?9Z#0<$o?lXXbX3O(l_?f)i3_~r+GcO-x#+x^X zfsZl0>Rj2iP1rsT;+b;Mr? z4Vu&O)Q5ru4j;qaSP5gA{az@XTS1NpT0d9Xhl_FkkRpcEGA0(QQ~YMh#&zwDUkNzm z6cgkdgl9W{iL6ArJ1TQHqnQ^SQ1WGu?FT|93$Ba}mPCH~!$3}0Y0g zcoG%bdTd$bmBx9Y<`Jc+=Cp4}c@EUfjiz;Rcz101p z=?#i$wo>gBE9|szaZMt-d4nUIhBnYRuBVyx+p?5#aZQgUe(!ah`J#l1$%bl5avL27 zU2~@V`3Ic&!?FhDX@Cw!R4%xtWark#p8DLT)HCZ?VJxf^yr@AD*!ERK3#L$E^*Yr? zzN&uF9Roh4rP+r`Z#7U$tzl6>k!b~HgM$C<_crP=vC>6=q{j?(I}!9>g3rJU(&){o z`R^E*9%+kEa8H_fkD9VT7(Fks&Y-RcHaUJYf-|B+eMXMaRM;{FKRiTB>1(=Iij4k1(X__|WqAd-~t#2@UQ}Z&<1Th0azdXfoll!dd)6>1miA z!&=6sDJm=e$?L&06+Q3`D-HNSkK-3$3DdZMX-6Xjn;wd#9A{~ur!2NcX>(qY_oZL0~H7dnQ9sgLe!W>~2|RSW7|hWn<({Pg*xF$%B-!rKe^_R_vc z(LO!0agxxP;FWPV({8#lEv$&&GVakGus=@!3YVG`y^AO1m{2%Np;>HNA1e{=?ra1C}H zAwT0sbwG|!am;fl?*_t^^#yLDXZ*Nx)_FqueZi0c-G~omtpHW0Cu)mEJ`Z1X8brq$ z%vK##b~o*^b&Hz!hgrD=^6P8}aW40lhzMLB5T5*v`1QH?+L~-@CDi3+C@nRf2{7UE zyDIe{@LKw`Eu=Z%6<<_=#V|yxJIKiq_N?ZJ_v0$c)N4l07ZV_mIXG}glfBSPivOhw z-~+9GdckSpMBNR9eR`Y|9_)sXS+u_OiQ%!9rE(2AFjoxN8lk16Sb~^Sq6kRoEp3yD(mm`HsYIXcag_EAB8MHc}nahxVVUTts~U9P|f;7Ul$_` zStR4v&P4q_$KXOEni$lkxy8=9w8G&47VY0oDb^+jT+>ARe3NHUg~St`$RDxY)?;_F znqTujR&chZd2qHF7y8D$4&E3+e@J~!X3&BW4BF(Ebp#TEjrd+9SU!)j;qH+ZkL@AW z?J6Mj}v0_+D zH0qlbzCkHf|EZ`6c>5ig5NAFF%|La%M-}g(7&}Vx8K)qg30YD;H!S!??{;YivzrH0 z(M%2*b_S-)yh&Aiqai)GF^c!<1Xemj|13>dZ_M#)41SrP;OEMaRJ)bCeX*ZT7W`4Y zQ|8L@NHpD@Tf(5>1U(s5iW~Zdf7$@pAL`a3X@YUv1J>q-uJ_(Dy5nYTCUHC}1(dlI zt;5>DLcHh&jbysqt?G01MhXI3!8wgf){Hv}=0N|L$t8M#L7d6WscO8Om2|NBz2Ga^ zs86y%x$H18)~akOWD7@em7)ldlWgb?_sRN>-EcYQO_}aX@+b$dR{146>{kXWP4$nN{V0_+|3{Lt|8uX_fhKh~i{(x%cj*PU$i{PO(5$uA? zQzO>a6oPj-TUk&{zq?JD2MNb6Mf~V3g$ra+PB;ujLJ2JM(a7N*b`y{MX--!fAd}5C zF$D_b8S;+Np(!cW)(hnv5b@@|EMt*RLKF*wy>ykFhEhlPN~n_Bj>LT9B^_yj>z#fx z3JuE4H&?Cc!;G@}E*3k`HK#8ag`yE3Z1)5JUlSua%qkF zkTu|<9{w9OSi$qr)WD#7EzITnch=xnR63E*d~WGvi*Co9BBE?ETHud;!Z)7&wz+l6 zuKODYG1>I1U#a%&(GNJ`AqRfg=H!BtSl+_;CEeufF-#+*2EMMz-22@>18=8PH{PHd z);mN=aR0MPF>eutLiS#-AOX>#2%+pTGEOj!j4L(m0~&xR=0+g#HNpno6@veLhJp}e zyNVC$a>4;!9&iGvU_dj&xbKt@^t6r%f^)+}eV^suRTLP52+BVs0kOLwg6n`=NUv50E7My8XQUh?y%mW62OT1pMrKI3Q(r`7vU&@93=G~A?b(^pvC-8x=bSk zZ60BQR96WB1Z@9Df(M1IQh+YrU8sEjB=Tc2;(zBn-pete*icZE|M&Uc+oHg`|1o`g zH~m+k=D$o);{Rs)b<9Zo|9_Z6L6QHLNki(N>Dw^^i1LITprZeeqIaT#+)fw)PlllU zldphHC)t!0Gf(i9zgVm>`*TbmITF zH1FZ4{wrjRCx{t^26VK_2srZuWuY*EMAsMrJYFFCH35Ky7bq8<0K|ey2wHnrFMZyr z&^yEgX{{3i@&iE5>xKZ{Ads36G3a!i50D!C4?^~cLB<<|fc1!XN(HJRM)H^21sEs%vv+Mu0h*HkLHaEffMwc0n6)JhNXY#M5w@iO@dfXY z0c6dM2a4Hd1SA*#qYj@jK}uVgAZdaBj8t6uuhUNe>)ne9vfd#C6qLV9+@Q7{MnF#0 zJ7fd-ivG_~u3bVvOzpcw1u~ZSp8-kl(sunnX>L~*K-ByWDM2E8>;Si6kn^58AZQxI xVa^It*?521mj4+UJO?7%w*+`EfEcU=@KhDx-s^WzP+ae~{CgHDE&XryzW}Nww%-5% diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4ed3bdea4..5116c5b18 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip -distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c78733..a69d9cb6c 100755 --- a/gradlew +++ b/gradlew @@ -205,6 +205,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..53a6b238d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 7f21f6e80e7b452d0a37797538da14bd80d3fcc6 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Tue, 9 Aug 2022 19:19:03 -0400 Subject: [PATCH 115/152] Update AGP and clojars Maven URL --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 322a47a6f..739f2e618 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:7.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -20,6 +20,6 @@ allprojects { google() mavenCentral() maven { url "https://jitpack.io" } - maven { url "https://clojars.org/repo" } + maven { url "https://repo.clojars.org" } } } From 2de33d8d075d17eb9885dd2c1302c80aa42a14c9 Mon Sep 17 00:00:00 2001 From: mhmdanas Date: Thu, 11 Aug 2022 19:24:14 +0300 Subject: [PATCH 116/152] Clarify that span shouldn't be in translated READMEs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3a34816d..52e6eef1a 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Also, since they are free and open source software, neither the app nor the Extr * Open in Kodi * Watch/Block age-restricted material - + ## Installation and updates From 737a331c85ba8387535c14e5882f46c336cf8061 Mon Sep 17 00:00:00 2001 From: mhmdanas Date: Thu, 11 Aug 2022 19:34:23 +0300 Subject: [PATCH 117/152] Remove extra whitespace from issue and PR templates --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +++--- .github/ISSUE_TEMPLATE/feature_request.yml | 3 +-- .github/ISSUE_TEMPLATE/question.yml | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3abb1fbb1..37ce61376 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -40,7 +40,7 @@ body: label: Steps to reproduce the bug description: | What did you do for the bug to show up? - + If you can't cause the bug to show up again reliably (and hence don't have a proper set of steps to give us), please still try to give as many details as possible on how you think you encountered the bug. placeholder: | 1. Go to '...' @@ -69,11 +69,11 @@ body: label: Screenshots/Screen recordings description: | A picture or video is worth a thousand words. - + If applicable, add screenshots or a screen recording to help explain your problem. GitHub supports uploading them directly in the text box. If your file is too big for Github to accept, try to compress it (ZIP-file) or feel free to paste a link to an image/video hoster here instead. - + :heavy_exclamation_mark: DON'T POST SCREENSHOTS OF THE ERROR PAGE. Instead, follow the instructions in the "Logs" section below. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9fc3c1632..7290d8c5b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -8,7 +8,6 @@ body: Thank you for helping to make NewPipe better by suggesting a feature. :hugs: Your ideas are highly welcome! The app is made for you, the users, after all. - - type: checkboxes id: checklist attributes: @@ -43,7 +42,7 @@ body: Describe any problem or limitation you come across while using the app which would be solved by this feature. validations: required: true - + - type: textarea id: additional-information attributes: diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 8cf22d8af..f78e2afc6 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -27,7 +27,7 @@ body: label: What is/are your question(s)? validations: required: true - + - type: textarea id: additional-information attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 10e40af2a..abc1665eb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -25,7 +25,7 @@ - -#### APK testing +#### APK testing The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR. From 4f6b5b3b89879a53039a868c2007bcbdbb3972f8 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 15:27:29 +0530 Subject: [PATCH 118/152] Use ListAdapter in PeertubeInstanceListFragment. --- .../PeertubeInstanceListFragment.java | 204 ++++++++---------- 1 file changed, 93 insertions(+), 111 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index 92b9a0362..1158b3d83 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -12,28 +12,27 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ProgressBar; import android.widget.RadioButton; -import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.AppCompatImageView; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.grack.nanojson.JsonStringWriter; import com.grack.nanojson.JsonWriter; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogEditTextBinding; +import org.schabi.newpipe.databinding.FragmentInstanceListBinding; +import org.schabi.newpipe.databinding.ItemInstanceBinding; import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.PeertubeHelper; @@ -41,7 +40,6 @@ import org.schabi.newpipe.util.ThemeHelper; import java.util.ArrayList; import java.util.Collections; -import java.util.List; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; @@ -50,12 +48,11 @@ import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; public class PeertubeInstanceListFragment extends Fragment { - private final List instanceList = new ArrayList<>(); private PeertubeInstance selectedInstance; private String savedInstanceListKey; private InstanceListAdapter instanceListAdapter; - private ProgressBar progressBar; + private FragmentInstanceListBinding binding; private SharedPreferences sharedPreferences; private CompositeDisposable disposables = new CompositeDisposable(); @@ -71,7 +68,6 @@ public class PeertubeInstanceListFragment extends Fragment { sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); savedInstanceListKey = getString(R.string.peertube_instance_list_key); selectedInstance = PeertubeHelper.getCurrentInstance(); - updateInstanceList(); setHasOptionsMenu(true); } @@ -79,7 +75,8 @@ public class PeertubeInstanceListFragment extends Fragment { @Override public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_instance_list, container, false); + binding = FragmentInstanceListBinding.inflate(inflater, container, false); + return binding.getRoot(); } @Override @@ -87,26 +84,17 @@ public class PeertubeInstanceListFragment extends Fragment { @Nullable final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); - initViews(rootView); - } - - private void initViews(@NonNull final View rootView) { - final TextView instanceHelpTV = rootView.findViewById(R.id.instanceHelpTV); - instanceHelpTV.setText(getString(R.string.peertube_instance_url_help, + binding.instanceHelpTV.setText(getString(R.string.peertube_instance_url_help, getString(R.string.peertube_instance_list_url))); - - initButton(rootView); - - final RecyclerView listInstances = rootView.findViewById(R.id.instances); - listInstances.setLayoutManager(new LinearLayoutManager(requireContext())); + binding.addInstanceButton.setOnClickListener(v -> showAddItemDialog(requireContext())); + binding.instances.setLayoutManager(new LinearLayoutManager(requireContext())); final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); - itemTouchHelper.attachToRecyclerView(listInstances); + itemTouchHelper.attachToRecyclerView(binding.instances); instanceListAdapter = new InstanceListAdapter(requireContext(), itemTouchHelper); - listInstances.setAdapter(instanceListAdapter); - - progressBar = rootView.findViewById(R.id.loading_progress_bar); + binding.instances.setAdapter(instanceListAdapter); + instanceListAdapter.submitList(PeertubeHelper.getInstanceList(requireContext())); } @Override @@ -131,6 +119,12 @@ public class PeertubeInstanceListFragment extends Fragment { disposables = null; } + @Override + public void onDestroyView() { + binding = null; + super.onDestroyView(); + } + /*////////////////////////////////////////////////////////////////////////// // Menu //////////////////////////////////////////////////////////////////////////*/ @@ -156,11 +150,6 @@ public class PeertubeInstanceListFragment extends Fragment { // Utils //////////////////////////////////////////////////////////////////////////*/ - private void updateInstanceList() { - instanceList.clear(); - instanceList.addAll(PeertubeHelper.getInstanceList(requireContext())); - } - private void selectInstance(final PeertubeInstance instance) { selectedInstance = PeertubeHelper.selectInstance(instance, requireContext()); sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply(); @@ -168,7 +157,7 @@ public class PeertubeInstanceListFragment extends Fragment { private void saveChanges() { final JsonStringWriter jsonWriter = JsonWriter.string().object().array("instances"); - for (final PeertubeInstance instance : instanceList) { + for (final PeertubeInstance instance : instanceListAdapter.getCurrentList()) { jsonWriter.object(); jsonWriter.value("name", instance.getName()); jsonWriter.value("url", instance.getUrl()); @@ -179,28 +168,21 @@ public class PeertubeInstanceListFragment extends Fragment { } private void restoreDefaults() { - new AlertDialog.Builder(requireContext()) + final Context context = requireContext(); + new AlertDialog.Builder(context) .setTitle(R.string.restore_defaults) .setMessage(R.string.restore_defaults_confirmation) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.ok, (dialog, which) -> { sharedPreferences.edit().remove(savedInstanceListKey).apply(); selectInstance(PeertubeInstance.DEFAULT_INSTANCE); - updateInstanceList(); - instanceListAdapter.notifyDataSetChanged(); + instanceListAdapter.submitList(PeertubeHelper.getInstanceList(context)); }) .show(); } - private void initButton(final View rootView) { - final FloatingActionButton fab = rootView.findViewById(R.id.addInstanceButton); - fab.setOnClickListener(v -> - showAddItemDialog(requireContext())); - } - private void showAddItemDialog(final Context c) { - final DialogEditTextBinding dialogBinding = - DialogEditTextBinding.inflate(getLayoutInflater()); + final var dialogBinding = DialogEditTextBinding.inflate(getLayoutInflater()); dialogBinding.dialogEditText.setInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); dialogBinding.dialogEditText.setHint(R.string.peertube_instance_add_help); @@ -222,17 +204,17 @@ public class PeertubeInstanceListFragment extends Fragment { if (cleanUrl == null) { return; } - progressBar.setVisibility(View.VISIBLE); + binding.loadingProgressBar.setVisibility(View.VISIBLE); final Disposable disposable = Single.fromCallable(() -> { final PeertubeInstance instance = new PeertubeInstance(cleanUrl); instance.fetchInstanceMetaData(); return instance; }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) .subscribe((instance) -> { - progressBar.setVisibility(View.GONE); + binding.loadingProgressBar.setVisibility(View.GONE); add(instance); }, e -> { - progressBar.setVisibility(View.GONE); + binding.loadingProgressBar.setVisibility(View.GONE); Toast.makeText(getActivity(), R.string.peertube_instance_add_fail, Toast.LENGTH_SHORT).show(); }); @@ -255,7 +237,7 @@ public class PeertubeInstanceListFragment extends Fragment { return null; } // only allow if not already exists - for (final PeertubeInstance instance : instanceList) { + for (final PeertubeInstance instance : instanceListAdapter.getCurrentList()) { if (instance.getUrl().equals(cleanUrl)) { Toast.makeText(getActivity(), R.string.peertube_instance_add_exists, Toast.LENGTH_SHORT).show(); @@ -266,8 +248,9 @@ public class PeertubeInstanceListFragment extends Fragment { } private void add(final PeertubeInstance instance) { - instanceList.add(instance); - instanceListAdapter.notifyDataSetChanged(); + final var list = new ArrayList<>(instanceListAdapter.getCurrentList()); + list.add(instance); + instanceListAdapter.submitList(list); } private ItemTouchHelper.SimpleCallback getItemTouchCallback() { @@ -281,8 +264,7 @@ public class PeertubeInstanceListFragment extends Fragment { final long msSinceStartScroll) { final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); - final int minimumAbsVelocity = Math.max(12, - Math.abs(standardSpeed)); + final int minimumAbsVelocity = Math.max(12, Math.abs(standardSpeed)); return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); } @@ -316,17 +298,19 @@ public class PeertubeInstanceListFragment extends Fragment { final int swipeDir) { final int position = viewHolder.getBindingAdapterPosition(); // do not allow swiping the selected instance - if (instanceList.get(position).getUrl().equals(selectedInstance.getUrl())) { + if (instanceListAdapter.getCurrentList().get(position).getUrl() + .equals(selectedInstance.getUrl())) { instanceListAdapter.notifyItemChanged(position); return; } - instanceList.remove(position); - instanceListAdapter.notifyItemRemoved(position); + final var list = new ArrayList<>(instanceListAdapter.getCurrentList()); + list.remove(position); - if (instanceList.isEmpty()) { - instanceList.add(selectedInstance); - instanceListAdapter.notifyItemInserted(0); + if (list.isEmpty()) { + list.add(selectedInstance); } + + instanceListAdapter.submitList(list); } }; } @@ -336,96 +320,94 @@ public class PeertubeInstanceListFragment extends Fragment { //////////////////////////////////////////////////////////////////////////*/ private class InstanceListAdapter - extends RecyclerView.Adapter { + extends ListAdapter { private final LayoutInflater inflater; private final ItemTouchHelper itemTouchHelper; private RadioButton lastChecked; InstanceListAdapter(final Context context, final ItemTouchHelper itemTouchHelper) { + super(new PeertubeInstanceCallback()); this.itemTouchHelper = itemTouchHelper; this.inflater = LayoutInflater.from(context); } public void swapItems(final int fromPosition, final int toPosition) { - Collections.swap(instanceList, fromPosition, toPosition); - notifyItemMoved(fromPosition, toPosition); + final var list = new ArrayList<>(getCurrentList()); + Collections.swap(list, fromPosition, toPosition); + submitList(list); } @NonNull @Override public InstanceListAdapter.TabViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - final View view = inflater.inflate(R.layout.item_instance, parent, false); - return new InstanceListAdapter.TabViewHolder(view); + return new InstanceListAdapter.TabViewHolder(ItemInstanceBinding.inflate(inflater, + parent, false)); } @Override public void onBindViewHolder(@NonNull final InstanceListAdapter.TabViewHolder holder, final int position) { - holder.bind(position, holder); - } - - @Override - public int getItemCount() { - return instanceList.size(); + holder.bind(position); } class TabViewHolder extends RecyclerView.ViewHolder { - private final AppCompatImageView instanceIconView; - private final TextView instanceNameView; - private final TextView instanceUrlView; - private final RadioButton instanceRB; - private final ImageView handle; + private final ItemInstanceBinding itemBinding; - TabViewHolder(final View itemView) { - super(itemView); - - instanceIconView = itemView.findViewById(R.id.instanceIcon); - instanceNameView = itemView.findViewById(R.id.instanceName); - instanceUrlView = itemView.findViewById(R.id.instanceUrl); - instanceRB = itemView.findViewById(R.id.selectInstanceRB); - handle = itemView.findViewById(R.id.handle); + TabViewHolder(final ItemInstanceBinding binding) { + super(binding.getRoot()); + this.itemBinding = binding; } @SuppressLint("ClickableViewAccessibility") - void bind(final int position, final TabViewHolder holder) { - handle.setOnTouchListener(getOnTouchListener(holder)); - - final PeertubeInstance instance = instanceList.get(position); - instanceNameView.setText(instance.getName()); - instanceUrlView.setText(instance.getUrl()); - instanceRB.setOnCheckedChangeListener(null); - if (selectedInstance.getUrl().equals(instance.getUrl())) { - if (lastChecked != null && lastChecked != instanceRB) { - lastChecked.setChecked(false); - } - instanceRB.setChecked(true); - lastChecked = instanceRB; - } - instanceRB.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) { - selectInstance(instance); - if (lastChecked != null && lastChecked != instanceRB) { - lastChecked.setChecked(false); - } - lastChecked = instanceRB; - } - }); - instanceIconView.setImageResource(R.drawable.ic_placeholder_peertube); - } - - @SuppressLint("ClickableViewAccessibility") - private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) { - return (view, motionEvent) -> { + void bind(final int position) { + itemBinding.handle.setOnTouchListener((view, motionEvent) -> { if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { if (itemTouchHelper != null && getItemCount() > 1) { - itemTouchHelper.startDrag(item); + itemTouchHelper.startDrag(this); return true; } } return false; - }; + }); + + final PeertubeInstance instance = getItem(position); + itemBinding.instanceName.setText(instance.getName()); + itemBinding.instanceUrl.setText(instance.getUrl()); + itemBinding.selectInstanceRB.setOnCheckedChangeListener(null); + if (selectedInstance.getUrl().equals(instance.getUrl())) { + if (lastChecked != null && lastChecked != itemBinding.selectInstanceRB) { + lastChecked.setChecked(false); + } + itemBinding.selectInstanceRB.setChecked(true); + lastChecked = itemBinding.selectInstanceRB; + } + itemBinding.selectInstanceRB.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + selectInstance(instance); + if (lastChecked != null && lastChecked != itemBinding.selectInstanceRB) { + lastChecked.setChecked(false); + } + lastChecked = itemBinding.selectInstanceRB; + } + }); + itemBinding.instanceIcon.setImageResource(R.drawable.ic_placeholder_peertube); } } } + + private static class PeertubeInstanceCallback extends DiffUtil.ItemCallback { + @Override + public boolean areItemsTheSame(@NonNull final PeertubeInstance oldItem, + @NonNull final PeertubeInstance newItem) { + return oldItem.getUrl().equals(newItem.getUrl()); + } + + @Override + public boolean areContentsTheSame(@NonNull final PeertubeInstance oldItem, + @NonNull final PeertubeInstance newItem) { + return oldItem.getName().equals(newItem.getName()) + && oldItem.getUrl().equals(newItem.getUrl()); + } + } } From 67669c286b4bad85fef0854ceb3cb3a3f45e471d Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 15:53:04 +0530 Subject: [PATCH 119/152] Use ListAdapter in SuggestionListAdapter. --- .../fragments/list/search/SearchFragment.java | 10 +-- .../list/search/SuggestionListAdapter.java | 88 +++++++------------ 2 files changed, 37 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 008163890..6827ddaaf 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -200,7 +200,7 @@ public class SearchFragment extends BaseListFragment()); + suggestionListAdapter.submitList(null); showKeyboardSearch(); }); @@ -945,7 +945,7 @@ public class SearchFragment extends BaseListFragment suggestionListAdapter.setItems(suggestions)); + suggestionListAdapter.submitList(suggestions); if (suggestionsPanelVisible && isErrorPanelVisible()) { hideLoading(); @@ -1066,14 +1066,14 @@ public class SearchFragment extends BaseListFragment { - private final ArrayList items = new ArrayList<>(); - private final Context context; + extends ListAdapter { private OnSuggestionItemSelected listener; - public SuggestionListAdapter(final Context context) { - this.context = context; - } - - public void setItems(final List items) { - this.items.clear(); - this.items.addAll(items); - notifyDataSetChanged(); + public SuggestionListAdapter() { + super(new SuggestionItemCallback()); } public void setListener(final OnSuggestionItemSelected listener) { @@ -39,45 +27,32 @@ public class SuggestionListAdapter @Override public SuggestionItemHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - return new SuggestionItemHolder(LayoutInflater.from(context) - .inflate(R.layout.item_search_suggestion, parent, false)); + return new SuggestionItemHolder(ItemSearchSuggestionBinding + .inflate(LayoutInflater.from(parent.getContext()), parent, false)); } @Override public void onBindViewHolder(final SuggestionItemHolder holder, final int position) { final SuggestionItem currentItem = getItem(position); holder.updateFrom(currentItem); - holder.queryView.setOnClickListener(v -> { + holder.itemBinding.suggestionSearch.setOnClickListener(v -> { if (listener != null) { listener.onSuggestionItemSelected(currentItem); } }); - holder.queryView.setOnLongClickListener(v -> { + holder.itemBinding.suggestionSearch.setOnLongClickListener(v -> { if (listener != null) { listener.onSuggestionItemLongClick(currentItem); } return true; }); - holder.insertView.setOnClickListener(v -> { + holder.itemBinding.suggestionInsert.setOnClickListener(v -> { if (listener != null) { listener.onSuggestionItemInserted(currentItem); } }); } - SuggestionItem getItem(final int position) { - return items.get(position); - } - - @Override - public int getItemCount() { - return items.size(); - } - - public boolean isEmpty() { - return getItemCount() == 0; - } - public interface OnSuggestionItemSelected { void onSuggestionItemSelected(SuggestionItem item); @@ -87,30 +62,31 @@ public class SuggestionListAdapter } public static final class SuggestionItemHolder extends RecyclerView.ViewHolder { - private final TextView itemSuggestionQuery; - private final ImageView suggestionIcon; - private final View queryView; - private final View insertView; + private final ItemSearchSuggestionBinding itemBinding; - // Cache some ids, as they can potentially be constantly updated/recycled - private final int historyResId; - private final int searchResId; - - private SuggestionItemHolder(final View rootView) { - super(rootView); - suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon); - itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query); - - queryView = rootView.findViewById(R.id.suggestion_search); - insertView = rootView.findViewById(R.id.suggestion_insert); - - historyResId = R.drawable.ic_history; - searchResId = R.drawable.ic_search; + private SuggestionItemHolder(final ItemSearchSuggestionBinding binding) { + super(binding.getRoot()); + this.itemBinding = binding; } private void updateFrom(final SuggestionItem item) { - suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId); - itemSuggestionQuery.setText(item.query); + itemBinding.itemSuggestionIcon.setImageResource(item.fromHistory ? R.drawable.ic_history + : R.drawable.ic_search); + itemBinding.itemSuggestionQuery.setText(item.query); + } + } + + private static class SuggestionItemCallback extends DiffUtil.ItemCallback { + @Override + public boolean areItemsTheSame(@NonNull final SuggestionItem oldItem, + @NonNull final SuggestionItem newItem) { + return oldItem.query.equals(newItem.query); + } + + @Override + public boolean areContentsTheSame(@NonNull final SuggestionItem oldItem, + @NonNull final SuggestionItem newItem) { + return oldItem.equals(newItem); } } } From 5e0788b99c7b0a43936f179cb94273d5046081c3 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 1 Aug 2022 06:11:35 +0530 Subject: [PATCH 120/152] Use ListAdapter in PreferenceSearchAdapter. --- .../PreferenceSearchAdapter.java | 61 +++++++++---------- .../PreferenceSearchFragment.java | 11 +--- .../preferencesearch/PreferenceSearcher.java | 3 +- 3 files changed, 34 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java index 02fbf9577..d6e2021a1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java @@ -1,54 +1,48 @@ package org.schabi.newpipe.settings.preferencesearch; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.databinding.SettingsPreferencesearchListItemResultBinding; -import java.util.ArrayList; -import java.util.List; import java.util.function.Consumer; class PreferenceSearchAdapter - extends RecyclerView.Adapter { - private List dataset = new ArrayList<>(); + extends ListAdapter { private Consumer onItemClickListener; + PreferenceSearchAdapter() { + super(new PreferenceCallback()); + } + @NonNull @Override - public PreferenceViewHolder onCreateViewHolder( - @NonNull final ViewGroup parent, - final int viewType - ) { - return new PreferenceViewHolder( - SettingsPreferencesearchListItemResultBinding.inflate( - LayoutInflater.from(parent.getContext()), - parent, - false)); + public PreferenceViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, + final int viewType) { + return new PreferenceViewHolder(SettingsPreferencesearchListItemResultBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); } @Override - public void onBindViewHolder( - @NonNull final PreferenceViewHolder holder, - final int position - ) { - final PreferenceSearchItem item = dataset.get(position); + public void onBindViewHolder(@NonNull final PreferenceViewHolder holder, final int position) { + final PreferenceSearchItem item = getItem(position); holder.binding.title.setText(item.getTitle()); - if (TextUtils.isEmpty(item.getSummary())) { + if (item.getSummary().isEmpty()) { holder.binding.summary.setVisibility(View.GONE); } else { holder.binding.summary.setVisibility(View.VISIBLE); holder.binding.summary.setText(item.getSummary()); } - if (TextUtils.isEmpty(item.getBreadcrumbs())) { + if (item.getBreadcrumbs().isEmpty()) { holder.binding.breadcrumbs.setVisibility(View.GONE); } else { holder.binding.breadcrumbs.setVisibility(View.VISIBLE); @@ -62,16 +56,6 @@ class PreferenceSearchAdapter }); } - void setContent(final List items) { - dataset = new ArrayList<>(items); - this.notifyDataSetChanged(); - } - - @Override - public int getItemCount() { - return dataset.size(); - } - void setOnItemClickListener(final Consumer onItemClickListener) { this.onItemClickListener = onItemClickListener; } @@ -84,4 +68,19 @@ class PreferenceSearchAdapter this.binding = binding; } } + + private static class PreferenceCallback extends DiffUtil.ItemCallback { + @Override + public boolean areItemsTheSame(@NonNull final PreferenceSearchItem oldItem, + @NonNull final PreferenceSearchItem newItem) { + return oldItem.getKey().equals(newItem.getKey()); + } + + @Override + public boolean areContentsTheSame(@NonNull final PreferenceSearchItem oldItem, + @NonNull final PreferenceSearchItem newItem) { + return oldItem.getAllRelevantSearchFields().equals(newItem + .getAllRelevantSearchFields()); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java index 308abbc4e..9d169d660 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.settings.preferencesearch; import android.os.Bundle; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -13,7 +12,6 @@ import androidx.recyclerview.widget.LinearLayoutManager; import org.schabi.newpipe.databinding.SettingsPreferencesearchFragmentBinding; -import java.util.ArrayList; import java.util.List; /** @@ -54,13 +52,8 @@ public class PreferenceSearchFragment extends Fragment { return; } - final List results = - !TextUtils.isEmpty(keyword) - ? searcher.searchFor(keyword) - : new ArrayList<>(); - - adapter.setContent(new ArrayList<>(results)); - + final List results = searcher.searchFor(keyword); + adapter.submitList(results); setEmptyViewShown(results.isEmpty()); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java index 176dc5d14..b3efc8dd1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java @@ -3,6 +3,7 @@ package org.schabi.newpipe.settings.preferencesearch; import android.text.TextUtils; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -21,7 +22,7 @@ public class PreferenceSearcher { List searchFor(final String keyword) { if (TextUtils.isEmpty(keyword)) { - return new ArrayList<>(); + return Collections.emptyList(); } return configuration.getSearcher() From a50b9bd6ffde21b233a070b4887f1735572953e4 Mon Sep 17 00:00:00 2001 From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Date: Sun, 21 Aug 2022 17:39:57 +0000 Subject: [PATCH 121/152] Add FAQ entry to the template checklists (#8822) Co-authored-by: Mohammed Anas --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 2 ++ .github/ISSUE_TEMPLATE/question.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3abb1fbb1..d165daea3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -18,6 +18,8 @@ body: required: true - label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true + - label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my problem isn't listed." + required: true - label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise." required: true - label: "This issue contains only one bug." diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9fc3c1632..fbc61b579 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -16,6 +16,8 @@ body: options: - label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true + - label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my problem isn't listed." + required: true - label: "I'm aware that this is a request for NewPipe itself and that requests for adding a new service need to be made at [NewPipeExtractor](https://github.com/TeamNewPipe/NewPipeExtractor/issues)." required: true - label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise." diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 8cf22d8af..458ed6f2e 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -16,6 +16,8 @@ body: options: - label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true + - label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my question isn't listed." + required: true - label: "I have taken the time to fill in all the required details. I understand that the question will be dismissed otherwise." required: true - label: "I have read and understood the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/dev/.github/CONTRIBUTING.md)." From 52dbfdee00d4ca4879f8915c5eea4a363aebdc35 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 15:06:19 +0200 Subject: [PATCH 122/152] Keep strong references to Picasso notification icon loading targets Before the Target would sometimes be garbage collected before being called with the loaded channel icon, since Picasso holds weak references to targets. This meant that sometimes a new streams notification would not be shown, because the lambda that should have shown it had already been garbage collected. --- .../feed/notifications/NotificationHelper.kt | 31 +++++++++++++++++-- .../schabi/newpipe/util/PicassoHelper.java | 28 +++-------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index 3a08b3e4a..351975486 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -4,6 +4,8 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.provider.Settings @@ -11,6 +13,8 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager +import com.squareup.picasso.Picasso +import com.squareup.picasso.Target import org.schabi.newpipe.R import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.local.feed.service.FeedUpdateInfo @@ -27,6 +31,8 @@ class NotificationHelper(val context: Context) { Context.NOTIFICATION_SERVICE ) as NotificationManager + private val iconLoadingTargets = ArrayList() + /** * Show a notification about new streams from a single channel. * Opening the notification will open the corresponding channel page. @@ -77,10 +83,29 @@ class NotificationHelper(val context: Context) { ) ) - PicassoHelper.loadNotificationIcon(data.avatarUrl) { bitmap -> - bitmap?.let { builder.setLargeIcon(it) } // set only if != null - manager.notify(data.pseudoId, builder.build()) + // a Target is like a listener for image loading events + val target = object : Target { + override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { + builder.setLargeIcon(bitmap) // set only if there is actually one + manager.notify(data.pseudoId, builder.build()) + iconLoadingTargets.remove(this) // allow it to be garbage-collected + } + + override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) { + manager.notify(data.pseudoId, builder.build()) + iconLoadingTargets.remove(this) // allow it to be garbage-collected + } + + override fun onPrepareLoad(placeHolderDrawable: Drawable) { + // Nothing to do + } } + + // add the target to the list to hold a strong reference and prevent it from being garbage + // collected, since Picasso only holds weak references to targets + iconLoadingTargets.add(target) + + PicassoHelper.loadNotificationIcon(data.avatarUrl).into(target) } companion object { diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 54140b0fb..fc7600d4b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -5,7 +5,6 @@ import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; import androidx.annotation.Nullable; @@ -14,7 +13,6 @@ import com.squareup.picasso.LruCache; import com.squareup.picasso.OkHttp3Downloader; import com.squareup.picasso.Picasso; import com.squareup.picasso.RequestCreator; -import com.squareup.picasso.Target; import com.squareup.picasso.Transformation; import org.schabi.newpipe.R; @@ -22,7 +20,6 @@ import org.schabi.newpipe.R; import java.io.File; import java.io.IOException; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import okhttp3.OkHttpClient; @@ -120,6 +117,10 @@ public final class PicassoHelper { return picassoInstance.load(url); } + public static RequestCreator loadNotificationIcon(final String url) { + return loadImageDefault(url, R.drawable.ic_newpipe_triangle_white); + } + public static RequestCreator loadScaledDownThumbnail(final Context context, final String url) { // scale down the notification thumbnail for performance @@ -170,27 +171,6 @@ public final class PicassoHelper { return picassoCache.get(imageUrl + "\n"); } - public static void loadNotificationIcon(final String url, - final Consumer bitmapConsumer) { - loadImageDefault(url, R.drawable.ic_newpipe_triangle_white) - .into(new Target() { - @Override - public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { - bitmapConsumer.accept(bitmap); - } - - @Override - public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - bitmapConsumer.accept(null); - } - - @Override - public void onPrepareLoad(final Drawable placeHolderDrawable) { - // Nothing to do - } - }); - } - private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { return loadImageDefault(url, placeholderResId, true); From c054ea07372ab33049047cbcfcf5ae5584c05dea Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 18:00:43 +0200 Subject: [PATCH 123/152] Create MediaSessionPlayerUi --- .../org/schabi/newpipe/player/Player.java | 43 +++-------- .../schabi/newpipe/player/PlayerService.java | 6 +- .../MediaSessionManager.java | 4 +- .../mediasession/MediaSessionPlayerUi.java | 74 +++++++++++++++++++ .../player/notification/NotificationUtil.java | 10 ++- .../schabi/newpipe/player/ui/PlayerUi.java | 3 +- .../newpipe/player/ui/PlayerUiList.java | 17 ++++- 7 files changed, 113 insertions(+), 44 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{helper => mediasession}/MediaSessionManager.java (97%) create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java 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 159d361c1..e1bc2f061 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -99,14 +99,13 @@ import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.LoadController; -import org.schabi.newpipe.player.helper.MediaSessionManager; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.mediaitem.MediaItemTag; +import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; -import org.schabi.newpipe.player.playback.PlayerMediaSession; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; @@ -196,7 +195,6 @@ public final class Player implements PlaybackListener, Listener { private ExoPlayer simpleExoPlayer; private AudioReactor audioReactor; - private MediaSessionManager mediaSessionManager; @NonNull private final DefaultTrackSelector trackSelector; @NonNull private final LoadController loadController; @@ -225,7 +223,7 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ @SuppressWarnings("MemberName") // keep the unusual member name - private final PlayerUiList UIs = new PlayerUiList(); + private final PlayerUiList UIs; private BroadcastReceiver broadcastReceiver; private IntentFilter intentFilter; @@ -265,6 +263,15 @@ public final class Player implements PlaybackListener, Listener { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); + + // The UIs added here should always be present. They will be initialized when the player + // reaches the initialization step. Make sure the media session ui is before the + // 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 NotificationPlayerUi(this) + ); } private VideoPlaybackResolver.QualityResolver getQualityResolver() { @@ -431,11 +438,6 @@ public final class Player implements PlaybackListener, Listener { } private void initUIsForCurrentPlayerType() { - //noinspection SimplifyOptionalCallChains - if (!UIs.get(NotificationPlayerUi.class).isPresent()) { - UIs.addAndPrepare(new NotificationPlayerUi(this)); - } - if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) || (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) { // correct UI already in place @@ -506,8 +508,6 @@ public final class Player implements PlaybackListener, Listener { simpleExoPlayer.setHandleAudioBecomingNoisy(true); audioReactor = new AudioReactor(context, simpleExoPlayer); - mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, - new PlayerMediaSession(this)); registerBroadcastReceiver(); @@ -558,9 +558,6 @@ public final class Player implements PlaybackListener, Listener { if (playQueueManager != null) { playQueueManager.dispose(); } - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - } } public void destroy() { @@ -723,11 +720,6 @@ public final class Player implements PlaybackListener, Listener { Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received"); } break; - case Intent.ACTION_HEADSET_PLUG: //FIXME - /*notificationManager.cancel(NOTIFICATION_ID); - mediaSessionManager.dispose(); - mediaSessionManager.enable(getBaseContext(), basePlayerImpl.simpleExoPlayer);*/ - break; } UIs.call(playerUi -> playerUi.onBroadcastReceived(intent)); @@ -1738,15 +1730,6 @@ public final class Player implements PlaybackListener, Listener { initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); - final boolean showThumbnail = prefs.getBoolean( - context.getString(R.string.show_thumbnail_key), true); - mediaSessionManager.setMetadata( - getVideoTitle(), - getUploaderName(), - showThumbnail ? Optional.ofNullable(getThumbnail()) : Optional.empty(), - StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() - ); - notifyMetadataUpdateToListeners(); UIs.call(playerUi -> playerUi.onMetadataChanged(info)); } @@ -2194,10 +2177,6 @@ public final class Player implements PlaybackListener, Listener { return prefs; } - public MediaSessionManager getMediaSessionManager() { - return mediaSessionManager; - } - public PlayerType getPlayerType() { return playerType; 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 8d982617a..33b024e3d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -28,6 +28,7 @@ import android.os.Binder; import android.os.IBinder; import android.util.Log; +import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.util.ThemeHelper; @@ -73,9 +74,8 @@ public final class PlayerService extends Service { } player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); - } + player.UIs().get(MediaSessionPlayerUi.class) + .ifPresent(ui -> ui.handleMediaButtonIntent(intent)); return START_NOT_STICKY; } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java similarity index 97% rename from app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java rename to app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index a8735dc08..61bc9e2e7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.helper; +package org.schabi.newpipe.player.mediasession; import android.content.Context; import android.content.Intent; @@ -18,8 +18,6 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.player.mediasession.MediaSessionCallback; -import org.schabi.newpipe.player.mediasession.PlayQueueNavigator; import java.util.Optional; 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 new file mode 100644 index 000000000..d2de11ccf --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -0,0 +1,74 @@ +package org.schabi.newpipe.player.mediasession; + +import android.content.Intent; +import android.support.v4.media.session.MediaSessionCompat; + +import androidx.annotation.NonNull; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playback.PlayerMediaSession; +import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; + +import java.util.Optional; + +public class MediaSessionPlayerUi extends PlayerUi { + + private MediaSessionManager mediaSessionManager; + + public MediaSessionPlayerUi(@NonNull final Player player) { + super(player); + } + + @Override + public void initPlayer() { + super.initPlayer(); + if (mediaSessionManager != null) { + mediaSessionManager.dispose(); + } + mediaSessionManager = new MediaSessionManager(context, player.getExoPlayer(), + new PlayerMediaSession(player)); + } + + @Override + public void destroyPlayer() { + super.destroyPlayer(); + if (mediaSessionManager != null) { + mediaSessionManager.dispose(); + mediaSessionManager = null; + } + } + + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + // TODO decide whether to handle ACTION_HEADSET_PLUG or not + } + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + + mediaSessionManager.setMetadata( + player.getVideoTitle(), + player.getUploaderName(), + showThumbnail ? Optional.ofNullable(player.getThumbnail()) : Optional.empty(), + StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() + ); + } + + public void handleMediaButtonIntent(final Intent intent) { + if (mediaSessionManager != null) { + mediaSessionManager.handleMediaButtonIntent(intent); + } + } + + public Optional getSessionToken() { + return Optional.ofNullable(mediaSessionManager).map(MediaSessionManager::getSessionToken); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 1a91bc66d..29ec7a981 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -19,11 +19,13 @@ import androidx.core.content.ContextCompat; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.util.NavigationHelper; import java.util.List; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static androidx.media.app.NotificationCompat.MediaStyle; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; @@ -101,9 +103,11 @@ public final class NotificationUtil { player.getContext(), player.getPrefs(), nonNothingSlotCount); final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); - builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() - .setMediaSession(player.getMediaSessionManager().getSessionToken()) - .setShowActionsInCompactView(compactSlots)) + final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots); + player.UIs().get(MediaSessionPlayerUi.class).flatMap(MediaSessionPlayerUi::getSessionToken) + .ifPresent(mediaStyle::setMediaSession); + + builder.setStyle(mediaStyle) .setPriority(NotificationCompat.PRIORITY_HIGH) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setCategory(NotificationCompat.CATEGORY_TRANSPORT) diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 9ce04bfd5..57e2ec2a2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -29,7 +29,8 @@ public abstract class PlayerUi { @NonNull protected final Player player; /** - * @param player the player instance that will be usable throughout the lifetime of this UI + * @param player the player instance that will be usable throughout the lifetime of this UI; its + * context should already have been initialized */ protected PlayerUi(@NonNull final Player player) { this.context = player.getContext(); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java index 05c0ed5b3..24fec3b8a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -8,6 +8,19 @@ import java.util.function.Consumer; public final class PlayerUiList { final List playerUis = new ArrayList<>(); + /** + * Creates a {@link PlayerUiList} starting with the provided player uis. The provided player uis + * will not be prepared like those passed to {@link #addAndPrepare(PlayerUi)}, because when + * the {@link PlayerUiList} constructor is called, the player is still not running and it + * wouldn't make sense to initialize uis then. Instead the player will initialize them by doing + * proper calls to {@link #call(Consumer)}. + * + * @param initialPlayerUis the player uis this list should start with; the order will be kept + */ + public PlayerUiList(final PlayerUi... initialPlayerUis) { + playerUis.addAll(List.of(initialPlayerUis)); + } + /** * Adds the provided player ui to the list and calls on it the initialization functions that * apply based on the current player state. The preparation step needs to be done since when UIs @@ -67,11 +80,11 @@ public final class PlayerUiList { } /** - * Calls the provided consumer on all player UIs in the list. + * Calls the provided consumer on all player UIs in the list, in order of addition. * @param consumer the consumer to call with player UIs */ public void call(final Consumer consumer) { //noinspection SimplifyStreamApiCallChains - playerUis.stream().forEach(consumer); + playerUis.stream().forEachOrdered(consumer); } } From bc33322d4be00a0c34db81248e938074555874b7 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 22:40:21 +0200 Subject: [PATCH 124/152] Remove useless MediaSessionCallback The player is now passed directly, it made no sense to wrap around it in a callback that was not really a callback but rather, actually, a wrapper. --- .../mediasession/MediaSessionCallback.java | 21 ---- .../mediasession/MediaSessionManager.java | 16 +-- .../mediasession/MediaSessionPlayerUi.java | 4 +- .../mediasession/PlayQueueNavigator.java | 100 +++++++++++++----- .../player/playback/PlayerMediaSession.java | 99 ----------------- 5 files changed, 83 insertions(+), 157 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java delete mode 100644 app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java deleted file mode 100644 index c4b02d985..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.schabi.newpipe.player.mediasession; - -import android.support.v4.media.MediaDescriptionCompat; - -public interface MediaSessionCallback { - void playPrevious(); - - void playNext(); - - void playItemAtIndex(int index); - - int getCurrentPlayingIndex(); - - int getQueueSize(); - - MediaDescriptionCompat getQueueMetadata(int index); - - void play(); - - void pause(); -} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 61bc9e2e7..69f7d38fe 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -14,10 +14,11 @@ import androidx.annotation.Nullable; import androidx.media.session.MediaButtonReceiver; import com.google.android.exoplayer2.ForwardingPlayer; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import java.util.Optional; @@ -36,8 +37,7 @@ public class MediaSessionManager { private int lastAlbumArtHashCode; public MediaSessionManager(@NonNull final Context context, - @NonNull final Player player, - @NonNull final MediaSessionCallback callback) { + @NonNull final Player player) { mediaSession = new MediaSessionCompat(context, TAG); mediaSession.setActive(true); @@ -53,16 +53,18 @@ public class MediaSessionManager { .build()); sessionConnector = new MediaSessionConnector(mediaSession); - sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback)); - sessionConnector.setPlayer(new ForwardingPlayer(player) { + sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); + sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { @Override public void play() { - callback.play(); + player.play(); + // hide the player controls even if the play command came from the media session + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); } @Override public void pause() { - callback.pause(); + player.pause(); } }); } 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 d2de11ccf..a2eca575f 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 @@ -8,7 +8,6 @@ import androidx.annotation.NonNull; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.playback.PlayerMediaSession; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.util.StreamTypeUtil; @@ -28,8 +27,7 @@ public class MediaSessionPlayerUi extends PlayerUi { if (mediaSessionManager != null) { mediaSessionManager.dispose(); } - mediaSessionManager = new MediaSessionManager(context, player.getExoPlayer(), - new PlayerMediaSession(player)); + mediaSessionManager = new MediaSessionManager(context, player); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index 92cd425c5..7bd27bfdc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -1,106 +1,152 @@ package org.schabi.newpipe.player.mediasession; +import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; +import android.support.v4.media.MediaDescriptionCompat; +import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; + public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator { - public static final int DEFAULT_MAX_QUEUE_SIZE = 10; + private static final int MAX_QUEUE_SIZE = 10; private final MediaSessionCompat mediaSession; - private final MediaSessionCallback callback; - private final int maxQueueSize; + private final Player player; private long activeQueueItemId; public PlayQueueNavigator(@NonNull final MediaSessionCompat mediaSession, - @NonNull final MediaSessionCallback callback) { + @NonNull final Player player) { this.mediaSession = mediaSession; - this.callback = callback; - this.maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; + this.player = player; this.activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; } @Override - public long getSupportedQueueNavigatorActions(@Nullable final Player player) { + public long getSupportedQueueNavigatorActions( + @Nullable final com.google.android.exoplayer2.Player exoPlayer) { return ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS | ACTION_SKIP_TO_QUEUE_ITEM; } @Override - public void onTimelineChanged(@NonNull final Player player) { + public void onTimelineChanged(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { publishFloatingQueueWindow(); } @Override - public void onCurrentMediaItemIndexChanged(@NonNull final Player player) { + public void onCurrentMediaItemIndexChanged( + @NonNull final com.google.android.exoplayer2.Player exoPlayer) { if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID - || player.getCurrentTimeline().getWindowCount() > maxQueueSize) { + || exoPlayer.getCurrentTimeline().getWindowCount() > MAX_QUEUE_SIZE) { publishFloatingQueueWindow(); - } else if (!player.getCurrentTimeline().isEmpty()) { - activeQueueItemId = player.getCurrentMediaItemIndex(); + } else if (!exoPlayer.getCurrentTimeline().isEmpty()) { + activeQueueItemId = exoPlayer.getCurrentMediaItemIndex(); } } @Override - public long getActiveQueueItemId(@Nullable final Player player) { - return callback.getCurrentPlayingIndex(); + public long getActiveQueueItemId( + @Nullable final com.google.android.exoplayer2.Player exoPlayer) { + return Optional.ofNullable(player.getPlayQueue()).map(PlayQueue::getIndex).orElse(-1); } @Override - public void onSkipToPrevious(@NonNull final Player player) { - callback.playPrevious(); + public void onSkipToPrevious(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { + player.playPrevious(); } @Override - public void onSkipToQueueItem(@NonNull final Player player, final long id) { - callback.playItemAtIndex((int) id); + public void onSkipToQueueItem(@NonNull final com.google.android.exoplayer2.Player exoPlayer, + final long id) { + if (player.getPlayQueue() != null) { + player.selectQueueItem(player.getPlayQueue().getItem((int) id)); + } } @Override - public void onSkipToNext(@NonNull final Player player) { - callback.playNext(); + public void onSkipToNext(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { + player.playNext(); } private void publishFloatingQueueWindow() { - if (callback.getQueueSize() == 0) { + final int windowCount = Optional.ofNullable(player.getPlayQueue()) + .map(PlayQueue::size) + .orElse(0); + if (windowCount == 0) { mediaSession.setQueue(Collections.emptyList()); activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; return; } // Yes this is almost a copypasta, got a problem with that? =\ - final int windowCount = callback.getQueueSize(); - final int currentWindowIndex = callback.getCurrentPlayingIndex(); - final int queueSize = Math.min(maxQueueSize, windowCount); + final int currentWindowIndex = player.getPlayQueue().getIndex(); + final int queueSize = Math.min(MAX_QUEUE_SIZE, windowCount); final int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0, windowCount - queueSize); final List queue = new ArrayList<>(); for (int i = startIndex; i < startIndex + queueSize; i++) { - queue.add(new MediaSessionCompat.QueueItem(callback.getQueueMetadata(i), i)); + queue.add(new MediaSessionCompat.QueueItem(getQueueMetadata(i), i)); } mediaSession.setQueue(queue); activeQueueItemId = currentWindowIndex; } + public MediaDescriptionCompat getQueueMetadata(final int index) { + if (player.getPlayQueue() == null) { + return null; + } + final PlayQueueItem item = player.getPlayQueue().getItem(index); + if (item == null) { + return null; + } + + final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() + .setMediaId(String.valueOf(index)) + .setTitle(item.getTitle()) + .setSubtitle(item.getUploader()); + + // set additional metadata for A2DP/AVRCP + final Bundle additionalMetadata = new Bundle(); + additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); + additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); + additionalMetadata + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); + additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); + additionalMetadata + .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); + descBuilder.setExtras(additionalMetadata); + + final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); + if (thumbnailUri != null) { + descBuilder.setIconUri(thumbnailUri); + } + + return descBuilder.build(); + } + @Override - public boolean onCommand(@NonNull final Player player, + public boolean onCommand(@NonNull final com.google.android.exoplayer2.Player exoPlayer, @NonNull final String command, @Nullable final Bundle extras, @Nullable final ResultReceiver cb) { diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java deleted file mode 100644 index 3c41acc75..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.schabi.newpipe.player.playback; - -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.media.MediaDescriptionCompat; -import android.support.v4.media.MediaMetadataCompat; - -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.mediasession.MediaSessionCallback; -import org.schabi.newpipe.player.playqueue.PlayQueueItem; -import org.schabi.newpipe.player.ui.VideoPlayerUi; - -public class PlayerMediaSession implements MediaSessionCallback { - private final Player player; - - public PlayerMediaSession(final Player player) { - this.player = player; - } - - @Override - public void playPrevious() { - player.playPrevious(); - } - - @Override - public void playNext() { - player.playNext(); - } - - @Override - public void playItemAtIndex(final int index) { - if (player.getPlayQueue() == null) { - return; - } - player.selectQueueItem(player.getPlayQueue().getItem(index)); - } - - @Override - public int getCurrentPlayingIndex() { - if (player.getPlayQueue() == null) { - return -1; - } - return player.getPlayQueue().getIndex(); - } - - @Override - public int getQueueSize() { - if (player.getPlayQueue() == null) { - return -1; - } - return player.getPlayQueue().size(); - } - - @Override - public MediaDescriptionCompat getQueueMetadata(final int index) { - if (player.getPlayQueue() == null) { - return null; - } - final PlayQueueItem item = player.getPlayQueue().getItem(index); - if (item == null) { - return null; - } - - final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() - .setMediaId(String.valueOf(index)) - .setTitle(item.getTitle()) - .setSubtitle(item.getUploader()); - - // set additional metadata for A2DP/AVRCP - final Bundle additionalMetadata = new Bundle(); - additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); - additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); - additionalMetadata - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); - additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); - additionalMetadata - .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); - descBuilder.setExtras(additionalMetadata); - - final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); - if (thumbnailUri != null) { - descBuilder.setIconUri(thumbnailUri); - } - - return descBuilder.build(); - } - - @Override - public void play() { - player.play(); - // hide the player controls even if the play command came from the media session - player.UIs().get(VideoPlayerUi.class).ifPresent(playerUi -> playerUi.hideControls(0, 0)); - } - - @Override - public void pause() { - player.pause(); - } -} From 3cc43e9fb9eb32063deebcfaf29531aa4588fc3d Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 22:44:59 +0200 Subject: [PATCH 125/152] Fix thumbnail sometimes not set to media session metadata The thumbnail was not being updated in the media session metadata after it was loaded, since there was no metadata update in that case, only a notification update. --- .../newpipe/player/mediasession/MediaSessionPlayerUi.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 a2eca575f..e0343820e 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 @@ -1,9 +1,11 @@ package org.schabi.newpipe.player.mediasession; import android.content.Intent; +import android.graphics.Bitmap; import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; @@ -60,6 +62,12 @@ public class MediaSessionPlayerUi extends PlayerUi { ); } + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + player.getCurrentStreamInfo().ifPresent(this::onMetadataChanged); + } + public void handleMediaButtonIntent(final Intent intent) { if (mediaSessionManager != null) { mediaSessionManager.handleMediaButtonIntent(intent); From f3a9b81b670c0e999a9d2fcc8b657d3e85e182f0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 23:03:42 +0200 Subject: [PATCH 126/152] Fix sometimes seeing outdated thumbnail in notification Before the thumbnail finishes loading for the new video the player is now playing, the old thumbnail was being used, leading to wrong thumbnails set in the media session and the notification. --- .../java/org/schabi/newpipe/player/Player.java | 14 +++++++++----- .../org/schabi/newpipe/util/PicassoHelper.java | 7 +++++++ 2 files changed, 16 insertions(+), 5 deletions(-) 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 e1bc2f061..13dd1d938 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -748,11 +748,15 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail loading - private void initThumbnail(final String url) { + private void loadCurrentThumbnail(final String url) { if (DEBUG) { - Log.d(TAG, "Thumbnail - initThumbnail() called with url = [" + Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = [" + (url == null ? "null" : url) + "]"); } + + // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media + // session metadata while the new thumbnail is being loaded by Picasso. + currentThumbnail = null; if (isNullOrEmpty(url)) { return; } @@ -762,8 +766,8 @@ public final class Player implements PlaybackListener, Listener { @Override public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingComplete() called with: url = [" + url - + "], " + "loadedImage = [" + bitmap + " -> " + bitmap.getWidth() + "x" + Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: url = [" + url + + "], " + "bitmap = [" + bitmap + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" + from + "]"); } @@ -1727,7 +1731,7 @@ public final class Player implements PlaybackListener, Listener { maybeAutoQueueNextStream(info); - initThumbnail(info.getThumbnailUrl()); + loadCurrentThumbnail(info.getThumbnailUrl()); registerStreamViewed(); notifyMetadataUpdateToListeners(); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index fc7600d4b..5739b930b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -1,10 +1,12 @@ package org.schabi.newpipe.util; +import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; +import android.util.Log; import androidx.annotation.Nullable; @@ -24,6 +26,7 @@ import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; public final class PicassoHelper { + private static final String TAG = PicassoHelper.class.getSimpleName(); public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; @@ -129,6 +132,10 @@ public final class PicassoHelper { .transform(new Transformation() { @Override public Bitmap transform(final Bitmap source) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - transform() called"); + } + final float notificationThumbnailWidth = Math.min( context.getResources() .getDimension(R.dimen.player_notification_thumbnail_width), From d73ca41cfe6dd3e39bf3f7d751d5870f307a336b Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 23:44:17 +0200 Subject: [PATCH 127/152] Even when thumbnails should not be shown, set it to null in notification This makes sure the thumbnail is removed from the notification if the user disables thumbnails --- .../player/notification/NotificationUtil.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 29ec7a981..84e9cc3bc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -9,6 +9,7 @@ import android.os.Build; import android.util.Log; import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; @@ -137,12 +138,9 @@ public final class NotificationUtil { notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); + updateActions(notificationBuilder); - final boolean showThumbnail = player.getPrefs().getBoolean( - player.getContext().getString(R.string.show_thumbnail_key), true); - if (showThumbnail) { - setLargeIcon(notificationBuilder); - } + setLargeIcon(notificationBuilder); } @@ -344,17 +342,26 @@ public final class NotificationUtil { ///////////////////////////////////////////////////// private void setLargeIcon(final NotificationCompat.Builder builder) { + final boolean showThumbnail = player.getPrefs().getBoolean( + player.getContext().getString(R.string.show_thumbnail_key), true); + final Bitmap thumbnail = player.getThumbnail(); + if (thumbnail == null || !showThumbnail) { + // since the builder is reused, make sure the thumbnail is unset if there is not one + builder.setLargeIcon(null); + return; + } + final boolean scaleImageToSquareAspectRatio = player.getPrefs().getBoolean( player.getContext().getString(R.string.scale_to_square_image_in_notifications_key), false); if (scaleImageToSquareAspectRatio) { - builder.setLargeIcon(getBitmapWithSquareAspectRatio(player.getThumbnail())); + builder.setLargeIcon(getBitmapWithSquareAspectRatio(thumbnail)); } else { - builder.setLargeIcon(player.getThumbnail()); + builder.setLargeIcon(thumbnail); } } - private Bitmap getBitmapWithSquareAspectRatio(final Bitmap bitmap) { + private Bitmap getBitmapWithSquareAspectRatio(@NonNull final Bitmap bitmap) { // Find the smaller dimension and then take a center portion of the image that // has that size. final int w = bitmap.getWidth(); From 8bff445ec3d314a35d3243d4fff2711184265533 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 10:17:22 +0200 Subject: [PATCH 128/152] Remove useless checks before updating metadata A while ago NewPipe called the metadata update function very often, so checks were needed to ensure not wasting time updating metadata if it were already up to date. Now, instead, the metadata update function is called exactly when needed, i.e. when metadata changes, so such checks are not needed anymore (and were probably also a little resource-heavy). --- .../org/schabi/newpipe/player/Player.java | 5 - .../mediasession/MediaSessionManager.java | 123 ++---------------- .../mediasession/MediaSessionPlayerUi.java | 2 +- 3 files changed, 11 insertions(+), 119 deletions(-) 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 13dd1d938..22d46bcbe 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -54,7 +54,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.util.Log; @@ -1773,10 +1772,6 @@ public final class Player implements PlaybackListener, Listener { @Nullable public Bitmap getThumbnail() { - if (currentThumbnail == null) { - currentThumbnail = BitmapFactory.decodeResource( - context.getResources(), R.drawable.placeholder_thumbnail_video); - } return currentThumbnail; } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 69f7d38fe..98b6d1b32 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -20,8 +20,6 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.VideoPlayerUi; -import java.util.Optional; - public class MediaSessionManager { private static final String TAG = MediaSessionManager.class.getSimpleName(); public static final boolean DEBUG = MainActivity.DEBUG; @@ -31,11 +29,6 @@ public class MediaSessionManager { @NonNull private final MediaSessionConnector sessionConnector; - private int lastTitleHashCode; - private int lastArtistHashCode; - private long lastDuration; - private int lastAlbumArtHashCode; - public MediaSessionManager(@NonNull final Context context, @NonNull final Player player) { mediaSession = new MediaSessionCompat(context, TAG); @@ -84,134 +77,38 @@ public class MediaSessionManager { * * @param title {@link MediaMetadataCompat#METADATA_KEY_TITLE} * @param artist {@link MediaMetadataCompat#METADATA_KEY_ARTIST} - * @param optAlbumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART} + * @param albumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART}, if not null * @param duration {@link MediaMetadataCompat#METADATA_KEY_DURATION} * - should be a negative value for unknown durations, e.g. for livestreams */ public void setMetadata(@NonNull final String title, @NonNull final String artist, - @NonNull final Optional optAlbumArt, - final long duration - ) { + @Nullable final Bitmap albumArt, + final long duration) { if (DEBUG) { - Log.d(TAG, "setMetadata called:" - + " t: " + title - + " a: " + artist - + " thumb: " + ( - optAlbumArt.isPresent() - ? optAlbumArt.get().hashCode() - : "") - + " d: " + duration); + Log.d(TAG, "setMetadata called with: title = [" + title + "], artist = [" + artist + + "], albumArt = [" + (albumArt == null ? "null" : albumArt.hashCode()) + + "], duration = [" + duration + "]"); } if (!mediaSession.isActive()) { if (DEBUG) { - Log.d(TAG, "setMetadata: mediaSession not active - exiting"); + Log.d(TAG, "setMetadata: media session not active, exiting"); } return; } - if (!checkIfMetadataShouldBeSet(title, artist, optAlbumArt, duration)) { - if (DEBUG) { - Log.d(TAG, "setMetadata: No update required - exiting"); - } - return; - } - - if (DEBUG) { - Log.d(TAG, "setMetadata: N_Metadata update:" - + " t: " + title - + " a: " + artist - + " thumb: " + ( - optAlbumArt.isPresent() - ? optAlbumArt.get().hashCode() - : "") - + " d: " + duration); - } - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - if (optAlbumArt.isPresent()) { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, optAlbumArt.get()); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, optAlbumArt.get()); + if (albumArt != null) { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArt); } mediaSession.setMetadata(builder.build()); - - lastTitleHashCode = title.hashCode(); - lastArtistHashCode = artist.hashCode(); - lastDuration = duration; - optAlbumArt.ifPresent(bitmap -> lastAlbumArtHashCode = bitmap.hashCode()); - } - - private boolean checkIfMetadataShouldBeSet( - @NonNull final String title, - @NonNull final String artist, - @NonNull final Optional optAlbumArt, - final long duration - ) { - // Check if the values have changed since the last time - if (title.hashCode() != lastTitleHashCode - || artist.hashCode() != lastArtistHashCode - || duration != lastDuration - || (optAlbumArt.isPresent() && optAlbumArt.get().hashCode() != lastAlbumArtHashCode) - ) { - if (DEBUG) { - Log.d(TAG, - "checkIfMetadataShouldBeSet: true - reason: changed values since last"); - } - return true; - } - - // Check if the currently set metadata is valid - if (getMetadataTitle() == null - || getMetadataArtist() == null - // Note that the duration can be <= 0 for live streams - ) { - if (DEBUG) { - if (getMetadataTitle() == null) { - Log.d(TAG, - "N_getMetadataTitle: title == null"); - } else if (getMetadataArtist() == null) { - Log.d(TAG, - "N_getMetadataArtist: artist == null"); - } - } - return true; - } - - // If we got an album art check if the current set AlbumArt is null - if (optAlbumArt.isPresent() && getMetadataAlbumArt() == null) { - if (DEBUG) { - Log.d(TAG, "N_getMetadataAlbumArt: thumb == null"); - } - return true; - } - - // Default - no update required - return false; - } - - - @Nullable - private Bitmap getMetadataAlbumArt() { - return mediaSession.getController().getMetadata() - .getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART); - } - - @Nullable - private String getMetadataTitle() { - return mediaSession.getController().getMetadata() - .getString(MediaMetadataCompat.METADATA_KEY_TITLE); - } - - @Nullable - private String getMetadataArtist() { - return mediaSession.getController().getMetadata() - .getString(MediaMetadataCompat.METADATA_KEY_ARTIST); } /** 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 e0343820e..2140be26d 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 @@ -57,7 +57,7 @@ public class MediaSessionPlayerUi extends PlayerUi { mediaSessionManager.setMetadata( player.getVideoTitle(), player.getUploaderName(), - showThumbnail ? Optional.ofNullable(player.getThumbnail()) : Optional.empty(), + showThumbnail ? player.getThumbnail() : null, StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() ); } From f80d1dc48dcc0539ac7faa20b085b0adc2c5fe64 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 11:31:49 +0200 Subject: [PATCH 129/152] Let exoplayer decide when to update metadata Though still make sure metadata is updated after the thumbnail is loaded. This fixes the wrong seekbar properties (duration and current position) being shown in the notification sometimes. --- .../mediasession/MediaSessionManager.java | 87 ++++++++----------- .../mediasession/MediaSessionPlayerUi.java | 22 +---- 2 files changed, 40 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 98b6d1b32..c6766fbcb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -2,10 +2,8 @@ package org.schabi.newpipe.player.mediasession; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; -import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; import android.view.KeyEvent; @@ -17,8 +15,12 @@ import com.google.android.exoplayer2.ForwardingPlayer; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.VideoPlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; + +import java.util.Optional; public class MediaSessionManager { private static final String TAG = MediaSessionManager.class.getSimpleName(); @@ -34,17 +36,6 @@ public class MediaSessionManager { mediaSession = new MediaSessionCompat(context, TAG); mediaSession.setActive(true); - mediaSession.setPlaybackState(new PlaybackStateCompat.Builder() - .setState(PlaybackStateCompat.STATE_NONE, -1, 1) - .setActions(PlaybackStateCompat.ACTION_SEEK_TO - | PlaybackStateCompat.ACTION_PLAY - | PlaybackStateCompat.ACTION_PAUSE // was play and pause now play/pause - | PlaybackStateCompat.ACTION_SKIP_TO_NEXT - | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_SET_REPEAT_MODE - | PlaybackStateCompat.ACTION_STOP) - .build()); - sessionConnector = new MediaSessionConnector(mediaSession); sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { @@ -60,6 +51,37 @@ public class MediaSessionManager { player.pause(); } }); + + sessionConnector.setMetadataDeduplicationEnabled(true); + sessionConnector.setMediaMetadataProvider(exoPlayer -> { + if (DEBUG) { + Log.d(TAG, "MediaMetadataProvider#getMetadata called"); + } + + // set title and artist + final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); + + // set duration (-1 for livestreams, since they don't have a duration) + final long duration = player.getCurrentStreamInfo() + .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) + .map(info -> info.getDuration() * 1000L) + .orElse(-1L); + builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); + + // set album art, unless the user asked not to, or there is no thumbnail available + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + Optional.ofNullable(player.getThumbnail()) + .filter(bitmap -> showThumbnail) + .ifPresent(bitmap -> { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); + }); + + return builder.build(); + }); } @Nullable @@ -72,43 +94,8 @@ public class MediaSessionManager { return mediaSession.getSessionToken(); } - /** - * sets the Metadata - if required. - * - * @param title {@link MediaMetadataCompat#METADATA_KEY_TITLE} - * @param artist {@link MediaMetadataCompat#METADATA_KEY_ARTIST} - * @param albumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART}, if not null - * @param duration {@link MediaMetadataCompat#METADATA_KEY_DURATION} - * - should be a negative value for unknown durations, e.g. for livestreams - */ - public void setMetadata(@NonNull final String title, - @NonNull final String artist, - @Nullable final Bitmap albumArt, - final long duration) { - if (DEBUG) { - Log.d(TAG, "setMetadata called with: title = [" + title + "], artist = [" + artist - + "], albumArt = [" + (albumArt == null ? "null" : albumArt.hashCode()) - + "], duration = [" + duration + "]"); - } - - if (!mediaSession.isActive()) { - if (DEBUG) { - Log.d(TAG, "setMetadata: media session not active, exiting"); - } - return; - } - - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - - if (albumArt != null) { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArt); - } - - mediaSession.setMetadata(builder.build()); + void triggerMetadataUpdate() { + sessionConnector.invalidateMediaSessionMetadata(); } /** 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 2140be26d..c78a3a6b1 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 @@ -7,11 +7,8 @@ import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.PlayerUi; -import org.schabi.newpipe.util.StreamTypeUtil; import java.util.Optional; @@ -47,25 +44,12 @@ public class MediaSessionPlayerUi extends PlayerUi { // TODO decide whether to handle ACTION_HEADSET_PLUG or not } - @Override - public void onMetadataChanged(@NonNull final StreamInfo info) { - super.onMetadataChanged(info); - - final boolean showThumbnail = player.getPrefs().getBoolean( - context.getString(R.string.show_thumbnail_key), true); - - mediaSessionManager.setMetadata( - player.getVideoTitle(), - player.getUploaderName(), - showThumbnail ? player.getThumbnail() : null, - StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() - ); - } - @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - player.getCurrentStreamInfo().ifPresent(this::onMetadataChanged); + if (mediaSessionManager != null) { + mediaSessionManager.triggerMetadataUpdate(); + } } public void handleMediaButtonIntent(final Intent intent) { From 11bd2369e5e8c0e5352fe76dada02089664f36c0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 12:00:32 +0200 Subject: [PATCH 130/152] Merge MediaSessionManager into MediaSessionPlayerUi --- .../mediasession/MediaSessionManager.java | 110 ------------------ .../mediasession/MediaSessionPlayerUi.java | 104 ++++++++++++++--- 2 files changed, 90 insertions(+), 124 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java deleted file mode 100644 index c6766fbcb..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.schabi.newpipe.player.mediasession; - -import android.content.Context; -import android.content.Intent; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.util.Log; -import android.view.KeyEvent; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.media.session.MediaButtonReceiver; - -import com.google.android.exoplayer2.ForwardingPlayer; -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; - -import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.R; -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.ui.VideoPlayerUi; -import org.schabi.newpipe.util.StreamTypeUtil; - -import java.util.Optional; - -public class MediaSessionManager { - private static final String TAG = MediaSessionManager.class.getSimpleName(); - public static final boolean DEBUG = MainActivity.DEBUG; - - @NonNull - private final MediaSessionCompat mediaSession; - @NonNull - private final MediaSessionConnector sessionConnector; - - public MediaSessionManager(@NonNull final Context context, - @NonNull final Player player) { - mediaSession = new MediaSessionCompat(context, TAG); - mediaSession.setActive(true); - - sessionConnector = new MediaSessionConnector(mediaSession); - sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); - sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { - @Override - public void play() { - player.play(); - // hide the player controls even if the play command came from the media session - player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); - } - - @Override - public void pause() { - player.pause(); - } - }); - - sessionConnector.setMetadataDeduplicationEnabled(true); - sessionConnector.setMediaMetadataProvider(exoPlayer -> { - if (DEBUG) { - Log.d(TAG, "MediaMetadataProvider#getMetadata called"); - } - - // set title and artist - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); - - // set duration (-1 for livestreams, since they don't have a duration) - final long duration = player.getCurrentStreamInfo() - .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) - .map(info -> info.getDuration() * 1000L) - .orElse(-1L); - builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - - // set album art, unless the user asked not to, or there is no thumbnail available - final boolean showThumbnail = player.getPrefs().getBoolean( - context.getString(R.string.show_thumbnail_key), true); - Optional.ofNullable(player.getThumbnail()) - .filter(bitmap -> showThumbnail) - .ifPresent(bitmap -> { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); - }); - - return builder.build(); - }); - } - - @Nullable - @SuppressWarnings("UnusedReturnValue") - public KeyEvent handleMediaButtonIntent(final Intent intent) { - return MediaButtonReceiver.handleIntent(mediaSession, intent); - } - - public MediaSessionCompat.Token getSessionToken() { - return mediaSession.getSessionToken(); - } - - void triggerMetadataUpdate() { - sessionConnector.invalidateMediaSessionMetadata(); - } - - /** - * Should be called on player destruction to prevent leakage. - */ - public void dispose() { - sessionConnector.setPlayer(null); - sessionConnector.setQueueNavigator(null); - mediaSession.setActive(false); - mediaSession.release(); - } -} 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 c78a3a6b1..92a137900 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 @@ -1,20 +1,33 @@ package org.schabi.newpipe.player.mediasession; +import static org.schabi.newpipe.MainActivity.DEBUG; + import android.content.Intent; import android.graphics.Bitmap; +import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.media.session.MediaButtonReceiver; +import com.google.android.exoplayer2.ForwardingPlayer; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; + +import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.player.ui.VideoPlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; import java.util.Optional; public class MediaSessionPlayerUi extends PlayerUi { + private static final String TAG = "MediaSessUi"; - private MediaSessionManager mediaSessionManager; + private MediaSessionCompat mediaSession; + private MediaSessionConnector sessionConnector; public MediaSessionPlayerUi(@NonNull final Player player) { super(player); @@ -23,18 +36,31 @@ public class MediaSessionPlayerUi extends PlayerUi { @Override public void initPlayer() { super.initPlayer(); - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - } - mediaSessionManager = new MediaSessionManager(context, player); + 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()); + + sessionConnector.setMetadataDeduplicationEnabled(true); + sessionConnector.setMediaMetadataProvider(exoPlayer -> buildMediaMetadata()); } @Override public void destroyPlayer() { super.destroyPlayer(); - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - mediaSessionManager = null; + if (sessionConnector != null) { + sessionConnector.setPlayer(null); + sessionConnector.setQueueNavigator(null); + sessionConnector = null; + } + if (mediaSession != null) { + mediaSession.setActive(false); + mediaSession.release(); + mediaSession = null; } } @@ -47,18 +73,68 @@ public class MediaSessionPlayerUi extends PlayerUi { @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - if (mediaSessionManager != null) { - mediaSessionManager.triggerMetadataUpdate(); + if (sessionConnector != null) { + // the thumbnail is now loaded: invalidate the metadata to trigger a metadata update + sessionConnector.invalidateMediaSessionMetadata(); } } + public void handleMediaButtonIntent(final Intent intent) { - if (mediaSessionManager != null) { - mediaSessionManager.handleMediaButtonIntent(intent); - } + MediaButtonReceiver.handleIntent(mediaSession, intent); } public Optional getSessionToken() { - return Optional.ofNullable(mediaSessionManager).map(MediaSessionManager::getSessionToken); + return Optional.ofNullable(mediaSession).map(MediaSessionCompat::getSessionToken); + } + + + private ForwardingPlayer getForwardingPlayer() { + // ForwardingPlayer means that all media session actions called on this player are + // forwarded directly to the connected exoplayer, except for the overridden methods. So + // override play and pause since our player adds more functionality to them over exoplayer. + return new ForwardingPlayer(player.getExoPlayer()) { + @Override + public void play() { + player.play(); + // hide the player controls even if the play command came from the media session + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); + } + + @Override + public void pause() { + player.pause(); + } + }; + } + + private MediaMetadataCompat buildMediaMetadata() { + if (DEBUG) { + Log.d(TAG, "buildMediaMetadata called"); + } + + // set title and artist + final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); + + // set duration (-1 for livestreams or if unknown, see the METADATA_KEY_DURATION docs) + final long duration = player.getCurrentStreamInfo() + .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) + .map(info -> info.getDuration() * 1000L) + .orElse(-1L); + builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); + + // set album art, unless the user asked not to, or there is no thumbnail available + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + Optional.ofNullable(player.getThumbnail()) + .filter(bitmap -> showThumbnail) + .ifPresent(bitmap -> { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); + }); + + return builder.build(); } } From 510efaae976c1fe15a85c3f81c4a49573760097f Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 14:39:25 +0200 Subject: [PATCH 131/152] Keep strong reference to Picasso thumbnail loading target Before the Target would sometimes be garbage collected before being called with the loaded thumbnail, since Picasso holds weak references to targets --- .../org/schabi/newpipe/player/Player.java | 79 ++++++++++++------- .../schabi/newpipe/util/PicassoHelper.java | 2 - 2 files changed, 51 insertions(+), 30 deletions(-) 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 22d46bcbe..45b9b0fde 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -174,6 +174,7 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ public static final int RENDERER_UNAVAILABLE = -1; + private static final String PICASSO_PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -232,6 +233,11 @@ public final class Player implements PlaybackListener, Listener { @NonNull private final SerialDisposable progressUpdateDisposable = new SerialDisposable(); @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable(); + // This is the only listener we need for thumbnail loading, since there is always at most only + // one thumbnail being loaded at a time. This field is also here to maintain a strong reference, + // which would otherwise be garbage collected since Picasso holds weak references to targets. + @NonNull private final Target currentThumbnailTarget; + /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -263,6 +269,8 @@ public final class Player implements PlaybackListener, Listener { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); + currentThumbnailTarget = getCurrentThumbnailTarget(); + // The UIs added here should always be present. They will be initialized when the player // reaches the initialization step. Make sure the media session ui is before the // notification ui in the UIs list, since the notification depends on the media session in @@ -573,7 +581,7 @@ public final class Player implements PlaybackListener, Listener { databaseUpdateDisposable.clear(); progressUpdateDisposable.set(null); - PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading + cancelLoadingCurrentThumbnail(); UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object } @@ -747,12 +755,47 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail loading + private Target getCurrentThumbnailTarget() { + // a Picasso target is just a listener for thumbnail loading events + return new Target() { + @Override + public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: bitmap = [" + bitmap + + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" + + from + "]"); + } + currentThumbnail = bitmap; + // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. + UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); + } + + @Override + public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { + Log.e(TAG, "Thumbnail - onBitmapFailed() called", e); + currentThumbnail = null; + // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. + UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); + } + + @Override + public void onPrepareLoad(final Drawable placeHolderDrawable) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onPrepareLoad() called"); + } + } + }; + } + private void loadCurrentThumbnail(final String url) { if (DEBUG) { Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = [" + (url == null ? "null" : url) + "]"); } + // first cancel any previous loading + cancelLoadingCurrentThumbnail(); + // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media // session metadata while the new thumbnail is being loaded by Picasso. currentThumbnail = null; @@ -761,34 +804,14 @@ public final class Player implements PlaybackListener, Listener { } // scale down the notification thumbnail for performance - PicassoHelper.loadScaledDownThumbnail(context, url).into(new Target() { - @Override - public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: url = [" + url - + "], " + "bitmap = [" + bitmap + " -> " + bitmap.getWidth() + "x" - + bitmap.getHeight() + "], from = [" + from + "]"); - } + PicassoHelper.loadScaledDownThumbnail(context, url) + .tag(PICASSO_PLAYER_THUMBNAIL_TAG) + .into(currentThumbnailTarget); + } - currentThumbnail = bitmap; - // there is a new thumbnail, so changed the end screen thumbnail, too. - UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); - } - - @Override - public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); - currentThumbnail = null; - UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); - } - - @Override - public void onPrepareLoad(final Drawable placeHolderDrawable) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onPrepareLoad() called: url = [" + url + "]"); - } - } - }); + private void cancelLoadingCurrentThumbnail() { + // cancel the Picasso job associated with the player thumbnail, if any + PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG); } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 5739b930b..2e781631e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -27,7 +27,6 @@ import okhttp3.OkHttpClient; public final class PicassoHelper { private static final String TAG = PicassoHelper.class.getSimpleName(); - public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; @@ -128,7 +127,6 @@ public final class PicassoHelper { public static RequestCreator loadScaledDownThumbnail(final Context context, final String url) { // scale down the notification thumbnail for performance return PicassoHelper.loadThumbnail(url) - .tag(PLAYER_THUMBNAIL_TAG) .transform(new Transformation() { @Override public Bitmap transform(final Bitmap source) { From 973a9660115cc809a99ae571af45f0b67a9c7c03 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 26 Jul 2022 16:35:57 +0200 Subject: [PATCH 132/152] Review suggestions --- .../newpipe/player/mediasession/PlayQueueNavigator.java | 2 +- .../newpipe/player/notification/NotificationUtil.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index 7bd27bfdc..e84c0837b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -126,7 +126,7 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator .setTitle(item.getTitle()) .setSubtitle(item.getUploader()); - // set additional metadata for A2DP/AVRCP + // set additional metadata for A2DP/AVRCP (Audio/Video Bluetooth profiles) final Bundle additionalMetadata = new Bundle(); additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 84e9cc3bc..2c3199a28 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -105,8 +105,10 @@ public final class NotificationUtil { final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots); - player.UIs().get(MediaSessionPlayerUi.class).flatMap(MediaSessionPlayerUi::getSessionToken) - .ifPresent(mediaStyle::setMediaSession); + player.UIs() + .get(MediaSessionPlayerUi.class) + .flatMap(MediaSessionPlayerUi::getSessionToken) + .ifPresent(mediaStyle::setMediaSession); builder.setStyle(mediaStyle) .setPriority(NotificationCompat.PRIORITY_HIGH) From 59d1ded94e876bd040a7c23a7a2e3ae18987df07 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:59:46 +0200 Subject: [PATCH 133/152] Fixed sonar detected problems + Automatically fixed code style (imports) --- .../java/org/schabi/newpipe/player/Player.java | 2 +- .../mediasession/MediaSessionPlayerUi.java | 6 ------ .../mediasession/PlayQueueNavigator.java | 18 +++++++++--------- 3 files changed, 10 insertions(+), 16 deletions(-) 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 45b9b0fde..319c163e8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -222,7 +222,7 @@ public final class Player implements PlaybackListener, Listener { // UIs, listeners and disposables //////////////////////////////////////////////////////////////////////////*/ - @SuppressWarnings("MemberName") // keep the unusual member name + @SuppressWarnings({"MemberName", "java:S116"}) // keep the unusual member name private final PlayerUiList UIs; private BroadcastReceiver broadcastReceiver; 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 92a137900..e9541ab06 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 @@ -64,12 +64,6 @@ public class MediaSessionPlayerUi extends PlayerUi { } } - @Override - public void onBroadcastReceived(final Intent intent) { - super.onBroadcastReceived(intent); - // TODO decide whether to handle ACTION_HEADSET_PLUG or not - } - @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index e84c0837b..2e54b1129 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.player.mediasession; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; @@ -13,19 +17,15 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.util.Util; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; + import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; - -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.playqueue.PlayQueue; -import org.schabi.newpipe.player.playqueue.PlayQueueItem; - public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator { private static final int MAX_QUEUE_SIZE = 10; @@ -132,7 +132,7 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); - additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); + additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1L); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); descBuilder.setExtras(additionalMetadata); From 6805c75c9cf90f9448e266eda34866136f0c71c8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 2 Aug 2022 14:46:11 +0200 Subject: [PATCH 134/152] Fix surface view not resizing video correctly Also fix yet another random null pointer exception that could happen when adding the video player view --- .../fragments/detail/VideoDetailFragment.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) 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 3b1bdaede..594006ab5 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 @@ -1220,7 +1220,7 @@ public final class VideoDetailFragment } final PlayQueue queue = setupPlayQueueForIntent(false); - addVideoPlayerView(); + tryAddVideoPlayerView(); final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(), PlayerService.class, queue, true, autoPlayEnabled); @@ -1301,21 +1301,27 @@ public final class VideoDetailFragment && PlayerHelper.isAutoplayAllowedByUser(requireContext()); } - private void addVideoPlayerView() { - if (!isPlayerAvailable() || getView() == null) { - return; - } - setHeightThumbnail(); + private void tryAddVideoPlayerView() { + // do all the null checks in the posted lambda, since the player, the binding and the view + // could be set or unset before the lambda gets executed on the next main thread cycle + new Handler(Looper.getMainLooper()).post(() -> { + if (!isPlayerAvailable() || getView() == null) { + return; + } - // Prevent from re-adding a view multiple times - new Handler(Looper.getMainLooper()).post(() -> - player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { - if (binding != null) { - playerUi.removeViewFromParent(); - binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); - playerUi.setupVideoSurfaceIfNeeded(); - } - })); + // setup the surface view height, so that it fits the video correctly + setHeightThumbnail(); + + player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + // sometimes binding would be null here, even though getView() != null above u.u + if (binding != null) { + // prevent from re-adding a view multiple times + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + } + }); + }); } private void removeVideoPlayerView() { @@ -1784,7 +1790,7 @@ public final class VideoDetailFragment @Override public void onViewCreated() { - addVideoPlayerView(); + tryAddVideoPlayerView(); } @Override @@ -1926,7 +1932,7 @@ public final class VideoDetailFragment } scrollToTop(); - addVideoPlayerView(); + tryAddVideoPlayerView(); } @Override From 500acce178d1e9f21d811f7e9f3b3bfac6c4aeb0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 24 Aug 2022 15:08:24 +0200 Subject: [PATCH 135/152] Fix regression in screen rotation animation --- .../newpipe/fragments/detail/VideoDetailFragment.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 594006ab5..9800b2b0a 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 @@ -1302,8 +1302,14 @@ public final class VideoDetailFragment } private void tryAddVideoPlayerView() { - // do all the null checks in the posted lambda, since the player, the binding and the view - // could be set or unset before the lambda gets executed on the next main thread cycle + if (isPlayerAvailable() && getView() != null) { + // Setup the surface view height, so that it fits the video correctly; this is done also + // here, and not only in the Handler, to avoid a choppy fullscreen rotation animation. + setHeightThumbnail(); + } + + // do all the null checks in the posted lambda, too, since the player, the binding and the + // view could be set or unset before the lambda gets executed on the next main thread cycle new Handler(Looper.getMainLooper()).post(() -> { if (!isPlayerAvailable() || getView() == null) { return; From ca0f56eea815bd2112f2e36655c4f753dd975a70 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 24 Aug 2022 16:03:15 +0200 Subject: [PATCH 136/152] Avoid setting invalid states to bottom sheet callback --- .../fragments/detail/VideoDetailFragment.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) 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 9800b2b0a..0ec1efe57 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 @@ -180,6 +180,8 @@ public final class VideoDetailFragment @State int bottomSheetState = BottomSheetBehavior.STATE_EXPANDED; @State + int lastStableBottomSheetState = BottomSheetBehavior.STATE_EXPANDED; + @State protected boolean autoPlayEnabled = true; @Nullable @@ -269,7 +271,7 @@ public final class VideoDetailFragment public static VideoDetailFragment getInstanceInCollapsedState() { final VideoDetailFragment instance = new VideoDetailFragment(); - instance.bottomSheetState = BottomSheetBehavior.STATE_COLLAPSED; + instance.updateBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED); return instance; } @@ -1170,7 +1172,7 @@ public final class VideoDetailFragment // doesn't tell which state it was settling to, and thus the bottom sheet settles to // STATE_COLLAPSED. This can be solved by manually setting the state that will be // restored (i.e. bottomSheetState) to STATE_EXPANDED. - bottomSheetState = BottomSheetBehavior.STATE_EXPANDED; + updateBottomSheetState(BottomSheetBehavior.STATE_EXPANDED); // toggle landscape in order to open directly in fullscreen onScreenRotationButtonClicked(); } @@ -2284,7 +2286,9 @@ public final class VideoDetailFragment final FrameLayout bottomSheetLayout = activity.findViewById(R.id.fragment_player_holder); bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetLayout); - bottomSheetBehavior.setState(bottomSheetState); + bottomSheetBehavior.setState(lastStableBottomSheetState); + updateBottomSheetState(lastStableBottomSheetState); + final int peekHeight = getResources().getDimensionPixelSize(R.dimen.mini_player_height); if (bottomSheetState != BottomSheetBehavior.STATE_HIDDEN) { manageSpaceAtTheBottom(false); @@ -2300,7 +2304,7 @@ public final class VideoDetailFragment bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull final View bottomSheet, final int newState) { - bottomSheetState = newState; + updateBottomSheetState(newState); switch (newState) { case BottomSheetBehavior.STATE_HIDDEN: @@ -2441,4 +2445,12 @@ public final class VideoDetailFragment return player.UIs().get(VideoPlayerUi.class) .map(playerUi -> playerUi.getBinding().getRoot()); } + + private void updateBottomSheetState(final int newState) { + bottomSheetState = newState; + if (newState != BottomSheetBehavior.STATE_DRAGGING + && newState != BottomSheetBehavior.STATE_SETTLING) { + lastStableBottomSheetState = newState; + } + } } From f9994abb94453f97080bdd48200c6009b965a43c Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 24 Aug 2022 17:48:02 +0200 Subject: [PATCH 137/152] Prevent tapping on thumbnail if video details are not loaded --- .../fragments/detail/VideoDetailFragment.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) 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 0ec1efe57..09e085791 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 @@ -505,12 +505,18 @@ public final class VideoDetailFragment } break; case R.id.detail_thumbnail_root_layout: - autoPlayEnabled = true; // forcefully start playing - // FIXME Workaround #7427 - if (isPlayerAvailable()) { - player.setRecovery(); + // make sure not to open any player if there is nothing currently loaded! + // FIXME removing this `if` causes the player service to start correctly, then stop, + // then restart badly without calling `startForeground()`, causing a crash when + // later closing the detail fragment + if (currentInfo != null) { + autoPlayEnabled = true; // forcefully start playing + // FIXME Workaround #7427 + if (isPlayerAvailable()) { + player.setRecovery(); + } + openVideoPlayerAutoFullscreen(); } - openVideoPlayerAutoFullscreen(); break; case R.id.detail_title_root_layout: toggleTitleAndSecondaryControls(); From 67b5de38b1cdb88a160b34ec18a0ae2e4248ce41 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 27 Aug 2022 11:53:53 +0200 Subject: [PATCH 138/152] Translated using Weblate (Chinese (Simplified)) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (70 of 70 strings) Translated using Weblate (Czech) Currently translated at 100.0% (70 of 70 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (70 of 70 strings) Translated using Weblate (Azerbaijani) Currently translated at 100.0% (69 of 69 strings) Translated using Weblate (Interlingua) Currently translated at 35.0% (224 of 640 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 62.3% (43 of 69 strings) Translated using Weblate (Korean) Currently translated at 10.1% (7 of 69 strings) Translated using Weblate (French) Currently translated at 89.8% (62 of 69 strings) Translated using Weblate (Hebrew) Currently translated at 55.0% (38 of 69 strings) Translated using Weblate (Czech) Currently translated at 100.0% (69 of 69 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (69 of 69 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 13.0% (9 of 69 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Bengali (Bangladesh)) Currently translated at 5.8% (4 of 68 strings) Translated using Weblate (French) Currently translated at 89.7% (61 of 68 strings) Translated using Weblate (French) Currently translated at 89.7% (61 of 68 strings) Translated using Weblate (Bengali) Currently translated at 22.0% (15 of 68 strings) Translated using Weblate (Azerbaijani) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 73.5% (50 of 68 strings) Translated using Weblate (Russian) Currently translated at 30.8% (21 of 68 strings) Translated using Weblate (German) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hungarian) Currently translated at 7.3% (5 of 68 strings) Translated using Weblate (Croatian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Czech) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hungarian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Polish) Currently translated at 58.8% (40 of 68 strings) Translated using Weblate (Bengali) Currently translated at 88.7% (568 of 640 strings) Translated using Weblate (Malayalam) Currently translated at 90.7% (581 of 640 strings) Translated using Weblate (Interlingua) Currently translated at 33.5% (215 of 640 strings) Translated using Weblate (Croatian) Currently translated at 98.1% (628 of 640 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hungarian) Currently translated at 93.9% (601 of 640 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Somali) Currently translated at 89.0% (570 of 640 strings) Translated using Weblate (German) Currently translated at 66.1% (45 of 68 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Armenian) Currently translated at 29.2% (187 of 640 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 99.5% (637 of 640 strings) Translated using Weblate (Urdu) Currently translated at 67.1% (430 of 640 strings) Translated using Weblate (Croatian) Currently translated at 97.5% (624 of 640 strings) Translated using Weblate (Portuguese) Currently translated at 61.7% (42 of 68 strings) Translated using Weblate (Russian) Currently translated at 22.0% (15 of 68 strings) Translated using Weblate (Russian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 72.0% (49 of 68 strings) Translated using Weblate (Italian) Currently translated at 41.1% (28 of 68 strings) Translated using Weblate (Czech) Currently translated at 100.0% (68 of 68 strings) Translated using Weblate (Portuguese (Portugal)) Currently translated at 99.6% (638 of 640 strings) Translated using Weblate (Sardinian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Galician) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Estonian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Azerbaijani) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Bulgarian) Currently translated at 72.0% (461 of 640 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Bengali (Bangladesh)) Currently translated at 64.6% (414 of 640 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Persian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Polish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Czech) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Greek) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Basque) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Italian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (German) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Italian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Slovak) Currently translated at 8.8% (6 of 68 strings) Translated using Weblate (Slovak) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 70.5% (48 of 68 strings) Translated using Weblate (Filipino) Currently translated at 36.8% (236 of 640 strings) Translated using Weblate (Bulgarian) Currently translated at 70.6% (452 of 640 strings) Translated using Weblate (Russian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 32.3% (22 of 68 strings) Translated using Weblate (Telugu) Currently translated at 65.6% (420 of 640 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 97.1% (622 of 640 strings) Translated using Weblate (Italian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Japanese) Currently translated at 99.2% (635 of 640 strings) Co-authored-by: Agnieszka C Co-authored-by: Ajeje Brazorf Co-authored-by: Alex25820 Co-authored-by: Danial Behzadi Co-authored-by: Davit Mayilyan Co-authored-by: Edward Co-authored-by: Emin Tufan Çetin Co-authored-by: Eric Co-authored-by: Evghenii Botnari Co-authored-by: Fjuro Co-authored-by: Francisco Ruiz Co-authored-by: Giovanni Donisi Co-authored-by: GnuPGを使うべきだ Co-authored-by: Gontzal Manuel Pujana Onaindia Co-authored-by: Hin Weisner Co-authored-by: Hosted Weblate Co-authored-by: Igor Sorocean Co-authored-by: Ihor Hordiichuk Co-authored-by: JScocktail Co-authored-by: Jalaluddin Co-authored-by: Jeff Huang Co-authored-by: Josu Co-authored-by: Karl Tammik Co-authored-by: Laura Vasconcelos Pereira Felippe Co-authored-by: Lenn Art Co-authored-by: Linerly Co-authored-by: Louis V Co-authored-by: Marian Hanzel Co-authored-by: MatthieuPh Co-authored-by: Milo Ivir Co-authored-by: Mohammed Anas Co-authored-by: MΛX Co-authored-by: Nadir Nour Co-authored-by: Napstaguy04 Co-authored-by: Nizami Co-authored-by: Oymate Co-authored-by: Pieter van der Razemond Co-authored-by: Ray Co-authored-by: Rex_sa Co-authored-by: Ricardo Co-authored-by: S3aBreeze Co-authored-by: SC Co-authored-by: STV Co-authored-by: Samar Ali Co-authored-by: Santhosh J Co-authored-by: Software In Interlingua Co-authored-by: TXRdev Archive Co-authored-by: Tadeusz Dudek Co-authored-by: ThePlanetaryDroid Co-authored-by: Translator Co-authored-by: Vasilis K Co-authored-by: VfBFan Co-authored-by: Viktor Co-authored-by: WB Co-authored-by: Xəyyam Qocayev Co-authored-by: Yaron Shahrabani Co-authored-by: Zoldtukor Co-authored-by: chr56 Co-authored-by: i-am-SangWoo-Lee Co-authored-by: nautilusx Co-authored-by: pjammo Co-authored-by: rakijagamer-2003 Co-authored-by: remon-drk Co-authored-by: ssantos Co-authored-by: subba raidu Co-authored-by: yunna Co-authored-by: Симеон Цветков Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/az/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn_BD/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/ Translation: NewPipe/Metadata --- app/src/main/res/values-ar/strings.xml | 11 +- app/src/main/res/values-az/strings.xml | 7 +- app/src/main/res/values-bg/strings.xml | 15 +- app/src/main/res/values-bn-rBD/strings.xml | 1 + app/src/main/res/values-bn/strings.xml | 2 + app/src/main/res/values-cs/strings.xml | 49 +- app/src/main/res/values-de/strings.xml | 9 +- app/src/main/res/values-el/strings.xml | 7 +- app/src/main/res/values-es/strings.xml | 12 +- app/src/main/res/values-et/strings.xml | 6 +- app/src/main/res/values-eu/strings.xml | 13 +- app/src/main/res/values-fa/strings.xml | 6 +- app/src/main/res/values-fil/strings.xml | 7 +- app/src/main/res/values-fr/strings.xml | 15 +- app/src/main/res/values-gl/strings.xml | 13 +- app/src/main/res/values-he/strings.xml | 6 +- app/src/main/res/values-hr/strings.xml | 494 +++++++++--------- app/src/main/res/values-hu/strings.xml | 41 +- app/src/main/res/values-hy/strings.xml | 3 + app/src/main/res/values-ia/strings.xml | 12 + app/src/main/res/values-in/strings.xml | 5 +- app/src/main/res/values-it/strings.xml | 7 +- app/src/main/res/values-ja/strings.xml | 83 +-- app/src/main/res/values-ml/strings.xml | 4 +- app/src/main/res/values-nl/strings.xml | 8 + app/src/main/res/values-pl/strings.xml | 5 +- app/src/main/res/values-pt-rBR/strings.xml | 5 +- app/src/main/res/values-pt-rPT/strings.xml | 2 + app/src/main/res/values-pt/strings.xml | 6 +- app/src/main/res/values-ro/strings.xml | 13 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-sc/strings.xml | 6 +- app/src/main/res/values-sk/strings.xml | 8 + app/src/main/res/values-so/strings.xml | 4 +- app/src/main/res/values-sv/strings.xml | 9 +- app/src/main/res/values-te/strings.xml | 25 + app/src/main/res/values-tr/strings.xml | 37 +- app/src/main/res/values-uk/strings.xml | 5 +- app/src/main/res/values-ur/strings.xml | 6 +- app/src/main/res/values-vi/strings.xml | 95 ++-- app/src/main/res/values-zh-rCN/strings.xml | 6 +- app/src/main/res/values-zh-rHK/strings.xml | 61 ++- app/src/main/res/values-zh-rTW/strings.xml | 5 +- .../metadata/android/az/changelogs/988.txt | 2 + .../metadata/android/bn/changelogs/66.txt | 33 ++ .../metadata/android/bn/changelogs/730.txt | 4 +- .../metadata/android/bn/changelogs/770.txt | 2 +- .../metadata/android/bn/changelogs/956.txt | 2 +- .../metadata/android/bn/short_description.txt | 2 +- .../metadata/android/bn_BD/changelogs/63.txt | 8 + .../metadata/android/bn_BD/changelogs/64.txt | 8 + .../android/bn_BD/full_description.txt | 1 + .../android/bn_BD/short_description.txt | 1 + .../metadata/android/cs/changelogs/63.txt | 8 + .../metadata/android/cs/changelogs/64.txt | 8 + .../metadata/android/cs/changelogs/65.txt | 26 + .../metadata/android/cs/changelogs/66.txt | 16 + .../metadata/android/cs/changelogs/68.txt | 15 + .../metadata/android/cs/changelogs/69.txt | 9 + .../metadata/android/cs/changelogs/70.txt | 7 + .../metadata/android/cs/changelogs/71.txt | 7 + .../metadata/android/cs/changelogs/730.txt | 2 + .../metadata/android/cs/changelogs/740.txt | 23 + .../metadata/android/cs/changelogs/750.txt | 14 + .../metadata/android/cs/changelogs/760.txt | 14 + .../metadata/android/cs/changelogs/770.txt | 4 + .../metadata/android/cs/changelogs/780.txt | 11 + .../metadata/android/cs/changelogs/790.txt | 9 + .../metadata/android/cs/changelogs/800.txt | 10 + .../metadata/android/cs/changelogs/810.txt | 8 + .../metadata/android/cs/changelogs/820.txt | 1 + .../metadata/android/cs/changelogs/830.txt | 1 + .../metadata/android/cs/changelogs/840.txt | 8 + .../metadata/android/cs/changelogs/850.txt | 1 + .../metadata/android/cs/changelogs/860.txt | 7 + .../metadata/android/cs/changelogs/870.txt | 2 + .../metadata/android/cs/changelogs/900.txt | 13 + .../metadata/android/cs/changelogs/910.txt | 1 + .../metadata/android/cs/changelogs/920.txt | 9 + .../metadata/android/cs/changelogs/930.txt | 10 + .../metadata/android/cs/changelogs/940.txt | 9 + .../metadata/android/cs/changelogs/950.txt | 4 + .../metadata/android/cs/changelogs/951.txt | 6 + .../metadata/android/cs/changelogs/953.txt | 1 + .../metadata/android/cs/changelogs/954.txt | 6 + .../metadata/android/cs/changelogs/955.txt | 3 + .../metadata/android/cs/changelogs/956.txt | 1 + .../metadata/android/cs/changelogs/957.txt | 8 + .../metadata/android/cs/changelogs/958.txt | 15 + .../metadata/android/cs/changelogs/959.txt | 3 + .../metadata/android/cs/changelogs/960.txt | 4 + .../metadata/android/cs/changelogs/961.txt | 12 + .../metadata/android/cs/changelogs/963.txt | 1 + .../metadata/android/cs/changelogs/964.txt | 8 + .../metadata/android/cs/changelogs/965.txt | 6 + .../metadata/android/cs/changelogs/966.txt | 14 + .../metadata/android/cs/changelogs/967.txt | 1 + .../metadata/android/cs/changelogs/968.txt | 7 + .../metadata/android/cs/changelogs/969.txt | 8 + .../metadata/android/cs/changelogs/970.txt | 11 + .../metadata/android/cs/changelogs/971.txt | 3 + .../metadata/android/cs/changelogs/972.txt | 14 + .../metadata/android/cs/changelogs/973.txt | 4 + .../metadata/android/cs/changelogs/974.txt | 5 + .../metadata/android/cs/changelogs/975.txt | 17 + .../metadata/android/cs/changelogs/976.txt | 10 + .../metadata/android/cs/changelogs/977.txt | 10 + .../metadata/android/cs/changelogs/978.txt | 1 + .../metadata/android/cs/changelogs/979.txt | 2 + .../metadata/android/cs/changelogs/980.txt | 13 + .../metadata/android/cs/changelogs/981.txt | 2 + .../metadata/android/cs/changelogs/982.txt | 1 + .../metadata/android/cs/changelogs/983.txt | 9 + .../metadata/android/cs/changelogs/984.txt | 7 + .../metadata/android/cs/changelogs/985.txt | 1 + .../metadata/android/cs/changelogs/986.txt | 16 + .../metadata/android/cs/changelogs/987.txt | 12 + .../metadata/android/cs/changelogs/988.txt | 2 + .../metadata/android/cs/changelogs/989.txt | 3 + .../metadata/android/de/changelogs/987.txt | 2 +- .../metadata/android/fr/changelogs/63.txt | 4 +- .../metadata/android/fr/changelogs/65.txt | 26 + .../metadata/android/fr/changelogs/66.txt | 28 + .../metadata/android/fr/changelogs/68.txt | 31 ++ .../metadata/android/fr/changelogs/69.txt | 19 + .../metadata/android/fr/changelogs/70.txt | 25 + .../metadata/android/fr/changelogs/71.txt | 14 +- .../metadata/android/fr/changelogs/964.txt | 8 + .../metadata/android/fr/changelogs/966.txt | 14 + .../metadata/android/fr/changelogs/969.txt | 8 + .../metadata/android/fr/changelogs/970.txt | 11 + .../metadata/android/fr/changelogs/971.txt | 3 + .../metadata/android/fr/changelogs/973.txt | 4 + .../metadata/android/fr/changelogs/974.txt | 5 + .../metadata/android/fr/changelogs/977.txt | 8 + .../metadata/android/fr/changelogs/980.txt | 13 + .../metadata/android/fr/changelogs/987.txt | 6 +- .../metadata/android/fr/changelogs/988.txt | 2 + .../metadata/android/fr/short_description.txt | 2 +- .../metadata/android/he/changelogs/988.txt | 2 + .../metadata/android/hu/changelogs/65.txt | 26 + .../metadata/android/hu/short_description.txt | 2 +- .../metadata/android/it/changelogs/63.txt | 10 +- .../metadata/android/it/changelogs/730.txt | 2 + .../metadata/android/ko/changelogs/63.txt | 8 + .../metadata/android/ko/changelogs/64.txt | 8 + .../metadata/android/pl/changelogs/964.txt | 8 + .../metadata/android/pl/changelogs/965.txt | 5 + .../metadata/android/pt/changelogs/955.txt | 6 +- .../metadata/android/ru/changelogs/65.txt | 1 + .../metadata/android/ru/changelogs/66.txt | 1 + .../metadata/android/ru/changelogs/68.txt | 1 + .../metadata/android/ru/changelogs/69.txt | 1 + .../metadata/android/ru/changelogs/70.txt | 1 + .../metadata/android/ru/changelogs/780.txt | 12 + .../metadata/android/ru/changelogs/790.txt | 1 + .../metadata/android/ru/changelogs/985.txt | 1 + .../metadata/android/ru/changelogs/987.txt | 12 + .../metadata/android/sk/changelogs/987.txt | 12 + .../metadata/android/tr/changelogs/63.txt | 4 +- .../metadata/android/tr/full_description.txt | 3 +- .../metadata/android/uk/changelogs/988.txt | 2 + .../metadata/android/uk/changelogs/989.txt | 3 + .../android/zh-Hans/changelogs/988.txt | 2 + .../android/zh-Hans/changelogs/989.txt | 3 + .../android/zh-Hant/changelogs/988.txt | 2 + .../android/zh_Hant_HK/changelogs/988.txt | 2 + 167 files changed, 1613 insertions(+), 466 deletions(-) create mode 100644 fastlane/metadata/android/az/changelogs/988.txt create mode 100644 fastlane/metadata/android/bn/changelogs/66.txt create mode 100644 fastlane/metadata/android/bn_BD/changelogs/63.txt create mode 100644 fastlane/metadata/android/bn_BD/changelogs/64.txt create mode 100644 fastlane/metadata/android/bn_BD/full_description.txt create mode 100644 fastlane/metadata/android/bn_BD/short_description.txt create mode 100644 fastlane/metadata/android/cs/changelogs/63.txt create mode 100644 fastlane/metadata/android/cs/changelogs/64.txt create mode 100644 fastlane/metadata/android/cs/changelogs/65.txt create mode 100644 fastlane/metadata/android/cs/changelogs/66.txt create mode 100644 fastlane/metadata/android/cs/changelogs/68.txt create mode 100644 fastlane/metadata/android/cs/changelogs/69.txt create mode 100644 fastlane/metadata/android/cs/changelogs/70.txt create mode 100644 fastlane/metadata/android/cs/changelogs/71.txt create mode 100644 fastlane/metadata/android/cs/changelogs/730.txt create mode 100644 fastlane/metadata/android/cs/changelogs/740.txt create mode 100644 fastlane/metadata/android/cs/changelogs/750.txt create mode 100644 fastlane/metadata/android/cs/changelogs/760.txt create mode 100644 fastlane/metadata/android/cs/changelogs/770.txt create mode 100644 fastlane/metadata/android/cs/changelogs/780.txt create mode 100644 fastlane/metadata/android/cs/changelogs/790.txt create mode 100644 fastlane/metadata/android/cs/changelogs/800.txt create mode 100644 fastlane/metadata/android/cs/changelogs/810.txt create mode 100644 fastlane/metadata/android/cs/changelogs/820.txt create mode 100644 fastlane/metadata/android/cs/changelogs/830.txt create mode 100644 fastlane/metadata/android/cs/changelogs/840.txt create mode 100644 fastlane/metadata/android/cs/changelogs/850.txt create mode 100644 fastlane/metadata/android/cs/changelogs/860.txt create mode 100644 fastlane/metadata/android/cs/changelogs/870.txt create mode 100644 fastlane/metadata/android/cs/changelogs/900.txt create mode 100644 fastlane/metadata/android/cs/changelogs/910.txt create mode 100644 fastlane/metadata/android/cs/changelogs/920.txt create mode 100644 fastlane/metadata/android/cs/changelogs/930.txt create mode 100644 fastlane/metadata/android/cs/changelogs/940.txt create mode 100644 fastlane/metadata/android/cs/changelogs/950.txt create mode 100644 fastlane/metadata/android/cs/changelogs/951.txt create mode 100644 fastlane/metadata/android/cs/changelogs/953.txt create mode 100644 fastlane/metadata/android/cs/changelogs/954.txt create mode 100644 fastlane/metadata/android/cs/changelogs/955.txt create mode 100644 fastlane/metadata/android/cs/changelogs/956.txt create mode 100644 fastlane/metadata/android/cs/changelogs/957.txt create mode 100644 fastlane/metadata/android/cs/changelogs/958.txt create mode 100644 fastlane/metadata/android/cs/changelogs/959.txt create mode 100644 fastlane/metadata/android/cs/changelogs/960.txt create mode 100644 fastlane/metadata/android/cs/changelogs/961.txt create mode 100644 fastlane/metadata/android/cs/changelogs/963.txt create mode 100644 fastlane/metadata/android/cs/changelogs/964.txt create mode 100644 fastlane/metadata/android/cs/changelogs/965.txt create mode 100644 fastlane/metadata/android/cs/changelogs/966.txt create mode 100644 fastlane/metadata/android/cs/changelogs/967.txt create mode 100644 fastlane/metadata/android/cs/changelogs/968.txt create mode 100644 fastlane/metadata/android/cs/changelogs/969.txt create mode 100644 fastlane/metadata/android/cs/changelogs/970.txt create mode 100644 fastlane/metadata/android/cs/changelogs/971.txt create mode 100644 fastlane/metadata/android/cs/changelogs/972.txt create mode 100644 fastlane/metadata/android/cs/changelogs/973.txt create mode 100644 fastlane/metadata/android/cs/changelogs/974.txt create mode 100644 fastlane/metadata/android/cs/changelogs/975.txt create mode 100644 fastlane/metadata/android/cs/changelogs/976.txt create mode 100644 fastlane/metadata/android/cs/changelogs/977.txt create mode 100644 fastlane/metadata/android/cs/changelogs/978.txt create mode 100644 fastlane/metadata/android/cs/changelogs/979.txt create mode 100644 fastlane/metadata/android/cs/changelogs/980.txt create mode 100644 fastlane/metadata/android/cs/changelogs/981.txt create mode 100644 fastlane/metadata/android/cs/changelogs/982.txt create mode 100644 fastlane/metadata/android/cs/changelogs/983.txt create mode 100644 fastlane/metadata/android/cs/changelogs/984.txt create mode 100644 fastlane/metadata/android/cs/changelogs/985.txt create mode 100644 fastlane/metadata/android/cs/changelogs/986.txt create mode 100644 fastlane/metadata/android/cs/changelogs/987.txt create mode 100644 fastlane/metadata/android/cs/changelogs/988.txt create mode 100644 fastlane/metadata/android/cs/changelogs/989.txt create mode 100644 fastlane/metadata/android/fr/changelogs/65.txt create mode 100644 fastlane/metadata/android/fr/changelogs/66.txt create mode 100644 fastlane/metadata/android/fr/changelogs/68.txt create mode 100644 fastlane/metadata/android/fr/changelogs/69.txt create mode 100644 fastlane/metadata/android/fr/changelogs/70.txt create mode 100644 fastlane/metadata/android/fr/changelogs/964.txt create mode 100644 fastlane/metadata/android/fr/changelogs/966.txt create mode 100644 fastlane/metadata/android/fr/changelogs/969.txt create mode 100644 fastlane/metadata/android/fr/changelogs/970.txt create mode 100644 fastlane/metadata/android/fr/changelogs/971.txt create mode 100644 fastlane/metadata/android/fr/changelogs/973.txt create mode 100644 fastlane/metadata/android/fr/changelogs/974.txt create mode 100644 fastlane/metadata/android/fr/changelogs/977.txt create mode 100644 fastlane/metadata/android/fr/changelogs/980.txt create mode 100644 fastlane/metadata/android/fr/changelogs/988.txt create mode 100644 fastlane/metadata/android/he/changelogs/988.txt create mode 100644 fastlane/metadata/android/hu/changelogs/65.txt create mode 100644 fastlane/metadata/android/it/changelogs/730.txt create mode 100644 fastlane/metadata/android/ko/changelogs/63.txt create mode 100644 fastlane/metadata/android/ko/changelogs/64.txt create mode 100644 fastlane/metadata/android/pl/changelogs/964.txt create mode 100644 fastlane/metadata/android/pl/changelogs/965.txt create mode 100644 fastlane/metadata/android/ru/changelogs/65.txt create mode 100644 fastlane/metadata/android/ru/changelogs/66.txt create mode 100644 fastlane/metadata/android/ru/changelogs/68.txt create mode 100644 fastlane/metadata/android/ru/changelogs/69.txt create mode 100644 fastlane/metadata/android/ru/changelogs/70.txt create mode 100644 fastlane/metadata/android/ru/changelogs/780.txt create mode 100644 fastlane/metadata/android/ru/changelogs/790.txt create mode 100644 fastlane/metadata/android/ru/changelogs/985.txt create mode 100644 fastlane/metadata/android/ru/changelogs/987.txt create mode 100644 fastlane/metadata/android/sk/changelogs/987.txt create mode 100644 fastlane/metadata/android/uk/changelogs/988.txt create mode 100644 fastlane/metadata/android/uk/changelogs/989.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/988.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/989.txt create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/988.txt create mode 100644 fastlane/metadata/android/zh_Hant_HK/changelogs/988.txt diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 71d849a2f..700a612b8 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -200,7 +200,7 @@ © %1$sبواسطة%2$sتحت%3$s صفحة الكشك حدد كشك - ابدأ التشغيل في الخلفية + بدأ التشغيل في الخلفية المحتوى الإفتراضي حسب البلد الإنتقال إلى التشغيل في الخلفية الإنتقال إلى التشغيل في النافذة المنبثقة @@ -250,7 +250,7 @@ لا يمكن أن يكون اسم الملف فارغًا حدث خطأٌ ما: %1$s ملف مضغوط ZIP غير صالح - إزالة الفواصل المرجعية + إزالة الإشارة المرجعية تناسب مع الشاشة توليد تلقائي إستيراد @@ -600,8 +600,8 @@ زر الإجراء الثالث زر الإجراء الثاني زر الإجراء الأول - قياس الصورة المصغرة للفيديو المعروض في الإشعار من 16:9 إلى 1:1 نسبة العرض إلى الارتفاع (قد يؤدي إلى تشوهات) - مقياس الصورة المصغرة إلى نسبة عرض إلى ارتفاع 1:1 + قم بقص الصورة المصغرة للفيديو الموضحة في الإشعار من نسبة العرض إلى الارتفاع 16: 9 إلى 1: 1 + اقتصاص الصورة المصغرة إلى نسبة العرض إلى الارتفاع 1:1 امسح ملفات تعريف الارتباط التي يخزنها NewPipe عند حل reCAPTCHA تم مسح ملفات تعريف الارتباط reCAPTCHA امسح ملفات تعريف الارتباط reCAPTCHA @@ -684,7 +684,7 @@ جودة منخفضة (أصغر) جودة عالية (أكبر) معاينة مصغرة على شريط التمرير - علّمه كفيديو تمت مشاهدته + تعليم كفيديو تمت مشاهدته أُعجب بها منشئ المحتوى أظهر أشرطة ملونة لبيكاسو أعلى الصور تشير إلى مصدرها: الأحمر للشبكة والأزرق للقرص والأخضر للذاكرة إظهار مؤشرات الصور @@ -769,4 +769,5 @@ تنسيق غير معروف جودة غير معروفة حجم الفاصل الزمني لتحميل التشغيل + عرض مقاطع الفيديو المستقبلية \ No newline at end of file diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 2d9257e3c..bd5948622 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -405,7 +405,7 @@ Sükut zamanı sürətlə irəlilə Yeni yayım bildirişləri Abunəliklərdən yeni yayımlar haqqında bildiriş göndər - Tezliyin yoxlanılması + Yoxlama tezliyi Tələb olunan şəbəkə bağlantısı İstənilən şəbəkə Tətbiq keçidində kiçildin @@ -496,7 +496,7 @@ Video oynadıcı Video fayl xülasəsi prosesi üçün bildirişlər Açın - Kiçik şəkili 1:1 aspekt nisbətinə ölçün + Miniatürü 1:1 aspekt nisbətinə kəsin Yükləmə intervalının həcmini dəyişdirin (hazırda %s). Daha aşağı dəyər ilkin video yükləməni sürətləndirə bilər. Dəyişikliklər oynadıcının yenidən başladılmasını tələb edir. Yayım yaradıcısı, məzmunu və ya axtarış sorğusu haqqında əlavə məlumat olan üst məlumat qutularını gizlətmək üçün söndürün Əlaqədar yayımı əlavə etməklə (təkrar etməyən) sonlanacaq oynatma sırasını davam etdir @@ -706,7 +706,7 @@ \nZəhmət olmasa ,Yaddaş Giriş Çərçivəsinə uyğun fayl menecerini quraşdırın. Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil. İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə bilər və keçidlər kliklənməyə bilər. - Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 nisbətinə qədər ölçün (pozuntulara səbəb ola bilər) + Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 nisbətinə qədər kəsin Aşağıdakı bildiriş fəaliyyətini hər birinin üzərinə toxunaraq redaktə edin. Sağdakı təsdiq qutularından istifadə edərək yığcam bildirişdə göstərilməsi üçün onlardan üçə qədərini seçin Belə fayl/məzmun mənbəyi yoxdur Seçilmiş yayım xarici oynadıcılar tərəfindən dəstəklənmir @@ -717,4 +717,5 @@ Naməlum format Naməlum keyfiyyət Oynatma yükləmə intervalı həcmi + Gələcək videoları göstərin \ No newline at end of file diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index dc13a16c2..5ac2b1db8 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -446,7 +446,7 @@ Лиценз %s посочва следната причина: Маркери - Достъпност + Поверителност Език Вътрешен Включен @@ -553,4 +553,17 @@ Показвай цветни Picasso-панделки в горната част на изображенията като индикатор за техния произход (червен – от мрежата, син – от диска и червен – от паметта) Автоматична (тази на устройството) Мащабиране на миниатюрата в известието от 16:9 към 1:1 формат (възможни са изкривявания) + Покажи гледани + Избете плейлист + Известия + Изчистване на бисквитките от reCAPTCHA + Бисквитките от reCAPTCHA бяха почистени + Проверяване за актуализации… + , + Провери за актуализации + Процент + Неизвестно качество + Неизвестен формат + Наскоро добавено + Буфериране \ No newline at end of file diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 02d35d384..1de4d3a50 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -459,4 +459,5 @@ দেখা হিসেবে মার্ক করো ট্যাগসমূহ অ্যান্ড্রয়েডকে থাম্বনেইলের প্রধান রং অনুযায়ী রঙিন করুন (উল্লেখ্য যে, এটি সব ডিভাইসে উপলব্ধ নয়) + অজানা ধরন \ No newline at end of file diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index a1acf9fd1..6a634a3a4 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -622,4 +622,6 @@ বাহ্যিক প্লেয়ারের জন্য কোনো অডিও স্ট্রিম নেই বাহ্যিক প্লেয়ারের জন্য কোনো ভিডিও স্ট্রিম নেই অজ্ঞাত মান + নতুন ধারার বিজ্ঞপ্তি + নেটওয়ার্ক সংযোগ দরকার \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 2e840629d..8ab1e2584 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -366,7 +366,7 @@ Vyčkávání Pozastaveno ve frontě - post-processing + zpracování Zařadit do fronty Akce odmítnuta systémem Stahování se nezdařilo @@ -384,7 +384,7 @@ Server neposílá data Server neakceptuje vícevláknové stahování, opakujte akci s @string/msg_threads = 1 Nenalezeno - Post-processing selhal + Zpracování selhalo Zastavit Maximální počet pokusů o opakování Maximální počet pokusů před zrušením stahování @@ -570,8 +570,8 @@ Třetí akční tlačítko Druhé akční tlačítko První akční tlačítko - Zmenšit miniaturu videa zobrazenou v oznámení z poměru stran 16: 9 na 1: 1 (může způsobit zkreslení) - Změnit poměr stran miniatury na 1:1 + Oříznout miniaturu videa zobrazenou v oznámení z poměru stran 16:9 na 1:1 + Oříznout poměr stran miniatury na 1:1 Ukázat memory leaks Zařazeno do fronty Zařadit do fronty @@ -584,10 +584,10 @@ Přibarvit oznámení Použít miniaturu pro pozadí zamknuté obrazovky a oznámení Zobrazit miniaturu - Oznámení o hašování videa + Oznámení o hashování Nedávné - Počítám haš - Oznámení o postupu hašování videa + Počítám hash + Oznámení o postupu hashování videa Vypnout, abyste skryli rámečky s meta informací s údaji o autorovi streamu, obsahu streamu nebo požadavků hledání Zobrazit meta informaci Kapitoly @@ -596,7 +596,7 @@ Zobrazit popis Otevřít s Na Vašem zařízení není aplikace, která to umí otevřít - Podobné strýmy + Podobné položky Vypnout pro skrytí popisu videa a doplňkové informace Zbořit aplikaci Stahování bylo zahájeno @@ -683,7 +683,7 @@ Vytvořit oznámení o chybě Kontrola aktualizací… Ukázat „Shodit přehrávač“ - Nové položky feedů + Nové položky Pro tuto akci nebyl nalezen žádný vhodný správce souborů. \nProsím, nainstalujte správce souborů kompatibilní se Storage Access Framework. Oznámení o hlášení chyb @@ -697,29 +697,38 @@ Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače. LeakCanary není dostupné Výchozí ExoPlayer - Nastavit oznámení k právě přehrávanému strýmu - Oznámení o nových strýmech - Oznámit o nových strýmech k objednání + Nastavit oznámení o právě přehrávaném streamu + Oznámení o nových streamech + Oznámit o nových strýmech od vašich odběrů Frekvence kontroly Jakákoli síť Nutné síťové připojení Smazat všechny stažené soubory z disku\? Objednali jste si nyní tento kanál Všechny přepnout - Nové strýmy - Oznámení o nových strýmech k objednání - Spustit kontrolu nových strýmů - Oznámení přehrávače + Nové streamy + Oznámení o nových streamech od odběrů + Spustit kontrolu nových streamů + Upozornění přehrávače Oznámení - Načítám podrobnosti o strýmu… + Načítám podrobnosti o streamu… Oznámení jsou vypnuta Přijímat oznámení , - %s nový strým - %s nové strýmy - %s nových strýmů + %s nový stream + %s nové streamy + %s nových streamů Procento Půltón + Velikost intervalu načtení přehrávání + Vybraný stream není podporován externími přehrávači + U externích přehrávačů nejsou dostupné žádné zvukové streamy + Neznámý formát + Neznámá kvalita + Zobrazit nadcházející videa + Streamy, které zatím nejsou podporovány systémem stahování, nebudou zobrazeny + Vyberte kvalitu pro externí přehrávače + U externích přehrávačů nejsou dostupné žádné video streamy \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4c8e9cfe1..5ff079f7a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,7 +1,7 @@ Veröffentlicht am %1$s - Kein Stream-Player gefunden. Möchtest du den VLC installieren\? + Kein Stream-Player gefunden. Möchtest du VLC installieren\? Installieren Abbrechen Im Browser öffnen @@ -161,7 +161,7 @@ Die meisten Sonderzeichen Wiedergabe fortsetzen Player - Nichts hier außer dem Zirpen der Grillen + Nichts hier, außer dem Zirpen der Grillen Möchtest du dieses Element aus dem Suchverlauf löschen\? Leere Seite Einen Kanal auswählen @@ -558,8 +558,8 @@ Dritte Aktionstaste Zweite Aktionstaste Erste Aktionstaste - Skaliert das in der Benachrichtigung angezeigte Vorschaubild von 16:9 auf ein 1:1 Seitenverhältnis (kann zu Verzerrungen führen) - Vorschaubild auf 1:1 Seitenverhältnis skalieren + Beschneidet das in der Benachrichtigung angezeigte Video-Vorschaubild von 16:9 auf ein 1:1 Seitenverhältnis + Vorschaubild auf 1:1 Seitenverhältnis zuschneiden Zufällig Puffern Wiederholen @@ -717,4 +717,5 @@ Streams, die noch nicht vom Downloader unterstützt werden, werden nicht angezeigt Der ausgewählte Stream wird von externen Playern nicht unterstützt Größe des Ladeintervalls für die Wiedergabe + Zukünftige Videos anzeigen \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index d9239547b..b1b11e24d 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -498,8 +498,8 @@ Κουμπί τρίτης ενέργειας Κουμπί δεύτερης ενέργειας Κουμπί πρώτης ενέργειας - Κλιμάκωση της μικρογραφίας βίντεο που εμφανίζεται στην ειδοποίηση από 16:9 σε αναλογία διαστάσεων 1:1 (μπορεί να προκαλέσει παραμορφώσεις) - Κλιμάκωση μικρογραφίας σε αναλογία διαστάσεων 1:1 + Περικοπή της μικρογραφίας βίντεο που εμφανίζεται στην ειδοποίηση από 16:9 σε αναλογία διαστάσεων 1:1 + Περικοπή μικρογραφίας σε αναλογία διαστάσεων 1:1 Φόρτωση Πιστεύετε ότι η ροή φορτώνει πολύ αργά; Δοκιμάστε να ενεργοποιήσετε τη γρήγορη φόρτωση (από τις ρυθμίσεις ή πατώντας το παρακάτω κουμπί). \n @@ -717,4 +717,5 @@ Άγνωστος τύπος αρχείου Άγνωστη ποιότητα Μέγεθος διαστήματος φόρτωσης αναπαραγωγής - + Εμφάνιση μελλοντικών βίντεο + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 56d529b04..edf4769e8 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -544,7 +544,7 @@ Copiar informe con formato Mostrando resultados para: %s Orden aleatorio - Escalar miniatura a relación de aspecto 1:1 + Recortar miniatura a relación de aspecto 1:1 Nunca Solo en Wi-Fi Comenzar reproducción automáticamente — %s @@ -558,13 +558,13 @@ Almacenar en memoria (búfer) Repetir ¡Puedes seleccionar como máximo tres acciones para mostrar en la notificación compacta! - Edita cada acción de notificación debajo pulsando sobre ella. Selecciona hasta tres de ellas para que aparezcan en la notificación compacta usando las casillas de verificación a la derecha + Edita cada una de las acciones en la notificación pulsando sobre ellas. Selecciona hasta tres de ellas para mostrarlas en la notificación compacta usando las casillas de verificación de la derecha. Botón de quinta acción Botón de cuarta acción Botón de tercera acción Botón de segunda acción Botón de primera acción - Escalar la relación de aspecto de la miniatura del vídeo mostrada en la notificación de 16:9 a 1:1 (puede ocasionar distorsiones) + Recortar la relación de aspecto de la miniatura del vídeo mostrada en la notificación de 16:9 a 1:1 Vaciar las cookies que NewPipe guarda al resolver un reCAPTCHA Mostrar contenido inapropiado para niños porque tiene un limite de edad (como 18+) Mostrar pérdidas de memoria @@ -574,7 +574,7 @@ Limpiar las cookies reCAPTCHA YouTube provee un «Modo restringido», el cual oculta contenido potencialmente solo apto para adultos Ajustar color de notificación - Permitir a Android personalizar el color de la notificación con el color principal de la imagen (ten en cuenta que esta opción no funciona en todos los dispositivos) + Permitir a Android personalizar el color de la notificación usando el color principal de la miniatura (ten en cuenta que esta opción no funciona en todos los dispositivos) Usar miniatura como fondo de pantalla de bloqueo y notificaciones Mostrar vista previa Desactivar para ocultar información adicional sobre el creador o contenido de la transmisión @@ -603,7 +603,7 @@ La descarga ha comenzado Resolver Puedes seleccionar tu tema nocturno favorito a continuación - Tema de Noche + Modo oscuro Selecciona tu tema nocturno favorito — %s Automático (tema del dispositivo) Mostrar detalles del canal @@ -719,4 +719,6 @@ Elija la calidad para reproductores externos Formato desconocido Calidad desconocida + Mostrar videos futuros + Tamaño del intervalo de carga de reproducción \ No newline at end of file diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 11b5266b0..2d776155c 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -426,8 +426,8 @@ Kolmas tegevusnupp Teine tegevusnupp Esimene tegevusnupp - Skaleeri teavituses kuvatav video pisipilt 16:9 külgede suhtest 1:1 suhtesse (võib põhjustada häireid) - Skaleeri pisipilt 1:1 küljesuhtesse + Kadreeri teavituses kuvatav video pisipilt 16:9 külgede suhtest 1:1 suhtesse + Kadreeri pisipilt 1:1 küljesuhtesse Arvutan räsi Hiljutised Kirjeldus @@ -716,4 +716,6 @@ Valitud meediavood ei ole toetatud välises pleieris Protsent Pooltoon + Taasesituseks vajalike andmete laadimise samm + Näita tulevasi videoid \ No newline at end of file diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 8098db146..c2d92c517 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -562,8 +562,8 @@ Hirugarren ekintzaren botoia Bigarren ekintzaren botoia Lehenego ekintzaren botoia - Eskalatu jakinarazpenetan erakusten den bideo miniaturaren formatu-ratioa 16:9tik 1:1era (distortsioak sor ditzake) - Miniatura 1:1 formatu-ratiora eskalatu + Ebaki jakinarazpenetan erakusten den bideo miniaturaren formatu-ratioa 16:9tik 1:1era + Miniatura 1:1 formatu-ratiora ebaki %s bilaketaren erantzunak erakusten Ilaran jarri da Jarri ilaran @@ -709,4 +709,13 @@ Beharrezko sare konexioa Portzentaia Semitonoa + Erreprodukzioaren kargatze-tartearen tamaina + Deskargatzaileak onartzen ez dituen jarioak ez dira erakusten + Hautatutako jarioa ez dago kanpoko erreproduzigailu batengatik onartuta + Ez dago kanpoko erreproduzigailu batengatik onartuta dagoen audio jariorik + Ez dago kanpoko erreproduzigailu batengatik onartuta dagoen bideo jariorik + Formatu ezezaguna + Kalitate ezezaguna + Erakutsi etorkizuneko bideoak + Hautatu kanpoko erreproduzigailuen kalitatea \ No newline at end of file diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 97fd534f1..6b607b6ae 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -557,8 +557,8 @@ سومین دکمه کنشی دومین دکمه کنشی اولین دکمه کنشی - تصویر بندانگشتی ویدیو که در اعلان نمایش می‌یابد، از نسبت ۱۶:۹ به ۱:۱ تغییر اندازه پیدا کند (ممکن است منجر به اعوجاج شود) - تغییر مقیاس تصویر بندانگشتی به نسبت ۱:۱ + تصویر بندانگشتی ویدیو که در اعلان نمایش می‌یابد، از نسبت ۱۶:۹ به ۱:۱ بریده می‌شود + برش تصویر بندانگشتی به نسبت ۱:۱ کیفیت پایین (کوچک‌تر) کیفیت بالا (بزرگ‌تر) نظرها از کار افتاده‌اند @@ -716,4 +716,6 @@ گزینش کیفیت برای پخش‌کننده‌های خارجی قالب ناشناخته کیفیت ناشناخته + اندازهٔ دورهٔ بار کردن پخش + نمایش ویدیوهای آینده \ No newline at end of file diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index d8c6682d6..758e522cd 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -12,7 +12,7 @@ I-download I-download ang stream file Maghanap - Ayos ng App + Pagsasaayos \"%1$s\" ba ang tinutukoy mo\? Ibahagi sa Gumamit ng ibang video player @@ -30,7 +30,7 @@ Pumili ng Tab Anong Bago Likuran - Popup + Naka-lutang Idagdag sa Download folder ng mga video Pumili ng download folder para sa mga video file @@ -257,4 +257,7 @@ Notipikasyon ng player Mga track Mga gumagamit + Hangganan ng Edad + Oo, pati na rin ang mga napanood nang video + Kusa (tema ng device) \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e09f292b0..4a79fbea9 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -6,7 +6,7 @@ Télécharger Dossier de téléchargement vidéo Choisissez le dossier de téléchargement des vidéos - Les fichiers vidéo téléchargés sont stockées ici + Les vidéos téléchargées sont stockées ici Installer Installer l’application Kore manquante \? Aucun lecteur de flux trouvé. Installer VLC \? @@ -99,7 +99,7 @@ Seuls certains appareils peuvent lire des vidéos 2K/4K Format vidéo par défaut Mémoriser les propriétés de la fenêtre flottante - Mémorise les dernières taille et position de la fenêtre flottante + Mémoriser les dernières taille et position de la fenêtre flottante Effacer G Le son peut être absent à certaines définitions @@ -551,7 +551,7 @@ Impossible de reconnaitre l’URL fournie. Voulez-vous l’ouvrir avec une autre application \? Ajouter automatiquement à la liste de lecture La liste de lecture du lecteur actif sera remplacée - Confirmer avant de supprimer la liste de lecture + Confirmer avant de supprimer une liste de lecture Rien Chargement Lire aléatoirement @@ -563,8 +563,8 @@ Troisième bouton d’action Deuxième bouton d’action Premier bouton d’action - Mettre à l\'échelle la miniature de la vidéo affichée dans la notification du format 16:9 au format 1:1 (peut provoquer des déformations) - Redimensionner la miniature au format 1:1 + Recadrer la miniature de la vidéo affichée dans la notification du format 16:9 au format 1:1 + Recadrer la miniature au format 1:1 Afficher les fuites de mémoire Ajouté à la file d’attente Ajouter à la file d’attente @@ -663,8 +663,8 @@ Balayez un élément pour le supprimer Ne pas lancer les vidéos dans le mini lecteur mais directement en plein écran si la rotation automatique est verrouillée. Vous pouvez toujours accéder au mini-lecteur en quittant le mode plein écran Lancer le lecteur principal en plein écran - Ajouter à la liste de lecture - Suivant dans la liste de lecture + Lire consécutivement + Video placée après la lecture Traitement en cours… Veuillez patienter Vérifier manuellement de nouvelles versions Vérification des mises à jour… @@ -719,4 +719,5 @@ Le flux séléctionné n\'est pas supporté par les lecteurs externes Aucun flux vidéo n\'est disponible pour les lecteurs externes Taille de l\'intervalle de chargement de la lecture + Afficher les futures vidéos \ 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 5cf8abcf0..701d1b8cc 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -548,8 +548,8 @@ Terceiro botón de acción Cuarto botón de acción Quinto botón de acción - Escalar a miniatura do vídeo amosado na notificación da relación de aspecto 16:9 a 1:1 (pode intruducir distorsións) - Escala miniatura á relación de aspecto 1:1 + Cortar a miniatura do vídeo amosado na notificación da relación de aspecto 16:9 a 1:1 + Cortar miniatura á relación de aspecto 1:1 Apagado Modo tableta Abrir sitio Web @@ -709,4 +709,13 @@ As notificacións están desactivadas Recibir notificacións Agora está subscrito a esta canle + Emisións non soportadas polo descarregador non son mostradas + Seleccione a calidade para reprodutores externos + Formato descoñecido + Calidade descoñecida + Tamaño do intervalo de carregamento da reprodución + As emisións seleccionadas non son soportadas polos reprodutores externos + Non hai emisións de vídeo dispoñíbeis para reprodutores externos + Ver futuros vídeos + Non hai emisións de audio dispoñíbeis para reprodutores externos \ 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 de5128493..b9056922a 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -580,8 +580,8 @@ כפתור פעולה שלישי כפתור פעולה ראשון כפתור פעולה שני - לשנות את יחס התצוגה הממוזערת שמופיעה בהתראות מיחס תצוגה של 16:9 ל־1:1 (עשוי לעוות את התמונה) - שינוי גודל התצוגה הממוזערת ליחס תצוגה 1:1 + חיתוך התצוגה הממוזערת שמופיעה בהתראות מיחס תצוגה של 16:9 ל־1:1 + חיתוך התצוגה הממוזערת ליחס תצוגה 1:1 הצגת דליפות זיכרון נוסף לתור הוספה לתור @@ -742,4 +742,6 @@ אין תזרימי וידאו שזמינים לנגנים חיצוניים בחירת איכות לנגנים חיצוניים תצורה לא מוכרת + גודל משך טעינת נגינה + הצגת סרטונים עתידיים \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index cb7db9f44..439835e06 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -1,63 +1,63 @@ - Za početak dodirnite povećalo. + Počni dodirom na povećalo. Objavljeno %1$s - Reproduktor za stream nije pronađen. Instalirati VLC\? + Nije pronađen nijedan player streamova. Želiš li instalirati VLC\? Instaliraj Odustani Otvori u pregledniku Otvori skočni prozor - Podijeli + Dijeli Preuzimanje - Pretraživanje + Pretraga Postavke - Jeste li mislili „%1$s”\? - Podijeli pomoću - Koristi vanjski reproduktor videozapisa - Uklanja zvuk pri nekim rezolucijama - Koristi vanjski reproduktor za zvuk + Misliš li „%1$s”\? + Dijeli s + Koristi vanjski video player + Uklanja audiosnimku pri nekim rezolucijama + Koristi vanjski audio player Pretplati se Pretplaćeno - Pretplata na kanalu otkazana - Nije moguće promijeniti pretplatu - Nije moguće osvježiti pretplatu + Pretplata na kanal otkazana + Nije bilo moguće promijeniti pretplatu + Nije bilo moguće aktualizirati pretplatu Pretplate Što je novo Pozadina Skočni prozor Mapa za preuzimanje videozapisa - Preuzeti videozapisi spremaju se ovdje - Odaberi mapu za preuzimanje videozapisa - Mapa za preuzimanje zvuka - Preuzete audio-datoteke spremaju se ovdje - Odaberi mapu za preuzimanje audio-datoteka - Standardna rezolucija - Standardna rezolucija skočnog prozora - Prikaži više rezolucije - Samo neki uređaji podržavaju reprodukciju 2K/4K videozapisa + Preuzete video datoteke se spremaju ovdje + Odaberi mapu za preuzimanje video datoteka + Mapa za preuzimanje audiosnimaka + Preuzete datoteke audiosnimaka se spremaju ovdje + Odaberi mapu za preuzimanje datoteka audiosnimaka + Zadana rezolucija + Zadana rezolucija skočnog prozora + Prikaži veće rezolucije + Samo neki uređaji podržavaju reprodukciju 2K/4K videa Reproduciraj s Kodijem Instalirati nedostajući Kore program\? - Prikaži opciju \"Reproduciraj putem Kodija\" + Prikaži opciju „Reproduciraj pomoću Kodija” Prikaži opciju za reproduciranje videozapisa putem Kodija - Zvuk - Zadani format zvuka - Zadani format videozapisa + Audiosnimka + Zadani audio format + Zadani video format Tema Svijetla Tamna Crna Zapamti veličinu i poziciju skočnog prozora Zapamti posljednju veličinu i poziciju skočnog prozora - Prijedlozi pri traženju + Prijedlozi pretrage Odaberi prijedloge koji se prikazuju pri traženju Povijest pretraživanja - Svaku pretragu spremi lokalno - Pregledna Povijest - Spremaj povijest gledanja + Spremi pretrage lokalno + Povijest gledanja + Prati gledana videa Nastavi reprodukciju Nastavi reproducirati nakon prekidanja (npr. telefonski pozivi) Preuzmi - Prikaži \'Sljedeće\' i \'Slične\' videozapise + Prikaži videa „Sljedeći” i „Slični” URL nije podržan Zadani jezik sadržaja Video i audio @@ -76,11 +76,11 @@ Najbolja rezolucija Greška Greška u mreži - Nije moguće učitati sve ikone - Nije moguće dešifrirati URL potpis videozapisa - Nije moguće dohvatiti stranicu + Nije bilo moguće učitati sve sličice + Nije bilo moguće dešifrirati URL potpis videozapisa + Nije bilo moguće obraditi stranicu Sadržaj nije dostupan - Nije moguće postaviti izbornik za preuzimanje + Nije bilo moguće postaviti izbornik za preuzimanje Program/korisničko sučelje su preknuli raditi Oprosti, ovo se nije trebalo dogoditi. Prijavi pogrešku putem e-maila @@ -89,14 +89,14 @@ Informacije: Što se dogodilo: Što:\\nZahtjev:\\nJezik sadržaja:\\nZemlja sadržaja:\\nJezik programa:\\nUsluga:\\nGMT vrijeme:\\nPaket:\\nVerzija:\\nVerzija OS-a: - Vaš komentar (na engleskom): + Tvoj komentar (na engleskom): Detalji: Pokreni video, trajanje: - Profilna slika prenositelja - Goreglasovi - Doljeglasovi - Videozapis - Zvuk + Sličica avatara prenositelja + Sviđanja + Nesviđanja + Video + Audio Pokušaj ponovo tis. mil @@ -104,26 +104,26 @@ Počni Pauziraj Izbriši - Kontrolna suma + Kontrolni zbroj U redu Naziv datoteke - Niti + Komponente procesa Greška - NewPipe preuzima - Dodirni za detalje - Molimo pričekajte… + NewPipe preuzimanje + Dodirni za prikaz detalja + Pričekaj … Kopirano u međuspremnik - Molimo kasnije u postavkama odaberite mapu za preuzimanje + Odaberi mapu za preuzimanje kasnije u postavkama Ova dozvola je potrebna za \notvaranje skočnog prozora reCAPTCHA zadatak - Traži se reCAPTCHA zadatak + Zatražen je reCAPTCHA zadatak Preuzimanja Dozvoljeni znakovi u nazivima datoteka Nedozvoljeni znakovi su zamjenjeni ovima Znak za zamjenu Slova i brojevi - Posebni znakovi + Najviše posebnih znakova O NewPipeu Licence treće strane © %1$s od %2$s pod %3$s @@ -131,15 +131,15 @@ Licence Slobodan i mali YouTube program za Android. Pogledaj na GitHubu - Licenca za NewPipe + NewPipe licenca Ako imate ideja za prijevod, promjene u dizajnu, čišćenje koda ili neke veće promjene u kodu, pomoć je uvijek dobro došla. Što više radimo, to bolji postajemo! Pročitaj licencu Doprinos Povijest Povijest NewPipe obavijest - Obavijesti za NewPipe reproduktore - Reproduktor + Obavijesti za NewPipe playera + Player Ponašanje Povijest i predmemorija Poništi @@ -155,70 +155,70 @@ %s pregled %s pregleda - %s pregledi + %s pregleda - Nema videozapisa + Nema videa - %s videozapis - %s videozapisa - %s videozapisa + %s video + %s videa + %s videa Reproduciraj sve - Nije moguće reproducirati ovaj stream - Dogodila se neoporavljiva greška reproduktora - Oporavljanje od greške reproduktora + Nije bilo moguće reproducirati ovaj stream + Dogodila se neoporavljiva greška playera + Oporavljanje od greške playera Prikaži savjet za držanje - Prikaži savjet kad se pritisne gumb za pozadinsku ili skočni prozor u detaljima videa: - Želite li izbrisati ovu stavku iz povijesti pretraživanja? + Prikaži savjet kad se pritisne gumb za pozadinu ili skočni gumb u videu „Detalji:” + Želiš li izbrisati ovu stavku iz povijesti pretrage\? Sadržaj Prazna stranica - Kiosk stranica - Kanal - Odaberite kanal - Niste pretplaćeni na nijedan kanal - Odaberite kiosk + Stranica kioska + Stranica kanala + Odaberi kanal + Još nema pretplata na nijedan kanal + Odaberi jedan kiosk U trendu - Vrh 50 - Novo i popularno + 50 najboljih + Novi i popularni Ukloni Detalji - Postavke zvuka + Postavke za audiosnimke Drži pritisnuto za dodavanje u popis izvođenja [Nepoznato] Doniraj - Web stranica + Web-stranica Započni reprodukciju u pozadini Reproduciraj u skočnom prozoru Otvori ladicu Zatvori ladicu - Video reproduktor - Pozadinski reproduktor - Skočni reproduktor + Video player + Pozadinski player + Skočni player Uvjek pitaj Dohvaćanje podataka … - Učitava se odabrani sadržaj + Učitavanje traženog sadržaja Nova playlista Preimenuj Ime Dodaj u playlistu - Postavi kao minijaturu playliste + Postavi kao sličicu playliste Zabilježi playlistu Ukloni zabilješku Izbrisati ovu playlistu\? Playlista je stvorena - Dodano kao playlistu - Minijatura playliste se promijenila. + Dodano u playlistu + Sličica playliste je promijenjena. Bez titlova - Popuni - Ispuniti - Povećaj - Auto generirano + Prilagodi + Ispuni + Zumiraj + Automatski generirani Praćenje curenja memorije može uzrokovati greške u radu programa prilikom odlaganje gomile Izvijesti o krajevima životnog ciklusa Prikaži informacije - Zabilježeni popisi + Zabilježene playliste Dodaj u - Učitaj slike + Učitaj sličice Slikovna predmemorija obrisana Izbriši metapodatke iz predmemorije Kanali @@ -233,7 +233,7 @@ Prijeđi na glavni Uvezi bazu podataka Izvezi bazu podataka - Poništava vašu trenutačnu povijest, pretplate, playliste i (opcionalno) postavke + Poništava tvoju trenutačnu povijest, pretplate, playliste i (opcionalno) postavke Izvezi povijest, pretplate, playliste i postavke Izbriši povijest gledanja Briše povijest reproduciranih streamova i pozicije reprodukcije @@ -245,29 +245,29 @@ Nema takve mape Naziv datoteke ne može biti prazan Dogodila se greška: %1$s - Povucite za promjenu redoslijeda + Povuci za promjenu redoslijeda Stvori Odbaci Preimenuj 1 stavka izbrisana. Nijedan program nije instaliran za reprodukciju ove datoteke Vrati - Posjetite web stranicu NewPipe za više informacija i vijesti. - NewPipeova pravila o privatnosti - Pročitajte pravila o privatnosti + Posjeti NewPipe web-stranicu za više informacija i vijesti. + NewPipe pravila o privatnosti + Pročitaj pravila o privatnosti Zadnje svirano - Najviše svirano + Najviše reproducirano Izvezeno Uvezeno Nema važeće ZIP datoteke Upozorenje: Nije moguće uvesti sve datoteke. - Ovo će poništiti vaše trenutne postavke. - Želite li također uvesti postavke? + Ovo će prepisati tvoje trenutačne postavke. + Želiš li također uvesti postavke\? Uvoz Uvoz iz Izvoz u - Uvoz… - Izvoz… + Uvoz … + Izvoz … Uvoz datoteke Prethodni izvoz Nije bilo moguće uvesti pretplate @@ -277,14 +277,14 @@ \n1. Idi na ovaj URL: %1$s \n2. Prijavi se \n3. Pritisni „Uključeni svi podaci”, zatim „Poništi odabir svih”, a zatim odaberi samo „pretplate” i pritisni „U redu” -\n4. Pritisni na „Nastavi”, a zatim „Stvori izvoz” -\n5. Pritisni na „Preuzmi” -\n6. Dolje pritisni na UVEZI DATOEKU i odaberi .zip datoteku za peuzimanje -\n7. [Ako uvoz .zip datoteke ne uspije] Izdvoji .csv datoteku (pod \"YouTube and YouTube Music/subscriptions/subscriptions.json\"). Dolje pritisni UVEZI DATOTEKU i odaberi izdvojenu csv datoteku - vašID, soundcloud.com/vašID - Uzmite u obzir da ova operacija može uzrokovat veliku potrošnju prometa. +\n4. Pritisni „Sljedeći korak”, a zatim „Stvori izvoz” +\n5. Pritisni gumb „Preuzmi” nakon što se pojavi +\n6. Dolje pritisni UVEZI DATOEKU i odaberi preuzetu .zip datoteku +\n7. [Ako uvoz .zip datoteke ne uspije] izdvoji .csv datoteku (pod „YouTube and YouTube Music/subscriptions/subscriptions.json”), dolje pritisni UVEZI DATOTEKU i odaberi izdvojenu csv datoteku + tvojID, soundcloud.com/tvojid + Ova operacija može prouzročiti veliku potrošnju mrežnog prometa. \n -\nŽelite li nastaviti? +\nŽeliš li nastaviti\? Kontrole brzine reprodukcije Premotaj naprijed tijekom šutnje Korak @@ -294,19 +294,19 @@ Bez ograničenja Ograniči rezoluciju tijekom korištenja mobilnih podataka Nijedan - Reproduktor za stream nije pronađen (možeš instalirati VLC za reprodukciju). + Nije pronađen nijedan player streamova (možeš instalirati VLC za reprodukciju). Preuzmi datoteku streama Koristi brzo netočno premotavanje - Netočno premotavanje omogućava reproduktoru da premota brže uz manju točnost. Premotavanje od 5, 15 ili 25 sekundi s ovime nije moguće + Netočno premotavanje omogućuje playeru brže premotavanje uz manju točnost. Premotavanje od 5, 15 ili 25 sekundi s ovime ne radi Otkaži pretplatu Odaberi karticu - Ažuriranja + Aktualiziranja Događaji Datoteka obrisana Obavijest za nove NewPipe verzije Briše povijest ključnih riječi pretraživanja Vanjska pohrana nije dostupna - Ažuriranja + Aktualiziranja Prikaži obavijest i zatraži aktualiziranje programa kad je dostupna nova verzija Popis Popločeno @@ -314,51 +314,51 @@ Dodirni za preuzimanje Preuzimanje nije uspjelo Prikaži pogrešku - Isključi za sprječavanje učitavanja sličica, čime se štede podatci i memorija. Promjena postavke čisti predmemoriju u radnoj memoriji i u pohrani + Isključi za sprečavanje učitavanja sličica, čime se štedi korištenje podataka i memorije. Promjene čiste predmemoriju slika radne memorije i diska Izbriši sve podatke web-stranica iz predmemorije Metapodaci su izbrisani Automatski dodaj sljedeći stream u popisa izvođenja - Nastavi završavati (ne ponavljajući) popis izvođenja dodavanjem povezanog streama + Nastavi završavati (ne ponavljajući) popis reprodukcija dodavanjem povezanog streama Kontrola glasnoće pomoću gesti - Koristi geste za kontrolu glasnoće + Koristi geste za upravljanje glasnoćom playera Kontrola svjetline pomoću gesti - Koristi gesture za kontrolu svjetline + Koristi gesture za upravljanje svjetlinom playera Zadana zemlja sadržaja Otkrivanje grešaka Obavijest o novoj verziji programa Preuzimanje na vanjsku SD karticu nije moguće. Ponovo postaviti lokaciju mape za preuzimanje\? - Vanjski reproduktori ne podržavaju ove vrste veza + Vanjski playeri ne podržavaju ove vrste poveznica Nije pronađen nijedan videozapis - Nije pronađen nijedan audio zapis + Nije pronađena nijedna audiosnimka Nema takve datoteke/izvora sadržaja Datoteka ne postoji ili joj nedostaje dopuštenje za čitanje ili pisanje Nema dostupnih zapisa za preuzimanje - Neuspjelo čitanje spremljenih kartica, stoga se koriste zadane - Vratiti zadane - Želite li vratiti zadane postavke\? + Nije bilo moguće čitati spremljene kartice, stoga se koriste zadane + Obnovi standardne vrijednosti + Želiš li obnoviti standardne vrijednosti\? Broj pretplatnika nije dostupan - NewPipe razvijaju volonteri koji provode vrijeme donoseći vam najbolje iskustvo. Vratite im kako biste programerima učinili da NewPipe bude još bolji dok uživate u šalici kave. + NewPipe razvijaju volonteri koji provode vrijeme kako bi doprinijeli najboljem iskustvu. Doprinesi programerima kako bi poboljšali NewPipe dok uživaju u šalici kave. Koje su kartice prikazane na glavnoj stranici Konferencije - Željena radnja otvaranja streama + Željena radnja za otvaranje Zadana radnja pri otvaranju sadržaja — %s Titlovi - Promijeni veličinu podnaslova reproduktora i pozadinske stilove reproduktora. Za stupanje na snagu, program se mora ponovo pokrenuti - Prisilno izvješćivanje o greškama Rx-a koje se ne mogu isporučiti izvan \'fragmenta\' ili životnog ciklusa aktivnosti nakon odlaganja - Uvezite SoundCloud profil tako da upišete URL ili svoj ID: + Promijeni veličinu podnaslova i pozadinske stilove playera. Zahtijeva ponovno pokretanje programa + Prisilno izvijesti o neisporučivim Rx iznimaka izvan fragmenta ili životnog ciklusa aktivnosti nakon odlaganja + Uvezi SoundCloud profil upisom URL-a ili svog ID-a: \n -\n1. Omogućite \"način rada na radnoj površini\" u web-pregledniku (stranica nije dostupna na mobilnim uređajima) -\n2. Idite na ovaj URL: %1$s -\n3. Ulogirajte se -\n4. Kopirajte URL profila na koji ste preusmjereni. - brzina +\n1. Omogući „način rada na radnoj površini” u web-pregledniku (stranica nije dostupna na mobilnim uređajima) +\n2. Idi na ovaj URL: %1$s +\n3. Prijavi se +\n4. Kopiraj URL profila na koji te se preusmjerava. + Brzina Visina tona Odspoji (može prouzročiti izobličenje) - Sklopi prilikom mijenjanje programa - Radnja prilikom prebacivanja na drugi program iz glavnog video reproduktora – %s - Smanji na pozadinski reproduktor - Smanji na skočni reproduktor - Način prikaza popisa + Smanji prilikom mijenjanje programa + Radnja prilikom prebacivanja na drugi program iz glavnog video playera – %s + Smanji na pozadinski player + Smanji na skočni player + Način prikaza kao popis Automatski Gotovo Na čekanju @@ -367,18 +367,18 @@ naknadna obrada Popis izvođenja Sustav je odbio radnju - Generirajte jedinstveni naziv + Generiraj jedinstveni naziv Prepiši Datoteka s tim nazivom već postoji Preuzeta datoteka s tim nazivom već postoji Datoteka s ovim nazivom se već preuzima Odredišna mapa ne može biti stvorena Datoteka se ne može stvoriti - Nije moguće uspostaviti sigurnu vezu - Nije moguće pronaći server + Nije bilo moguće uspostaviti sigurnu vezu + Nije bilo moguće pronaći server Nije moguće povezati se s serverom Server ne šalje podatke - Poslužitelj ne prihvaća preuzimanja s više niti, pokušaj ponovo s @string/msg_threads = 1 + Poslužitelj ne prihvaća preuzimanja višestrukih procesa, pokušaj ponovo s @string/msg_threads = 1 Nije pronađeno Naknadna obrada nije uspjela Stop @@ -390,23 +390,23 @@ Isključi, kako bi se komentari sakrili Automatska reprodukcija Nema komentara - Komentare nije moguće učitati + Nije bilo moguće učitati komentare Zatvori - NewPipe je copyleft libre software: Može se koristiti, proučavati i poboljšavati po volji. Konkretno, može se redistribuirati i / ili modificirati pod uvjetima GNU opće javne licence koju je objavila Free Software Foundation, bilo licence verzije 3, ili (po vlastitom izboru) bilo koje kasnije verzije. + NewPipe je copyleft libre softver: Može se koristiti, proučavati i poboljšavati po volji. Konkretno, može se redistribuirati i / ili modificirati pod uvjetima GNU opće javne licence koju je objavila zaklada Free Software Foundation, pod verzijom 3 licence, ili (po vlastitom izboru) bilo koje kasnije verzije. Projekt NewPipe ozbiljno shvaća tvou privatnost. Stoga program ne prikuplja nikakve podatke bez tvog pristanka. \nNewPipe pravila o privatnosti detaljno objašnjavaju koji se podaci šalju i spremaju kad šalješ izvještaje o prekidu rada programa. - Kako bismo se uskladili s Europskom općom uredbom o zaštiti podataka (GDPR), upozoravamo vas na politiku privatnosti tvrtke NewPipe. Pažljivo ga pročitajte. -\nZa slanje izvješća o pogreškama potrebno je prihvatiti politiku privatnosti. + Kako bismo se uskladili s Europskom općom uredbom o zaštiti podataka (GDPR), ovime upozoravamo na NewPipe politiku privatnosti. Pažljivo je pročitaj. +\nZa slanje izvješća o pogreškama moraš prihvatiti politiku privatnosti. Nastavi reprodukciju - Vrati zadnji položaj reprodukcije + Obnovi zadnji položaj reprodukcije Pozicije na popisima - Prikaži poziciju reprodukcije na listi + Prikaži poziciju reprodukcije u popisima Obriši podatke Pozicije reprodukcije su izbrisane Datoteka je premještena ili izbrisana Datoteka s ovim nazivom već čeka na preuzimanje Vrijeme povezanosti je isteklo - Želite li očistiti povijest preuzimanja ili izbrisati sve preuzete datoteke\? + Želiš li izbrisati povijest preuzimanja ili izbrisati sve preuzete datoteke\? Započni preuzimanja Zaustavi preuzimanja Pitaj gdje preuzeti @@ -416,50 +416,50 @@ Nitko ne gleda Nitko ne sluša Jezik će se promijeniti nakon ponovnog pokretanja programa - Zadani Kiosk + Standardni kiosk Podržani su samo HTTP URL-ovi - Lokalno - Nedavno dodano - Autogenerirano (prenositelj nedefiniran) + Lokalni + Nedavno dodani + Automatski generirano (prenositelj nedefiniran) Očisti povijest preuzimanja Izbriši preuzete datoteke Dopusti prikaz iznad drugih programa Jezik programa Zadani sustav - Videozapisi - Isključi + Videa + Isključi zvuk Uključi Pomoć - Učitavanje feeda… - Želite li izbrisati ovu grupu\? - Novi - Uvijek ažuriraj + Učitavanje feeda … + Želiš li izbrisati ovu grupu\? + Nova + Uvijek aktualiziraj Omogući brz način Onemogući brz način Memorija uređaja je popunjena - Najomiljenije - Pritisnite \"Gotovo\" kad riješeno + Najomiljeniji + Pritisni „Gotovo” kad je riješeno Gotovo - ∞ videozapisa - 100+ videozapisa - Prijavite grešku na GitHub + ∞ videa + Više od 100 videa + Prijavi grešku na GitHub-u Umjetnici Albumi Pjesme - Napravio %s + Stvoren od %s Nikada Ograniči popis preuzimanja Koristi birač mapa sustava (SAF) Ukloni pregledano - Ukloni pogledane videozapise\? + Ukloni pogledana videa\? %d sekunda - %d sekundi + %d sekunde %d sekundi - %d minutu - %d minuta + %d minuta + %d minute %d minuta @@ -469,7 +469,7 @@ %d sat - %d sati + %d sata %d sati Nije učitano: %d @@ -481,63 +481,63 @@ Gumb druge radnje Gumb prve radnje Prikazuju se rezultati za: %s - Nije moguće prepoznati URL. Želiš li otvoriti s drugim programom\? - Smanjiti omjer minijatura na 1:1 + Nije bilo moguće prepoznati URL. Želiš li otvoriti s drugim programom\? + Odreži sličicu na omjer 1:1 Učitavanje u predmemoriju Istovremeno se pokreće jedno preuzimanje Dodano u popis izvođenja Dodaj u popis izvođenja - Popis izvođenja + Reproduciraj popis izvođenja Automatski popis izvođenja - Popis izvođenja aktivnog reproduktora će se zamijeniti - Prebacivanje s jednog reproduktora na drugi može zamijeniti popisa izvođenja + Popis izvođenja aktivnog playera će se zamijeniti + Prebacivanje s jednog playera na drugi može zamijeniti tvoj popis izvođenja Pitaj prije pražnjenja popisa izvođenja %s slušatelj %s slušatelja %s slušatelja - datoteka ne može biti prepisana - Promijeni omjer prikazane minijature videa u obavijesti iz 16:9 na 1:1 (može prouzročiti izobličenja) + datoteka se ne može prepisato + Odreži prikazane sličice videa u obavijesti iz omjera 16:9 na 1:1 U kompaktnom prikazu obavijesti mogu se odabrati najviše 3 radnje! Od %s - Minijatura avatara kanala + Sličica avatara kanala Dohvati iz određenog feeda kad je dostupno Vrijeme nakon zadnjeg aktualiziranja prije nego što se pretplata smatra zastarjelom – %s - Prag aktualiziranja feedova + Prag za aktualiziranje feedova Feed Prikaži samo negrupirane pretplate Prazno ime grupe - %d odabrani - %d odabrana + %d odabrana + %d odabrane %d odabranih - Obrada feeda … + Obrada feeda u tijeku … Zadnje aktualiziranje feeda: %s Grupe kanala - Da, i djelomično pogledane videozapise - Odaberi primjerak + Da, i djelomično pogledana videa + Odaberi jednu instancu Aplikacija će te pitati kamo spremati preuzimanja. \nOmogući birač mapa sustava (SAF), ako želiš preuzimati na vanjsku SD karticu Nije moguće obnoviti ovo preuzimanje Napredak je izgubljen, jer je datoteka izbrisana NewPipe se zatvorio tijekom rada s datotekom Stranica playliste - Videzapisi koji su gledani prije i nakon dodavanja u playlistu će se ukloniti. + Videa koji su gledani prije i nakon dodavanja u playlistu će se ukloniti. \nStvarno ih želiš ukloniti\? Ovo je nepovratna radnja! Još nema zabilježenih playlista Odaberi playlistu obnavljanje Samo na Wi-Fi mreži - Pokreni automatski – %s + Pokreni reprodukciju automatski – %s Prikaži curenje memorije %s gledatelj %s gledatelja %s gledatelja - Uklj/Isklj uslugu, trenutačno odabrana: + Uključi/isključi uslugu, trenutačno odabrana: Kopiraj formatirani izveštaj Izbriši kolačiće koje NewPipe sprema nakon rješavanja reCAPTCHA reCAPTCHA kolačići su izbrisani @@ -548,51 +548,51 @@ YouTube nudi postavku „Ograničeni način rada”, čime se skriva sadržaj za odrasle Uključi YouTube postavku „Ograničeni način rada” Prikaži sadržaj koji vjerojatno nije prikladan za djecu, jer je dobno ograničen (kategorija 18) - Primjerak već postoji - Neuspjela provjera primjerka - Upiši URL primjerka - Dodaj primjerak - Pronađi omiljene primjerke na %s - Odaberi tvoje omiljene PeerTube primjerke - PeerTube primjerci + Instanca već postoji + Nije bilo moguće provjeriti instancu + Upiši URL instance + Dodaj instancu + Pronađi instance koje voliš na %s + Odaberi svoje omiljene PeerTube instance + PeerTube instance Vrijeme premotavanja prema naprijed ili natrag Ništa Promiješaj Ponovi - Provjeri je li tvoj problem već postoji. Dupla pojava problema krade nam vrijeme koje bismo mogli utrošiti na ispravljanje same greške. + Provjeri je li problem već postoji. Prijavljivanje istog već prijavljenog problema krade nam vrijeme koje bismo mogli utrošiti na ispravljanje greške. Za uređivanje radnji u obavijestima, dodirni ih. Označi do tri radnje za prikaz u kompaktnoj obavijesti koristeći oznake na desnoj strani - Zbog ograničenja ExoPlayera, trajanje traženja postavljeno je na %d s - Neka Android prilagodi boju obavijesti prema glavnoj boji minijature (ovo nije dostupno na svim uređajima) + Zbog ograničenja ExoPlayera, trajanje premotavanja postavljeno je na %d s + Neka Android prilagodi boju obavijesti prema glavnoj boji sličice (ovo nije dostupno na svim uređajima) Oboji obavijest NewPipe još ne podržava ovaj sadržaj. \n \nNadamo se da će biti podržan u budućoj verziji. - Prikaži minijaturu kao pozadinu pri zaključanom ekranu i unutar obavijesti - Prikaži minijaturu + Koristi sličicu za pozadinu zaključanog ekrana i za obavijesti + Prikaži sličicu Prikaži izvorno vrijeme elemenata - „Okvir za pristup spremištu” omogućuje preuzimanje na SD karticu + „Storage Access Framework” omogućuje preuzimanje na SD karticu Izvorni tekstovi usluga bit će vidljivi u elementima prijenosa Dostupno je u nekim uslugama. Obično je puno brže, ali može dohvatiti ograničenu količinu stavki i često nepotpune podatke (npr. bez trajanja, vrste stavke, bez stanja uživo) - Mislite li da je učitavanje feeda prespor\? Ako je to slučaj, pokušajte omogućiti brzo učitavanje (možete ga promijeniti u postavkama ili pritiskom na donji gumb). + Misliš da je učitavanje feeda presporo\? Ako da, pokušaj omogućiti brzo učitavanje (možeš ga promijeniti u postavkama ili pritiskom na donji gumb). \n -\nNewPipe nudi dvije strategije ulaganja feeda: -\n• Dohvaćanje cijelog pretplatničkog kanala, koji je spor, ali cjelovit. -\n• Korištenje namjenske krajnje točke usluge, koja je brza, ali obično nije potpuna. +\nNewPipe nudi dvije strategije učitavanja feeda: +\n• Dohvaćanje cijelog pretplatničkog kanala, što je sporo, ali cjelovito. +\n• Korištenje namjenske krajnje točke usluge, što je brzo, ali obično nepotpuno. \n -\nRazlika je u tome što brzom obično nedostaju neke informacije, poput trajanja ili vrste stavke (ne može razlikovati videozapise uživo od uobičajenih), a možda će vratiti i manje predmeta. +\nRazlika je u tome što brzom načinu obično nedostaju neke informacije, poput trajanja ili vrste predmeta (ne može razlikovati videa uživo od običnih videa), a možda će vratiti i manje predmeta. \n \nYouTube je primjer usluge koja nudi ovaj brzi način sa svojim RSS feedom. \n -\nDakle, izbor se svodi na ono što više volite: brzinu ili precizne informacije. +\nDakle, izbor se svodi na ono što više voliš: brzinu ili precizne informacije. Izračunavanje šifriranja Obavijest šifriranja videa Obavijesti o napretku šifriranja videa Nedavni Isključi za skrivanje polja metapodataka s dodatnim podacima o autoru streama, sadržaju streama ili zahtjevu za pretraživanje Prikaži metapodatke - Slični videozapisi + Povezani predmeti Nijedan program na tvom uređaju ovo ne može otvoriti - Poglavlja videozapisa + Poglavlja Opis Komentari Isključi za skrivanje opisa videozapisa i dodatnih informacija @@ -620,12 +620,12 @@ Isklj. Uklj. Način rada na tabletu - Otvori stranicu - Srce autora + Otvori web-stranicu + Od autora obilježeno srcem Privatno Nenavedeno Javno - URL minijature + URL sličice Poslužitelj Podrška Jezik @@ -633,41 +633,41 @@ Licenca Oznake Kategorija - Onemogućite odabir teksta u opisu - Omogućite odabir teksta u opisu + Onemogući biranje teksta u opisu + Omogući biranje teksta u opisu Račun ukinut Prikaži pogledane stavke Autorov račun je ukinut. -\nNewPipe ubuduće neće moći učitavati ovaj feed. -\nŽelite li otkazati pretplatu na ovaj kanal\? - Nije moguće učitati feed za \'%s\'. +\nNewPipe ubuduće neće moći učitavati ovaj feed. +\nŽeliš li otkazati pretplatu na ovaj kanal\? + Nije bilo moguće učitati feed za „%s”. Pogreška pri učitavanju feeda - Počevši od Androida 10, podržan je samo \'Storage Access Framework\' + Počevši od Androida 10, podržano je samo radno okruženje „Storage Access Framework” Od vas će se tražiti gdje spremiti svako preuzimanje Ne prikazuj - Niska kvaliteta (manji) + Niska kvaliteta (manja) Visoka kvaliteta (veća) - Pregled sličica trake za pretraživanje - Mapa za preuzimanje još nije postavljena, odaberite zadanu mapu za preuzimanje + Pregled sličica premotavanja + Mapa za preuzimanje još nije postavljena, odaberi standardnu mapu za preuzimanje Komentari su onemogućeni Označi kao pogledano Način rada brzog 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. - %s daje ovaj razlog: - Obrada... Pričekajte trenutak - Povucite stavke da biste ih uklonili + %s pruža ovaj razlog: + Obrada u tijeku … Može malo potrajati + Za ukljanjanje stavki povuci ih Prikazati indikatore slike - Dovršeno %s preuzimanje - Dovršena %s preuzimanja - Dovršeno %s preuzimanja + Preuzimanje je gotovo + %s preuzimanja su gotova + %s preuzimanja su gotova - Pokreni glavni reproduktor u cjeloekranskom prikazu - Reproduciraj sljedeće - U redu čekanja - Prikažite Picassove vrpce u boji na slikama koje označavaju njihov izvor: crvena za mrežu, plava za disk i zelena za memoriju + Pokreni glavni player u cjeloekranskom prikazu + Reproduciraj sljedeći + Sljedeći u popisu izvođenja + Prikaži Picassove vrpce u boji na slikama koje označavaju njihov izvor: crvena za mrežu, plava za disk i zelena za memoriju Izbrisano %1$s preuzimanje Izbrisana %1$s preuzimanja @@ -678,37 +678,59 @@ Traženje novih verzija … Prijedlozi lokalne pretrage Traži nove verzije - Nemoj pokretati videa u mini reproduktoru, već se izravno pokreni cjeloekranski prikaz, ako je automatsko okretanje zaključano. Mini reproduktoru i dalje možeš pristupiti izlaskom iz cjeloekranskog prikaza - Novi feedovi + Nemoj pokretati videa u mini playeru, već izravno pokreni cjeloekranski prikaz, ako je automatsko okretanje zaključano. Mini playeru i dalje možeš pristupiti napuštanjem cjeloekranskog prikaza + Novi elementi feeda Obavijest o prijavi greške Obavijesti za prijavu grešaka Stvori obavijest o grešci NewPipe je naišao na grešku, dodirni za prijavu Došlo je do greške, pogledaj obavijest Prekini rad playera - Obavijest reproduktora - Prilagođavanje obavijesti reproduktora + Obavijest playera + Konfiguriraj obavijest trenutačno reproduciranog streama Obavijesti Novi videozapisi - Obavijesti novih streamova pretplaćenih kanala - Želite li izbrisati sve preuzete datoteke\? + Obavijesti novih streamova od pretplaćenih kanala + Želiš li izbrisati sve preuzete datoteke\? Obavijesti su onemogućene - Pretplatili ste se ovome kanalu + Pretplatio/la si se na ovaj kanal , - Uključiti/isključiti sve + Uključi/isključi sve Bilo kakva mreža Obavijesti novih streamova pretplaćenih kanala - Pokaži zalogajnicu greške - Učitavanje pojedinosti streama… - Pokrenite provjeru novih streamova - Učestalost provjere - LeakCanary nije dostupno + Prikaži kratku poruku greške + Učitavanje pojedinosti streama … + Pokreni traženje novih streamova + Prvjeravanje učestalosti + Biblioteka „LeakCanary” nije dostupna Podešavanje visine tona po glazbenim polutonovima Obavijesti o novim streamovima Potrebna mrežna veza Zadano za ExoPlayer - Primite obavijesti - Za ovu radnju nije pronađen odgovarajući upravitelj datoteka. -\nMolimo vas da instalirate upravitelj za datoteke ili da pokušate onemogućiti \'%s\' u postavkama preuzimanja. + Primaj obavijesti + Za ovu radnju nije pronađen odgovarajući upravljač datoteka. +\nInstaliraj upravljač datoteka ili pokušaj onemogućiti „%s” u postavkama preuzimanja. Prikvačeni komentar + Prikazuje opciju prekida rada kad se player koristi + Prikaži „Prekini rad playera” + ExoPlayer standard + Posto + Poluton + Streamovi koje program za preuzimanje još ne podržava se ne prikazuju + Vanjski playeri ne podržavaju odabrani stream + Promijeni veličinu intervala učitavanja (trenutačno %s). Niža vrijednost može ubrzati početno učitavanje videa. Promjene zahtijevaju ponovno pokretanje playera. + + %s novi stream + %s nova streama + %s novih streamova + + Veličina intervala učitavanja reprodukcije + Nepoznat format + Nepoznata kvaliteta + Nijedan stream audiosnimaka nije dostupan za vanjske playere + Nijedan video stream nije dostupan za vanjske playere + Odaberi kvalitetu za vanjske playere + Prikaži buduća videa + Za ovu radnju nije pronađen odgovrajući upravljač datoteka. +\nInstaliraj „Storage Access Framework” kompatibilni upravljač datoteka. \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e25183985..49b472379 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -319,7 +319,7 @@ Eltüntetés Lejátszási lista könyvjelzőzése Egy hasonló videó hozzáadása a befejeződő (nem ismétlődő) lejátszási sorhoz - Sor + Sorba állítás a fájl nem írható felül Az előre- és visszatekerés időtartama Utolsó lejátszási pozíció visszaállítása @@ -362,11 +362,11 @@ Nincs talalat A kiszolgáló nem fogad többszálú letöltést, próbálkozzon újra ezzel: @string/msg_threads = 1 A kiszolgáló nem küld adatokat - Nem lehet csatlakozni a kiszolgálóhoz + A kiszolgáló szerver nem elérhető A kiszolgáló nem található Nem sikerült biztonságos kapcsolatot létesíteni A célmappa nem hozható létre - A fájlt nem lehet létrehozni + A fájlt nem sikerült létrehozni Hiba megjelenítése Ezzel a névvel egy letöltés már várakozik Ezzel a névvel egy letöltés már folyamatban van @@ -683,4 +683,39 @@ Rögzített megjegyzés LeakCanary nem elérhető + Lejátszó értesítés + Módosítsa a betöltési intervallum méretét (jelenleg %s). Az alacsonyabb érték felgyorsíthatja a videó kezdeti betöltését. A változtatásokhoz a lejátszó újraindítása szükséges. + Az aktuális lejátszás konfigurálása értesítés + Értesítések + Új élő közvetítések + Értesítések új élő közvetítésekről a feliratkozott csatornák esetén + Élő közvetítés betöltése.… + Keressen új élő közvetítést + Új élő közvetítés értesítések + Értesítésen új élő közvetítés esetén a feliratkozott csatornákhoz + Ellenőrzési gyakoriság + Szükséges hálózati kapcsolat + Bármilyen hálózat + Törli az összes letöltött fájlt a lemezről\? + Értesítsen + Értesítéstek kikapcsolva + Lejátszási intervallum mérete + Százaléka + + %s új elő közvetítés + %s új elő közvetítések + + ExoPlayer alapértelmezett + Feliratkoztál erre a csatornára + , + Azok az élő adások melyek nem támogatottak a letöltő által, rejtve vannak. + A választott élő adást nem lehet külső lejátszóval lejátszani. + Összes váltása + Külső lejátszók számára nem érhető el az hang csatorna + Külső lejátszók számára nem érhető el videó + Válassz minőséget külső lejátszókhoz + Ismeretlen formátum + Ismeretlen minőség + Félhang + Jövőbeli videók megjelenítése \ No newline at end of file diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml index 10d397aef..218854841 100644 --- a/app/src/main/res/values-hy/strings.xml +++ b/app/src/main/res/values-hy/strings.xml @@ -217,4 +217,7 @@ Հանրային Պիտակներ Ծանուցումները անջատված են + Ոչինչ բացի դատարկությունից + Կրկին փորձել + Հոսքի նորերը \ No newline at end of file diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index d5bc90e6f..f4383defb 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -233,4 +233,16 @@ Solmente alicun apparatos pote reproducer videos 2K/4K Initiar le reproductor principal in schermo plen Solmente le URLs HTTPS es supportate + Repeter + Aleatori + Cargante fe + Privacitate + Licentia + Comenciava le discarga + Private + Aperir le sito web + Per %s + Monstrar le videos futur + Radio + Create per %s \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index e84fb393a..d4d2db5a8 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -121,7 +121,7 @@ Terlepas apakah Anda memiliki ide untuk; terjemahan, perubahan desain, pembersihan kode, atau perubahan kode yang signifikan, segala bantuan akan selalu diterima. Semakin banyak akan semakin baik jadinya! Baca lisensi Kontribusi - Subscribe + Berlangganan Disubscribe Apa Yang Baru Lanjutkan pemutaran @@ -550,7 +550,7 @@ Tombol tindakan ketiga Tombol tindakan kedua Tombol tindakan pertama - Ubah ukuran thumbnail yang ditampilkan di notifikasi dari rasio aspek 16:9 ke 1:1 (mungkin terdistorsi) + Ubah ukuran thumbnail yang ditampilkan di notifikasi dari rasio aspek 16:9 ke 1:1 Ubah ukuran thumbnail ke rasio aspek 1:1 Tampilkan kebocoran memori Ditambahkan @@ -704,4 +704,5 @@ Pilih kualitas untuk pemain eksternal Format tidak diketahui Ukuran interval pemuatan playback + Tampilkan video mendatang \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 96d541d80..c2d0a2c6f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -560,8 +560,8 @@ Casuale Niente Ripeti - Ridimensiona copertina alla proporzione 1:1 - Modifica la proporzione della copertina del video mostrata nella notifica da 16:9 a 1:1 (può introdurre distorsioni) + Ritaglia copertina con proporzione 1:1 + Ritaglia la copertina del video mostrata nella notifica, cambiando la proporzione da 16:9 a 1:1 Mostra memory leak Aggiunto alla coda Accoda @@ -716,5 +716,6 @@ Seleziona qualità per lettori esterni Qualità sconosciuta Formato sconosciuto - Dimensione dell\'intervallo di caricamento della riproduzione + Dimensione intervallo di caricamento della riproduzione + Mostra video futuri \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index e65897de3..223a5f21a 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -14,7 +14,7 @@ 動画を保存するフォルダー ダウンロードした動画をここに保存します 動画ファイルをダウンロードするフォルダーを選択して下さい - デフォルトの解像度 + デフォルトの画質 Kodi で再生 Kore をインストールしますか? 「Kodi で再生」オプションを表示 @@ -24,7 +24,7 @@ ダウンロード 「次の動画」と「関連動画」を表示 対応していないURLです - デフォルトの言語 + デフォルトの言語設定 動画と音声 ビデオ再生、時間: 投稿者アイコンのサムネイル @@ -49,8 +49,8 @@ 保存メニューを設定できませんでした コンテンツ 年齢制限のあるコンテンツを表示 - 申し訳ありません。発生すべきでものではありませんでした。 - メールで不具合を報告 + 申し訳ありません。想定外のエラーが発生しました。 + 不具合をメールで報告 申し訳ありません、不具合が発生しました。 報告 情報: @@ -70,15 +70,15 @@ ファイル名 同時接続数 エラー - NewPipe ダウンロード中 + ダウンロード中 (NewPipe) タップして詳細を表示 お待ちください… クリップボードにコピーしました - 後ほど設定でダウンロードフォルダを定義してください + 後ほど、ダウンロードフォルダーを設定してください ダウンロード ダウンロード 不具合報告 - アプリ/UI がクラッシュしました + アプリ(UI)がクラッシュしました どんな問題:\\nリクエスト:\\nコンテンツ言語:\\nコンテンツ国:\\nアプリ言語:\\nサービス:\\nGMT 時間:\\nパッケージ:\\nバージョン:\\nOSバージョン: reCAPTCHA の要求 reCAPTCHA を要求しました @@ -93,18 +93,18 @@ ポップアップモードで再生中 無効 デフォルトの動画形式 - デフォルトのポップアップ解像度 - より高い解像度を表示 - 一部のデバイスのみ2K/4K動画を再生できます + デフォルトの画質 (ポップアップ表示) + より高い画質を表示 + 2K/4K動画は一部のデバイスでのみ再生できます バックグラウンド ポップアップ 消去 - ポップアップの属性を記憶 - ポップアップしたサイズと位置を記憶します + ポップアップの属性を記憶する + ポップアップのサイズと位置を記憶する 一部の解像度では音声がありません 検索候補の表示 検索時に表示する候補を選択します - 最高の解像度 + 最高 NewPipe について サードパーティー ライセンス © %1$s 作者 %2$s ライセンス %3$s @@ -125,8 +125,8 @@ 新着 検索履歴 検索履歴を記憶します - 視聴履歴 - 再生した履歴を記憶します + 再生履歴 + 再生履歴を記憶します 再生の再開 電話などによる中断の後、再生を再開します プレイヤー @@ -138,9 +138,9 @@ NewPipe の通知 [不明] 動画の再生ができませんでした - 回復不能な再生エラーが発生しました - 何も見つかりませんでした - チャンネル登録なし + 回復不能なエラーが発生しました + 一致する結果はありませんでした + チャンネル登録者なし 動画がありません 保存 ファイル名に使用可能な文字 @@ -178,7 +178,7 @@ データベースをエクスポート 既存の履歴、登録リスト、プレイリストおよび (任意) 設定は上書きされます 再生履歴、登録チャンネル一覧、プレイリストおよび設定をエクスポートします - 再生エラーからの回復中 + エラーから回復中です 外部プレイヤーは、これらのタイプのリンクをサポートしていません エクスポートしました インポートしました @@ -201,7 +201,7 @@ キャッシュを消去 アプリ内のキャッシュデータをすべて削除します キャッシュが消去されました - 次のを自動でキューに追加する + 次の動画を自動でキューに追加する デバッグ ファイル 動画が見つかりません @@ -255,7 +255,7 @@ プライバシーポリシーを確認 おおまかなシーク おおまかなシークを使用することで精度が下がる代わりに高速にシークができます。5 秒、15 秒または 25 秒間隔のシークはできません - すべてのサムネイルの読み込みと保存を無効化します。このオプションを切り替えるとメモリおよびディスク上の画像キャッシュが消去されます + サムネイルの読み込みと保存を無効化します。(このオプションを切り替えるとメモリとディスク上の画像キャッシュが消去されます) キューに関連動画を追加して再生を続ける (繰り返ししない場合) すべての再生履歴を削除しますか? すべての検索履歴を削除しますか? @@ -276,11 +276,11 @@ 最も再生された動画 拡大 プレイリスト - 「長押ししてキュー」のヒントを表示 + 「長押しでキューに追加」のヒントを表示 トラック NewPipe のプレイヤーの通知 新着と人気 - 長押ししてキューに追加 + 長押しでキューに追加 ポップアップで連続再生を開始 お好みの「開く」アクション コンテンツを開くときのデフォルト動作 — %s @@ -302,7 +302,7 @@ 同意する 拒否する 制限なし - モバイルデータ使用時の解像度の制限 + モバイルネットワーク使用時の画質 アプリ切り替え時の最小化 プレイヤーから他のアプリに切り替え時の動作 — %s 何もしない @@ -345,7 +345,7 @@ NewPipe のアップデートがあります! タップでダウンロード 完了 - 保留中 + 順番に処理中 一時停止 順番待ちに追加しました 保存処理をしています @@ -397,7 +397,7 @@ 再生位置を削除しました ファイルが移動または削除されました ファイルを上書きできません - この名前の保留中のダウンロードがあります + 同じファイル名のダウンロードが既に進行中です ファイルの作業中に NewPipe が閉じられました デバイスに空き容量がありません ファイルが削除されたため、進行状況が失われました @@ -428,7 +428,7 @@ %s 人が聴取中 アプリを再起動すると、言語が変更されます - 高速早送り/巻き戻し時間 + 高速早送り/巻き戻し間隔 PeerTube インスタンス PeerTube インスタンスを選択する あなたに最適なインスタンスを探す: %s @@ -531,7 +531,7 @@ プレイリスト ページ プレイリストを選択してください 自動的に再生を開始します — %s - 自動キュー + 自動でキューに追加 アクティブなプレイヤーのキューが入れ替わります プレイヤーを別のプレイヤーに切り替えるとキューが置き換わる可能性があります しない @@ -539,8 +539,8 @@ キューを再生 キューを消去する前に確認する URL を認識できませんでした。他のアプリで開きますか? - 通知に表示される動画サムネイルを 16:9 から 1:1 のアスペクト比にスケールします (ゆがみが生じるかもしれません) - サムネイルを 1:1 のアスペクト比にスケールする + 通知に表示されるサムネイルを 16:9 から正方形にします + サムネイルを正方形にする 以下をタップして通知のアクションを編集します。右側にあるチェックボックスを使用してコンパクトな通知に表示するものを 3 つまで選択します コンパクトな通知に表示されるアクションは 3 つまで選ぶことができます! 5 番目のアクションボタン @@ -569,8 +569,8 @@ 動画のハッシュ化通知 動画のハッシュ化進行状況の通知 コメント - 無効にするとビデオの概要と追加情報を非表示にします - 説明を表示 + 無効にすると動画の概要欄と追加情報を非表示にします + 概要欄を表示 最近 開く 説明 @@ -612,7 +612,7 @@ サムネイルの URL ウェブサイトを開く ダウンロードのたびに保存する場所を尋ねます - ダウンロードフォルダがまだ設定されていません。今すぐデフォルトのフォルダを選択してください + ダウンロードフォルダーがまだ設定されていません。今すぐデフォルトのフォルダーを選択してください Android 10 以降は \'Storage Access Framework\' のみがサポートされます 高速モードでこの情報の詳細は提供されません。 \'%s\' のフィードを読み込めませんでした。 @@ -633,13 +633,13 @@ 低品質 (小) 高品質 (大) シークバーのサムネイルプレビュー - コメントは無効です + コメントは無効になっています 視聴済みとしてマーク リモート検索候補 ローカル検索候補 アイテムをスワイプして削除 直接フルスクリーンモードに切り替えて、ミニプレイヤーで動画を開始しません。自動回転がロックされている場合でも、フルスクリーンを終了することでミニプレイヤーにアクセスできます - フルスクリーンでメインプレイヤーを開始 + プレイヤーをフルスクリーンで開始 %1$s つのダウンロードを削除しました @@ -660,7 +660,7 @@ 新しいフィードアイテム エラー報告通知 エラーが発生しました。通知をご覧ください - NewPipe はエラーに遭遇しました。タップして報告 + エラーが発生しました (タップすると報告できます) スナックバーにエラーを表示 固定されたコメント この動作に適切なファイルマネージャが見つかりませんでした。 @@ -691,9 +691,18 @@ 新しいストリーム 通知 現在再生しているストリームの通知を構成 - 読み込む間隔を変更します (現在 %s)。小さい値にすると初回読み込み時間が短くなります。変更にはプレイヤーの再起動が必要です。 + 読み込み間隔を変更します (現在 %s)。小さくすると再生開始までの時間が短くなります。変更を適用するには再起動が必要です。 必要なネットワークの種類 パーセント 半音 すべてのネットワーク + データの読み込み間隔 + 未知の形式 + 未知の品質 + サポートされてない動画は表示されていません + 選択された動画は外部プレイヤーではサポートされていません + 外部プレイヤーで利用できる音声情報がありません + 外部プレイヤーで利用できる映像情報がありません + 外部プレイヤーでの品質を選択 + 次の動画を表示する \ No newline at end of file diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 0e2cf6962..9e4d29cfe 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -539,7 +539,7 @@ ഫോർമാറ്റുചെയ്‌ത റിപ്പോർട്ട് പകർത്തുക പ്ലേലിസ്റ്റ് പേജ് GitHub- ൽ റിപ്പോർട്ട് ചെയ്യുക - ലഘുചിത്രം 1: 1 വീക്ഷണാനുപാതത്തിലേക്ക് സ്കെയിൽ ചെയ്യുക + ലഘുചിത്രം 1: 1 വീക്ഷണാനുപാതത്തിലേക്ക് ക്രോപ് ചെയ്യുക വീഡിയോ കവർ ചിത്രത്തിന്റെ പ്രധാന നിറത്തിന് അനുസരിച്ചു നോട്ടിഫിക്കേഷന്റെ കളർ മാറ്റാൻ ആൻഡ്രോയ്ഡിനെ അനുവദിക്കുക (ഇത് എല്ലാം ഉപകരണങ്ങളിലും ലഭ്യമല്ല ) നോട്ടിഫിക്കേഷൻ വർണ്ണാഭമാകുക ഒന്നുമില്ല @@ -547,7 +547,7 @@ ആവർത്തിക്കുക കോം‌പാക്റ്റ് അറിയിപ്പിൽ‌ കാണിക്കുന്നതിന് നിങ്ങൾക്ക് പരമാവധി മൂന്ന് പ്രവർ‌ത്തനങ്ങൾ‌ തിരഞ്ഞെടുക്കാനാകും! ഇതിനോടൊപ്പം തുറക്കുക - നോട്ടിഫിക്കേഷനിൽ കാണിക്കുന്ന വീഡിയോ കവർ ചിത്രം 16:9 എന്ന അനുപാതത്തിൽ നിന്നും 1:1 ലേക്ക് മാറ്റാം (പ്രശ്നങ്ങൾ ഉണ്ടാവാൻ സാധ്യത ) + നോട്ടിഫിക്കേഷനിൽ കാണിക്കുന്ന വീഡിയോ കവർ ചിത്രം 16:9 എന്ന അനുപാതത്തിൽ നിന്നും 1:1 ലേക്ക് ക്രോപ് ചെയ്യുക ഡൗൺലോഡ് ആരംഭിച്ചു ചുവടെ നിങ്ങളുടെ പ്രിയപ്പെട്ട രാത്രി തീം തിരഞ്ഞെടുക്കാം നിങ്ങളുടെ പ്രിയപ്പെട്ട രാത്രി തീം തിരഞ്ഞെടുക്കുക — %s diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index fd80a7d54..c6d7697c5 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -709,4 +709,12 @@ Je bent nu geabonneerd op dit kanaal Alle gedownloade bestanden van schijf wissen\? + De gekozen stream wordt niet ondersteund door externe spelers + Er zijn geen videostreams beschikbaar voor externe spelers + Onbekende kwaliteit + Streams die nog niet kunnen worden opgeslagen, worden niet getoond + Geen audiostreams beschikbaar voor externe spelers + Kies kwaliteit voor externe spelers + Onbekend bestandstype + Intervalgrootte tijdens afspelen \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 283c1f223..a009023d5 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -575,8 +575,8 @@ Przycisk trzeciej akcji Przycisk drugiej akcji Przycisk pierwszej akcji - Skaluj miniaturę wideo wyświetlaną w powiadomieniu z proporcji 16:9 do 1:1 (może powodować zniekształcenia) - Skaluj miniaturę do proporcji 1:1 + Przycinaj miniaturę wideo wyświetlaną w powiadomieniu z proporcji 16:9 do 1:1 + Przycinaj miniaturę do proporcji 1:1 Dodano do kolejki Dodaj do kolejki Pokaż wycieki pamięci @@ -738,4 +738,5 @@ Wybierz jakość dla zewnętrznych odtwarzaczy Nieznany format Nieznana jakość + Pokaż przyszłe wideo \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 0a4b2c2ad..9d19a8585 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -560,8 +560,8 @@ Terceiro botão de ação Segundo botão de ação Primeiro botão de ação - Dimensionar a miniatura do vídeo mostrada na notificação da proporção 16:9 para 1:1 (pode apresentar distorções) - Dimensionar a miniatura para a proporção de 1:1 + Cortar a miniatura do vídeo mostrada na notificação da proporção 16:9 para 1:1 + Cortar a miniatura para a proporção de 1:1 Mostrar vazamentos de memória Na fila Pôr na fila @@ -717,4 +717,5 @@ Formato desconhecido Qualidade desconhecida Tamanho do intervalo de carregamento da reprodução + Mostrar vídeos futuros \ No newline at end of file diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 4f3559eeb..1f6b75501 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -716,4 +716,6 @@ Formato desconhecido Qualidade desconhecida Selecione a qualidade para reprodutores externos + Tamanho do intervalo de carregamento da reprodução + Mostrar vídeos futuros \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index b1f579298..86306377f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -544,8 +544,8 @@ Terceiro botão de ação Segundo botão de ação Primeiro botão de ação - Ajustar miniatura de vídeo mostrada na notificação de 16:9 para 1:1 (pode haver distorções) - Ajustar miniatura à proporção de 1:1 + Cortar a miniatura de vídeo mostrada na notificação de 16:9 para 1:1 + Cortar miniatura na proporção 1:1 Iniciar reprodução automaticamente — %s Reproduzir fila Nunca @@ -716,4 +716,6 @@ Formato desconhecido Qualidade desconhecida A transmissão selecionada não é suportada por reprodutores externos + Mostrar vídeos futuros + Tamanho do intervalo de carregamento da reprodução \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 58a38ea6e..52c1ef5a3 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -378,8 +378,8 @@ Al treilea buton de acțiune Al doilea buton de acțiune Primul buton de acțiune - Scalați miniatura video afișată în notificare de la raportul de aspect 16:9 la 1:1 (poate introduce distorsiuni) - Scalare miniatură la raport aspect 1:1 + Tăiați miniatura video afișată în notificare de la raportul de aspect 16:9 la 1:1 (poate introduce distorsiuni) + Tăiere miniatură la raportul de aspect 1:1 Se arată rezultate pentru:%s Nicio aplicație de pe dispozitivul dvs. nu poate deschide acesta Capitole @@ -722,4 +722,13 @@ V-ați abonat la acest canal Comutați toate , + Fluxurile care încă nu pot fi descărcate nu sunt afișate + Fluxul selectat nu este acceptat de playerele externe + Nu sunt disponibile fluxuri audio pentru playerele externe + Nu sunt disponibile fluxuri video pentru playerele externe + Selectați calitatea pentru playerele externe + Format necunoscut + Calitate necunoscută + Dimensiunea intervalului de încărcare de redare + Afișați videoclipurile din viitor \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c91368c2a..50e0b4d48 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -718,7 +718,7 @@ Закреплённый комментарий LeakCanary недоступна Стандартное значение ExoPlayer - Изменить размер интервала загрузки (сейчас %s). Меньшее значение может ускорить начальную загрузку видео. Изменение значения потребует перезапуска плеера. + Изменить размер предварительной загрузки (сейчас %s). Меньшее значение может ускорить загрузку видео. При изменении требуется перезапуск плеера. Загрузка деталей трансляции… Проверить на наличие новых трансляций Удалить все загруженные файлы\? @@ -733,4 +733,6 @@ Нет ни одного доступного аудио потока для внешних проигрывателей Выберите качество для внешних проигрывателей Неизвестное качество + Размер предварительной загрузки + Показывать будущие видео \ No newline at end of file diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 81a71402e..d3a92b116 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -550,7 +550,7 @@ Pedi una cunfirma in antis de iscantzellare una lista Òrdine casuale Modìfica cada atzione de notìfica inoghe in suta incarchende·la. Ischerta·nde finas a tres de ammustrare in sa notìfica cumpata impreende sas casellas de controllu a destra - Iscala sa miniadura ammustrada in sa notìfica dae su formadu in 16:9 a cussu 1:1 (diat pòdere causare istorchimentos) + Sega sa miniadura ammustrada in sa notìfica dae su formadu in 16:9 a cussu 1:1 Nudda Carrighende Repite @@ -560,7 +560,7 @@ Su de tres butones de atzione Su de duos butones de atzione Su de unu butone de atzione - Pone in iscala sa miniadura in formadu 1:1 + Sega sa miniadura in formadu 1:1 Ammustra sas pèrdidas de memòria Annànghidu a sa lista Pone in lista @@ -716,4 +716,6 @@ Formadu disconnotu Calidade disconnota Su flussu seletzionadu no est galu suportadu dae letores esternos + Mannària de s\'intervallu de carrigamentu de sa riprodutzione + Ammustra sos vìdeos imbenientes \ No newline at end of file diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 4645fcfff..3d7888432 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -722,4 +722,12 @@ Poltón Nahrávanie podrobností streamu… Percent + Vybraný stream nie je podporovaný externými prehrávačmi + Žiadne audio streamy nie sú k dispozícií pre externé prehrávače + Vybrať kvalitu pre externé prehrávače + Neznámy formát + Interval medzipamäte + Streamy nepodporované sťahovačom sa nezobrazujú + Žiadne video streamy nie sú k dispozícií pre externé prehrávače + Neznáma kvalita \ No newline at end of file diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index d12b1e4d4..431ef57ad 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -19,7 +19,7 @@ Batoonka hawsha sadexaad Batoonka hawsha labaad Batoonka hawsha koowaad - La ekaysii galka muuqaalka xaga ogaysiisyada ka muuqda cabirka 1:1 ayadoo laga soo baddalayo 16:9 (wuxuu keeni karaa shucaac) + La ekaysii galka muuqaalka xaga ogaysiisyada ka muuqda cabirka 1:1 ayadoo laga soo baddalayo 16:9 Galka la ekaysii cabirka 1:1 Soo bandhig istikhyaar ah in muuqaalka lagu furo xarunta madadaalada Kodi Soodhig istikhyaarka \"Ku fur Kodi\" @@ -649,4 +649,6 @@ Tus tilmaamayaasha sawirka Soojeedinada raadinta banaanka Soojeedinada raadinta gudaha + Cabirka soodaarida udhexeeya + Jabi Daareha \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index edcc13b08..daf2a4485 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -524,9 +524,9 @@ Skapad av %s Ingen spellista har bokmärkts än Visa endast prenumerationer som inte grupperats - Skala miniatyrbild till 1: 1 bildförhållande + Beskär miniatyrbild till 1: 1 bildförhållande Endast över Wi-Fi - Skala videominiatyrbilden som visas i aviseringen från 16:9- till 1:1-förhållande (kan orsaka bildförvrängning) + Beskär videominiatyrbilden som visas i aviseringen från 16:9 till 1:1 bildförhållande Starta uppspelning automatiskt — %s Uppspelningskö Kunde inte känna igen URL:en. Vill du öppna med annan app\? @@ -713,4 +713,9 @@ Okänd kvalitet Inga ljudströmmar tillgängliga för externa spelare Okänt format + Videoströmmar som ännu inte stöds av nedladdaren visas inte + Inläsningsintervalls storlek + Välj kvalitet för externa spelare + Visa framtida videor + Den valda videoströmmen stöds inte av externa spelare \ No newline at end of file diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 3958e02b0..22ad6fbf5 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -425,4 +425,29 @@ స్వయంచాలకంగా రూపొందించబడింది (ఎక్కించినవారు కనబడుటలేదు) స్వయంచాలకంగా రూపొందించబడింది శీర్షికలు + సభ్యత్వాల కోసం కొత్త స్ట్రీమ్‌ల గురించి నోటిఫికేషన్‌లు + స్థితి పునరుద్ధరణ + లయ + కొత్త స్ట్రీమ్స్ + + %s కొత్త స్ట్రీమ్ + %s కొత్త స్ట్రీమ్స్ + + స్ట్రీమ్ వివరాలను లోడ్ చేస్తోంది… + మెమరీ లీక్‌లను చూపించు + జీవితచక్రం లేని లోపాలను నివేదించండి + ఈ చర్య నెట్‌వర్క్ ఖరీదైనదని గుర్తుంచుకోండి. +\n +\nమీరు కొనసాగించాలనుకుంటున్నారా\? + శాతం + అర్ధరాగం + ప్లేబ్యాక్ లోడ్ విరామం పరిమాణం + వస్తువులపై అసలు క్రిత సమయాన్ని చూపుము + పారవేయడం తర్వాత ఫ్రాగ్మెంట్ లేదా యాక్టివిటీ లైఫ్‌సైకిల్ వెలుపల బట్వాడా చేయలేని Rx మినహాయింపులను బలవంతంగా నివేదించడం + మెమరీ లీక్ మానిటరింగ్ హీప్ డంపింగ్ చేసినప్పుడు యాప్ స్పందించక పోవడానికి కారణం కావచ్చు + శృతి + అన్‌హుక్ (వక్రీకరణకు కారణం కావచ్చు) + అడుగు + నిశ్శబ్ద సమయంలో వేగంగా ముందుకు వెళ్లుము + ప్లేబ్యాక్ స్పీడ్ నియంత్రణలు \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 0986fd74b..072b92193 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,8 +1,8 @@ - Başlamak için büyütece dokunun. + Başlamak için büyütece dokun. %1$s tarihinde yayınlandı - Akış oynatıcısı bulunamadı. VLC yüklensin mi\? + Akış oynatıcısı bulunamadı. VLC kurulsun mu\? Yükle İptal Tarayıcıda aç @@ -13,14 +13,14 @@ Bunu mu demek istediniz: \"%1$s\"\? Şununla paylaş Dış video oynatıcı kullan - Dış ses oynatıcı kullanın + Dış ses oynatıcı kullan Video indirme klasörü İndirilen video dosyaları burada depolanır Video dosyaları için indirme klasörünü seç Ses indirme klasörü İndirilen ses dosyaları burada depolanır Ses dosyaları için indirme klasörünü seç - Varsayılan çözünürlük + Öntanımlı çözünürlük Kodi ile oynat Eksik Kore uygulaması yüklensin mi\? \"Kodi ile oynat\" seçeneğini göster @@ -103,7 +103,7 @@ Açılan pencerenin son boyutunu ve konumunu hatırla Bazı çözünürlüklerde sesi kaldırır Arama önerileri - Arama yaparken gösterilecek önerileri seçin + Ararken gösterilecek önerileri seç En iyi çözünürlük NewPipe Hakkında Üçüncü Taraf Lisansları @@ -130,7 +130,7 @@ Abonelikler Yenilikler Arama geçmişi - Arama sorgularını yerel olarak saklayın + Arama sorgularını yerel olarak sakla İzleme geçmişi İzlenen videoların kaydını tut Oynatmayı sürdür @@ -191,7 +191,7 @@ Ana Görünüme Geç Çekmeceyi Aç Çekmeceyi Kapat - Akış oynatıcı bulunamadı (Oynatmak için VLC yükleyebilirsiniz). + Akış oynatıcı bulunamadı (Oynatmak için VLC kurabilirsiniz). Her Zaman Yalnızca Bir Kez Dış oynatıcılar bu tür bağlantıları desteklemez @@ -560,8 +560,8 @@ Üçüncü eylem düğmesi İkinci eylem düğmesi Birinci eylem düğmesi - Bildirimde gösterilen video küçük resmini 16:9\'dan 1:1 en/boy oranına ölçeklendir (bozulmalara neden olabilir) - Küçük resmi 1:1 en/boy oranına ölçeklendir + Bildirimde gösterilen video küçük resmini 16:9\'dan 1:1 en boy oranına kırp + Küçük resmi 1:1 en boy oranına kırp Bellek sızıntılarını göster Sıraya eklendi Kuyruğa ekle @@ -578,7 +578,7 @@ Video dosya özetleme süreci için bildirimler Video dosya özeti bildirimi En Son - Akış oluşturucu, akış içeriği veya bir arama isteği hakkında ek bilgiler içeren meta bilgi kutularını gizlemek için kapatın + Akış oluşturucu, akış içeriği veya arama isteğiyle ilgili ek bilgiler içeren üst veri bilgi kutularını gizlemek için kapat Üst bilgiyi göster Bölümler Açıklama @@ -693,7 +693,7 @@ Yeni akışları denetlemeyi çalıştır Oynatıcı bildirimi - Oynatılan akış bildirimini yapılandırın + Oynatılan akış bildirimini yapılandır Yeni akışlar Akış ayrıntıları yükleniyor… Abonelikler için yeni akışlarla ilgili bildirimler @@ -701,7 +701,7 @@ Bildirimler devre dışı Bildirim alın Artık bu kanala abone oldunuz - Aboneliklerden yeni akışlar hakkında bildirim gönder + Aboneliklerden yeni akışlarla ilgili bildirim gönder Denetleme sıklığı Herhangi bir ağ İndirilen tüm dosyalar diskten silinsin mi\? @@ -709,12 +709,13 @@ Tümünü değiştir Yüzde Ara ton - Seçilen yayın harici oynatıcılar tarafından desteklenmiyor - İndirici tarafından henüz desteklenmeyen yayınlar gösterilmez - Harici oynatıcılar için ses yayını yok - Harici oynatıcılar için video yayını yok - Harici oynatıcılar için kalite seçin + Seçilen akış dış oynatıcılarca desteklenmiyor + İndiricice henüz desteklenmeyen akışlar gösterilmez + Dış oynatıcılar için ses akışı yok + Dış oynatıcılar için video akışı yok + Dış oynatıcılar için nitelik seç Bilinmeyen biçim - Bilinmeyen kalite + Bilinmeyen nitelik Oynatma yükleme aralığı boyutu + Gelecekteki videoları göster \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 736a49c8e..835c87f37 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -558,7 +558,7 @@ Кнопка третьої дії Кнопка другої дії Кнопка першої дії - Збільшити ескіз до масштабу 1:1 + Обрізати ескіз до пропорцій 1:1 Відкрити через Завантаження почалося Виберіть нічну тему — %s @@ -616,7 +616,7 @@ Кольорове сповіщення У компактному сповіщенні є не більше трьох дій! Дії можна змінити, натиснувши на них. Позначте не більше трьох для показу в компактному сповіщенні - Масштабувати мініатюру відео 16: 9 до 1:1 (можливі спотворення) + Обрізати мініатюру відео показувану в сповіщенні з пропорцій 16: 9 до 1:1 Вимкнення тунелювання медіаданих за наявності чорного екрана або гальмування під час відтворення відео Вимкнути тунелювання медіа «Фреймворк доступу до сховища» (SAF) підтримується лише починаючи з Android 10 @@ -734,4 +734,5 @@ Виберіть якість для зовнішніх програвачів Невідома якість Розмір інтервалу завантаження відтворення + Показати наступні відео \ No newline at end of file diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index b58f0946d..2c3582209 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -1,6 +1,6 @@ - شروع کرنے کے لیے \"تلاش\" پر ٹیپ کریں + شروع کرنے کے لیے \"کلاں نما شیشہ\" پر ٹیپ کریں %1$s کو شائع ہوا انسٹال منسوخ کریں @@ -494,8 +494,10 @@ تیسرا ایکشن بٹن دوسرا ایکشن بٹن پہلا ایکشن بٹن - نوٹیفیکیشن میں دکھائے جانے والے ویڈیو تھمب نیل کو 16: 9 سے 1:1 پہلو تناسب (شاید بگاڑ پیدا ہوسکتا ہے) میں اسکیل کریں + نوٹیفیکیشن میں دکھائے جانے والے ویڈیو تھمب نیل کو 16: 9 سے 1:1 پہلو تناسب میں اسکیل کریں تھمب نیل کو 1:1 کی تناسب میں رکھیں %s کے لئے نتائج دکھا رہا ہے کے ساتھ کھولیں + ویڈیو پلیئر کو کریش کریں + دیکھے ہوئے کو نشان لگائیں \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 2346a4cf9..3a933ef98 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -67,13 +67,13 @@ Nội dung không khả dụng Không thể thiết lập menu tải về Ứng dụng / Giao diện người dùng bị lỗi - Ai Da, NewPipe đã gặp lỗi. Tôi lấy làm tiếc cho bạn. + :( Lmao, app đã xảy ra lỗi. Hãy lướt xuống dưới để xem lỗi. Báo lỗi qua email Xin lỗi, đã xảy ra sự cố. Báo cáo Thông tin: Chuyện gì đã xảy ra: - Cái gì:\\nYêu cầu:\\nNgôn ngữ của nội dung:\\nVùng miền (quốc gia) của nội dung:\\nNgôn ngữ của ứng dụng:\\nDịch vụ:\\nThời gian GMT:\\nTên gói:\\nPhiên bản:\\nPhiên bản hệ điều hành: + Loại lỗi:\\nYêu cầu:\\nNgôn ngữ của nội dung:\\nVùng miền (quốc gia) của nội dung:\\nNgôn ngữ của ứng dụng:\\nDịch vụ:\\nThời gian GMT:\\nTên gói:\\nPhiên bản:\\nPhiên bản hệ điều hành: Nhận xét của bạn (bằng tiếng Anh): Chi tiết: Xem video, thời lượng: @@ -83,7 +83,7 @@ Video Âm thanh Thử lại - ngàn + nghìn triệu tỉ Bắt đầu @@ -96,7 +96,7 @@ Lỗi NewPipe đang tải xuống Chạm để biết chi tiết - Xin hãy đợi… + Đợi chút xíu nha… Đã sao chép vào clipboard Hãy chọn một thư mục để tải xuống trong phần cài đặt Chế độ popup cần quyền này @@ -108,10 +108,10 @@ © %1$s bởi %2$s dưới %3$s Thông tin Giấy phép - Trình phát nội dung nhẹ và mã nguồn mở cho Android. + Trình phát video nhẹ và mã nguồn mở cho Android. Xem trên GitHub Giấy phép của NewPipe - Sự đóng góp luôn được hoan nghênh – cho dù bạn dịch, có ý tưởng thiết kế, dọn code, hay thay đổi rất nhiều phần code. Càng làm nhiều thì ứng dụng này sẽ càng tốt! + Sự đóng góp của bạn luôn được hoan nghênh – kể cả khi bạn dịch, thay đổi giao diện, dọn code hay thay đổi những thứ khác, sự giúp đỡ của bạn vẫn đáng được trân trọng. Bạn càng làm nhiều, ứng dụng này sẽ càng tốt hơn bao giờ hết (Miễn đừng dịch vớ vẩn là được, nhé :]] )! Đọc giấy phép Đóng góp Ngôn ngữ nội dung ưu tiên @@ -169,7 +169,7 @@ Chuyển sang Main Nhập cơ sở dữ liệu Xuất cơ sở dữ liệu - Ghi đè lịch sử, danh sách đăng ký, playlist (và cài đặt, nếu có chọn) hiện tại của bạn + Ghi đè lịch sử, kênh đăng ký, playlist hiện tại (và cài đặt, nếu có) của bạn Xuất lịch sử, danh sách đăng ký, playlist và cài đặt Xóa lịch sử xem Xóa lịch sử các luồng đã phát và vị trí phát @@ -179,7 +179,7 @@ Xóa lịch sử của từ khóa tìm kiếm Xóa toàn bộ lịch sử tìm kiếm\? Đã xóa lịch sử tìm kiếm - Không thể phát luồng này + Không thể phát video này Đã xảy ra lỗi trình phát không thể khôi phục Phục hồi lại trình phát bị lỗi Trình phát ngoài không hỗ trợ các loại liên kết này @@ -191,7 +191,7 @@ Tên tệp không được để trống Đã xảy ra lỗi: %1$s Không có luồng nào để tải về - Không có gì ở đây + Không có gì cả Kéo để sắp xếp lại Không có người đăng ký @@ -217,8 +217,8 @@ Hầu hết các ký tự đặc biệt Không có ứng dụng nào được cài đặt để phát tệp này Đóng góp - NewPipe được phát triển bởi các tình nguyện viên dành thời gian mang lại cho bạn trải nghiệm tốt nhất. Đóng góp một tách cà phê để giúp các nhà phát triển làm NewPipe tốt hơn nữa. - Trả lại + NewPipe được phát triển bởi các tình nguyện viên dành thời gian và tâm huyết của mình để mang lại cho bạn trải nghiệm tốt nhất. Đóng góp một chai nước suối để giúp chúng tôi làm NewPipe tốt hơn nữa. + Ủng hộ một chai nước suối Trang web Truy cập trang web NewPipe để biết thêm thông tin và tin tức. Chính sách bảo mật của NewPipe @@ -244,8 +244,8 @@ Cảnh báo: Không thể nhập tất cả các tệp. Thao tác này sẽ ghi đè cài đặt hiện tại của bạn. Bạn cũng muốn nhập cài đặt? - Thịnh hành - Mới và hot + Thịnh hành :D + Mới và đang hot Loại bỏ Chi tiết Cài đặt âm thanh @@ -292,7 +292,7 @@ Xuất trước Không thể nhập đăng ký Không thể xuất đăng ký - Nhập danh sách đăng ký YouTube từ một bản Google Takeout: + Nhập danh sách đăng ký YouTube từ Google Takeout: \n \n1. Vào URL này: %1$s \n2. Đăng nhập khi được yêu cầu @@ -307,14 +307,14 @@ \n2. Truy cập URL này: %1$s \n3. Đăng nhập khi được hỏi \n4. Sao chép URL tiểu sử mà bạn đã được chuyển hướng đến. - Hãy nhớ rằng hoạt động này có thể khiến bạn bị trừ tiền. + Hãy nhớ rằng hoạt động này có thể khiến bạn bị mất kênh bạn đã đăng ký trước đó. \n \nBạn có muốn tiếp tục không\? Điều khiển tốc độ phát lại - Speed - Chiều cao - Bỏ gắn (có thể gây méo) - Tua đi nhanh trong khi im lặng + Tốc độ + Độ cao + Bỏ gắn (có thể gây méo nhưng vui) + Tua nhanh trong im lặng Tiếp theo Đặt lại Để tuân thủ Quy định bảo vệ dữ liệu chung của châu Âu (GDPR), chúng tôi sẽ thu hút sự chú ý của bạn đến chính sách bảo mật của NewPipe. Vui lòng đọc kỹ. @@ -322,7 +322,7 @@ Chấp nhận Từ chối Không giới hạn - Giới hạn độ phân giải khi sử dụng dữ liệu di động + Giới hạn độ phân giải khi sử dụng 3G, 4G Thu nhỏ khi chuyển qua ứng dụng khác Hành động khi chuyển sang ứng dụng khác từ trình phát chính — %s Không @@ -354,7 +354,7 @@ Danh sách Lưới Tự động - Đã có bản cập nhật NewPipe! + Đã có bản cập nhật mới! Nhấn để tải về Xong đã tạm dừng @@ -438,14 +438,14 @@ Video đã xem trước và sau khi được thêm vào playlist sẽ bị xóa. \nBạn có chắc không\? Video sẽ không thể hồi phục được! Xóa video đã xem\? - Xóa đã xem + Xóa video đã xem Mặt định hệ thống Ngôn ngữ ứng dụng \'Storage Access Framework\' cho phép tải về thẻ SD Sử dụng trình chọn thư mục của hệ thống (SAF) Xóa file đã tải về Xóa lịch sử tải về - Không thể khôi phục bản download này + Không thể khôi phục bản tải xuống này Bật tiếng Tắt tiếng Yêu thích nhất @@ -503,8 +503,8 @@ Có thể được với một số dịch vụ, thường sẽ nhanh hơn nhưng có thể bị giới hạn nội dung nhận được hoặc nội dung nhận được không đầy đủ (v.d. thời lượng, trạng thái,..) Luôn cập nhật Khoảng thời gian kể từ lần cuối cập nhật thông tin kênh trước khi nó được coi là hết hạn — %s - Ngưỡng thời gian cập nhật feed - Nguồn cấp (feed) + Ngưỡng thời gian cập nhật thông báo + Thông báo (feed) Tạo mới Bạn muốn xóa nhóm kênh này\? Tên nhóm kênh trống @@ -516,7 +516,7 @@ Đang xử lý thông báo… Số kênh không tải được: %d Đang tải thông báo… - Feed cập nhật lần cuối vào: %s + Thông báo cập nhật lần cuối vào: %s Do giới hạn của ExoPlayer, khoảng thời gian tua đã được đặt lại thành %d giây đang khôi phục Tự tạo (không tìm thấy người upload) @@ -526,11 +526,11 @@ Chỉ hiện các kênh chưa được nhóm Không bao giờ Chỉ trên Wi-Fi - Hành vi tự động phát — %s + Tự động phát — %s Phát hàng đợi (Video) Không có danh sách nào ở đây Chọn danh sách - Vui lòng kiểm tra xem vấn đề bạn đang gặp đã có báo cáo trước đó chưa. Nếu bạn tạo nhiều báo cáo trùng lặp, bạn sẽ làm tốn thời gian để chúng tôi đọc thay vì thực sự sửa lỗi. + Vui lòng kiểm tra xem vấn đề mà bạn đang gặp đã báo cáo trước đó hay chưa. Nếu bạn tạo quá nhiều báo cáo trùng lặp, bạn sẽ khiến cho chúng tôi tốn thời gian để đọc chúng thay vì sửa lỗi bạn gặp. Báo cáo trên GitHub Sao chép bản báo cáo đã được định dạng Không thể đọc URL này. Mở với app khác\? @@ -548,16 +548,16 @@ Nút hành động thứ tư Nút hành động thứ ba Nút hành động thứ hai - Nút hành đông đầu tiên - Phóng ảnh thu nhỏ của video trong thông báo từ tỉ lệ 16:9 xuống 1:1 (có thể gây méo ảnh) - Phóng ảnh thu nhỏ theo tỉ lệ 1:1 + Nút hành động đầu tiên + Chỉnh ảnh thu nhỏ của video trên thanh thông báo từ tỉ lệ 16:9 thành 1:1 (có thể gây méo ảnh) + Chỉnh ảnh thu nhỏ thành tỉ lệ 1:1 Đang hiện kết quả cho: %s - Hàng - Hiển thị nội dung không an toàn cho trẻ em vì có giới hạn độ tuổi (+18) + Thêm vào danh sách đang phát + Hiển thị nội dung không an toàn cho trẻ em vì có giới hạn độ tuổi (18+) Hiện ảnh thu nhỏ (thumbnail) trên nền màn hình khóa và trong thông báo Xem hình thu nhỏ - Kiểm tra bộ - Hàng + Kiểm tra bộ nhớ + Đã thêm vào danh sách đang phát Xoá Cookie mà NewPipe lưu trữ sau khi bạn vượt Cookie reCAPTCHA đã được xóa Xóa bỏ Cookie của reCAPCHA @@ -621,11 +621,11 @@ Tài khoản đã bị chấm dứt Hiện các mục đã xem Chế độ nguồn dữ liệu nhanh không cung cấp thêm thông tin về cái này. - Tài khoản của tác giả đã bị chấm dứt. + Tài khoản của người này đã bị chấm dứt. \nNewPipe sẽ không thể tải nguồn dữ liệu này trong tương lai. \nBạn có muốn huỷ đăng ký kênh này không\? - Không thể tải nguồn dữ liệu cho \'%s\'. - Lỗi khi tải nguồn dữ liệu + Không thể tải thông báo cho \'%s\'. + Lỗi khi tải nguồn thông báo \'Storage Access Framework\' chỉ được hỗ trợ từ Android 10 trở đi Bạn sẽ được hỏi nơi bạn muốn lưu mỗi mục tải xuống Chưa có thư mục tải xuống nào được đặt, hãy chọn thư mục tải xuống mặc định ngay @@ -633,8 +633,8 @@ Chất lượng thấp (nhỏ hơn) Chất lượng cao (lớn hơn) Xem trước hình thu nhỏ trên thanh tua - Bình luận đang bị tắt - Đã được người tạo thả tim + Bình luận đã bị tắt + Đã được chủ kênh thả \"thính\" Đánh dấu là đã xem Hiện ruy băng được tô màu Picasso ở trên cùng các hình ảnh và chỉ ra nguồn của chúng: đỏ đối với mạng, xanh lam đối với ổ đĩa và xanh lá đối với bộ nhớ Hiện dấu chỉ hình ảnh @@ -650,11 +650,11 @@ Không bắt đầu video ở trình phát mini, mà chuyển trực tiếp thành chế độ toàn màn hình, nếu tự động xoay bị khóa. Bạn vẫn có thể truy cập trình phát mini bằng cách thoát khỏi toàn màn hình Khởi động trình phát chính ở toàn màn hình Đã cho mục tiếp vào hàng đợi - Cho mục tiếp vào hàng đợi + Cho video kế tiếp vào hàng đợi Đang thực hiện...Có thể mất một lúc Thông báo lỗi Thông báo để báo cáo lỗi - NewPipe đã gặp lỗi, nhấn để báo cáo + NewPipe đã gặp sự cố, nhấn để xem và báo cáo Có lỗi xảy ra, hãy xem thông báo Hiện \"làm trình phát dừng\" Hiện tùy chọn dừng đột ngột khi sử dụng trình phát @@ -687,7 +687,7 @@ Thông báo về video mới từ kênh bạn đã đăng ký Thời gian kiểm tra Yêu cầu kết nối mạng - Bất kỳ mạng nào (có thể tính phí) + Bất kỳ loại mạng nào (có thể tính phí) Xóa tất cả tệp đã tải xuống khỏi ổ đĩa\? Thông báo bị tắt Được thông báo @@ -696,4 +696,13 @@ Phần trăm , Nửa cung + Luồng video mà không được trình tải xuống hỗ trợ sẽ không hiện + Không có video khả dụng cho trình chạy ngoài + Video bạn chọn không hỗ trợ trình chạy bên ngoài + Video này không có âm thanh khả dụng cho trình chạy ngoài + Chọn chất lượng cho trình chạy ngoài + Định dạng không xác định (:P) + Độ phân giải không xác định + Kích thước khoảng thời gian tải + Hiện video đề xuất \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 3c307f195..c7f91b7ad 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -550,8 +550,8 @@ 第三操作按钮 第二操作按钮 第一操作按钮 - 将通知中视频缩略图的长宽比从 16:9 强制缩放到 1:1(可能会导致失真) - 强制缩放缩略图至 1:1 比例 + 将通知中视频缩略图的长宽比从 16:9 裁剪到 1:1 + 裁剪缩略图至 1:1 比例 显示内存泄漏 已加入播放队列 加入播放队列 @@ -704,4 +704,6 @@ 没有视频流可用于外部播放器 不显示下载器尚不支持的串流 未知画质 + 回放加载间隔大小 + 显示未来视频 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 9b142a747..9cf2f5ee1 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -142,12 +142,12 @@ 本機搜尋建議 遠端搜尋建議 喺本機儲存搵過嘅查詢 - 睇過嘅紀錄 + 睇過有紀錄 恢復播放 返返最後播放到去嗰個位 清單度睇到播到去邊 - 縮圖放到去 1:1 長寬比 - 顯示喺通知嘅影片縮圖由 16:9 放到去 1:1 長寬比 (話唔定會鬆郁矇) + 縮圖以 1:1 長寬比框起 + 顯示喺通知嘅影片縮圖由 16:9 剪成 1:1 長寬比 通知色彩化 等 Android 根據縮圖嘅主色自訂通知嘅顏色 (注意:唔係部部機都用得) 夜色 @@ -200,7 +200,7 @@ 撈起去浮面 匯入資料庫 主播放器用全螢幕開啟 - 如果自動旋轉鎖定,開啟影片嘅時候唔用袖珍播放器就直接飛去全螢幕模式。您仍可結束全螢幕返返去袖珍播放器 + 開啟影片嘅時候唔用袖珍播放器就直接飛去全螢幕模式,如果自動旋轉鎖定嘅話。您仍可結束全螢幕返返去袖珍播放器 認唔出呢個 URL。要唔要用另一個 app 開? YouTube 提供嘅「嚴格篩選模式」可以過濾潛在嘅成人內容 有年齡限制 (例如 18+) 故可能兒童不宜嘅內容都照顯示 @@ -322,7 +322,7 @@ 100+ 部影片 ∞ 部影片 未開放留言 - 建立 + 加新 未設定下載資料夾,請立即揀選預設嘅下載資料夾 刪除咗 1 個項目。 執執佢 @@ -394,7 +394,7 @@ 下載 課金 匯出唔到訂閱 - 修改播放器字幕啲字嘅大細同背景款式。要重新開過個 app 先會生效 + 修改播放器字幕大細同背景款式。要重新開過個 app 先會生效 執好就撳一下「搞掂」 靜音 處理緊… 可能要等等 @@ -466,7 +466,7 @@ 喺幕後開始播放 部機冇晒位 頂櫳重試幾多次 - 若然有機會用到流動數據嘅時候,或者用得著,雖則有啲下載或者冇得暫停 + 若然有機會用到流動數據嘅時候,可能會用得著,雖則有啲下載或者冇得暫停 輪住下載 內部 私人 @@ -615,7 +615,7 @@ 當喺主影片播放器轉去第個 app 嘅時候點做好 — %s 借過幕後播 借過浮面播 - 自動開啟播放 — %s + 自動開始播放 — %s 上返行人路 時間軸預覽縮圖 載入緊摘要… @@ -644,7 +644,7 @@ 著作者嘅帳戶已經被終止。 \nNewPipe 日後唔會載入到呢個摘要。 \n您要唔要取消訂閱呢個頻道? - 某啲服務會提供,通常快趣好多,但項目數量可能有限兼詳情欠奉 (例如片長、項目類型、直播狀態) + 某啲服務有提供,通常會快趣好多,但項目數量可能有限兼欠奉詳情 (例如片長、項目類型、直播狀態) 剷走播放到邊個位 係咪要全部剷走晒播放到邊個位? 百分比 @@ -662,4 +662,47 @@ 揀選一個站 轉換播放器嘅時候,排隊播可能會清零 NewPipe 係「著佐權」(copyleft) 自由軟件:您可以隨意使用、考究、分享同改進佢。具體而言,您可以依據自由軟件基金會發佈嘅《GNU 通用公眾特許條款》第 3 版或 (按您選擇) 之後任一版本之下嘅條款,重新分發及/或修改呢個軟件。 + 載入相距大細 + 互動頁面 + 預設嘅互動站 + 輸入 URL 或者您嘅 ID 去匯入 SoundCloud 個人檔案: +\n +\n一、喺網頁瀏覽器啟用「桌面版模式」(個網唔支援手機版) +\n二、去呢個網址:%1$s +\n三、叫您就登入 +\n四、複製佢彈返您去個人檔案嗰版個 URL。 + 您個 ID、soundcloud.com/您個id + 揀選互動站 + 顯示返項目原底話時隔幾耐 + 停用多媒體隧道 + 頻道成軍 + 成軍名留空 + 黃袍 + 您係咪要刪除呢個成軍? + 淨係顯示未成軍嘅訂閱 + 啲圖都要騷 Picasso 三色碼顯示源頭:紅碼係網絡上高落嚟,藍碼係儲存喺磁碟本地,綠碼係潛伏喺記憶體中 + 服務原底嘅字會騷返喺串流項目上面 + 影像要推三色碼 + 顯示定預告上畫嘅影片 + 若果播片嘅時候窒下窒下或者黑畫面,就停用多媒體隧道啦 + 點樣用 Google 匯出嚟匯入 YouTube 訂閱: +\n +\n一、去呢個網址:%1$s +\n二、叫您就登入 +\n三、撳一下「包含所有資料」,再撳一下「全部不選」,之後淨係剔返「訂閱」,然後撳「確定」 +\n四、撳一下「下一步」然後揀「建立匯出」 +\n五、個掣騷出嚟嘅時候就撳一下「下載」 +\n六、返返嚟呢度,喺下低撳「匯入檔案」,揀返下載咗嗰個 .zip 檔案 +\n七、[個 .zip 匯入唔到點算好] 將個 .csv 檔案解壓縮抽返出嚟 (通常係擺喺「YouTube and YouTube Music/subscriptions/subscriptions.csv」),喺下低撳「匯入檔案」,揀返抽出嚟個 csv 檔案 + 係咪覺得摘要「懸浮於半路太久,可否再快兩步」?可以試下啟用快速載入 (您可以喺設定度更改,又或者撳一下下低個掣)。 +\n +\nNewPipe 提供兩種載入摘要嘅方針: +\n• 攞晒成個訂閱頻道,慢得嚟志在夠完整。 +\n• 用特設嘅服務終端,快得嚟啲料爭少少。 +\n +\n兩者嘅分別在於,快趣嗰個通常都係爭噉啲料:譬如話項目嘅片長同類型 (分唔到係直播定上載),同埋攞返嚟數目可能會少啲。 +\n +\nYouTube 就係其中一個服務,有用 RSS 摘要提供呢個快趣嘅門路。 +\n +\n所以就睇您點揀:想快定要準。 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 47117ee90..fb908e841 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -550,8 +550,8 @@ 第三動作按鈕 第二動作按鈕 第一動作按鈕 - 將通知中顯示的影片縮圖從 16:9 縮放到 1:1(可能會導致變形) - 把縮圖縮放到 1:1 的長寬比 + 將通知中顯示的影片縮圖從 16:9 裁剪到 1:1 + 將縮圖裁剪為 1:1 的長寬比 顯示記憶體洩漏 已加入佇列 加入佇列 @@ -704,4 +704,5 @@ 沒有可用於外部播放程式的視訊串流 選取外部播放程式的畫質 播放載入間隔大小 + 顯示未來影片 \ No newline at end of file diff --git a/fastlane/metadata/android/az/changelogs/988.txt b/fastlane/metadata/android/az/changelogs/988.txt new file mode 100644 index 000000000..525c0f8cc --- /dev/null +++ b/fastlane/metadata/android/az/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Videonu başlatmağa başladıqda "Heç bir yayım əldə etmək mümkün olmadı" xətası aradan qaldırıldı +[YouTube] Vide sorğusu əvəzinə "Bu məzmun bu tətbiqdə əlçatan deyil" xətası aradan qaldırıldı diff --git a/fastlane/metadata/android/bn/changelogs/66.txt b/fastlane/metadata/android/bn/changelogs/66.txt new file mode 100644 index 000000000..05aeec04c --- /dev/null +++ b/fastlane/metadata/android/bn/changelogs/66.txt @@ -0,0 +1,33 @@ +# v0.13.7 এর পরিবর্তনসূচী + +### স্থির +- v0.13.6 এর বাছাই করা ফিল্টার সমস্যাগুলি ঠিক করুন + +# v0.13.6 এর পরিবর্তনসূচী + +### উন্নতি + +- বার্গারমেনু আইকন অ্যানিমেশন #1486 অক্ষম করা হয়েছে +- ডাউনলোড #1472 মুছে ফেলা হয়েছে পূর্বাবস্থায় +- শেয়ার মেনু #1498 এ ডাউনলোড এর অপশন +- লং ট্যাপ মেনু #1454 এ শেয়ার অপশন যোগ করা হয়েছে +- প্রস্থান #1354 এ প্রধান প্লেয়ার ছোট করা যাবে +- লাইব্রেরি সংস্করণ আপডেট এবং ডাটাবেস ব্যাকআপ নিষ্কাশন #1510 +- ExoPlayer 2.8.2 আপডেট #1392 + - দ্রুত গতি পরিবর্তনের জন্য বিভিন্ন ধাপের আকার সমর্থন করতে প্লেব্যাক গতি নিয়ন্ত্রণ ডায়ালগ পুনরায় কাজ করেছে৷ + - প্লেব্যাক গতি নিয়ন্ত্রণে নীরবতার সময় ফাস্ট-ফরওয়ার্ডে একটি টগল যুক্ত করা হয়েছে। এটি অডিওবুক এবং নির্দিষ্ট সঙ্গীত ঘরানার জন্য সহায়ক হওয়া উচিত এবং একটি সত্যিকারের বিরামহীন অভিজ্ঞতা আনতে পারে (এবং প্রচুর নীরবতার সাথে একটি গান ভাঙতে পারে =\\)। + - ম্যানুয়ালি না করে প্লেয়ারে অভ্যন্তরীণভাবে মিডিয়ার পাশাপাশি মেটাডেটা পাস করার অনুমতি দেওয়ার জন্য রিফ্যাক্টর করা মিডিয়া সোর্স রেজোলিউশন। এখন আমাদের কাছে মেটাডেটার একটি একক উৎস আছে এবং প্লেব্যাক শুরু হলে সরাসরি উপলব্ধ। + - প্লেলিস্ট ফ্র্যাগমেন্ট খোলার সময় নতুন মেটাডেটা উপলব্ধ হলে রিমোট প্লেলিস্ট মেটাডেটা আপডেট হচ্ছে না। + - বিভিন্ন UI ফিক্স: #1383, ব্যাকগ্রাউন্ড প্লেয়ার নোটিফিকেশন কন্ট্রোল এখন সবসময় সাদা, ফ্লিংিংয়ের মাধ্যমে পপআপ প্লেয়ার বন্ধ করা সহজ +- মাল্টি সার্ভিসের জন্য রিফ্যাক্টরযুক্ত আর্কিটেকচার সহ নতুন এক্সট্র্যাক্টর ব্যবহার করুন + +### সমাধান + +- #1440 ব্রোকেন ভিডিও ইনফো লেআউট #1491 ঠিক করুন +- দেখুন ইতিহাস সংশোধন #1497 + - #1495, ব্যবহারকারী প্লেলিস্ট অ্যাক্সেস করার সাথে সাথে মেটাডেটা (থাম্বনেল, শিরোনাম এবং ভিডিও গণনা) আপডেট করে। + - #1475, ডাটাবেসে একটি ভিউ রেজিস্টার করে যখন ব্যবহারকারী ডিটেইল ফ্র্যাগমেন্টে এক্সটার্নাল প্লেয়ারে একটি ভিডিও শুরু করে। +- পপআপ মোডের ক্ষেত্রে ক্রিনের সময়সীমা ঠিক করুন। #1463 (স্থির #640) +- প্রধান ভিডিও প্লেয়ার ফিক্স #1509 + - [#1412] ফিক্সড রিপিট মোড প্লেয়ার এনপিই সৃষ্টি করে যখন প্লেয়ার অ্যাক্টিভিটি ব্যাকগ্রাউন্ডে থাকে তখন নতুন ইন্টেন্ট পাওয়া যায়। + - পপআপের জন্য ফিক্সড মিনিমাইজিং প্লেয়ার প্লেয়ারকে ধ্বংস করে না যখন পপআপের অনুমতি না দেওয়া হয়। diff --git a/fastlane/metadata/android/bn/changelogs/730.txt b/fastlane/metadata/android/bn/changelogs/730.txt index fd2b8e1bb..4d0e49a84 100644 --- a/fastlane/metadata/android/bn/changelogs/730.txt +++ b/fastlane/metadata/android/bn/changelogs/730.txt @@ -1,2 +1,2 @@ -# ঠিক করা -- দ্রুত ঠিককরণ ডিক্রিপ্ট ফাংশন ত্রুটি আবারো। +# নিস্কাসিত +- দ্রুত হট নিষ্কাসন ডিক্রিপ্ট ফাংশন ত্রুটি আরেকবার। diff --git a/fastlane/metadata/android/bn/changelogs/770.txt b/fastlane/metadata/android/bn/changelogs/770.txt index 0c853ea74..5653c91ff 100644 --- a/fastlane/metadata/android/bn/changelogs/770.txt +++ b/fastlane/metadata/android/bn/changelogs/770.txt @@ -1,4 +1,4 @@ ০.১৭.২ এ পরিবর্তনসূচী -ঠিককরণ +নিষ্কাসন • কোনো ভিডিও নেই সমস্যা ঠিক করা হয়েছে diff --git a/fastlane/metadata/android/bn/changelogs/956.txt b/fastlane/metadata/android/bn/changelogs/956.txt index a01a4c8ed..48caa0b16 100644 --- a/fastlane/metadata/android/bn/changelogs/956.txt +++ b/fastlane/metadata/android/bn/changelogs/956.txt @@ -1 +1 @@ -[ইউটিউব] যখন কোন ভিডিও লোড হচ্ছিলো ক্র্যাশ ফিক্স করা হয়েছে +[ইউটিউব] কোনো ভিডিও লোড হওয়ার মধ্যের ক্র্যাশ নিষ্কাসন করা হয়েছে diff --git a/fastlane/metadata/android/bn/short_description.txt b/fastlane/metadata/android/bn/short_description.txt index 1085f6c73..6c5a1ea73 100644 --- a/fastlane/metadata/android/bn/short_description.txt +++ b/fastlane/metadata/android/bn/short_description.txt @@ -1 +1 @@ -অ্যানড্রয়েডের জন্য একটি মুক্ত ও হালকা ইউটিউব ফ্রন্টএন্ড। +অ্যানড্রয়েডের জন্য একটি মুক্ত ও সরল ইউটিউব ফ্রন্টএন্ড। diff --git a/fastlane/metadata/android/bn_BD/changelogs/63.txt b/fastlane/metadata/android/bn_BD/changelogs/63.txt new file mode 100644 index 000000000..efb6558d3 --- /dev/null +++ b/fastlane/metadata/android/bn_BD/changelogs/63.txt @@ -0,0 +1,8 @@ +### উন্নতিসমূহ +- Import/export সেটিংস #1333 +- চিত্রণ কমানো (দক্ষতার উন্নতি) #1371 +- ছোটখাটো কোডে উন্নতি #1375 +- GDPR এর সবকিছু যোগ হওয়া #1420 + +### নিষ্কাসীত +- ডাউনলোডার: .giga ফাইল থেকে অসমাপ্ত ডাউনলোড গুলোর থেমে যাওয়া লোড এর নিষ্কাসন #1407 diff --git a/fastlane/metadata/android/bn_BD/changelogs/64.txt b/fastlane/metadata/android/bn_BD/changelogs/64.txt new file mode 100644 index 000000000..502ceb579 --- /dev/null +++ b/fastlane/metadata/android/bn_BD/changelogs/64.txt @@ -0,0 +1,8 @@ +### উন্নতিসমুহ +- মোবাইল ডেটা ব্যাবহারে ভিডিও মানে সীমা দেয়ার ক্ষমতা যোগ করা হয়েছে। #1339 +- সেশন এর মাধ্যমে উজ্জ্বলতা মনে রাখা। #1442 +- দুর্বল সিপিইউগুলোতে ডাউনলোডের দক্ষতার উন্নতি। #1431 +- মিডিয়া সেশনগুলোই সাহায্যকারী যোগ করা হয়েছে। #1433 + +### নিষ্কাশন +- ডাউনলোডগুলো খুলতে বিধ্বস্ত হওয়া নিষ্কাসন( ছেড়ে রাখা নির্মাণ গুলোর জন্যেও নিষ্কাসন উপলুদ্ধ) #1441 diff --git a/fastlane/metadata/android/bn_BD/full_description.txt b/fastlane/metadata/android/bn_BD/full_description.txt new file mode 100644 index 000000000..ff00b080f --- /dev/null +++ b/fastlane/metadata/android/bn_BD/full_description.txt @@ -0,0 +1 @@ +নিউপাইপ গুগলের বা ইউটিউবের কোনো ফ্রেমওয়ার্ক লাইব্রেরি ব্যাবহার করেনা। এটা শুধু ওয়েবসাইট গুলোকে পারস করে যে তথ্যগুলো দরকার সেগুলোর প্রয়োজনে। এজন্যেই এই অ্যাপটা গুগলের কোনো সেবা ইনস্টল করা ছাড়াই ব্যাবহার করা যায়। আর, নিউপাইপ ব্যাবহার করতে তোমার কোনো ইউটিউব একাউন্ট প্রয়োজন হবে না, আর এইটা ফেশোর মতো। diff --git a/fastlane/metadata/android/bn_BD/short_description.txt b/fastlane/metadata/android/bn_BD/short_description.txt new file mode 100644 index 000000000..c14b3261b --- /dev/null +++ b/fastlane/metadata/android/bn_BD/short_description.txt @@ -0,0 +1 @@ +অ্যান্ড্রয়েড এর জন্যে একটা মুক্ত সরল ইউটিউব ফ্রন্টএন্ড। diff --git a/fastlane/metadata/android/cs/changelogs/63.txt b/fastlane/metadata/android/cs/changelogs/63.txt new file mode 100644 index 000000000..f98de65de --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/63.txt @@ -0,0 +1,8 @@ +### Improvements +- Import/export settings #1333 +- Reduce overdraw (performance improvement) #1371 +- Small code improvements #1375 +- Add everything about GDPR #1420 + +### Fixed +- Downloader: Fix crash on loading unfinished downloads from .giga files #1107 diff --git a/fastlane/metadata/android/cs/changelogs/64.txt b/fastlane/metadata/android/cs/changelogs/64.txt new file mode 100644 index 000000000..7b2bd1ce5 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/64.txt @@ -0,0 +1,8 @@ +### Vylepšení +- Přidáni možnosti omezení kvality videa při použití mobilních dat. #1339 +- Zapamatování jasu pro relaci. #1442 +- Zlepšený výkon pro stahování se slabším CPU #1431 +- Přidána (fungující) podpora pro mediální relace #1433 + +### Oprava +- Opraveno selhání aplikace při otevření stáhnutých souborů (oprava je nyní k dispozici pro vydané sestavy. #1441 diff --git a/fastlane/metadata/android/cs/changelogs/65.txt b/fastlane/metadata/android/cs/changelogs/65.txt new file mode 100644 index 000000000..8523ba313 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/65.txt @@ -0,0 +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. + - + - + - + - +- +- +- +- +- +- + - + - +- +- + - + -. diff --git a/fastlane/metadata/android/cs/changelogs/66.txt b/fastlane/metadata/android/cs/changelogs/66.txt new file mode 100644 index 000000000..d62f6db4f --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/66.txt @@ -0,0 +1,16 @@ +# Changelog of v0.13.7 + +### Fixed +- Fix sort filter issues of v0.13.6 + +# Changelog of v0.13.6 + +### 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 diff --git a/fastlane/metadata/android/cs/changelogs/68.txt b/fastlane/metadata/android/cs/changelogs/68.txt new file mode 100644 index 000000000..528100d34 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/68.txt @@ -0,0 +1,15 @@ +# changes of v0.14.1 + +### Fixed +- Fixed failed to decrypt video url #1659 +- Fixed description link not extract well #1657 + +# changes of v0.14.0 + +### New +- New Drawer design #1461 +- New customizable front page #1461 + +### Improvements +- Reworked Gesture controls #1604 +- New way to close the popup player #1597 diff --git a/fastlane/metadata/android/cs/changelogs/69.txt b/fastlane/metadata/android/cs/changelogs/69.txt new file mode 100644 index 000000000..48113281b --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/69.txt @@ -0,0 +1,9 @@ +### New +- Odstranění a sdílení v odběrech dlouhým klepnutím #1516 +- Uživatelské rozhraní tabletu a rozvržení seznamu mřížky #1617 + +### Vylepšení +- ukládání a opětovné načítání naposledy použitého poměru stran #1748 +- Povolení lineárního rozložení v aktivitě Stahování s úplnými názvy videí #1771 +- Odstraňování a sdílení odběrů přímo z karty odběrů #1516 +- Enqueuing nyní spustí přehrávání videa, pokud fronta přehrávání již skončila #1783 diff --git a/fastlane/metadata/android/cs/changelogs/70.txt b/fastlane/metadata/android/cs/changelogs/70.txt new file mode 100644 index 000000000..6a4f43976 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/70.txt @@ -0,0 +1,7 @@ +UPOZORNĚNÍ: Tato verze je pravděpodobně plná chyb, stejně jako ta předchozí. Nicméně vzhledem k úplnému vypnutí od 17. je lepší rozbitá verze než žádná. Nebo ne? ¯\_(ツ)_/¯ + +### Vylepšení +* stažené soubory lze nyní otevřít jedním kliknutím #1879 +* podpora upuštění pro Android 4.1 - 4.3 #1884 +* odstranění starého přehrávače #1884 +* odstranění streamů z aktuální fronty přehrávání přejetím doprava #1915 diff --git a/fastlane/metadata/android/cs/changelogs/71.txt b/fastlane/metadata/android/cs/changelogs/71.txt new file mode 100644 index 000000000..a0fdaa76f --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/71.txt @@ -0,0 +1,7 @@ +### Vylepšení +* Přidání oznámení o aktualizaci aplikace pro sestavení na GitHubu (#1608 by @krtkush) +* Různá vylepšení downloaderu (#1944 by @kapodamy): + * přidat chybějící bílé ikony a použít hardcorový způsob změny barev ikon + * kontrola, zda je iterátor inicializován (oprava #2031) + * umožnit opakování stahování při chybě "post-processing failed" v novém muxeru + * nový muxer MPEG-4 opravuje nesynchronní video a audio toky (#2039) diff --git a/fastlane/metadata/android/cs/changelogs/730.txt b/fastlane/metadata/android/cs/changelogs/730.txt new file mode 100644 index 000000000..13d0879c8 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/730.txt @@ -0,0 +1,2 @@ +# Fixed +- Znovu opravte chybu funkce dešifrování. diff --git a/fastlane/metadata/android/cs/changelogs/740.txt b/fastlane/metadata/android/cs/changelogs/740.txt new file mode 100644 index 000000000..479d25e25 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/740.txt @@ -0,0 +1,23 @@ +

Improvements

+
    +
  • make links in comments clickable, increase text size
  • +
  • seek on clicking timestamp links in comments
  • +
  • show preferred tab based on recently selected state
  • +
  • add playlist to queue when long clicking on 'Background' in playlist window
  • +
  • search for shared text when it is not an URL
  • +
  • add "share at current time" button to the main video player
  • +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- diff --git a/fastlane/metadata/android/cs/changelogs/750.txt b/fastlane/metadata/android/cs/changelogs/750.txt new file mode 100644 index 000000000..325be0c28 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/750.txt @@ -0,0 +1,14 @@ +New +Playback resume #2288 +• Resume streams where you stopped last time +Downloader Enhancements #2149 +• Use Storage Access Framework to store downloads on external SD-cards +• New mp4 muxer +• Optionally change the download directory before starting a download +• Respect metered networks + + +Improved +• Removed gema strings #2295 +• Handle (auto)rotation changes during activity lifecycle #2444 +• Make long-press menus consistent #2368 diff --git a/fastlane/metadata/android/cs/changelogs/760.txt b/fastlane/metadata/android/cs/changelogs/760.txt new file mode 100644 index 000000000..d449ec53a --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/760.txt @@ -0,0 +1,14 @@ +Změny ve verzi 0.17.1 + +Nové stránky +- Thajská lokalizace + + +Vylepšené stránky +- Znovu přidána akce "začít přehrávat zde" v nabídkách pro dlouhé stisknutí pro seznamy skladeb #2518 +- Přidání přepínače pro výběr souborů SAF / legacy #2521 + +Opraveno +- Oprava mizení tlačítek v zobrazení stahování při přepínání aplikací #2487 +- Oprava pozice přehrávání se ukládá, i když je vypnutá historie sledování +- Oprava sníženého výkonu způsobeného pozicí přehrávání v zobrazeních seznamu #2517 diff --git a/fastlane/metadata/android/cs/changelogs/770.txt b/fastlane/metadata/android/cs/changelogs/770.txt new file mode 100644 index 000000000..0a07f6bdd --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/770.txt @@ -0,0 +1,4 @@ +Změny ve verzi 0.17.2 + +Oprava +- Oprava nebylo k dispozici žádné video diff --git a/fastlane/metadata/android/cs/changelogs/780.txt b/fastlane/metadata/android/cs/changelogs/780.txt new file mode 100644 index 000000000..32cc03d1f --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/780.txt @@ -0,0 +1,11 @@ +Změny ve verzi 0.17.3 + +Vylepšené stránky +- Přidána možnost vymazat stavy přehrávání #2550 +- Zobrazení skrytých adresářů ve výběru souborů #2591 +- Podpora otevírání adres URL z instancí `invidio.us` pomocí NewPipe #2488 +- Přidána podpora pro adresy URL `music.youtube.com` TeamNewPipe/NewPipeExtractor#194 + +Opraveno +- YouTube] Opraveno 'java.lang.IllegalArgumentException #192 +- YouTube] Opraveno nefunkční živé vysílání TeamNewPipe/NewPipeExtractor#195 diff --git a/fastlane/metadata/android/cs/changelogs/790.txt b/fastlane/metadata/android/cs/changelogs/790.txt new file mode 100644 index 000000000..04b5c5763 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/790.txt @@ -0,0 +1,9 @@ +Vylepšené stránky +- Přidání více nadpisů pro zlepšení přístupnosti pro nevidomé #2655 +- Udělejte jazyk nastavení složky pro stahování konzistentnější a méně nejednoznačný #2637 + +Opraveno +- Kontrola, zda je stažen poslední bajt v bloku #2646 +- Opraveno posouvání ve fragmentu detailu videa #2672 +- Odstranění dvojité animace vymazání vyhledávacího pole na jednu #2695 +- [SoundCloud] Oprava extrakce client_id #2745 diff --git a/fastlane/metadata/android/cs/changelogs/800.txt b/fastlane/metadata/android/cs/changelogs/800.txt new file mode 100644 index 000000000..0a22893f1 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/800.txt @@ -0,0 +1,10 @@ +Nový +- Podpora PeerTube bez P2P (#2201) [Beta]: + ◦ Sledování a stahování videí z instancí PeerTube + ◦ Přidání instancí v nastavení pro přístup ke kompletnímu světu PeerTube + ◦ V systémech Android 4.4 a 7.1 mohou být při přístupu k některým instancím problémy s přenosem SSL, což může vést k chybě sítě. + +- Downloader (#2679): + ◦ Vypočítat předpokládaný čas stahování + ◦ Stáhnout opus (soubory webm) jako ogg + ◦ Obnovení vypršených odkazů ke stažení pro obnovení stahování po dlouhé pauze diff --git a/fastlane/metadata/android/cs/changelogs/810.txt b/fastlane/metadata/android/cs/changelogs/810.txt new file mode 100644 index 000000000..c04a9cac9 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/810.txt @@ -0,0 +1,8 @@ +Nový +- Zobrazení miniatury videa na zamykací obrazovce při přehrávání na pozadí + +Vylepšená stránka +- Přidání místního seznamu skladeb do fronty při dlouhém stisknutí tlačítka na pozadí / vyskakovacího tlačítka +- Umožnit posouvání karet hlavní stránky a jejich skrytí, pokud je k dispozici pouze jedna karta +- Omezit počet aktualizací miniatur oznámení v přehrávači na pozadí +- Přidání fiktivní miniatury pro prázdné místní seznamy skladeb diff --git a/fastlane/metadata/android/cs/changelogs/820.txt b/fastlane/metadata/android/cs/changelogs/820.txt new file mode 100644 index 000000000..9dc52c6f5 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/820.txt @@ -0,0 +1 @@ +Opraven regex názvu dešifrovací funkce, který znemožňuje použití služby YouTube. diff --git a/fastlane/metadata/android/cs/changelogs/830.txt b/fastlane/metadata/android/cs/changelogs/830.txt new file mode 100644 index 000000000..1f6666912 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/830.txt @@ -0,0 +1 @@ +Aktualizováno klient_id služby SoundCloud pro opravu problémů se službou SoundCloud. diff --git a/fastlane/metadata/android/cs/changelogs/840.txt b/fastlane/metadata/android/cs/changelogs/840.txt new file mode 100644 index 000000000..73da3bcc5 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/840.txt @@ -0,0 +1,8 @@ +Nový +- Přidán volič jazyka pro změnu jazyka aplikace +- Přidáno tlačítko odeslat do Kodi do skládací nabídky přehrávače +- Přidána možnost kopírování komentářů při dlouhém stisknutí + +Vylepšena stránka +- Oprava aktivity ReCaptcha a správné ukládání získaných souborů cookie +- Odstraněna nabídka s tečkami ve prospěch šuplíku a skrytí tlačítka historie, pokud není v nastavení povolena historie sledování diff --git a/fastlane/metadata/android/cs/changelogs/850.txt b/fastlane/metadata/android/cs/changelogs/850.txt new file mode 100644 index 000000000..86fa0fc0f --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/850.txt @@ -0,0 +1 @@ +V tomto vydání byla aktualizována verze webových stránek YouTube. Stará verze webových stránek bude v březnu ukončena, a proto je nutné provést aktualizaci NewPipe. diff --git a/fastlane/metadata/android/cs/changelogs/860.txt b/fastlane/metadata/android/cs/changelogs/860.txt new file mode 100644 index 000000000..b1d6765f8 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/860.txt @@ -0,0 +1,7 @@ +Vylepšené stránky +- Uložení a obnovení, zda je výška tónu a tempo odpojeno, nebo ne +- Podpora výřezu displeje v přehrávači +- Kulaté zobrazení a počet účastníků +- Optimalizováno pro YouTube tak, aby využívalo méně dat + +V této verzi bylo opraveno více než 15 chyb souvisejících s YouTube. diff --git a/fastlane/metadata/android/cs/changelogs/870.txt b/fastlane/metadata/android/cs/changelogs/870.txt new file mode 100644 index 000000000..27cc41b5d --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/870.txt @@ -0,0 +1,2 @@ +Jedná se o opravnou verzi, která aktualizuje NewPipe tak, aby opět umožňovala používání služby SoundCloud bez větších potíží. +V extraktoru se nyní používá rozhraní API SoundCloud v2 a byla vylepšena detekce neplatných ID klientů. diff --git a/fastlane/metadata/android/cs/changelogs/900.txt b/fastlane/metadata/android/cs/changelogs/900.txt new file mode 100644 index 000000000..301ced059 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/900.txt @@ -0,0 +1,13 @@ +Nový +- Skupiny předplatného a tříděné kanály +- Tlačítko ztlumení zvuku v přehrávačích + +Vylepšené stránky +- Povoleno otevírání odkazů na music.youtube.com a media.ccc.de v aplikaci NewPipe +- Přemístění dvou nastavení ze vzhledu do obsahu +- Skrytí možností vyhledávání po 5, 15 a 25 sekundách, pokud je povoleno nepřesné vyhledávání + +Opraveno +- některá videa WebM nelze zobrazit +- zálohování databáze v systému Android P +- pád při sdílení staženého souboru diff --git a/fastlane/metadata/android/cs/changelogs/910.txt b/fastlane/metadata/android/cs/changelogs/910.txt new file mode 100644 index 000000000..130b5dc87 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/910.txt @@ -0,0 +1 @@ +Opravena migrace databáze, která v některých vzácných případech znemožňovala spuštění aplikace NewPipe. diff --git a/fastlane/metadata/android/cs/changelogs/920.txt b/fastlane/metadata/android/cs/changelogs/920.txt new file mode 100644 index 000000000..b5e3167ee --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/920.txt @@ -0,0 +1,9 @@ +Vylepšeno + +- Přidáno datum nahrání a počet zobrazení na položkách mřížky streamu +- Vylepšení rozvržení záhlaví zásuvky + +Opraveno + +- Opraveno tlačítko ztlumení zvuku způsobující pády na rozhraní API 19 +- Opraveno stahování dlouhých videí 1080p 60fps diff --git a/fastlane/metadata/android/cs/changelogs/930.txt b/fastlane/metadata/android/cs/changelogs/930.txt new file mode 100644 index 000000000..e72a3a61c --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/930.txt @@ -0,0 +1,10 @@ +Nový +- Vyhledávání na YouTube Music +- Základní podpora Android TV + +Vylepšené stránky +- Přidána možnost odstranit všechna sledovaná videa z místního seznamu skladeb +- Zobrazení zprávy, když obsah ještě není podporován, místo pádu. +- Vylepšena změna velikosti vyskakovacího přehrávače pomocí gest štípnutí +- Enqueue streamy při dlouhém stisknutí tlačítek na pozadí a vyskakovacích tlačítek v kanálu +- Vylepšené zpracování velikosti záhlaví zásuvky diff --git a/fastlane/metadata/android/cs/changelogs/940.txt b/fastlane/metadata/android/cs/changelogs/940.txt new file mode 100644 index 000000000..7988003d8 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/940.txt @@ -0,0 +1,9 @@ +Nový +- Přidání podpory pro komentáře SoundCloud +- Přidání nastavení omezeného režimu YouTube +- Zobrazení podrobností o nadřazeném kanálu PeerTube + +Vylepšené stránky +- Zobrazení tlačítka Kore pouze pro podporované služby +- Blokování gest přehrávače, která začínají na panelu NavigationBar nebo StatusBar +- Změna barvy pozadí tlačítek opakování a přihlášení k odběru na základě barvy služby diff --git a/fastlane/metadata/android/cs/changelogs/950.txt b/fastlane/metadata/android/cs/changelogs/950.txt new file mode 100644 index 000000000..21e52a365 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/950.txt @@ -0,0 +1,4 @@ +Tato verze přináší tři drobné opravy: +- Oprava přístupu k úložišti v systému Adroid 10+ +- Opraveno otevírání kiosků +- Opraveno rozbor trvání dlouhých videí diff --git a/fastlane/metadata/android/cs/changelogs/951.txt b/fastlane/metadata/android/cs/changelogs/951.txt new file mode 100644 index 000000000..e5e3b3a64 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/951.txt @@ -0,0 +1,6 @@ +Nový +- Přidání vyhledávání pro výběr odběru v dialogovém okně skupiny kanálů +- Přidání filtru do dialogového okna skupiny kanálů pro zobrazení pouze neseskupených odběrů +- Přidání karty seznamu skladeb na hlavní stránku +- Rychlé převíjení vpřed/vzad ve frontě přehrávačů na pozadí/vyskočení. +- Zobrazení návrhu vyhledávání: mysleli jste a zobrazení výsledku pro diff --git a/fastlane/metadata/android/cs/changelogs/953.txt b/fastlane/metadata/android/cs/changelogs/953.txt new file mode 100644 index 000000000..cb7626862 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/953.txt @@ -0,0 +1 @@ +Oprava extrakce dešifrovací funkce YouTube. diff --git a/fastlane/metadata/android/cs/changelogs/954.txt b/fastlane/metadata/android/cs/changelogs/954.txt new file mode 100644 index 000000000..e07b70aa2 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/954.txt @@ -0,0 +1,6 @@ +- nový pracovní postup aplikace: přehrávání videí na stránce s detailem, přejetí prstem dolů pro minimalizaci přehrávače +- Oznámení MediaStyle: přizpůsobitelné akce v oznámeních, zlepšení výkonu +- základní změna velikosti při používání aplikace NewPipe jako aplikace pro stolní počítače + +- zobrazení dialogu s možnostmi otevření v případě přípitku nepodporované adresy URL +- Zlepšení zkušeností s návrhy vyhledávání, pokud nelze načíst ty vzdálené diff --git a/fastlane/metadata/android/cs/changelogs/955.txt b/fastlane/metadata/android/cs/changelogs/955.txt new file mode 100644 index 000000000..ed158e34b --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/955.txt @@ -0,0 +1,3 @@ +[YouTube] Oprava vyhledávání pro některé uživatele +[YouTube] Oprava náhodných výjimek při dešifrování +[SoundCloud] Adresy URL, které končí lomítkem, jsou nyní zpracovávány správně diff --git a/fastlane/metadata/android/cs/changelogs/956.txt b/fastlane/metadata/android/cs/changelogs/956.txt new file mode 100644 index 000000000..f31882573 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/956.txt @@ -0,0 +1 @@ +[YouTube] Opraveno selhání při načítání jakéhokoli videa diff --git a/fastlane/metadata/android/cs/changelogs/957.txt b/fastlane/metadata/android/cs/changelogs/957.txt new file mode 100644 index 000000000..666c51097 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/957.txt @@ -0,0 +1,8 @@ +- Sjednocení specifických akcí enqueue do jedné +- Gesto dvěma prsty pro zavření přehrávače +- Povolení vymazání souborů cookie reCAPTCHA +- Možnost nezabarvovat oznámení +- Vylepšení způsobu otevírání detailů videa s cílem opravit nekonečné vyrovnávací paměti, chybné chování při sdílení do NewPipe a další nesrovnalosti +- Zrychlení videí na YouTube a oprava videí s věkovým omezením +- Oprava pádu při rychlém převíjení vpřed/vzad +- Nepřeuspořádávat seznamy přetahováním miniatur diff --git a/fastlane/metadata/android/cs/changelogs/958.txt b/fastlane/metadata/android/cs/changelogs/958.txt new file mode 100644 index 000000000..989f9ad61 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/958.txt @@ -0,0 +1,15 @@ +Nové a vylepšené: +- Znovu přidána možnost skrýt miniaturu na zamykací obrazovce +- Tažení pro obnovení kanálu +- Vylepšený výkon při načítání místních seznamů + +Opraveno: +- Opraven pád při spuštění aplikace NewPipe po jejím vyjmutí z paměti RAM +- Opraven pád při spuštění, když není připojení k internetu +- Opraveno: Respektování nastavení jasu a nastavení hlasitosti +- YouTube] Opraveny dlouhé seznamy skladeb + +Ostatní: +- Vyčištění kódu a několik interních vylepšení +- Aktualizace závislostí +- diff --git a/fastlane/metadata/android/cs/changelogs/959.txt b/fastlane/metadata/android/cs/changelogs/959.txt new file mode 100644 index 000000000..18a25645b --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/959.txt @@ -0,0 +1,3 @@ +Opravena nekonečná smyčka pádů po otevření hlášení chyb. +Aktualizován seznam instancí PeerTube, které lze automaticky otevřít pomocí NewPipe. +Aktualizovány překlady. diff --git a/fastlane/metadata/android/cs/changelogs/960.txt b/fastlane/metadata/android/cs/changelogs/960.txt new file mode 100644 index 000000000..c25277f64 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/960.txt @@ -0,0 +1,4 @@ +- Vylepšený popis možnosti exportu databáze v nastavení. +- Opraveno zpracování komentářů na YouTube. +- Opraveno zobrazení názvu služby media.ccc.de. +- Aktualizovány překlady. diff --git a/fastlane/metadata/android/cs/changelogs/961.txt b/fastlane/metadata/android/cs/changelogs/961.txt new file mode 100644 index 000000000..db711f0d4 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/961.txt @@ -0,0 +1,12 @@ +- [YouTube] Podpora mixu +- [YouTube] Zobrazení informací o veřejnoprávních vysílatelích a Covid-19 +- [media.ccc.de] Přidána nejnovější videa +- Přidán somálský překlad + +- Mnoho interních vylepšení + +- Opraveno sdílení videí z přehrávače +- Opraveno prázdné webové zobrazení ReCaptcha +- Opraven pád, ke kterému docházelo při odebírání streamu ze seznamu +- [PeerTube] Opraveny související streamy +- YouTube] Opraveno vyhledávání hudby na YouTube diff --git a/fastlane/metadata/android/cs/changelogs/963.txt b/fastlane/metadata/android/cs/changelogs/963.txt new file mode 100644 index 000000000..e971418af --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/963.txt @@ -0,0 +1 @@ +- [YouTube] Opraveno pokračování kanálu diff --git a/fastlane/metadata/android/cs/changelogs/964.txt b/fastlane/metadata/android/cs/changelogs/964.txt new file mode 100644 index 000000000..11eacbcd8 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/964.txt @@ -0,0 +1,8 @@ +- Přidána podpora kapitol v ovládání hráče +- [PeerTube] Přidáno vyhledávání v sépiové barvě +- Znovu přidáno tlačítko pro sdílení v zobrazení detailu videa a popis streamu přesunut do rozložení karet +- Zakázáno obnovení jasu, pokud je gesto jasu zakázáno +- Přidána položka seznamu pro přehrávání videa v Kodi +- Opraven pád v případě, že na některých zařízeních není nastaven výchozí prohlížeč, a vylepšeny dialogy sdílení +- +- diff --git a/fastlane/metadata/android/cs/changelogs/965.txt b/fastlane/metadata/android/cs/changelogs/965.txt new file mode 100644 index 000000000..c62dfa65c --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/965.txt @@ -0,0 +1,6 @@ +Opraven pád, ke kterému docházelo při změně pořadí skupin kanálů. +Opraveno získávání dalších videí YouTube z kanálů a seznamů skladeb. +Opraveno získávání komentářů YouTube. +Přidána podpora podcest /watch/, /v/ a /w/ v adresách URL YouTube. +Opraveno získávání id klienta služby SoundCloud a obsahu s geografickým omezením. +Přidána lokalizace do severní kurdštiny. diff --git a/fastlane/metadata/android/cs/changelogs/966.txt b/fastlane/metadata/android/cs/changelogs/966.txt new file mode 100644 index 000000000..212687b51 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/966.txt @@ -0,0 +1,14 @@ +Novinka: +- Přidat novou službu: Bandcamp + +Vylepšeno: +- Přidána možnost, aby aplikace následovala motiv zařízení +- Předcházení některým pádům zobrazením vylepšeného panelu chyb +- Zobrazení více informací o tom, proč je obsah nedostupný +- Hardwarové tlačítko mezerníku spouští přehrávání/pauzu +- Zobrazení přípitku "Stahování zahájeno" + +Opraveno: +- Oprava velmi malé miniatury v detailech videa při přehrávání na pozadí +- Oprava prázdného názvu v minimalizovaném přehrávači +- diff --git a/fastlane/metadata/android/cs/changelogs/967.txt b/fastlane/metadata/android/cs/changelogs/967.txt new file mode 100644 index 000000000..ba62e27eb --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/967.txt @@ -0,0 +1 @@ +Opraveno nesprávné fungování služby YouTube v EU. To bylo způsobeno novým systémem souborů cookie a souhlasu s ochranou osobních údajů, který vyžaduje, aby NewPipe nastavil soubor cookie CONSENT. diff --git a/fastlane/metadata/android/cs/changelogs/968.txt b/fastlane/metadata/android/cs/changelogs/968.txt new file mode 100644 index 000000000..5c14d8a5f --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/968.txt @@ -0,0 +1,7 @@ +Do nabídky dlouhého stisknutí tlačítka byla přidána možnost Podrobnosti o kanálu. +Přidána funkce přejmenování názvu seznamu skladeb z rozhraní seznamu skladeb. +Umožňuje uživateli pozastavit video během jeho ukládání do vyrovnávací paměti. +Vyleštěn bílý motiv. +Opraveno překrývání písem při použití větší velikosti písma. +Opraveno chybějící video na zařízeních Formuler a Zephier. +Opraveny různé pády. diff --git a/fastlane/metadata/android/cs/changelogs/969.txt b/fastlane/metadata/android/cs/changelogs/969.txt new file mode 100644 index 000000000..8c5814047 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/969.txt @@ -0,0 +1,8 @@ +- Povolení instalace na externí úložiště +- [Bandcamp] Přidána podpora pro zobrazení prvních tří komentářů u streamu +- Zobrazení přípitku "stahování zahájeno" pouze po zahájení stahování +- Nenastavovat soubor cookie reCaptcha, pokud není uložen žádný soubor cookie +- Přehrávač] Zlepšení výkonu mezipaměti +- Přehrávač] Opraveno automatické nepřehrávání přehrávače +- Zrušit předchozí Snackbary při mazání stahování +- Opraven pokus o odstranění objektu, který není v seznamu diff --git a/fastlane/metadata/android/cs/changelogs/970.txt b/fastlane/metadata/android/cs/changelogs/970.txt new file mode 100644 index 000000000..f526adf8c --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/970.txt @@ -0,0 +1,11 @@ +Nový +- Zobrazení metadat obsahu (značky, kategorie, licence, ...) pod popisem +- Přidána možnost "Zobrazit podrobnosti o kanálu" ve vzdálených (nelokálních) seznamech skladeb +- Přidána možnost "Otevřít v prohlížeči" do nabídky dlouhého stisknutí tlačítka + +Opravena stránka +- Opraven pád při otáčení na stránce s podrobnostmi o videu +- Opraveno tlačítko "Přehrát s Kodi" v přehrávači, které vždy vyzve k instalaci aplikace Kore +- Opraveno a vylepšeno nastavení cest pro import a export +- +- diff --git a/fastlane/metadata/android/cs/changelogs/971.txt b/fastlane/metadata/android/cs/changelogs/971.txt new file mode 100644 index 000000000..481b9edf1 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/971.txt @@ -0,0 +1,3 @@ +Hotfix +- Zvětšení vyrovnávací paměti pro přehrávání po obnovení vyrovnávací paměti +- Opraven pád na tabletech a televizorech při kliknutí na ikonu přehrávání v přehrávači diff --git a/fastlane/metadata/android/cs/changelogs/972.txt b/fastlane/metadata/android/cs/changelogs/972.txt new file mode 100644 index 000000000..d0989c7b9 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/972.txt @@ -0,0 +1,14 @@ +Nový +Rozpoznání časových razítek a hashtagů v popisu +Přidáno ruční nastavení režimu tabletu +Přidána možnost skrýt přehrávané položky ve zdroji + +Vylepšený +Správná podpora rozhraní Storage Access Framework +Lepší zpracování chyb nedostupných a ukončených kanálů +List sdílení Android pro uživatele Androidu 10+ nyní zobrazuje název obsahu. +Aktualizované instance Invidious a podpora předávaných odkazů. + +Stabilní +[YouTube] Obsah s věkovým omezením +- diff --git a/fastlane/metadata/android/cs/changelogs/973.txt b/fastlane/metadata/android/cs/changelogs/973.txt new file mode 100644 index 000000000..2eba9ea7c --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/973.txt @@ -0,0 +1,4 @@ +Hotfix +- Oprava ořezávání miniatur a názvů v mřížkovém rozložení kvůli špatnému výpočtu, kolik videí se vejde do jednoho řádku. +- Oprava dialogu stahování, který zmizí, aniž by cokoli provedl, pokud je otevřen z nabídky sdílení +- Aktualizace knihovny související s otevíráním externích činností, například výběrem souborů v rámci Storage Access Framework diff --git a/fastlane/metadata/android/cs/changelogs/974.txt b/fastlane/metadata/android/cs/changelogs/974.txt new file mode 100644 index 000000000..3149e4737 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/974.txt @@ -0,0 +1,5 @@ +Hotfix +- Oprava problémů s vyrovnávací pamětí způsobených škrcením YouTube +- Oprava extrakce komentářů YouTube a pádů s vypnutými komentáři +- Oprava vyhledávání hudby na YouTube +- Oprava živých přenosů PeerTube diff --git a/fastlane/metadata/android/cs/changelogs/975.txt b/fastlane/metadata/android/cs/changelogs/975.txt new file mode 100644 index 000000000..839bf0e36 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/975.txt @@ -0,0 +1,17 @@ +Nový +- Zobrazení náhledu miniatur při hledání +- Rozpoznání zakázaných komentářů +- Umožňuje označit položku kanálu jako sledovanou +- Zobrazit srdíčka komentářů + +Vylepšené stránky +- Vylepšení rozvržení metadat a značek +- Použití barvy služby na součásti uživatelského rozhraní + +Opraveno +- Oprava miniatur v mini přehrávači +- Oprava nekonečného vyrovnávací paměti u duplicitních položek fronty +- Opravy některých přehrávačů, jako je otáčení a rychlejší zavírání +- +- +- diff --git a/fastlane/metadata/android/cs/changelogs/976.txt b/fastlane/metadata/android/cs/changelogs/976.txt new file mode 100644 index 000000000..cc28400f5 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/976.txt @@ -0,0 +1,10 @@ +- Přidána možnost přímého otevření přehrávače ve fullscreenu +- Umožňuje vybrat, které typy návrhů vyhledávání se mají zobrazit +- Tmavé téma je nyní tmavší + přidána tmavá úvodní obrazovka +- Vylepšený nástroj pro výběr souborů, který šedě označuje nechtěné soubory +- Opraven import odběrů YouTube +- Opakované přehrávání streamu vyžaduje opětovné klepnutí na tlačítko přehrávání +- Opraveno ukončení zvukové relace +- +- +-. diff --git a/fastlane/metadata/android/cs/changelogs/977.txt b/fastlane/metadata/android/cs/changelogs/977.txt new file mode 100644 index 000000000..3418d9d43 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/977.txt @@ -0,0 +1,10 @@ +- Do nabídky dlouhého stisku bylo přidáno tlačítko "přehrát další". +- Do filtru záměrů byla přidána předpona cesty ke krátkým filmům YouTube +- Opraven import nastavení +- Výměna pozice panelu vyhledávání s tlačítky přehrávače na obrazovce Fronta +- Různé opravy související se správcem MediasessionManager +- Opraveno nedokončení panelu vyhledávání po skončení videa +- Zakázáno tunelování médií na RealtekATV +- Rozšířena klikatelná oblast minimalizovaných tlačítek přehrávače + +-. diff --git a/fastlane/metadata/android/cs/changelogs/978.txt b/fastlane/metadata/android/cs/changelogs/978.txt new file mode 100644 index 000000000..caaf1ac57 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/978.txt @@ -0,0 +1 @@ +Opraveno provádění kontroly nové verze NewPipe. Tato kontrola se někdy prováděla příliš brzy, a proto vedla k pádu aplikace. To by nyní mělo být opraveno. diff --git a/fastlane/metadata/android/cs/changelogs/979.txt b/fastlane/metadata/android/cs/changelogs/979.txt new file mode 100644 index 000000000..8d3ee1492 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/979.txt @@ -0,0 +1,2 @@ +- Opraveno obnovení přehrávání +- Vylepšení zajišťující, že služba, která určuje, zda má NewPipe kontrolovat nové verze, není spuštěna na pozadí diff --git a/fastlane/metadata/android/cs/changelogs/980.txt b/fastlane/metadata/android/cs/changelogs/980.txt new file mode 100644 index 000000000..59139b3a2 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/980.txt @@ -0,0 +1,13 @@ +Nový +- Přidání možnosti "Přidat do seznamu skladeb" do nabídky sdílení +- Přidána podpora pro krátké odkazy na y2u.be a PeerTube + +Vylepšené stránky +- Kompaktnější ovládání rychlosti přehrávání +- Kanál nyní zvýrazňuje nové položky +- Možnost "Zobrazit sledované položky" ve feedu je nyní uložena + +Opraveno +- Opravena extrakce lajků a dislajků na YouTube +- Opraveno automatické přehrávání po návratu z pozadí +A mnoho dalšího diff --git a/fastlane/metadata/android/cs/changelogs/981.txt b/fastlane/metadata/android/cs/changelogs/981.txt new file mode 100644 index 000000000..6dd389fd7 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/981.txt @@ -0,0 +1,2 @@ +Odstraněna podpora MediaParser, aby se opravilo selhání obnovení přehrávání po vyrovnávací paměti v systému Android 11+. +Zakázáno tunelování médií na přehrávači Philips QM16XE, aby se odstranily problémy s přehráváním. diff --git a/fastlane/metadata/android/cs/changelogs/982.txt b/fastlane/metadata/android/cs/changelogs/982.txt new file mode 100644 index 000000000..c666499a4 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/982.txt @@ -0,0 +1 @@ +Opraveno nepřehrávání jakéhokoli streamu ve službě YouTube. diff --git a/fastlane/metadata/android/cs/changelogs/983.txt b/fastlane/metadata/android/cs/changelogs/983.txt new file mode 100644 index 000000000..004ccca65 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/983.txt @@ -0,0 +1,9 @@ +Přidání nového uživatelského rozhraní a chování při hledání dvojitým klepnutím +Možnost vyhledávání v nastavení +Zvýraznění připnutých komentářů jako takových +Přidat podporu open-with-app pro instanci FSFE PeerTube +Přidat oznámení o chybách +Oprava přehrávání první položky fronty při změně hráče +Při vyrovnávací paměti během živých přenosů čekat déle, než dojde k selhání +Oprava pořadí výsledků místního vyhledávání +Oprava prázdných políček položek ve frontě přehrávání diff --git a/fastlane/metadata/android/cs/changelogs/984.txt b/fastlane/metadata/android/cs/changelogs/984.txt new file mode 100644 index 000000000..3b9eb35a4 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/984.txt @@ -0,0 +1,7 @@ +načtení dostatečného množství počátečních položek v seznamech, aby zaplnily celou obrazovku, a oprava posouvání na tabletech a televizorech. +Oprava náhodných pádů při procházení seznamů +Nechat překryvný oblouk rychlého vyhledávání hráče přejít pod uživatelské rozhraní systému +Vrátit změny výřezů při přehrávání ve více oknech, které způsobovaly regresi chybně umístěného přehrávače na některých telefonech +Zvýšit compileSdk z 30 na 31 +Aktualizovat knihovnu pro hlášení chyb +- diff --git a/fastlane/metadata/android/cs/changelogs/985.txt b/fastlane/metadata/android/cs/changelogs/985.txt new file mode 100644 index 000000000..7035a1112 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/985.txt @@ -0,0 +1 @@ +Opraveno nepřehrávání jakéhokoli streamu ve službě YouTube diff --git a/fastlane/metadata/android/cs/changelogs/986.txt b/fastlane/metadata/android/cs/changelogs/986.txt new file mode 100644 index 000000000..0ffe8dabc --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/986.txt @@ -0,0 +1,16 @@ +Nový +• Oznámení o nových streamech +• Bezproblémový přechod mezi přehrávači na pozadí a videem +• Změna výšky tónu podle půltónů +• Připojení fronty hlavního přehrávače k seznamu skladeb + +Vylepšený +• Zapamatujte si velikost kroku rychlosti / stoupání +• Zmírnění počátečního dlouhého ukládání do vyrovnávací paměti v přehrávači videa +• Vylepšete uživatelské rozhraní přehrávače pro Android TV +• Potvrďte před odstraněním všech stažených souborů + +Stabilní +• +• +• diff --git a/fastlane/metadata/android/cs/changelogs/987.txt b/fastlane/metadata/android/cs/changelogs/987.txt new file mode 100644 index 000000000..51cd846c2 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/987.txt @@ -0,0 +1,12 @@ +Nový +- Podpora jiných způsobů doručování než progresivního HTTP: rychlejší načítání přehrávání, opravy pro PeerTube a SoundCloud, přehrávání nedávno ukončených živých přenosů na YouTube. +- Tlačítko Přidat pro přidání vzdáleného seznamu skladeb do místního seznamu skladeb +- Náhled obrázku ve sdíleném listu systému Android 10+ + +Vylepšená stránka +- Vylepšení dialogového okna s parametry přehrávání +- Přesunutí tlačítek pro import/export předplatného do nabídky se třemi tečkami + +Opraveno +- +- diff --git a/fastlane/metadata/android/cs/changelogs/988.txt b/fastlane/metadata/android/cs/changelogs/988.txt new file mode 100644 index 000000000..76da8121c --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Oprava chyby "Nelze načíst žádný stream" při pokusu o přehrání jakéhokoli videa +[YouTube] Oprava zprávy "Následující obsah není v této aplikaci k dispozici." zobrazené místo požadovaného videa diff --git a/fastlane/metadata/android/cs/changelogs/989.txt b/fastlane/metadata/android/cs/changelogs/989.txt new file mode 100644 index 000000000..08ce8dd95 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/989.txt @@ -0,0 +1,3 @@ +• [YouTube] Oprava nekonečného načítání při pokusu přehrát jakékoli video +• [YouTube] Oprava omezování výkonu u některých videí +• Aktualizace knihovny jsoup na verzi 1.15.3, která obsahuje bezpečnostní opravu diff --git a/fastlane/metadata/android/de/changelogs/987.txt b/fastlane/metadata/android/de/changelogs/987.txt index b6870154c..a857b1caa 100644 --- a/fastlane/metadata/android/de/changelogs/987.txt +++ b/fastlane/metadata/android/de/changelogs/987.txt @@ -9,4 +9,4 @@ Verbesserte Behoben - Fix: Entfernen vollständig angesehener Videos aus der Wiedergabeliste -- Repariert das Thema des Freigabemenüs und den Eintrag "Zur Wiedergabeliste hinzufügen". +- Repariert das Thema des Freigabemenüs und den Eintrag "Zur Wiedergabeliste hinzufügen" diff --git a/fastlane/metadata/android/fr/changelogs/63.txt b/fastlane/metadata/android/fr/changelogs/63.txt index be078632b..b9abcd760 100644 --- a/fastlane/metadata/android/fr/changelogs/63.txt +++ b/fastlane/metadata/android/fr/changelogs/63.txt @@ -1,8 +1,8 @@ ### Améliorations -- Importation/exportation des paramètres #1333 +- Import/export des paramètres #1333 - Réduction overdraw (amélioration des performances) #1371 - Petites améliorations du code #1375 -- GDPR #1420 +- Ajout d'un popup RGPD #1420 ### Corrections - Téléchargeur : Correction d'un plantage lors du chargement de téléchargements inachevés de fichiers .giga #1407 diff --git a/fastlane/metadata/android/fr/changelogs/65.txt b/fastlane/metadata/android/fr/changelogs/65.txt new file mode 100644 index 000000000..bb664a3cb --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/65.txt @@ -0,0 +1,26 @@ +### Améliorations + +- L'animation de l'icône du burgermenu a été désactivé #1486 +- Annulation de la suppression des téléchargements #1472 +- Option de téléchargement dans le menu de partage #1498 +- Ajout d'une option de partage dans le menu "long tap" #1454 +- Réduction du lecteur principal à la sortie #1354 +- Mise à jour de la version de la bibliothèque et correction de la sauvegarde de la base de données #1510 +- Mise à jour de ExoPlayer 2.8.2 #1392 + - La boîte de dialogue de contrôle de la vitesse de lecture a été retravaillée pour prendre en charge différentes tailles de pas pour un changement de vitesse plus rapide. + - Ajout d'une option d'avance rapide pendant les silences dans le contrôle de la vitesse de lecture. Cela devrait être utile pour les livres audio et certains genres musicaux, et peut apporter une véritable expérience transparente (et peut casser une chanson avec beaucoup de silences =\\). + - Refonte de la résolution des sources de médias pour permettre le passage des métadonnées avec les médias en interne dans le lecteur, plutôt que de le faire manuellement. Maintenant, nous avons une seule source de métadonnées et elles sont directement disponibles lorsque la lecture commence. + - Correction des métadonnées des listes de lecture distantes qui ne sont pas mises à jour lorsque de nouvelles métadonnées sont disponibles lors de l'ouverture du fragment de liste de lecture. + - Diverses corrections de l'interface utilisateur : #1383, les contrôles de notification du lecteur en arrière-plan sont maintenant toujours blancs, il est plus facile de fermer le lecteur popup en le lançant. +- Utilisation d'un nouvel extracteur avec une architecture remaniée pour le multiservice. + +### Corrections + +- Correction #1440 Disposition des informations vidéo cassée #1491 +- Correction de l'historique des vues #1497 + - #1495, en mettant à jour les métadonnées (vignette, titre et nombre de vidéos) dès que l'utilisateur accède à la liste de lecture. + - #1475, en enregistrant une vue dans la base de données lorsque l'utilisateur lance une vidéo sur un lecteur externe sur le fragment de détail. +- Correction du timeout de la fenêtre en cas de mode popup. #1463 (Corrigé #640) +- Correction du lecteur vidéo principal #1509 + - Correction du mode répétition entraînant un NPE du lecteur lorsqu'une nouvelle intention est reçue alors que l'activité du lecteur est en arrière-plan. + - Correction de la réduction du lecteur en popup ne détruisant pas le lecteur lorsque la permission de popup n'est pas accordée. diff --git a/fastlane/metadata/android/fr/changelogs/66.txt b/fastlane/metadata/android/fr/changelogs/66.txt new file mode 100644 index 000000000..3a94c81e0 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/66.txt @@ -0,0 +1,28 @@ +# Journal des modifications de la v0.13.6 + +### Améliorations + +- L'animation de l'icône du menu « hamburger » a été désactivée #1486 +- Annulation de la suppression des téléchargements #1472 +- Option de téléchargement dans le menu de partage #1498 +- Ajout d'une option de partage dans le menu "long tap" #1454 +- Réduction du lecteur principal à la sortie #1354 +- Mise à jour de la version de la bibliothèque et correction de la sauvegarde de la base de données #1510 +- Mise à jour de ExoPlayer 2.8.2 #1392 + - La boîte de dialogue de contrôle de la vitesse de lecture a été retravaillée pour prendre en charge différentes tailles de pas pour un changement de vitesse plus rapide. + - Ajout d'une option d'avance rapide pendant les silences dans le contrôle de la vitesse de lecture. Cela devrait être utile pour les livres audio et certains genres musicaux, et peut apporter une véritable expérience transparente (et peut casser une chanson avec beaucoup de silences =\\). + - Refonte de la résolution des sources de médias pour permettre le passage des métadonnées avec les médias en interne dans le lecteur, plutôt que de le faire manuellement. Maintenant, nous avons une seule source de métadonnées et elles sont directement disponibles lorsque la lecture commence. + - Correction des métadonnées des listes de lecture distantes qui ne sont pas mises à jour lorsque de nouvelles métadonnées sont disponibles lors de l'ouverture du fragment de liste de lecture. + - Diverses corrections de l'interface utilisateur : #1383, les contrôles de notification du lecteur en arrière-plan sont maintenant toujours blancs, il est plus facile de fermer le lecteur popup en le lançant. +- Utilisation d'un nouvel extracteur avec une architecture remaniée pour le multiservice. + +### Corrections + +- Correction #1440 Disposition des informations vidéo cassée #1491 +- Correction de l'historique des vues #1497 + - #1495, en mettant à jour les métadonnées (vignette, titre et nombre de vidéos) dès que l'utilisateur accède à la liste de lecture. + - #1475, en enregistrant une vue dans la base de données lorsque l'utilisateur lance une vidéo sur un lecteur externe sur le fragment de détail. +- Correction du timeout de la fenêtre en cas de mode popup. #1463 (Corrigé #640) +- Correction du lecteur vidéo principal #1509 + - Correction du mode répétition entraînant un NPE du lecteur lorsqu'une nouvelle intention est reçue alors que l'activité du lecteur est en arrière-plan. + - Correction de la réduction du lecteur en popup ne détruisant pas le lecteur lorsque la permission de popup n'est pas accordée. diff --git a/fastlane/metadata/android/fr/changelogs/68.txt b/fastlane/metadata/android/fr/changelogs/68.txt new file mode 100644 index 000000000..9b2760c35 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/68.txt @@ -0,0 +1,31 @@ +# Modifications v0.14.1 + +### Corrections +- Échec du décryptage de l'URL vidéo #1659 +- Lien de description, ne s'extrayait pas bien #1657 + +# Modifications v0.14.0 + +### Nouveautés +- Design du dossier #1461 +- Page d'accueil personnalisable #1461 + +### Améliorations +- Contrôles gestuels retravaillés #1604 +- Nouvelle façon de fermer le lecteur popup #1597 + +### Corrections +- Erreur lorsque le nombre d'abonnements n'est pas disponible. Ferme #1649. + - Affiche "le nombre d'abonnés non disponible" dans ces cas. +- NPE lorsqu'une liste de lecture YouTube est vide. +- Kiosques dans SoundCloud +- Refactor et correction du bug #1623 +- Résultat de recherche cyclique #1562 +- Barre de recherche qui n'est pas mise en page de manière statique +- Vidéos YT Premium qui ne sont pas bloquées correctement +- Vidéos qui ne se chargent pas toujours (à cause du parsing DASH) +- Liens dans la description des vidéos +- Afficher un avertissement lorsque quelqu'un essaie de télécharger vers une carte SD externe +- Exception "rien indiqué" qui déclenche un rapport +- La vignette ne s'affiche pas dans le lecteur de fond pour Android 8.1 [voir ici](https://github.com/TeamNewPipe/NewPipe/issues/943) +- Enregistrement du récepteur de diffusion. Ferme le dossier #1641. diff --git a/fastlane/metadata/android/fr/changelogs/69.txt b/fastlane/metadata/android/fr/changelogs/69.txt new file mode 100644 index 000000000..c96b390d9 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/69.txt @@ -0,0 +1,19 @@ +### Nouveau +- Suppression et partage par appui long dans les abonnements #1516 +- Interface utilisateur pour tablettes et disposition de la liste en grille #1617 + +### Améliorations +- Stockage/recharge du dernier rapport d'aspect utilisé #1748 +- Activation de la disposition linéaire dans l'activité Téléchargements avec les noms complets des vidéos #1771 +- Suppression et partage des abonnements directement à partir de l'onglet abonnements #1516 +- La mise en file d'attente déclenche désormais la lecture de la vidéo si la file d'attente de lecture est déjà terminée #1783 +- Paramètres distincts pour les gestes de volume et de luminosité #1644 +- Ajout de la prise en charge de la localisation #1792 + +### Corrections +- Analyse de l'heure pour le format . , afin que NewPipe puisse être utilisé en Finlande. +- Compte d'abonnement +- Ajout permission de service de premier plan pour les appareils API 28+ #1830 + +### Bugs connus +- État de lecture ne peut être enregistré sur Android P diff --git a/fastlane/metadata/android/fr/changelogs/70.txt b/fastlane/metadata/android/fr/changelogs/70.txt new file mode 100644 index 000000000..fccfccd2b --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/70.txt @@ -0,0 +1,25 @@ +ATTENTION : Cette version est probablement un festival de bugs, tout comme la dernière. Cependant, en raison de la fermeture complète depuis la 17. une version cassée est mieux que pas de version. N'est-ce pas ? ¯\_(ツ)_/¯ + +### Améliorations +* Les fichiers téléchargés peuvent maintenant être ouverts en un seul clic. +* Suppression du support pour Android 4.1 - 4.3 #1884 +* Suppression de l'ancien lecteur #1884 +* Suppression des flux de la file d'attente de lecture actuelle en les faisant glisser vers la droite #1915 +* Suppression du flux en file d'attente automatique lorsqu'un nouveau flux est mis en file d'attente manuellement #1878 +* Post-traitement pour les téléchargements et implémentation des fonctionnalités manquantes #1759 par @kapodamy + * Infrastructure de post-traitement + * Infrastructure de gestion des erreurs (pour le téléchargeur) + * File d'attente au lieu de téléchargements multiples + * Déplacer les téléchargements sérialisés en attente (fichiers `.giga`) vers les données de l'application. + * Implémentation de la répétition maximale des téléchargements + * Mise en pause des téléchargements multi-threads + * Arrêter les téléchargements lors du passage au réseau mobile (ne fonctionne jamais, voir 2ème point) + * Sauvegarder le nombre de threads pour les prochains téléchargements + * Beaucoup d'incohérences corrigées + +### Corrigé +* Correction d'un crash avec la résolution par défaut réglée sur la meilleure et la résolution limitée des données mobiles #1835 +* Correction du crash du lecteur de pop-up #1874 +* NPE lors de l'ouverture du lecteur de fond #1901 +* Correction de l'insertion de nouveaux flux lorsque la mise en file d'attente automatique est activée #1878 +* Correction du problème de décryptage de Shuttown diff --git a/fastlane/metadata/android/fr/changelogs/71.txt b/fastlane/metadata/android/fr/changelogs/71.txt index 0fa046111..4d1a5b1f6 100644 --- a/fastlane/metadata/android/fr/changelogs/71.txt +++ b/fastlane/metadata/android/fr/changelogs/71.txt @@ -1,10 +1,10 @@ ### Améliorations -* Notification maj GitHub (#1608 par @krtkush) -* Améliorations téléchargeur (#1944 par @kapodamy) : -  * icônes blanches manquantes ; utilisation d'une méthode pour changer leurs couleurs -  * vérification si l'itérateur est initialisé (#2031) - * réessayer les téléchargements post-processing failed dans le nouveau muxer -  * nouveau muxer MPEG-4 corrigeant les flux non synchrones (#2039) +* Notification maj GitHub #1608 +* Améliorations téléchargeur #1944 : +  * Ajout des icônes blanches manquantes et utilisation d'une méthode hardcodé pour changer leurs couleurs +  * Vérification si l'itérateur est initialisé (#2031) + * Autoriser le ré-essai de téléchargement après une erreur "post-processing failed" dans le nouveau muxer +  * Nouveau muxer MPEG-4 corrigeant les flux non synchrones (#2039) ### Corrections -* Flux YouTube en direct s'arrêtent (#1996 par @yausername) +* Flux YouTube en direct s'arrêtent #1996 diff --git a/fastlane/metadata/android/fr/changelogs/964.txt b/fastlane/metadata/android/fr/changelogs/964.txt new file mode 100644 index 000000000..4d65340fc --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/964.txt @@ -0,0 +1,8 @@ +• Ajout des chapitres dans lecteur +• [PeerTube] Ajout recherche en sépia +• Ajout bouton de partage en vue détaillée de la vidéo, déplacement description du flux dans l'onglet +• Désactivation restauration de luminosité si le geste est désactivé +• Ajout élément de liste pour lire vidéos sur Kodi +• Correction crash si aucun navigateur par défaut défini, amélioration dialogues de partage +• Basculer lecture/pause avec bouton d'espace matériel en lecteur plein écran +• [media.ccc.de] Corrections diff --git a/fastlane/metadata/android/fr/changelogs/966.txt b/fastlane/metadata/android/fr/changelogs/966.txt new file mode 100644 index 000000000..7cbe82fbf --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/966.txt @@ -0,0 +1,14 @@ +Nouveautés +• Ajout Bandcamp + +Améliorations +• Ajout option pour que application suive thème de l'appareil +• Prévention plantages par affichage panneau d'erreurs amélioré +• Plus d'informations sur raison indisponibilité contenu +• Bouton matériel espace déclenche lecture/pause +• Affichage toast "Téléchargement commencé" + +Corrections +• Très petite vignette dans détails de vidéo lors de lecture en arrière-plan +• Titre vide dans lecteur réduit +• Dernier mode redimensionnement pas restauré correctement diff --git a/fastlane/metadata/android/fr/changelogs/969.txt b/fastlane/metadata/android/fr/changelogs/969.txt new file mode 100644 index 000000000..6ac4a9467 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/969.txt @@ -0,0 +1,8 @@ +• Autoriser installation sur un stockage externe +• [Bandcamp] Ajout fonction permettant d'afficher les trois premiers commentaires d'un flux +• Afficher 'download has started' uniquement lorsque téléchargement lancé +• Ne pas définir cookie reCaptcha lorsqu'aucun n'est stocké +• [Player] Amélioration performances cache +• [Player] Correction problème lecture automatique +• Désactiver barres d'état précédentes lors suppr. des téléchargements +• Correction suppression objet ne figurant pas dans la liste diff --git a/fastlane/metadata/android/fr/changelogs/970.txt b/fastlane/metadata/android/fr/changelogs/970.txt new file mode 100644 index 000000000..928d37822 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/970.txt @@ -0,0 +1,11 @@ +Nouveautés +• Affichage métadonnées du contenu sous la description +• Ajout option "Afficher les détails de la chaîne" dans les playlists distantes +• Ajout option "Ouvrir dans le navigateur" dans le menu de la touche longue + +Corrections +• Correction d'un crash de rotation sur la page de détails de la vidéo +• Correction du bouton "Jouer avec Kodi" qui demande toujours d'installer Kore +• Correction chemins d'import/export des paramètres +• Correction nombre de commentaires aimés +Et bien plus encore diff --git a/fastlane/metadata/android/fr/changelogs/971.txt b/fastlane/metadata/android/fr/changelogs/971.txt new file mode 100644 index 000000000..3b302a06b --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/971.txt @@ -0,0 +1,3 @@ +Correctifs +• Augmentation de la mémoire tampon pour la lecture après le re-buffer +• Correction d'un crash sur les tablettes et les téléviseurs lors d'un clic sur l'icône de la file d'attente dans le lecteur diff --git a/fastlane/metadata/android/fr/changelogs/973.txt b/fastlane/metadata/android/fr/changelogs/973.txt new file mode 100644 index 000000000..667279399 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/973.txt @@ -0,0 +1,4 @@ +Correctifs +• Correction des vignettes et des titres qui sont coupés dans la mise en page en vue grille, dû à un calcul erroné du nombre de vidéos pouvant tenir dans une rangée. +• Correction de la boîte de dialogue de téléchargement qui disparaît sans rien faire si elle est ouverte à partir du menu de partage +• Maj d'une bibliothèque liée à l'ouverture d'activités externes telles que le sélecteur de fichiers du framewok d'accès stockage diff --git a/fastlane/metadata/android/fr/changelogs/974.txt b/fastlane/metadata/android/fr/changelogs/974.txt new file mode 100644 index 000000000..d963abe24 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/974.txt @@ -0,0 +1,5 @@ +Correctifs +• Correction des problèmes de mise en mémoire tampon causés par la restriction de débit de YouTube +• Correction de l'extraction des commentaires de YouTube et des plantages avec les commentaires désactivés +• Correction de la recherche de musique sur YouTube +• Correction des directs PeerTube diff --git a/fastlane/metadata/android/fr/changelogs/977.txt b/fastlane/metadata/android/fr/changelogs/977.txt new file mode 100644 index 000000000..6232ffa75 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/977.txt @@ -0,0 +1,8 @@ +• Ajout bouton "lecture suivante" au menu de la pression longue +• Ajout préfixe du chemin des shorts YouTube au filtre d'intention +• Correction importation des paramètres +• Permutation position barre de recherche avec boutons du lecteur dans l'écran de la file d'attente +• Corrections liées à MediasessionManager +• Correction barre de progression qui ne se termine pas après fin de vidéo +• Désactivation tunneling média sur RealtekATV +• Élargissement zone cliquable des boutons de lecture minimisés diff --git a/fastlane/metadata/android/fr/changelogs/980.txt b/fastlane/metadata/android/fr/changelogs/980.txt new file mode 100644 index 000000000..6835f70c8 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/980.txt @@ -0,0 +1,13 @@ +Nouveautés +• Ajout option "Ajouter à la liste de lecture" au menu de partage +• Ajout prise en charge des liens courts y2u.be et PeerTube + +Améliorations +• Commandes de vitesse de lecture plus compactes +• Le flux met désormais en évidence les nouveaux éléments +• L'option "Afficher les éléments surveillés" dans le flux est maintenant enregistrée + +Corrections +• Correction extraction des likes/dislikes de YouTube +• Correction relecture automatique après le retour de l'arrière-plan +Et bien d'autres diff --git a/fastlane/metadata/android/fr/changelogs/987.txt b/fastlane/metadata/android/fr/changelogs/987.txt index e0e1bd7bd..1641d9a00 100644 --- a/fastlane/metadata/android/fr/changelogs/987.txt +++ b/fastlane/metadata/android/fr/changelogs/987.txt @@ -1,8 +1,8 @@ Nouveautés -• Prise en charge de d'autres méthodes de diffusion que le HTTP progressif : temps de chargement plus rapide, corrections pour PeerTube et SoundCloud, lecture des nouveaux flux en directs de YouTube -• Boutton pour ajouter une liste de lecture distante à une locale +• Prise en charge d'autres méthodes de diffusion que le HTTP progressif : temps de chargement plus rapide, corrections pour PeerTube et SoundCloud, lecture des nouveaux flux en directs de YouTube +• Bouton pour ajouter une liste de lecture distante à une locale • Prévisualisation d'images lors d'un partage pour Andoid 10+ Améliorations • Amélioration de la boîte de dialogue des paramètres de la lecture -• Déplacement des bouttons importation/exportation vers le menu à trois points +• Déplacement des boutons importation/exportation vers le menu à trois points diff --git a/fastlane/metadata/android/fr/changelogs/988.txt b/fastlane/metadata/android/fr/changelogs/988.txt new file mode 100644 index 000000000..7ac03bb2a --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Correction de l'erreur « Impossible d'obtenir un flux » lors de la lecture d'une vidéo +[YouTube] Correction du message « Le contenu suivant n'est pas disponible sur cette application. » affiché à la place de la vidéo demandée diff --git a/fastlane/metadata/android/fr/short_description.txt b/fastlane/metadata/android/fr/short_description.txt index a593ce32c..70048c15a 100644 --- a/fastlane/metadata/android/fr/short_description.txt +++ b/fastlane/metadata/android/fr/short_description.txt @@ -1 +1 @@ -Un lecteur multimédia libre et léger pour Android. +Une interface pour YouTube libre et légère sur Android. diff --git a/fastlane/metadata/android/he/changelogs/988.txt b/fastlane/metadata/android/he/changelogs/988.txt new file mode 100644 index 000000000..fc69457f7 --- /dev/null +++ b/fastlane/metadata/android/he/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] תוקנה השגיאה „אי אפשר לקבל שום תזרים” בעת ניסיון לנגן סרטונים +[YouTube] תוקנה ההודעה „התוכן הבא אינו זמין ביישומון הזה” שמופיעה במקום הסרטון המבוקש diff --git a/fastlane/metadata/android/hu/changelogs/65.txt b/fastlane/metadata/android/hu/changelogs/65.txt new file mode 100644 index 000000000..c3ee63ecc --- /dev/null +++ b/fastlane/metadata/android/hu/changelogs/65.txt @@ -0,0 +1,26 @@ +### Fejlesztések + +- A burgermenu ikon animációjának letiltása #1486 +- a letöltések törlésének visszavonása #1472 +- Letöltési lehetőség a #1498 megosztás menüben +- Megosztási lehetőség hozzáadva a hosszú érintéssel #1454 +- A fő játékos minimalizálása a 1354-es kijáratnál +- A könyvtár verziójának frissítése és az adatbázis biztonsági mentésének javítása #1510 +- ExoPlayer 2.8.2 frissítés #1392 + - Átdolgoztuk a lejátszási sebesség-vezérlő párbeszédpanelt, hogy támogassa a különböző lépésméreteket a gyorsabb sebességváltás érdekében. + - Hozzáadott egy kapcsolót a gyors előretekeréshez a lejátszási sebesség szabályozásában a csendek alatt. Ez hasznos lehet hangoskönyvek és bizonyos zenei műfajok esetében, és valódi zökkenőmentes élményt nyújthat (és megszakíthat egy dalt sok csenddel =\\). + - Átdolgozott médiaforrás felbontás, amely lehetővé teszi a metaadatok továbbítását a média mellett a lejátszón belül, nem pedig manuálisan. Most már egyetlen metaadatforrásunk van, és közvetlenül elérhető a lejátszás megkezdésekor. + - Javítva a távoli lejátszási lista metaadatai, amelyek nem frissülnek, amikor új metaadatok állnak rendelkezésre a lejátszási lista töredékének megnyitásakor. + - Különféle felhasználói felület-javítások: #1383, a háttérben lévő lejátszó értesítési vezérlői mostantól mindig fehérek, a felugró lejátszót egyszerűbben le lehet állítani dobással +- Használjon új kivonatot refaktorált architektúrával a többszolgáltatáshoz + +### Javítások + +- Javítás: #1440 Sérült videó információs elrendezés #1491 +- Előzmények megtekintése #1497. javítás + - #1495, a metaadatok (bélyegkép, cím és videószám) frissítésével, amint a felhasználó hozzáfér a lejátszási listához. + - #1475, egy nézet regisztrálásával az adatbázisban, amikor a felhasználó elindít egy videót a külső lejátszón a részletrészleten. +- Javítsa ki a képernyő időtúllépését felugró mód esetén. #1463 (fix #640) +- Fő videólejátszó javítás #1509 + - [#1412] Javítva az ismétlési mód, ami a játékos NPE-jét okozza, ha új szándék érkezik, miközben a játékos tevékenysége a háttérben van. + - Javítva, hogy a lejátszó előugró ablakra minimalizálja, nem semmisíti meg a lejátszót ha a popup engedélyt nem adják meg. diff --git a/fastlane/metadata/android/hu/short_description.txt b/fastlane/metadata/android/hu/short_description.txt index 0a96f5e90..50752eeaf 100644 --- a/fastlane/metadata/android/hu/short_description.txt +++ b/fastlane/metadata/android/hu/short_description.txt @@ -1 +1 @@ -Egy ingyenes és könnyű YouTube előtétprogram Androidra. +Ingyenes, könnyű YouTube felület Androidra. diff --git a/fastlane/metadata/android/it/changelogs/63.txt b/fastlane/metadata/android/it/changelogs/63.txt index dd8f7324d..a342392ad 100644 --- a/fastlane/metadata/android/it/changelogs/63.txt +++ b/fastlane/metadata/android/it/changelogs/63.txt @@ -1,8 +1,8 @@ ### Miglioramenti -- Impostazioni di importazione / esportazione # 1333 -- Ridotto l'overdraw (miglioramento delle prestazioni) # 1371 -- Piccoli miglioramenti al codice # 1375 -- Aggiunto tutto ciò che riguarda il GDPR # 1420 +- Impostazioni di importazione / esportazione #1333 +- Ridotto l'overdraw (miglioramento delle prestazioni) #1371 +- Piccoli miglioramenti al codice #1375 +- Aggiunto tutto ciò che riguarda il GDPR #1420 ### Risolto -- Downloader: risolto il crash durante il caricamento di download incompleti dai file .giga # 1407 +- Downloader: risolto il crash durante il caricamento di download incompleti dai file .giga #1407 diff --git a/fastlane/metadata/android/it/changelogs/730.txt b/fastlane/metadata/android/it/changelogs/730.txt new file mode 100644 index 000000000..3df41556f --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/730.txt @@ -0,0 +1,2 @@ +# Risolto +- Sistemato di nuovo un errore nella funzione di decifrazione. diff --git a/fastlane/metadata/android/ko/changelogs/63.txt b/fastlane/metadata/android/ko/changelogs/63.txt new file mode 100644 index 000000000..69ca21e97 --- /dev/null +++ b/fastlane/metadata/android/ko/changelogs/63.txt @@ -0,0 +1,8 @@ +### 변경점 +- 불러오기/내보내기 세팅 #1333 +- 오버드로우 현상 개선 (성능 개선) #1371 +- 코드 일부분 개선 #1375 +- GDPR에 관한 모든것 업데이트 #1420 + +### 해결된것 +- 다운로더 : 다운로드가 완료되지 않은 .giga파일을 로딩할때 발생하는 에러 해결#1407 diff --git a/fastlane/metadata/android/ko/changelogs/64.txt b/fastlane/metadata/android/ko/changelogs/64.txt new file mode 100644 index 000000000..4cef85a41 --- /dev/null +++ b/fastlane/metadata/android/ko/changelogs/64.txt @@ -0,0 +1,8 @@ +### 변경점 +- 불러오기/내보내기 세팅 #1333 +- 오버드로우 현상 개선 (성능 개선) #1371 +- 코드 일부분 개선 #1375 +- GDPR에 관한 모든것 업데이트 #1420 + +### 고친것 +- 다운로더 : 다운로드가 완료되지 않은 .giga파일을 로딩할때 발생하는 문제 해결#1407 diff --git a/fastlane/metadata/android/pl/changelogs/964.txt b/fastlane/metadata/android/pl/changelogs/964.txt new file mode 100644 index 000000000..1684d6985 --- /dev/null +++ b/fastlane/metadata/android/pl/changelogs/964.txt @@ -0,0 +1,8 @@ +• Dodano wsparcie rozdziałów w kontrolkach odtwarzacza +• [PeerTube] Dodano wyszukiwarkę Sepia +• Dodano ponownie przycisk udostępniania w sekcji szczegółów i przeniesiono informacje o strumieniu do układu karty +• Wyłączono przywracanie jasności przy wyłączonych gestach regulacji jasności +• Dodano element listy umożliwiający odtworzenie wideo w Kodi +• Naprawiono błąd przy braku domyślnej przeglądarki na niektórych urządzeniach i usprawniono menu udostępniania +• Przełączanie odtwarzanie/pauza poprzez wciśnięcie spacji na klawiaturze fizycznej w odtwarzaczu pełnoekranowym +• [media.cc.de] Różne poprawki i usprawnienia diff --git a/fastlane/metadata/android/pl/changelogs/965.txt b/fastlane/metadata/android/pl/changelogs/965.txt new file mode 100644 index 000000000..d336d984d --- /dev/null +++ b/fastlane/metadata/android/pl/changelogs/965.txt @@ -0,0 +1,5 @@ +Naprawiono błąd przy zmianie kolejności grup kanałów. +Naprawiono pobieranie kolejnych wideo z kanałów i playlist. +Naprawiono pobieranie komentarzy w YouTube. Dodano wsparcie dla ścieżek /watch/, /v/ oraz /w/ w URL-ach YouTube. +Naprawiono pobieranie ID użytkownika SoundCloud i zawartości z ograniczeniami geograficznymi. +Dodano język północnokurdyjski. diff --git a/fastlane/metadata/android/pt/changelogs/955.txt b/fastlane/metadata/android/pt/changelogs/955.txt index cd70b41c9..98bed58fe 100644 --- a/fastlane/metadata/android/pt/changelogs/955.txt +++ b/fastlane/metadata/android/pt/changelogs/955.txt @@ -1,3 +1,3 @@ -[YouTube] A procura por alguns utilizadores corrigida -[YouTube] Exceções de desencriptação aleatórias corrigidas -[SounCloud] URLs que terminam com uma barra são agora analisados corretamente +[YouTube] O problema com busca que afetava utilizadores foi corrigida +[YouTube] Exceções de desencriptação aleatórias foram corrigidas +[SounCloud] URLs que terminam com uma barra são analisadas corretamente diff --git a/fastlane/metadata/android/ru/changelogs/65.txt b/fastlane/metadata/android/ru/changelogs/65.txt new file mode 100644 index 000000000..51993fef3 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/65.txt @@ -0,0 +1 @@ +эскиз видео diff --git a/fastlane/metadata/android/ru/changelogs/66.txt b/fastlane/metadata/android/ru/changelogs/66.txt new file mode 100644 index 000000000..51993fef3 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/66.txt @@ -0,0 +1 @@ +эскиз видео diff --git a/fastlane/metadata/android/ru/changelogs/68.txt b/fastlane/metadata/android/ru/changelogs/68.txt new file mode 100644 index 000000000..51993fef3 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/68.txt @@ -0,0 +1 @@ +эскиз видео diff --git a/fastlane/metadata/android/ru/changelogs/69.txt b/fastlane/metadata/android/ru/changelogs/69.txt new file mode 100644 index 000000000..c081690f8 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/69.txt @@ -0,0 +1 @@ +настройки diff --git a/fastlane/metadata/android/ru/changelogs/70.txt b/fastlane/metadata/android/ru/changelogs/70.txt new file mode 100644 index 000000000..da96c42fd --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/70.txt @@ -0,0 +1 @@ +всплывающий diff --git a/fastlane/metadata/android/ru/changelogs/780.txt b/fastlane/metadata/android/ru/changelogs/780.txt new file mode 100644 index 000000000..24cfa6e7f --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/780.txt @@ -0,0 +1,12 @@ +Изменения в 0.17.3 + +Улучшено +• Добавлена возможность очистки состояний воспроизведения #2550 +• Показ скрытых каталогов в средстве выбора файлов #2591 +• Поддержка URL-адресов из экземпляров `invidio.us`, открываемых с помощью NewPipe #2488 +• Добавлена поддержка URL-адресов `music.youtube.com` TeamNewPipe/NewPipeExtractor #194 + +Исправлено +• [YouTube] Исправлена ошибка java.lang.IllegalArgumentException #192 +• [YouTube] Исправлены неработающие прямые трансляции TeamNewPipe/NewPipeExtractor#195 +• Исправлена проблема с производительностью в Android Pie при загрузке потока #2592 diff --git a/fastlane/metadata/android/ru/changelogs/790.txt b/fastlane/metadata/android/ru/changelogs/790.txt new file mode 100644 index 000000000..24d1c115a --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/790.txt @@ -0,0 +1 @@ +папки diff --git a/fastlane/metadata/android/ru/changelogs/985.txt b/fastlane/metadata/android/ru/changelogs/985.txt new file mode 100644 index 000000000..d3978869d --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/985.txt @@ -0,0 +1 @@ +Исправлено: YouTube не воспроизводил никакие потоки diff --git a/fastlane/metadata/android/ru/changelogs/987.txt b/fastlane/metadata/android/ru/changelogs/987.txt new file mode 100644 index 000000000..8e9702365 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/987.txt @@ -0,0 +1,12 @@ +Новое +• Поддержка методов доставки, отличных от прогрессивного HTTP: ускорение времени загрузки воспроизведения, исправления PeerTube и SoundCloud, воспроизведение недавно закончившихся трансляций YouTube +• Кнопка «Добавить», чтобы добавить удаленный плейлист к локальному +• Предпросмотр изображения на странице общего доступа Android 10+ + +Улучшено +• Улучшения окна параметров воспроизведения +• Перемещение кнопки импорта/экспорта подписки в трехточечное меню + +Исправлено +• Исправлено удаление полностью просмотренных видео из плейлиста +• Исправлена тема меню «Поделиться» и пункт «Добавить в плейлист» diff --git a/fastlane/metadata/android/sk/changelogs/987.txt b/fastlane/metadata/android/sk/changelogs/987.txt new file mode 100644 index 000000000..b291ba0d5 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/987.txt @@ -0,0 +1,12 @@ +Novinky +• Podpora spôsobov poskytovania iných ako progresívnych HTTP: rýchlejší čas načítania prehrávania, opravy pre PeerTube a SoundCloud, prehrávanie nedávno ukončených priamych prenosov YouTube +• Tlačidlo Pridať na pridanie vzdialeného zoznamu skladieb k lokálnemu +• Ukážka obrázka v zdieľanom hárku so systémom Android 10+ + +Vylepšenia +• Vylepšené dialógové okno parametrov prehrávania +• Presunuté tlačidlá importu/exportu odberov do ponuky (tri bodky) + +Opravy +• Oprava odstraňovania kompletne prehraných videí zo zoznamu videí +• Oprava témy v ponuke zdieľania a pri položke „pridať do zoznamu skladieb“ diff --git a/fastlane/metadata/android/tr/changelogs/63.txt b/fastlane/metadata/android/tr/changelogs/63.txt index b4ccdf68a..9370c537a 100644 --- a/fastlane/metadata/android/tr/changelogs/63.txt +++ b/fastlane/metadata/android/tr/changelogs/63.txt @@ -1,8 +1,8 @@ ### Geliştirmeler - İçe/Dışa aktarma ayarları #1333 -- Aşmalar azaltıldı(performance iyileştirmeleri) #1371 +- Aşmalar azaltıldı(performans iyileştirmeleri) #1371 - Küçük kod iyileştirmeleri #1375 - GPDR hakkında her şey eklendi #1420 ### Düzeltildi -- İndirici: .giga dosyalarından bitmeyen indirmeler yüklenirken çökmeler düzeltildi #1407 +- İndirici: .giga dosyalarından bitmemiş indirmeler yüklenirken çökmeler düzeltildi #1407 diff --git a/fastlane/metadata/android/tr/full_description.txt b/fastlane/metadata/android/tr/full_description.txt index e6ca6f1b4..11daef85b 100644 --- a/fastlane/metadata/android/tr/full_description.txt +++ b/fastlane/metadata/android/tr/full_description.txt @@ -1 +1,2 @@ -NewPipe herhangi bir Google çerçeve kütüphanesi veya YouTube API'si kullanmaz. Gereksindiği bilgileri edinirken yalnızca web sitesini ayrıştırır. Bu nedenle Google hizmetlerinin kurulmadığı aygıtlarda kullanılabilir. Ayrıca, NewPipe'ı kullanırken YouTube hesabına gereksinmezsiniz, özgür ve açık kaynaklı yazılımdır. +NewPipe herhangi bir Google çerçeve kütüphanesi veya YouTube API'ı kullanmaz. Sadece, ihtiyaç duyduğu bilgiyi edinmek için web sitesini ayrıştırır. +Bu nedenle Google hizmetlerinin kurulmadığı aygıtlarda kullanılabilir. Ayrıca, NewPipe'ı kullanırken YouTube hesabına ihtiyacınız yok, ve bu özgür ve açık kaynaklı bir yazılımdır. diff --git a/fastlane/metadata/android/uk/changelogs/988.txt b/fastlane/metadata/android/uk/changelogs/988.txt new file mode 100644 index 000000000..d882d430a --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Виправлено помилку «Не вдалося отримати жодного потоку» під час спроби відтворити будь-яке відео +[YouTube] Виправлено «Цей вміст недоступний у цьому застосунку.» замість запитаного відео diff --git a/fastlane/metadata/android/uk/changelogs/989.txt b/fastlane/metadata/android/uk/changelogs/989.txt new file mode 100644 index 000000000..2ccd1653a --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/989.txt @@ -0,0 +1,3 @@ +• [YouTube] Виправлено нескінченне завантаження за спроби відтворити будь-яке відео +• [YouTube] Виправлено тротлінг на деяких відео +• Оновлено бібліотеку jsoup до версії 1.15.3, яка включає виправлення безпеки diff --git a/fastlane/metadata/android/zh-Hans/changelogs/988.txt b/fastlane/metadata/android/zh-Hans/changelogs/988.txt new file mode 100644 index 000000000..67f53fc8f --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] 修复 试图播放任何视频时,显示"无法获得任何流 " +[YouTube] 修复 显示"以下内容在此应用中不可用 ",而不是所需要的视频 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/989.txt b/fastlane/metadata/android/zh-Hans/changelogs/989.txt new file mode 100644 index 000000000..0c89cb986 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/989.txt @@ -0,0 +1,3 @@ +- [YouTube] 修复 尝试播放任何视频时无限加载 +- [YouTube] 修复 某些视频的节流问题 +- 将jsoup库升级到1.15.3,其中包括一个安全修复 diff --git a/fastlane/metadata/android/zh-Hant/changelogs/988.txt b/fastlane/metadata/android/zh-Hant/changelogs/988.txt new file mode 100644 index 000000000..684c88444 --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] 修正嘗試播放任何影片時「無法取得任何串流」的錯誤 +[YouTube] 修正請求影片時顯示「以下內容不在此應用程式中可用」的訊息 diff --git a/fastlane/metadata/android/zh_Hant_HK/changelogs/988.txt b/fastlane/metadata/android/zh_Hant_HK/changelogs/988.txt new file mode 100644 index 000000000..bcc006d34 --- /dev/null +++ b/fastlane/metadata/android/zh_Hant_HK/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] 修正播咩片都「攞唔到任何串流」嘅問題 +[YouTube] 修正出現「呢部內容喺呢個 app 欠奉」嘅訊息,睇唔到請求嘅影片 From ebce4c5b7e437d0d3fc7c3e82f9257f6825bdd03 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 27 Aug 2022 14:47:57 +0200 Subject: [PATCH 139/152] Add changelog for v0.24.0 (990) --- .../metadata/android/en-US/changelogs/990.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/990.txt diff --git a/fastlane/metadata/android/en-US/changelogs/990.txt b/fastlane/metadata/android/en-US/changelogs/990.txt new file mode 100644 index 000000000..e12c20ba5 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/990.txt @@ -0,0 +1,15 @@ +This release drops support for Android 4.4 KitKat, now the minimum version is Android 5 Lollipop! + +New +• Download from long-press menu +• Hide future videos in feed +• Share local playlists + +Improved +• Refactor the player code into small components: less RAM used, less bugs +• Improve thumbnails' scale mode +• Vector-ize image placeholders + +Fixed +• Fix various issues with the player notification: outdated/missing media info, distorted thumbnail +• Fix fullscreen using 1/4 of screen From e6391a860a3aa443f398641947bbf7e757ef7a66 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 27 Aug 2022 14:52:49 +0200 Subject: [PATCH 140/152] Release v0.24.0 (990) --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d19b599d6..2c09f754f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { resValue "string", "app_name", "NewPipe" minSdk 21 targetSdk 29 - versionCode 989 - versionName "0.23.3" + versionCode 990 + versionName "0.24.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" From 7fbef35daab5b7122237cca636aa4ffc2a3368fe Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 28 Aug 2022 17:24:51 +0200 Subject: [PATCH 141/152] Unify onThumbnailLoaded calls to ensure UIs always updated --- .../java/org/schabi/newpipe/player/Player.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) 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 319c163e8..040070b5b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -765,17 +765,15 @@ public final class Player implements PlaybackListener, Listener { + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" + from + "]"); } - currentThumbnail = bitmap; // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. - UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); + onThumbnailLoaded(bitmap); } @Override public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { Log.e(TAG, "Thumbnail - onBitmapFailed() called", e); - currentThumbnail = null; // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. - UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); + onThumbnailLoaded(null); } @Override @@ -798,7 +796,7 @@ public final class Player implements PlaybackListener, Listener { // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media // session metadata while the new thumbnail is being loaded by Picasso. - currentThumbnail = null; + onThumbnailLoaded(null); if (isNullOrEmpty(url)) { return; } @@ -813,6 +811,16 @@ public final class Player implements PlaybackListener, Listener { // cancel the Picasso job associated with the player thumbnail, if any PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG); } + + private void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + // Avoid useless thumbnail updates, if the thumbnail has not actually changed. Based on the + // thumbnail loading code, this if would be skipped only when both bitmaps are `null`, since + // onThumbnailLoaded won't be called twice with the same nonnull bitmap by Picasso's target. + if (currentThumbnail != bitmap) { + currentThumbnail = bitmap; + UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); + } + } //endregion From 4a7af6f9ac50c0a5a3ed3b306d08d4b8aa622aea Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 28 Aug 2022 18:32:27 +0200 Subject: [PATCH 142/152] Remove thumbnail before sync, if outdated Also refactor onPlaybackSynchronize and add comments --- .../org/schabi/newpipe/player/Player.java | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) 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 040070b5b..99d36f66e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -1509,48 +1509,50 @@ public final class Player implements PlaybackListener, Listener { Log.d(TAG, "Playback - onPlaybackSynchronize(was blocked: " + wasBlocked + ") called with item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]"); } - if (exoPlayerIsNull() || playQueue == null) { - return; + if (exoPlayerIsNull() || playQueue == null || currentItem == item) { + return; // nothing to synchronize } - final boolean hasPlayQueueItemChanged = currentItem != item; + final int playQueueIndex = playQueue.indexOf(item); + final int playlistIndex = simpleExoPlayer.getCurrentMediaItemIndex(); + final int playlistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount(); + final boolean removeThumbnailBeforeSync = currentItem == null + || currentItem.getServiceId() != item.getServiceId() + || !currentItem.getUrl().equals(item.getUrl()); - final int currentPlayQueueIndex = playQueue.indexOf(item); - final int currentPlaylistIndex = simpleExoPlayer.getCurrentMediaItemIndex(); - final int currentPlaylistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount(); - - // If nothing to synchronize - if (!hasPlayQueueItemChanged) { - return; - } currentItem = item; - // Check if on wrong window - if (currentPlayQueueIndex != playQueue.getIndex()) { - Log.e(TAG, "Playback - Play Queue may be desynchronized: item " - + "index=[" + currentPlayQueueIndex + "], " - + "queue index=[" + playQueue.getIndex() + "]"); + if (playQueueIndex != playQueue.getIndex()) { + // wrong window (this should be impossible, as this method is called with + // `item=playQueue.getItem()`, so the index of that item must be equal to `getIndex()`) + Log.e(TAG, "Playback - Play Queue may be not in sync: item index=[" + + playQueueIndex + "], " + "queue index=[" + playQueue.getIndex() + "]"); - // Check if bad seek position - } else if ((currentPlaylistSize > 0 && currentPlayQueueIndex >= currentPlaylistSize) - || currentPlayQueueIndex < 0) { - Log.e(TAG, "Playback - Trying to seek to invalid " - + "index=[" + currentPlayQueueIndex + "] with " - + "playlist length=[" + currentPlaylistSize + "]"); + } else if ((playlistSize > 0 && playQueueIndex >= playlistSize) || playQueueIndex < 0) { + // the queue and the player's timeline are not in sync, since the play queue index + // points outside of the timeline + Log.e(TAG, "Playback - Trying to seek to invalid index=[" + playQueueIndex + + "] with playlist length=[" + playlistSize + "]"); - } else if (wasBlocked || currentPlaylistIndex != currentPlayQueueIndex || !isPlaying()) { + } else if (wasBlocked || playlistIndex != playQueueIndex || !isPlaying()) { + // either the player needs to be unblocked, or the play queue index has just been + // changed and needs to be synchronized, or the player is not playing if (DEBUG) { - Log.d(TAG, "Playback - Rewinding to correct " - + "index=[" + currentPlayQueueIndex + "], " - + "from=[" + currentPlaylistIndex + "], " - + "size=[" + currentPlaylistSize + "]."); + Log.d(TAG, "Playback - Rewinding to correct index=[" + playQueueIndex + "], " + + "from=[" + playlistIndex + "], size=[" + playlistSize + "]."); } + if (removeThumbnailBeforeSync) { + // unset the current (now outdated) thumbnail to ensure it is not used during sync + onThumbnailLoaded(null); + } + + // sync the player index with the queue index, and seek to the correct position if (item.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) { - simpleExoPlayer.seekTo(currentPlayQueueIndex, item.getRecoveryPosition()); - playQueue.unsetRecovery(currentPlayQueueIndex); + simpleExoPlayer.seekTo(playQueueIndex, item.getRecoveryPosition()); + playQueue.unsetRecovery(playQueueIndex); } else { - simpleExoPlayer.seekToDefaultPosition(currentPlayQueueIndex); + simpleExoPlayer.seekToDefaultPosition(playQueueIndex); } } } From f9109ebc81e5c7d9a01b63c4b68d2dc46b030f1c Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 28 Aug 2022 18:35:21 +0200 Subject: [PATCH 143/152] Use player.getThumbnail() instead of field in VideoPlayerUi --- .../newpipe/player/ui/VideoPlayerUi.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index a972d2f71..1709755f2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -109,7 +109,6 @@ public abstract class VideoPlayerUi extends PlayerUi private final Handler controlsVisibilityHandler = new Handler(Looper.getMainLooper()); @Nullable private SurfaceHolderCallback surfaceHolderCallback; boolean surfaceIsSetup = false; - @Nullable private Bitmap thumbnail = null; /*////////////////////////////////////////////////////////////////////////// @@ -385,9 +384,7 @@ public abstract class VideoPlayerUi extends PlayerUi @Override public void destroy() { super.destroy(); - if (binding != null) { - binding.endScreen.setImageBitmap(null); - } + binding.endScreen.setImageDrawable(null); deinitPlayerSeekOverlay(); deinitListeners(); } @@ -422,12 +419,10 @@ public abstract class VideoPlayerUi extends PlayerUi public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { - // When the orientation changed, the screen height might be smaller. - // If the end screen thumbnail is not re-scaled, - // it can be larger than the current screen height - // and thus enlarging the whole player. - // This causes the seekbar to be ouf the visible area. - updateEndScreenThumbnail(); + // When the orientation changes, the screen height might be smaller. If the end screen + // thumbnail is not re-scaled, it can be larger than the current screen height and thus + // enlarging the whole player. This causes the seekbar to be out of the visible area. + updateEndScreenThumbnail(player.getThumbnail()); } } //endregion @@ -449,11 +444,10 @@ public abstract class VideoPlayerUi extends PlayerUi @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - thumbnail = bitmap; - updateEndScreenThumbnail(); + updateEndScreenThumbnail(bitmap); } - private void updateEndScreenThumbnail() { + private void updateEndScreenThumbnail(@Nullable final Bitmap thumbnail) { if (thumbnail == null) { // remove end screen thumbnail binding.endScreen.setImageDrawable(null); From ed87465565eaa431afc95fabc82e6383e77125d9 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 28 Aug 2022 18:35:58 +0200 Subject: [PATCH 144/152] Only update notification large icon when it changes --- .../notification/NotificationPlayerUi.java | 2 +- .../player/notification/NotificationUtil.java | 21 ++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java index ed678a18c..4b1fc417f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java @@ -43,7 +43,7 @@ public final class NotificationPlayerUi extends PlayerUi { @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - notificationUtil.createNotificationIfNeededAndUpdate(false); + notificationUtil.updateThumbnail(); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 2c3199a28..3488ec61e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -24,6 +24,8 @@ import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.util.NavigationHelper; import java.util.List; +import java.util.Objects; +import java.util.Optional; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static androidx.media.app.NotificationCompat.MediaStyle; @@ -40,8 +42,6 @@ import static org.schabi.newpipe.player.notification.NotificationConstants.ACTIO /** * This is a utility class for player notifications. - * - * @author cool-student */ public final class NotificationUtil { private static final String TAG = NotificationUtil.class.getSimpleName(); @@ -79,6 +79,19 @@ public final class NotificationUtil { notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); } + public synchronized void updateThumbnail() { + if (notificationBuilder != null) { + if (DEBUG) { + Log.d(TAG, "updateThumbnail() called with thumbnail = [" + Integer.toHexString( + Optional.ofNullable(player.getThumbnail()).map(Objects::hashCode).orElse(0)) + + "], title = [" + player.getVideoTitle() + "]"); + } + + setLargeIcon(notificationBuilder); + notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); + } + } + private synchronized NotificationCompat.Builder createNotification() { if (DEBUG) { Log.d(TAG, "createNotification()"); @@ -123,6 +136,9 @@ public final class NotificationUtil { .setDeleteIntent(PendingIntent.getBroadcast(player.getContext(), NOTIFICATION_ID, new Intent(ACTION_CLOSE), FLAG_UPDATE_CURRENT)); + // set the initial value for the video thumbnail, updatable with updateNotificationThumbnail + setLargeIcon(builder); + return builder; } @@ -142,7 +158,6 @@ public final class NotificationUtil { notificationBuilder.setTicker(player.getVideoTitle()); updateActions(notificationBuilder); - setLargeIcon(notificationBuilder); } From 051c572e7fb5fa5a1a28e8d0efa54703e55f5084 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 13 Sep 2022 15:08:37 +0200 Subject: [PATCH 145/152] Show correct tooltips for actions in feed --- .../schabi/newpipe/local/feed/FeedFragment.kt | 19 +++++++++++++++++++ app/src/main/res/values/strings.xml | 4 +++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 899163050..d0608e833 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -41,6 +41,7 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.edit import androidx.core.os.bundleOf +import androidx.core.view.MenuItemCompat import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager @@ -286,6 +287,15 @@ class FeedFragment : BaseStateFragment() { requireContext(), if (showPlayedItems) R.drawable.ic_visibility_on else R.drawable.ic_visibility_off ) + MenuItemCompat.setTooltipText( + menuItem, + getString( + if (showPlayedItems) + R.string.feed_toggle_hide_played_items + else + R.string.feed_toggle_show_played_items + ) + ) } private fun updateToggleFutureItemsButton(menuItem: MenuItem) { @@ -294,6 +304,15 @@ class FeedFragment : BaseStateFragment() { requireContext(), if (showFutureItems) R.drawable.ic_history_future else R.drawable.ic_history ) + MenuItemCompat.setTooltipText( + menuItem, + getString( + if (showPlayedItems) + R.string.feed_toggle_hide_future_items + else + R.string.feed_toggle_show_future_items + ) + ) } // ////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b742f2517..8b4a2efcd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -684,6 +684,7 @@ \n \nSo the choice boils down to what you prefer: speed or precise information. Show watched items + Hide watched items This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version. Channel\'s avatar thumbnail Created by %s @@ -746,5 +747,6 @@ Select quality for external players Unknown format Unknown quality - Show future videos + Show future items + Hide future items \ No newline at end of file From fbf5549182a6cf39210ce027b63159684f7479d4 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 14 Sep 2022 14:01:59 +0200 Subject: [PATCH 146/152] Fix wrong icons being set on suggestion items The diff util wrongly considered as equal two items with the same text but with different `fromHistory` value --- .../newpipe/fragments/list/search/SuggestionListAdapter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java index f859c9e54..856ba22f1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java @@ -80,13 +80,14 @@ public class SuggestionListAdapter @Override public boolean areItemsTheSame(@NonNull final SuggestionItem oldItem, @NonNull final SuggestionItem newItem) { - return oldItem.query.equals(newItem.query); + return oldItem.fromHistory == newItem.fromHistory + && oldItem.query.equals(newItem.query); } @Override public boolean areContentsTheSame(@NonNull final SuggestionItem oldItem, @NonNull final SuggestionItem newItem) { - return oldItem.equals(newItem); + return true; // items' contents never change; the list of items themselves does } } } From 1f57c878598f41d69e704c79276973223c7c33ad Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 14 Sep 2022 14:03:15 +0200 Subject: [PATCH 147/152] Disable suggestion list animations: not meaningful The animations were just in the way and did not help in choosing items, since the suggestion items keep changing too much. --- .../schabi/newpipe/fragments/list/search/SearchFragment.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 6827ddaaf..0b1d145f2 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -340,6 +340,8 @@ public class SearchFragment extends BaseListFragment Date: Wed, 14 Sep 2022 14:39:32 +0200 Subject: [PATCH 148/152] Fix scroll issues in suggestion list Before if the list before updating contained item 'test' at position 0 and after updating that value went to the bottom, the list would incorrectly scroll to the bottom to follow that item. Now the scrolling is done after the list is updated. --- .../schabi/newpipe/fragments/list/search/SearchFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 0b1d145f2..5175e0096 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -946,8 +946,8 @@ public class SearchFragment extends BaseListFragment searchBinding.suggestionsList.scrollToPosition(0)); if (suggestionsPanelVisible && isErrorPanelVisible()) { hideLoading(); From 8313f6bb518690ef1fe7fdd907952d9c486521bb Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 19 Sep 2022 09:17:25 +0200 Subject: [PATCH 149/152] Update NewPipeExtractor again --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2c09f754f..2387881cf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -187,7 +187,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:6a858368c86bc9a55abee586eb6c733e86c26b97' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:a99af9bb6e755997f40c975b1a212d243f051389' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ From 489f052ef992d7ab978d52dedf6fab4c122ec6d3 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 19 Sep 2022 11:21:38 +0200 Subject: [PATCH 150/152] Fix feed menu tooltips (silly copy-paste error) --- app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index d0608e833..c9f926f06 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -307,7 +307,7 @@ class FeedFragment : BaseStateFragment() { MenuItemCompat.setTooltipText( menuItem, getString( - if (showPlayedItems) + if (showFutureItems) R.string.feed_toggle_hide_future_items else R.string.feed_toggle_show_future_items From 3110b08988a04d86a926d5a23cd0633160b0bd17 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sun, 25 Sep 2022 13:30:12 +0200 Subject: [PATCH 151/152] Translated using Weblate (Tamil) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 52.6% (337 of 640 strings) Translated using Weblate (Vietnamese) Currently translated at 99.5% (637 of 640 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Portuguese) Currently translated at 60.5% (43 of 71 strings) Translated using Weblate (Azerbaijani) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Tamil) Currently translated at 52.5% (336 of 640 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Slovak) Currently translated at 8.4% (6 of 71 strings) Translated using Weblate (Kurdish (Central)) Currently translated at 98.1% (628 of 640 strings) Translated using Weblate (Galician) Currently translated at 99.6% (638 of 640 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Slovak) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (German) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 88.7% (63 of 71 strings) Translated using Weblate (Hindi) Currently translated at 4.2% (3 of 71 strings) Translated using Weblate (Portuguese) Currently translated at 60.5% (43 of 71 strings) Translated using Weblate (Hindi) Currently translated at 68.7% (440 of 640 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Czech) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Italian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Russian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Dutch) Currently translated at 99.3% (636 of 640 strings) Translated using Weblate (English) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (71 of 71 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 63.3% (45 of 71 strings) Translated using Weblate (Swedish) Currently translated at 47.8% (34 of 71 strings) Translated using Weblate (French) Currently translated at 90.1% (64 of 71 strings) Translated using Weblate (Spanish) Currently translated at 57.7% (41 of 71 strings) Translated using Weblate (Polish) Currently translated at 57.7% (41 of 71 strings) Translated using Weblate (Czech) Currently translated at 100.0% (71 of 71 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (71 of 71 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 15.4% (11 of 71 strings) Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Korean) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 100.0% (640 of 640 strings) Co-authored-by: Agnieszka C Co-authored-by: Alex25820 Co-authored-by: Denys Nykula Co-authored-by: Eduardo Malaspina Co-authored-by: Eric Co-authored-by: Error Specialist Co-authored-by: Fjuro Co-authored-by: Hoseok Seo Co-authored-by: Hosted Weblate Co-authored-by: Igor Nedoboy Co-authored-by: Ihor Hordiichuk Co-authored-by: Jeff Huang Co-authored-by: Leonardo Brauna Co-authored-by: Maday Co-authored-by: Marc Barten Co-authored-by: Max Xie Co-authored-by: MohammedSR Vevo Co-authored-by: NTFSynergy Co-authored-by: Oğuz Ersen Co-authored-by: Ray Co-authored-by: Rex_sa Co-authored-by: SC Co-authored-by: SEENUVASAN T Co-authored-by: TXRdev Archive Co-authored-by: Terry Louwers Co-authored-by: TiA4f8R Co-authored-by: Tom Sawyer Co-authored-by: Translator Co-authored-by: Vas R Co-authored-by: VfBFan Co-authored-by: Yaron Shahrabani Co-authored-by: atilluF Co-authored-by: gallegonovato Co-authored-by: ssantos Co-authored-by: weughgh Co-authored-by: zmni Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/ Translation: NewPipe/Metadata --- app/src/main/res/values-ar/strings.xml | 6 +- app/src/main/res/values-az/strings.xml | 6 +- app/src/main/res/values-ckb/strings.xml | 4 +- app/src/main/res/values-cs/strings.xml | 6 +- app/src/main/res/values-de/strings.xml | 6 +- app/src/main/res/values-es/strings.xml | 8 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-gl/strings.xml | 3 +- app/src/main/res/values-he/strings.xml | 6 +- app/src/main/res/values-hi/strings.xml | 14 +- app/src/main/res/values-in/strings.xml | 10 +- app/src/main/res/values-it/strings.xml | 6 +- app/src/main/res/values-ko/strings.xml | 295 ++++++++++++++---- app/src/main/res/values-nl/strings.xml | 15 +- app/src/main/res/values-pt-rPT/strings.xml | 4 +- app/src/main/res/values-pt/strings.xml | 113 ++++--- app/src/main/res/values-ru/strings.xml | 30 +- app/src/main/res/values-sk/strings.xml | 9 +- app/src/main/res/values-sv/strings.xml | 22 +- app/src/main/res/values-ta/strings.xml | 5 +- app/src/main/res/values-tr/strings.xml | 6 +- app/src/main/res/values-uk/strings.xml | 6 +- app/src/main/res/values-vi/strings.xml | 30 +- app/src/main/res/values-zh-rCN/strings.xml | 9 +- app/src/main/res/values-zh-rHK/strings.xml | 16 +- app/src/main/res/values-zh-rTW/strings.xml | 6 +- app/src/main/res/values/strings.xml | 8 +- .../metadata/android/cs/changelogs/990.txt | 14 + .../metadata/android/es/changelogs/69.txt | 19 ++ .../metadata/android/es/changelogs/70.txt | 25 ++ .../metadata/android/es/changelogs/740.txt | 23 ++ .../metadata/android/es/changelogs/810.txt | 19 ++ .../metadata/android/es/changelogs/840.txt | 24 ++ .../metadata/android/es/changelogs/900.txt | 14 + .../metadata/android/es/changelogs/930.txt | 19 ++ .../metadata/android/es/changelogs/940.txt | 16 + .../metadata/android/es/changelogs/964.txt | 8 + .../metadata/android/es/changelogs/966.txt | 14 + .../metadata/android/es/changelogs/968.txt | 7 + .../metadata/android/es/changelogs/969.txt | 8 + .../metadata/android/es/changelogs/970.txt | 11 + .../metadata/android/es/changelogs/973.txt | 4 + .../metadata/android/es/changelogs/980.txt | 13 + .../metadata/android/es/changelogs/982.txt | 1 + .../metadata/android/es/changelogs/984.txt | 7 + .../metadata/android/es/changelogs/985.txt | 1 + .../metadata/android/es/changelogs/986.txt | 16 + .../metadata/android/es/changelogs/987.txt | 12 + .../metadata/android/es/changelogs/988.txt | 2 + .../metadata/android/es/changelogs/989.txt | 3 + .../metadata/android/es/changelogs/990.txt | 15 + .../metadata/android/fr/changelogs/989.txt | 3 + .../metadata/android/fr/changelogs/990.txt | 15 + .../metadata/android/hi/short_description.txt | 2 +- .../metadata/android/pl/changelogs/990.txt | 15 + .../metadata/android/pt/changelogs/65.txt | 26 ++ .../metadata/android/sk/changelogs/987.txt | 14 +- .../metadata/android/sv/changelogs/65.txt | 24 +- .../metadata/android/sv/changelogs/969.txt | 13 +- .../metadata/android/sv/changelogs/985.txt | 2 +- .../metadata/android/uk/changelogs/990.txt | 15 + .../android/zh-Hans/changelogs/990.txt | 15 + .../android/zh-Hant/changelogs/989.txt | 3 + .../android/zh-Hant/changelogs/990.txt | 15 + .../android/zh_Hant_HK/changelogs/989.txt | 3 + .../android/zh_Hant_HK/changelogs/990.txt | 15 + 66 files changed, 870 insertions(+), 256 deletions(-) create mode 100644 fastlane/metadata/android/cs/changelogs/990.txt create mode 100644 fastlane/metadata/android/es/changelogs/69.txt create mode 100644 fastlane/metadata/android/es/changelogs/70.txt create mode 100644 fastlane/metadata/android/es/changelogs/740.txt create mode 100644 fastlane/metadata/android/es/changelogs/810.txt create mode 100644 fastlane/metadata/android/es/changelogs/840.txt create mode 100644 fastlane/metadata/android/es/changelogs/900.txt create mode 100644 fastlane/metadata/android/es/changelogs/930.txt create mode 100644 fastlane/metadata/android/es/changelogs/940.txt create mode 100644 fastlane/metadata/android/es/changelogs/964.txt create mode 100644 fastlane/metadata/android/es/changelogs/966.txt create mode 100644 fastlane/metadata/android/es/changelogs/968.txt create mode 100644 fastlane/metadata/android/es/changelogs/969.txt create mode 100644 fastlane/metadata/android/es/changelogs/970.txt create mode 100644 fastlane/metadata/android/es/changelogs/973.txt create mode 100644 fastlane/metadata/android/es/changelogs/980.txt create mode 100644 fastlane/metadata/android/es/changelogs/982.txt create mode 100644 fastlane/metadata/android/es/changelogs/984.txt create mode 100644 fastlane/metadata/android/es/changelogs/985.txt create mode 100644 fastlane/metadata/android/es/changelogs/986.txt create mode 100644 fastlane/metadata/android/es/changelogs/987.txt create mode 100644 fastlane/metadata/android/es/changelogs/988.txt create mode 100644 fastlane/metadata/android/es/changelogs/989.txt create mode 100644 fastlane/metadata/android/es/changelogs/990.txt create mode 100644 fastlane/metadata/android/fr/changelogs/989.txt create mode 100644 fastlane/metadata/android/fr/changelogs/990.txt create mode 100644 fastlane/metadata/android/pl/changelogs/990.txt create mode 100644 fastlane/metadata/android/pt/changelogs/65.txt create mode 100644 fastlane/metadata/android/uk/changelogs/990.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/990.txt create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/989.txt create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/990.txt create mode 100644 fastlane/metadata/android/zh_Hant_HK/changelogs/989.txt create mode 100644 fastlane/metadata/android/zh_Hant_HK/changelogs/990.txt diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 700a612b8..83bae93b3 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -725,14 +725,14 @@ حدث خطأ، انظر للإشعار قم بإنشاء تنبيه بالخطأ لم يتم العثور على مدير ملفات مناسب لهذا الإجراء. -\nالرجاء تثبيت مدير ملفات أو محاولة تعطيل \"%s\" في إعدادات التنزيل. +\nيرجى تثبيت مدير ملفات أو محاولة تعطيل \"%s\" في إعدادات التنزيل إظهار خطأ snackbar لم يتم العثور على مدير ملفات مناسب لهذا الإجراء. -\nالرجاء تثبيت مدير ملفات متوافق مع Storage Access Framework. +\nالرجاء تثبيت مدير ملفات متوافق مع إطار عمل الوصول إلى التخزين تعليق مثبت LeakCanary غير متوفر الافتراضي ExoPlayer - تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل. + تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل تكوين إشعار مشغل البث الحالي الإشعارات تحميل تفاصيل البث… diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index bd5948622..057be6d02 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -497,7 +497,7 @@ Video fayl xülasəsi prosesi üçün bildirişlər Açın Miniatürü 1:1 aspekt nisbətinə kəsin - Yükləmə intervalının həcmini dəyişdirin (hazırda %s). Daha aşağı dəyər ilkin video yükləməni sürətləndirə bilər. Dəyişikliklər oynadıcının yenidən başladılmasını tələb edir. + Yükləmə intervalının həcmini dəyişdirin (hazırda %s). Daha aşağı dəyər ilkin video yükləməni sürətləndirə bilər. Dəyişikliklər oynadıcının yenidən başladılmasını tələb edir Yayım yaradıcısı, məzmunu və ya axtarış sorğusu haqqında əlavə məlumat olan üst məlumat qutularını gizlətmək üçün söndürün Əlaqədar yayımı əlavə etməklə (təkrar etməyən) sonlanacaq oynatma sırasını davam etdir Kənar axtarış təklifləri @@ -700,10 +700,10 @@ ExoPlayer məhdudiyyətlərinə görə axtarış müddəti %d saniyəyə təyin edildi Bəzi xidmətlərdə mövcuddur, adətən daha sürətli olur, lakin məhdud sayda elementləri və çox vaxt natamam məlumatı qaytara bilər (məsələn, müddət, element növü, canlı status yoxdur) Bu əməliyyat üçün uyğun fayl meneceri tapılmadı. -\nZəhmət olmasa, fayl menecerini quraşdırın və ya endirmə tənzimləmələrində \'%s\'-i deaktiv etməyə çalışın. +\nZəhmət olmasa, fayl menecerini quraşdırın və ya endirmə tənzimləmələrində \'%s\'-i deaktiv etməyə çalışın \'%s\' üçün axın yükləmək mümkün olmadı. Bu əməliyyat üçün uyğun fayl meneceri tapılmadı. -\nZəhmət olmasa ,Yaddaş Giriş Çərçivəsinə uyğun fayl menecerini quraşdırın. +\nZəhmət olmasa ,Yaddaş Giriş Çərçivəsinə uyğun fayl menecerini quraşdırın Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil. İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə bilər və keçidlər kliklənməyə bilər. Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 nisbətinə qədər kəsin diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index 8b6c08434..4c53c11b9 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -557,8 +557,8 @@ سێیه‌م كرداری دوگمه‌ دووه‌م كرداری دوگمه‌ یه‌كه‌م كرداری دوگمه‌ - وێنۆچكه‌ی ڤیدیۆ پێوانه‌ ده‌كرێته‌وه‌ له‌ پەیامەکاندا له‌ ڕه‌هه‌ندی 16:9 ه‌وه‌ بۆ ڕه‌هه‌ندی 1:1 - پێوانەكردنی وێنۆچكه‌ بۆ ڕه‌هه‌ندی 1:1 + وێنۆچكه‌ی ڤیدیۆ دەبڕدرێت له‌ پەیامەکاندا له‌ ڕه‌هه‌ندی 16:9 ه‌وه‌ بۆ ڕه‌هه‌ندی 1:1 + بڕینی وێنۆچكه‌ بۆ ڕه‌هه‌ندی ڕێژەیی 1:1 پیشاندانی ئەنجامەکانی: %s كردنه‌وه‌ له‌ ناكارایبكه‌ بۆ شاردنه‌وه‌ی چوارگۆشه‌ی مێتا و زانیاری زیاده‌ له‌سه‌ر بابەتی په‌خش و داواكاری گه‌ڕان diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 8ab1e2584..41bfafaac 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -685,16 +685,16 @@ Ukázat „Shodit přehrávač“ Nové položky Pro tuto akci nebyl nalezen žádný vhodný správce souborů. -\nProsím, nainstalujte správce souborů kompatibilní se Storage Access Framework. +\nNainstalujte správce souborů kompatibilní se Storage Access Framework Oznámení o hlášení chyb Oznámení za účelem hlášení chyb Pro tuto akci nebyl nalezen žádný vhodný správce souborů. -\nProsím, nainstalujte správce souborů nebo zkuste vypnout %s v nastaveních stahování. +\nNainstalujte správce souborů nebo zkuste vypnout \'%s\' v nastavení stahování Ukáže volbu pro zřícení během používání přehrávače Ukázat krátké oznámení o chybě Připnutý komentář Shodit přehrávač - Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače. + Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače LeakCanary není dostupné Výchozí ExoPlayer Nastavit oznámení o právě přehrávaném streamu diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5ff079f7a..e57c1874e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -680,12 +680,12 @@ Eine Fehlermeldung erstellen Fehler-Kurzmeldung anzeigen Es wurde kein geeigneter Dateimanager für diese Aktion gefunden. -\nBitte installiere einen Dateimanager oder versuche, \'%s\' in den Downloadeinstellungen zu deaktivieren. +\nBitte installiere einen Dateimanager oder versuche, \'%s\' in den Downloadeinstellungen zu deaktivieren Es wurde kein geeigneter Dateimanager für diese Aktion gefunden. -\nBitte installiere einen Storage Access Framework kompatiblen Dateimanager. +\nBitte installiere einen Storage Access Framework kompatiblen Dateimanager Angehefteter Kommentar LeakCanary ist nicht verfügbar - Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players. + Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players ExoPlayer Standard Benachrichtigungen Benachrichtigen über neue abonnierbare Streams diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index edf4769e8..7b7dfe99e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -680,14 +680,12 @@ Se produjo un error, vea la notificación Crear una notificación de error Mostrar un snackbar de error - No se encontró ningún gestor de archivos adecuado para esta acción. -\nPor favor instale un gestor de archivos o intente deshabilitar \"%s\" en lo ajustes de descargas. - No se encontró ningún gestor de archivos adecuado para esta acción. -\nPor favor instale un gestor de archivos compatible con \"Sistema de Acceso al Almacenamiento\". + No se encontró ningún administrador de archivos apropiado para esta acción. Instale un administrador de archivos o intente deshabilitar \'%s\' en la configuración de descarga + No se encontró ningún administrador de archivos apropiado para esta acción. Instale un administrador de archivos compatible con Storage Access Framework Comentario fijado LeakCanary no está disponible ExoPlayer valor por defecto - Cambia el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial del vídeo. Los cambios requieren un reinicio del reproductor. + Cambie el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial de video. Los cambios requieren un reinicio del reproductor Notificaciones Nuevos streams Notificación del reproductor diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4a79fbea9..63a2e19d1 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -664,7 +664,7 @@ Ne pas lancer les vidéos dans le mini lecteur mais directement en plein écran si la rotation automatique est verrouillée. Vous pouvez toujours accéder au mini-lecteur en quittant le mode plein écran Lancer le lecteur principal en plein écran Lire consécutivement - Video placée après la lecture + Flux placé à la suite Traitement en cours… Veuillez patienter Vérifier manuellement de nouvelles versions Vérification des mises à jour… diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 701d1b8cc..bebbf2483 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -657,8 +657,7 @@ Inciar reprodutor principal en pantalla completa Non iniciar vídeos no reprodutor mini, mais cambiar a pantalla completa directamente, se a rotación estiver bloqueada. Aínda pode acceder o reprodutor mini ao saír da pantalla completa Deslice os elementos para removelos - Non foi atopado ningún xestor de arquivos adecuado para esta acción. -\nPor favor instale un ou tente deshabilitar \'%s\' nas opcións de descarregamento. + Non se atopou ningún xestor de ficheiros axeitado para esta acción. Instala un xestor de ficheiros ou intenta desactivar \'%s\' na configuración de descarga Non foi atopado ningún xestor de arquivos adecuado para esta acción. \nPor favor instale un compatíbel co Sistema de Acceso ao Almacenamento. Valorado polo creador diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index b9056922a..a80d66849 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -701,14 +701,14 @@ NewPipe נתקל בשגיאה, לחיצה תדווח על כך אירעה שגיאה, נא לקרוא את ההתראה לא נמצא מנהל קבצים מתאים לפעולה זו. -\nנא להתקין מנהל קבצים או לנסות להשבית את ‚%s’ בהגדרות ההורדה. +\nנא להתקין מנהל קבצים או לנסות להשבית את ‚%s’ בהגדרות ההורדה התראת דיווח שגיאה לא נמצאו מנהלי קבצים שמתאימים לפעולה הזאת. -\nנא להתקין מנהל קבצים שתומך בתשתית גישה לאחסון. +\nנא להתקין מנהל קבצים שתומך בתשתית גישה לאחסון הערה ננעצה LeakCanary אינה זמינה ברירת מחדל של ExoPlayer - שינוי גודל מרווח הטעינה (כרגע %s). ערך נמוך יותר עשוי להאיץ את טעינת הווידאו הראשונית. שינויים דורשים את הפעלת הנגן מחדש. + שינוי גודל מרווח הטעינה (כרגע %s). ערך נמוך יותר עשוי להאיץ את טעינת הווידאו הראשונית. שינויים דורשים את הפעלת הנגן מחדש התראות על תזרימים חדשים להרשמה תדירות בדיקה נדרש חיבור לרשת diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 1d76be613..cd0cf6f52 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -10,7 +10,7 @@ सर्च करे सेटिंग्स सब्सक्राइब करें - सब्सक्राइबड + सस्क्राइब्ड चैनल अनसब्सक्राइब हुआ सब्सक्रिप्शनस बैकग्राउंड @@ -57,10 +57,10 @@ पॉपअप का डिफ़ॉल्ट रिज़ॉल्यूशन उच्च रिज़ॉल्यूशनस दिखाएं केवल कुछ ही डिवाइस 2K/4K मे विडियो चला सकते हैं - Kodi से चलायें + Kodi मे प्ले करे Kore ऐप नहीं मिली, इसे इनस्टॉल करें\? - \"Kodi से चलायें\" वाला विकल्प दिखाएँ - Kodi मीडिया सेंटर से विडियो चलाने के लिए विकल्प प्रदर्शित करें + \"Kodi मे प्ले करे\" वाला विकल्प दिखाएँ + Kodi मीडिया सेंटर से विडियो प्ले करने के लिए विकल्प प्रदर्शित करें डिफ़ॉल्ट ऑडियो फॉर्मेट डिफ़ॉल्ट विडियो फॉर्मेट ऐप थीम @@ -187,7 +187,7 @@ स्ट्रीम फाइल डाउनलोड करें जानकारी दिखाएं बुकमार्क किये गए प्लेलिस्टस - में जोड़े + में एड करे डिफ़ॉल्ट देश का विषय हमेशा के लिए सिर्फ एक बार के लिए @@ -518,9 +518,9 @@ विडीओ हैशिंग की प्रगति की सूचना वीडियो हैश अधिसूचना स्ट्रीम निर्माता, स्ट्रीम विषय सूची या खोज अनुरोध के बारे में अतिरिक्त जानकारी के साथ मेटा जानकारी बक्से को छिपाने के लिए बंद करें. - मेटा जानकारी दिखाएँ + Meta info दिखाएँ वीडियो का विवरण और अतिरिक्त जानकारी छिपाने के लिए इसे बंद करें - विवरण दिखाएं + डिस्क्रिप्शन दिखाएं सक्रिय प्लेअर की क़तार बदल दी जाएगी एक प्लेअर से दूसरे प्लेअर में जाने से आपकी कतार बदल सकती है मे खोलें diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index d4d2db5a8..f7fb4113a 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -664,15 +664,15 @@ Sebuah kegalatan terjadi, lihat notifikasinya Menampilkan sebuah snackbar kegalatan Buat sebuah notifikasi kegalatan - Tidak ada manajer file yang ditemukan untuk tindakan ini. -\nMohon instal sebuah manajer file atau coba menonaktifkan \'%s\' di pengaturan unduhan. + Tidak ada pengelola berkas yang ditemukan untuk tindakan ini. +\nSilakan pasang pengelola berkas atau coba nonaktifkan \'%s\' di pengaturan unduhan NewPipe mengalami sebuah kegalatan, ketuk untuk melaporkan - Tidak ada manajer file yang ditemukan untuk tindakan ini. -\nMohon instal sebuah manajer file yang kompatibel dengan Storage Access Framework. + Tidak ada pengelola berkas yang ditemukan untuk tindakan ini. +\nSilakan pasang pengelola berkas yang kompatibel dengan Storage Access Framework Komentar dipin LeakCanary tidak tersedia Default ExoPlayer - Ubah ukuran interval pemuatan (saat ini %s). Sebuah nilai yang rendah mungkin dapat membuat pemuatan video awal lebih cepat. Membutuhkan sebuah pemulaian ulang pada pemain. + Ubah ukuran interval pemuatan (saat ini %s). Nilai yang rendah mungkin dapat membuat pemuatan video awal lebih cepat. Perubahan membutuhkan pemutar dimulai ulang Memuat detail stream… Frekuensi pemeriksaan Dibutuhkan koneksi jaringan diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c2d0a2c6f..271d45ecb 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -674,17 +674,17 @@ NewPipe ha riscontrato un errore, tocca per segnalarlo Mostra un messaggio di errore Non è stato trovato alcun gestore di file appropriato per questa azione. -\nInstallane uno prova a disattivare \"%s\" nelle impostazioni di download. +\nInstallane uno prova a disattivare \"%s\" nelle impostazioni di download Notifica per segnalazione errori Notifiche per segnalare errori Si è verificato un errore, vedi la notifica Crea una notifica di errore Non è stato trovato alcun gestore di file appropriato per questa azione. -\nInstallane uno compatibile con Storage Access Framework. +\nInstallane uno compatibile con Storage Access Framework Commento in primo piano LeakCanary non è disponibile Predefinito ExoPlayer - Cambia la dimensione dell\'intervallo da caricare (attualmente %s). Un valore basso può velocizzare il caricamento iniziale del video. La modifica richiede il riavvio del lettore. + Cambia la dimensione dell\'intervallo da caricare (attualmente %s). Un valore basso può velocizzare il caricamento iniziale del video. La modifica richiede il riavvio del lettore Notifiche di nuovi contenuti dalle iscrizioni Frequenza controllo Connessione di rete richiesta diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index d0d54c62d..ad37ab778 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -13,7 +13,7 @@ 다음으로 공유 비디오 다운로드 폴더 다운로드된 비디오 파일이 이 곳에 저장됩니다 - 비디오 파일이 다운로드 될 폴더를 선택하세요 + 비디오 파일 다운로드 폴더 선택 기본 해상도 Kodi로 재생 Kore를 설치할까요\? @@ -34,14 +34,14 @@ 외부 오디오 플레이어 사용 오디오 다운로드 폴더 다운로드된 오디오 파일이 이 곳에 저장됩니다 - 오디오 파일이 다운로드 될 폴더를 선택하세요 + 오디오 파일 다운로드 폴더 선택 테마 어두운 테마 밝은 테마 외관 백그라운드에서 재생 중 네트워크 오류 - \"검색\" 버튼을 눌러서 시작하세요 + \"검색\" 버튼을 눌러서 시작하세요. 컨텐츠 연령 제한 컨텐츠 보여주기 라이브 @@ -53,7 +53,7 @@ 다운로드 메뉴를 설정할 수 없습니다 죄송합니다. 오류가 발생했습니다. 이메일을 통해 이 오류 보고 - 죄송합니다. 오류가 발생했습니다. + 죄송합니다. 문제가 발생했습니다. 보고 정보: 다음이 발생함: @@ -87,15 +87,15 @@ 팝업 크기 및 위치 기억 마지막으로 사용한 팝업 위치 및 크기를 기억합니다 검색 제안 - 검색 중에 제안을 표시합니다 + 검색할 때 표시할 제안 선택 검색 기록 검색 기록을 기기에 저장합니다 기록 보기 시청했던 비디오 기록을 저장합니다 - 자동으로 다시 재생 - 전화 통화 등으로 인해 재생이 중단된 이후에 다시 재생을 시작합니다 + 재생 재개 + 중단 후 계속 재생(예: 전화 통화) \"길게 눌러 대기열에 추가하기\" 팁 표시 - 비디오 상세 정보 페이지에서 백그라운드/팝업 재생 버튼을 누를 경우 팁을 표시합니다 + 비디오 \"세부사항:\"에서 배경 또는 팝업 버튼을 누를 때 팁 표시 플레이어 동작 기록 및 캐시 @@ -114,7 +114,7 @@ 이 스트림을 재생할 수 없습니다 복구할 수 없는 플레이어 오류가 발생했습니다 플레이어 오류로부터 복구 중 - 무엇을:\\n요청:\\n컨텐츠 언어:\\컨텐츠 국가:\\n앱 언어:\\n서비스:\\nGMT 기준 시간:\\n패키지:\\n버전:\\n안드로이드 버전: + 무엇을:\\n요청:\\n컨텐츠 언어:\\n컨텐츠 국가:\\n앱 언어:\\n서비스:\\nGMT 시간:\\n패키지:\\n버전:\\nOS 버전: 결과 없음 구독할 항목을 추가하세요 @@ -157,7 +157,7 @@ 번역, 디자인, 코딩 등 다양한 기여를 언제나 환영합니다. 더 나아지도록 도와주세요! GitHub에서 보기 기부 - 여러분들의 더 나은 경험을 위해 많은 사람들이 NewPipe를 개발하는데 노력을 기울이고 있습니다. NewPipe에 참여하는 개발자들이 커피 한 잔을 즐길 수 있도록 기부해주세요. + NewPipe는 최고의 사용자 경험을 제공하기 위해 자유 시간을 보내는 자원 봉사자에 의해 개발되었습니다. 개발자가 커피 한 잔을 즐기면서 NewPipe를 더욱 개선할 수 있도록 지원해주세요. 보답하기 웹사이트 NewPipe에 관한 더 많은 정보를 얻으려면 웹사이트를 방문하세요. @@ -175,13 +175,13 @@ 키오스크 선택 인기 급상승 탑 50 - 신작 & 뜨는 동영상 + 신작 및 인기 동영상 제거 상세 정보 오디오 설정 눌러서 대기열에 추가 백그라운드에서 재생 - 새 팝업에서 재생 + 팝업에서 재생 시작 스트림 플레이어를 찾을 수 없습니다 (VLC를 설치하여 동영상을 재생할 수 있습니다). 스트림 파일 다운로드하기 정보 보기 @@ -189,8 +189,8 @@ 이곳에 추가 정확하지는 않지만 빠른 탐색 정확하지 않은 탐색은 더 빠르게 위치를 탐색할 수 있지만 정확도는 떨어집니다. 5, 15, 25초 탐색은 이 기능과 같이 작동하지 않습니다 - 다음 스트림을 자동으로 대기열에 추가하기 - 이전 스트림이 반복 재생 대기열이 아닐 경우, 관련 스트림을 자동 재생합니다 + 다음 스트림을 자동 대기열에 추가 + 이전 스트림이 반복 재생 대기열이 아닐 경우, 관련 스트림을 자동 재생 기본 콘텐츠 국가 디버그 항상 @@ -200,7 +200,7 @@ 기본으로 전환 데이터베이스 가져오기 데이터베이스 내보내기 - 현재 시청 기록 및 구독 목록을 덮어쓰기 합니다 + 현재 시청 기록, 구독, 재생 목록, (선택 사항) 설정 재정의 시청 기록과 구독 목록, 재생 목록, 설정을 내보냅니다 외부 플레이어는 이러한 종류의 링크를 지원하지 않습니다 발견된 비디오 스트림 없음 @@ -259,22 +259,21 @@ 이전 내보내기 구독 목록 가져오기 실패 구독 목록 내보내기 실패 - Google 테이크아웃을 통해 YouTube 구독 목록을 가져올 수 있습니다: + 구글 테이크아웃에서 유튜브 구독 가져오기: \n -\n1. 이곳으로 가세요: %1$s -\n2. 요청에 따라 로그인을 진행합니다 -\n3. \"모든 데이터 포함됨\"을 누른 뒤 \"모두 선택 해제\"를 누릅니다. 그 다음 \"구독정보\"를 선택하고 \"확인\"을 누릅니다 -\n4. \"다음 단계\"를 누르고 \"내보내기 생성\"을 선택합니다 -\n5. \"다운로드\" 버튼을 눌러 다운로드 한 후 -\n6. 테이크아웃 ZIP파일을 압축해제 하여 .json 파일 (일반적으로 \"YouTube 및 YouTube Music/구독/구독.json\")을 받고 가져옵니다. - SoundCloud 프로필을 가져오시려면 URL 및 ID를 입력해주세요. +\n1. 다음 URL로 이동: %1$s +\n2. 요청 시 로그인 +\n3. \"모든 데이터 포함\"을 클릭한 다음 \"모두 선택 취소\"를 클릭한 후 \"구독\"만 선택하고 \"확인\" 클릭 +\n4. \"다음 단계\"를 클릭한 다음 \"내보내기 만들기\" 클릭 +\n5. \"다운로드\" 버튼이 나타나면 클릭 +\n6. 아래 파일 가져오기를 클릭하고 다운로드한 .zip 파일 선택 +\n7. [.zip 가져오기가 실패한 경우] .csv 파일(일반적으로 \"YouTube 및 YouTube Music/subscriptions/subscriptions.csv\" 아래에 있음)의 압축을 풀고, 아래 파일 가져오기를 클릭하고 압축을 푼 csv 파일 선택 + URL 또는 ID를 입력하여 SoundCloud 프로필을 가져옵니다: \n -\n프로필 URL을 찾으시려면 다음 과정을 따라해 주세요. -\n -\n1. 웹 브라우저의 \"데스크톱 모드\" 를 활성화하세요 -\n2. 이 주소로 가세요: %1$s -\n3. 로그인이 필요하면 하세요. -\n4. 리디렉트된 프로필 URL을 복사하세요. +\n1. 웹 브라우저에서 \"데스크톱 모드\"를 활성화합니다(모바일 장치에서는 사이트를 사용할 수 없습니다) +\n2. 다음 URL로 이동: %1$s +\n3. 요청 시 로그인 +\n4. 리디렉션된 프로필 URL을 복사합니다. 프로필ID, soundcloud.com/프로필ID 경고: 데이터가 많이 소모될 수 있습니다. \n @@ -288,13 +287,13 @@ 재생 속도 조절 템포 피치 - 영상과 소리 분리 (소리가 깨질 수 있음) + 영상과 소리 분리 (왜곡이 발생할 수 있음) 다운로드 가능한 스트림이 없습니다 이 파일을 재생할 수 있는 플레이어 앱이 없습니다 선호하는 열기 동작 컨텐츠를 열 때 사용할 기본 동작 — %s 자막 - 플레이어 자막 글자 크기와 배경 스타일을 변경합니다. 이를 적용하려면 앱을 재시작 해야 합니다. + 플레이어 자막 글자 크기와 배경 스타일을 수정합니다. 적용하려면 앱을 다시 시작해야 합니다 채널 재생목록 시청 기록 삭제하기 @@ -309,10 +308,11 @@ NewPipe 프로젝트는 사용자의 개인 정보 보호를 최우선으로 생각하며, 동의 없이 어떠한 정보도 수집하지 않습니다. \nNewPipe 개인정보 보호 정책에서는 오류 보고 시 어떠한 정보가 수집되고 저장되는지 자세히 명시되어 있습니다. 개인정보 보호 정책 읽기 - NewPipe는 카피레프트 자유 소프트웨어입니다. 사용자는 이 앱을 사용, 공유, 또는 수정할 수 있고, 수정 후 재배포 시 자유 소프트웨어 재단의 GNU 라이센스 버전 3 또는 그 이상의 버전을 포함해야 합니다. + NewPipe는 카피레프트 자유 소프트웨어입니다: 마음대로 사용하고, 연구하고, 공유하고, 개선할 수 있습니다. 특히 자유 소프트웨어 재단에서 발행한 GNU 일반 공중 사용 라이센스의 조건에 따라 라이센스 버전 3 또는 (귀하의 선택에 따라) 이후 버전을 재배포 및/또는 수정할 수 있습니다. 설정도 가져오시겠습니까\? 무음 구간 빨리 감기 - 유럽 연합 일반 데이터 보호 규정 (GDPR) 에 따라, 사용자는 NewPipe 개인정보 보호 정책을 읽고 꼼꼼히 확인해야 합니다. 버그 리포트를 보내시려면 개인정보 보호 정책에 동의해주세요. + 유럽 연합 일반 데이터 보호 규정(GDPR)을 준수하기 위해 NewPipe의 개인 정보 보호 정책에 주의를 기울입니다. 주의 깊게 읽으십시오. +\n버그 보고서를 보내려면 수락해야 합니다. 동의 동의하지 않음 데이터 제한 없음 @@ -320,9 +320,9 @@ 구독 취소 탭 선택 제스처 음량 조작 - 제스처를 사용해 플레이어의 음량을 조작합니다 + 제스처를 사용하여 플레이어 볼륨 제어 제스처 밝기 조작 - 제스처를 사용해 화면 밝기를 조작합니다 + 제스처를 사용하여 플레이어 밝기 제어 업데이트 트랙 사용자 @@ -340,7 +340,7 @@ 팝업 플레이어로 최소화 단계 초기화 - 저장된 탭을 읽는 중 오류가 발생하여 기본 탭을 사용합니다 + 저장된 탭을 읽을 수 없으므로 기본 탭 사용 초기화 초기 설정으로 복원하시겠습니까\? 구독자 수를 가져올 수 없습니다 @@ -351,7 +351,7 @@ 목록 격자 자동 - NewPipe 업데이트가 있습니다! + NewPipe 업데이트를 사용할 수 있습니다! 여기를 눌러서 다운로드 완료됨 대기열에 있음 @@ -367,11 +367,11 @@ 이 이름을 가진 다운로드 된 파일이 이미 있습니다 해당 이름을 가진 다운로드가 이미 진행중입니다 오류 표시 - 지정한 폴더를 만들 수 없습니다 - 파일을 만들 수 없습니다 - 보안 연결 실패 + 대상 폴더를 만들 수 없습니다 + 파일을 생성할 수 없습니다 + 보안 연결을 설정할 수 없음 서버를 찾을 수 없습니다 - 서버에 접속할 수 없습니다 + 서버에 연결할 수 없습니다 서버가 데이터를 전송하지 않고 있습니다 서버가 다중 스레드 다운로드를 받아들이지 않습니다, @string/msg_threads = 1 를 사용해 다시 시도해보세요 HTTP 찾을 수 없습니다 @@ -406,10 +406,10 @@ 다운로드 시작 다운로드 일시정지 다운로드 위치를 묻기 - 다운로드 할때 마다 저장위치를 물을 것 입니다 - SAF 사용 - 스토리지 액세스 프레임워크(SAF)는 외장 SD카드에 다운로드 할 수 있도록 해줍니다. -\n주석: 일부 기기와 호환되지 않을 수 있습니다 + 각 다운로드를 저장할 위치를 묻는 메시지가 표시됩니다. +\n외부 SD 카드에 다운로드하려면 시스템 폴더 선택기(SAF) 활성화 + 시스템 폴더 선택기(SAF) 사용 + \'저장영역 접속 프레임워크\'를 통해 외부 SD 카드로 다운로드 가능 재생 위치 삭제 모든 재생 위치를 삭제 모든 재생 위치를 삭제하시겠습니까\? @@ -419,26 +419,26 @@ 하나의 다운로드가 동시에 진행됩니다 서비스 토글, 현재 선택된 서비스: 기본 키오스크 - 시청자가 없습니다. + 시청자가 없음 %s 시청 - 듣고 있는 사람이 없습니다. + 듣고 있는 사람 없음 %s 듣는사람 - 앱을 재시작하면 언어가 변경됩니다. + 앱이 다시 시작되면 언어가 변경됩니다 빠른-감기/되감기 찾는 시간 피어튜브 인스턴스 - 당신이 선호하는 피어튜브 인스턴스를 선택하세요. - %s에서 당신이 좋아하는 인스턴스를 찾으세요. + 선호하는 PeerTube 인스턴스 선택 + %s에서 원하는 인스턴스 찾기 인스턴스 추가하기 - 인스턴스 URL을 입력하세요. - 인스턴스를 검증할 수 없습니다. - 오직 HTTPS URL들만 지원합니다. - 인스턴스가 이미 존재합니다. + 인스턴스 URL 입력 + 인스턴스를 확인할 수 없음 + HTTPS URL만 지원 + 인스턴스가 이미 존재 로컬 - 최근에 추가됨. + 최근에 추가됨 가장 선호하는 자동생성된(업로더를 찾지못함) 복구하기 @@ -474,8 +474,8 @@ 앨범 비디오 첫번째 버튼 - 알림에 표시되는 비디오 썸네일을 16:9에서 1:1 비율로 바꿉니다. (왜곡이 생길 수도 있습니다.) - 썸네일을 1:1 비율로 하기 + 알림에 표시된 비디오 썸네일을 16:9에서 1:1 화면비로 자릅니다 + 1:1 화면비로 썸네일 자르기 %s에 대한 검색 결과 셔플 연속 재생 @@ -498,8 +498,8 @@ 시청 기록 지우기 재생목록 실행 URL을 인식할 수 없습니다. 다른 앱으로 여시겠습니까\? - 대기열을 비우기 전 확인하도록 합니다. - 안드로이드에서 썸네일의 색상에 따라 알림 색상을 조절합니다. (지원되지 않는 기기가 있을 수 있습니다.) + 대기열을 지우기 전에 확인 요청 + 안드로이드에서 썸네일 이미지의 기본 색상에 따라 알림 색상을 사용자 지정 (일부 기기에서는 사용할 수 없음) 버퍼링 다섯번째 버튼 네번째 버튼 @@ -508,8 +508,8 @@ 다른 앱 위에 표시되는 권한 부여 메타 정보 표시 색상화된 알림 - 활성화된 플레이어 대기열이 교체됩니다. - 으로(로) 열기 + 활성 플레이어 대기열 교체 + 파일 열기 시청한 것으로 처리 비활성화하면 비디오 설명과 추가 정보를 표시하지 않습니다 설명 표시 @@ -532,4 +532,177 @@ 전체화면으로 주 플레이어 시작 비디오 해시 알림 GitHub에 보고 + 다음에 대기열에 추가됨 + 다음 대기열에 넣기 + 현재 재생 중인 스트림 알림 구성 + 연령 제한(예: 18세 이상)이 있으므로 어린이에게 적합하지 않을 수 있는 콘텐츠 표시 + 오류 보고 알림 + NewPipe에 오류가 발생했습니다. 보고하려면 탭하세요 + 오류가 발생했습니다. 알림을 참조하세요 + 서식이 지정된 보고서 복사 + 관련 항목 + 댓글이 비활성화되었습니다 + + %s개의 새로운 스트림 + + 해시 계산 + 다운로드 폴더가 아직 설정되지 않았습니다. 지금 기본 다운로드 폴더를 선택하세요 + 항목을 스와이프하여 제거 + 채널 세부정보 표시 + 대기열에 넣기 + 대기열에 추가됨 + 스트림 세부정보 불러오는 중… + 미디어 터널링 비활성화 + 서비스의 원본 텍스트가 스트림 항목에 표시됩니다 + 비디오 재생 시 검은색 화면이나 끊김 현상이 발생하는 경우 미디어 터널링을 비활성화합니다 + 이미지 표시기 표시 + 원본을 나타내는 이미지 위에 피카소 컬러 리본 표시: 네트워크는 빨간색, 디스크는 파란색, 메모리는 녹색 + \"플레이어 충돌\" 표시 + 플레이어를 사용할 때 충돌 옵션을 표시합니다 + 새로운 스트림 확인 실행 + 앱 충돌 + 오류 스낵바 표시 + 새로운 스트림 알림 + 구독에서 새로운 스트림에 대해 알림 + 주파수 확인 + 필요한 네트워크 연결 + 모든 네트워크 + 업데이트 확인 + 새로운 버전을 수동으로 확인 + 자동으로 재생 시작 — %s + Wi-Fi에서만 + 절대 + 탐색막대 썸네일 미리보기 + 고품질 (크게) + 저품질 (작음) + 표시하지 않음 + 업데이트 확인 중… + 디스크에서 다운로드한 모든 파일을 지우겠습니까\? + + %1$s 다운로드 삭제됨 + + 각 다운로드를 저장할 위치를 묻는 메시지가 표시됩니다 + 장치에서 열 수 있는 앱이 없습니다 + 설명에서 텍스트 선택 비활성화 + 태그 + + + 태블릿 모드 + 지역 검색 제안 + 원격 검색 제안 + 이 동영상은 연령 제한이 있습니다. +\n연령 제한 동영상에 대한 새로운 유튜브 정책으로 인해 NewPipe는 동영상 스트림에 접속할 수 없으므로 재생할 수 없습니다. + reCAPTCHA를 해결할 때 NewPipe가 저장하는 쿠키 지우기 + 설명 + 코멘트 + 해결 + 잠금 화면 배경과 알림 모두에 썸네일 사용 + 선택한 구독이 없습니다 + 시청 항목 표시 + 자동 대기열에 추가 + + %s 다운로드 완료 + + 앱 언어 + 예, 부분적으로 본 비디오 + 카테고리 + %s에 의해 + 아래에서 좋아하는 밤 테마를 선택할 수 있습니다 + 이 영상은 유튜브 뮤직 프리미엄 회원만 볼 수 있어 뉴파이프에서 스트리밍이나 다운로드가 불가능합니다. + 이 콘텐츠는 비공개이므로 NewPipe에서 스트리밍하거나 다운로드할 수 없습니다. + 내부 + 처리 중... 시간이 걸릴 수 있습니다 + 재생 로드 간격 크기 + ExoPlayer 제약으로 인해 탐색 시간이 %d초로 설정되었습니다 + 퍼센트 + 반음 + ExoPlayer 기본 + 시스템 기본값 + 새로운 피드 항목 + 그룹화되지 않은 구독만 표시 + \'%s\'에 대한 피드를 불러올 수 없습니다. + 제작자의 계정이 해지되었습니다. +\nNewPipe는 앞으로 이 피드를 불러올 수 없습니다. +\n이 채널의 구독을 취소하겠습니까\? + 빠른 공급 모드는 이에 대한 자세한 정보를 제공하지 않습니다. + 사용 가능한 경우 전용 피드에서 가져오기 + 이 콘텐츠는 유료 사용자만 사용할 수 있으므로 NewPipe에서 스트리밍 또는 다운로드할 수 없습니다. + 지원 + 언어 + 연령 제한 + 개인 + 라이센스 + 창작자의 마음 + 개인 + 비공개 + 썸네일 URL + 호스트 + 이제 이 채널을 구독했습니다 + 알림 받기 + 알림이 비활성화되었습니다 + 웹사이트 열기 + 외부 플레이어에서 사용할 수 있는 오디오 스트림이 없음 + 선택한 스트림은 외부 플레이어가 지원하지 않습니다 + 다운로더에서 아직 지원하지 않는 스트림은 표시되지 않습니다 + 모두 토글 + , + 알 수 없는 품질 + 항목에 원래 시간 전 표시 + 피드를 불러오는 중 오류 + LeakCanary를 사용할 수 없습니다 + 메모리 누수 표시 + 피드 업데이트 임계값 + 마지막 업데이트 후 구독이 오래된 것으로 간주되기 전의 시간 — %s + 항상 업데이트 + 빠른 모드 비활성화 + 빠른 모드 활성화 + %s에 의해 제작 + 채널의 아바타 썸네일 + + 최근 + 계정이 해지됨 + %s은(는) 다음과 같은 이유를 제공: + 이것은 적어도 귀하의 국가에서 SoundCloud Go+ 트랙이므로 NewPipe에서 스트리밍하거나 다운로드할 수 없습니다. + 자동 (장치 테마) + 고정된 댓글 + 추천 + 향후 동영상 표시 + 알 수 없는 형식 + 외부 플레이어의 품질 선택 + 외부 플레이어에 사용할 수 있는 비디오 스트림이 없음 + 스트림 작성자, 스트림 콘텐츠 또는 검색 요청에 대한 추가 정보가 있는 메타 정보 상자를 숨기려면 끄세요 + 로드 간격 크기를 변경합니다 (현재 %s). 값이 낮을수록 초기 비디오 로딩 속도가 빨라질 수 있습니다. 변경하려면 플레이어를 다시 시작해야 합니다. + 충돌에 대해 논의하는 문제가 이미 존재하는지 확인하세요. 중복 티켓을 생성할 때 실제 버그를 수정하는 데 시간을 할애할 수 있습니다. + 오류 알림 생성 + 구독 선택 + 일부 서비스에서 사용할 수 있으며 일반적으로 훨씬 빠르지만 제한된 양의 항목과 종종 불완전한 정보를 반환할 수 있습니다 (예: 기간 없음, 항목 유형, 라이브 상태 없음) + 안드로이드 10부터 \'저장영역 접속 프레임워크\'만 지원됩니다 + 재생 목록에 추가되기 전과 후에 시청한 동영상은 제거됩니다. +\n확실합니까\? 이것은 취소 할 수 없습니다! + 미니 플레이어에서 동영상을 시작하지 말고 자동 회전이 잠겨 있는 경우 전체 화면 모드로 직접 전환하십시오. 전체 화면을 종료하여 미니 플레이어에 계속 접속할 수 있습니다 + 공식 + 라디오 + 설명에서 텍스트 선택 활성화 + 이 콘텐츠는 귀하의 국가에서 사용할 수 없습니다. + 좋아하는 밤 테마 선택 — %s + 다운로드가 시작되었습니다 + 이제 설명 내에서 텍스트를 선택할 수 있습니다. 선택 모드에서는 페이지가 깜박이고 링크를 클릭할 수 없는 경우가 있습니다. + 이 작업에 적합한 파일 관리자를 찾을 수 없습니다. +\n저장영역 접속 프레임워크 호환 파일 관리자를 설치하십시오. + 이 작업에 적합한 파일 관리자를 찾을 수 없습니다. +\n파일 관리자를 설치하거나 다운로드 설정에서 \'%s\'을(를) 비활성화하세요. + 피드 로딩이 너무 느리다고 생각하십니까\? 그렇다면 빠른 로딩을 활성화해 보십시오 (설정에서 변경하거나 아래 버튼을 눌러 변경할 수 있습니다). +\n +\nNewPipe는 두 가지 피드 로딩 전략을 제공합니다: +\n• 느리지만 완전한 전체 구독 채널을 가져옵니다. +\n• 빠르지만 일반적으로 완전하지는 않은 전용 서비스 엔드포인트를 사용합니다. +\n +\n둘의 차이점은 빠른 동영상은 일반적으로 항목의 길이나 유형(라이브 동영상과 일반 동영상을 구분할 수 없음)과 같은 일부 정보가 부족하고 더 적은 항목을 반환할 수 있다는 것입니다. +\n +\n유튜브는 RSS 피드로 이 빠른 방법을 제공하는 서비스의 한 예입니다. +\n +\n따라서 선택은 속도 또는 정확한 정보 중에서 선호하는 것으로 귀결됩니다. + 이 기능은 아직 NewPipe에서 지원하지 않습니다. +\n +\n이후 버전에서 지원될 예정입니다. \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index c6d7697c5..dfcccd1ae 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -121,7 +121,7 @@ Ongeldige tekens worden vervangen door deze waarde Vervangend teken Letters en cijfers - Speciaalste tekens + Speciale tekens Abonneren Geabonneerd Abonnement opgezegd @@ -559,7 +559,7 @@ Derde actieknop Tweede actieknop Eerste actieknop - Schaal de miniatuurafbeelding van de video die getoond wordt in de notificatie van verhouding 16:9 naar 1:1 (dit kan vervorming creëren) + Schaal de miniatuurafbeelding van de video die getoond wordt in de notificatie van verhouding 16:9 naar 1:1 Schaal de miniatuurafbeelding tot verhouding 1:1 Automatisch in de wachtrij plaatsen Toon memory leaks @@ -709,12 +709,13 @@ Je bent nu geabonneerd op dit kanaal Alle gedownloade bestanden van schijf wissen\? - De gekozen stream wordt niet ondersteund door externe spelers + De geselecteerde stream wordt niet ondersteund door externe spelers Er zijn geen videostreams beschikbaar voor externe spelers Onbekende kwaliteit - Streams die nog niet kunnen worden opgeslagen, worden niet getoond - Geen audiostreams beschikbaar voor externe spelers - Kies kwaliteit voor externe spelers - Onbekend bestandstype + Streams die niet ondersteund worden door de downloader, worden niet getoond + Er zijn geen geluidsstreams beschikbaar voor externe spelers + Selecteer kwaliteit voor externe spelers + Onbekend formaat Intervalgrootte tijdens afspelen + Toon toekomstige video\'s \ No newline at end of file diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 1f6b75501..ebcda3f21 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -560,8 +560,8 @@ Terceiro botão de ação Segundo botão de ação Primeiro botão de ação - Ajustar miniatura de vídeo mostrada na notificação de 16:9 para 1:1 (pode introduzir distorções) - Ajustar miniatura à proporção de 1:1 + Cortar a miniatura de vídeo mostrada na notificação de 16:9 a 1:1 + Cortar miniatura na proporção 1:1 Mostrar \'leaks\' de memória Colocado na fila Colocar na fila diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 86306377f..5aa3d4f4d 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,21 +1,21 @@ Publicado em %1$s - Reprodutor de vídeo não encontrado. Instalar o VLC\? + Não tem um reprodutor de vídeo. Instalar o VLC\? Instalar Cancelar Abrir no navegador - Compartilhar - Baixar + Partilhar + Descarregar Pesquisar Definições - Você quis dizer \"%1$s\"\? - Compartilhar com + Será que queria dizer \"%1$s\"\? + Partilhar com Utilizar reprodutor de vídeo externo Utilizar reprodutor de áudio externo - Pasta para os arquivos de vídeo - Os arquivos de vídeo baixados serão guardados aqui - Escolha a pasta para colocar os arquivos de vídeo + Pasta para os ficheiros de vídeo + Os ficheiros de vídeo descarregados serão guardados aqui + Escolha a pasta para colocar os ficheiros de vídeo Resolução padrão Reproduzir no Kodi Instalar Kore\? @@ -38,9 +38,9 @@ Aparência Reprodução em segundo plano Erro de rede - Pasta para arquivos de áudio - Os arquivos de áudio baixados serão guardados aqui - Escolha a pasta para colocar os arquivos de áudio + Pasta para ficheiros de áudio + Os ficheiros de áudio descarregados serão guardados aqui + Escolha a pasta para colocar os ficheiros de áudio Erro Não foi possível carregar todas as miniaturas Não foi possível decifrar a assinatura do URL @@ -60,7 +60,7 @@ Vídeo Áudio Tentar novamente - Toque nesta belíssima lupa para começar. + Toque na lupa para começar. Em direto Transferências Transferências @@ -96,7 +96,7 @@ Resolução padrão para janela popup Mostrar resoluções mais altas Apenas alguns dispositivos conseguem reproduzir vídeos em 2K/4K - Pop-up + Popup Lembrar propriedades do popup Limpar Segundo plano @@ -110,18 +110,18 @@ © %1$s de %2$s nos termos da %3$s Acerca Licenças - Aplicação livre de reprodução de emissões para Android. + Aplicação livre de reprodução de transmissões para Android. Ver no GitHub Licença do NewPipe Se tem ideias para: tradução, alterações de desenho, limpeza de código, ou alterações significativas no código fonte - todas as ajudas são bem-vindas. Quanto mais se faz, melhor ficará! Ver licença Participar - Inscrever-se - Inscrito - Canal não inscrito - Não foi possível alterar a inscrição - Não foi possível atualizar a inscrição - Inscrições + Subscrever + Subscrito + Canal não subscrito + Não foi possível alterar a subscrição + Não foi possível atualizar a subscrição + Subscrições Novidades Histórico de pesquisa Guardar termos de pesquisa localmente @@ -140,16 +140,19 @@ Sem subscritores %s subscritor + %s subscritores %s subscritores Sem visualizações %s visualização + %s visualizações %s visualizações Sem vídeos %s vídeo + %s vídeos %s vídeos Transferências @@ -197,7 +200,7 @@ Mudar nome Doar Não foi encontrado um reprodutor (pode instalar o VLC para reproduzir). - Baixar arquivo de vídeo + Descarregar ficheiro de vídeo Adicionar a Utilizar pesquisa rápida A pesquisa inexata permite que esta seja mais rápida, mas reduz a precisão. Procurar por 5, 15 ou 25 segundos não funciona corretamente @@ -206,8 +209,8 @@ Cache de imagens limpa País padrão para conteúdo Depuração - Não foram encontradas emissões de vídeo - Não foram encontradas emissões de áudio + Não foram encontrados vídeos + Não foram encontrados áudios Pasta inexistente Fonte de conteúdo/ficheiro inexistente O ficheiro não existe ou não tem permissões para ler e/ou escrever @@ -301,7 +304,7 @@ O projeto NewPipe leva a sua privacidade muito a sério. Por isso, não recolhe nenhum dado sem o seu consentimento. \nA polícia de privacidade do NewPipe explica, em detalhe, os tipos de dados enviados sempre que submete um relatório de erro. Ver política de privacidade - Enfileirar o próximo stream automaticamente + Adicionar o próximo vídeo à fila automaticamente NewPipe é um software livre \"copyleft\": pode utilizar, estudar, partilhar e melhorar a aplicação. Especificamente, pode redistribuir e/ou modificar a aplicação nos termos da GNU General Public License, conforme publicada pela Free Software Foundation, tanto a versão 3 da licença ou (por opção) qualquer versão posterior. Também deseja importar as definições\? Toque longo para colocar na fila @@ -333,11 +336,11 @@ Nenhuma Ativar reprodutor em segundo plano Ativar reprodutor \'popup\' - Desinscrever-se + Cancelar subscrição Escolher separador - Gestos para controle de volume + Gestos para controlo de volume Utilizar gestos para controlar o volume do reprodutor - Gestos para controle de brilho + Gestos para controlo de brilho Utilizar gestos para controlar o brilho do reprodutor Atualizações Ficheiro eliminado @@ -425,11 +428,13 @@ Ninguém está a ver %s a ver + %s a ver %s a ver Ninguém está a ouvir %s ouvinte + %s ouvintes %s ouvintes O idioma será alterado assim que reiniciar a app @@ -479,8 +484,9 @@ Deseja apagar este grupo\? O nome do grupo está vazio - %d selecionada - %d selecionadas + %d selecionado + %d selecionados + %d selecionados Nenhuma subscrição selecionada Selecionar subscrições @@ -491,18 +497,22 @@ Grupos de canais %d dia + %d dias %d dias %d hora + %d horas %d horas %d minuto + %d minutos %d minutos %d segundo + %d segundos %d segundos Devido às restrições de ExoPlayer, a duração da pesquisa foi definida para %d segundos @@ -526,7 +536,7 @@ Sim e também os vídeos parcialmente vistos Remover vídeos visualizados\? Remover visualizados - Os textos originais dos serviços serão visíveis nos itens de fluxo + Os textos originais dos serviços serão visíveis nos itens do vídeo Mostrar antiguidade nos itens Ativar \"Modo restrito\" do YouTube Por %s @@ -539,21 +549,21 @@ Verifique se o seu erro já foi reportado. A criação de erros em duplicado tira-nos tempo que pode ser utilizado para corrigir os erros. Reportar no GitHub Copiar relatório formatado - Mostrando resultados para: %s + A mostrar resultados para: %s Quarto botão de ação Terceiro botão de ação Segundo botão de ação Primeiro botão de ação - Cortar a miniatura de vídeo mostrada na notificação de 16:9 para 1:1 + Ajustar miniatura de vídeo mostrada na notificação de 16:9 para 1:1 (pode introduzir distorções) Cortar miniatura na proporção 1:1 Iniciar reprodução automaticamente — %s Reproduzir fila Nunca - Carregando + A carregar A fila do reprodutor ativo será substituída URL não reconhecido. Abrir com outra aplicação\? Enfileiramento automático - Embaralhar + Baralhar Apenas em Wi-Fi Nada Mudar de um reprodutor para outro pode substituir a sua fila @@ -590,7 +600,7 @@ Abrir com A app travou Este vídeo tem uma restrição de idade. -\nDevido às novas políticas do YouTube com vídeos com restrição de idade, o NewPipe não pode acessar nenhum dos seus fluxos de vídeo, portanto, é incapaz de reproduzi-lo. +\nDevido às novas políticas do YouTube quanto a vídeos com restrição de idade, o NewPipe não pode aceder as estes vídeos, por isso não consegue reproduzi-lo. Este conteúdo só está disponível para utilizadores que pagaram, portanto não pode ser transmitido ou descarregado pelo NewPipe. Este vídeo está disponível apenas para os membros do YouTube Music Premium, portanto não pode ser transmitido ou descarregado pelo NewPipe. Este conteúdo é privado, portanto não pode ser transmitido ou descarregado pelo NewPipe. @@ -651,18 +661,20 @@ Sugestões de pesquisa remotas Sugestões de pesquisa locais - %1$s descarga apagada - %1$s descargas apagadas + %1$s descarga eliminada + %1$s descargas eliminadas + %1$s descargas eliminadas Descarga concluída + %s descargas concluídas %s descargas concluídas Deslizar itens para removê-los Não iniciar vídeos no reprodutor mini, mas ir diretamente ao ecrã completo se a rotação automática estiver bloqueada. Ainda pode aceder o reprodutor mini se sair do modo de ecrã completo Iniciar reprodutor principal em ecrã completo Enfileirado o próximo - Enfileirar o próximo + Pôr na fila o próximo A processar… Pode demorar um momento Procurar atualizações Verificar manualmente se existe uma nova versão @@ -678,31 +690,32 @@ Mostrar um snackbar de erro Criar uma notificação de erro Nenhum gestor de ficheiros apropriado foi encontrado para esta ação. -\nPor favor, instale um gestor de ficheiros ou tente desativar \'%s\' nas configurações de descarregar. +\nPor favor, instale um gestor de ficheiros ou tente desativar \'%s\' nas configurações de descarregar Nenhum gestor de ficheiros apropriado foi encontrado para esta ação. -\nPor favor, instale um gestor de ficheiros compatível com o Storage Access Framework. +\nPor favor, instale um gestor de ficheiros compatível com o Storage Access Framework Comentário fixado LeakCanary não está disponível Predefinido do ExoPlayer - Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar. + Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar Notificação do reprodutor - Configurar a notificação da reprodução do fluxo atual + Configurar a notificação da reprodução do vídeo atual Notificações - A carregar detalhes do fluxo… - Verificar se há novos fluxos - Notificações sobre novos fluxos - Notificar sobre novos fluxos de assinaturas + A carregar detalhes do vídeo… + Verificar se há novos vídeos + Notificações sobre novos vídeos + Notificar sobre novos vídeos nas assinaturas Frequência da verificação Conexão de rede necessária Qualquer rede Agora assinou este canal Alternar tudo Apagar todos os ficheiros descarregados do disco\? - Novos fluxos - Notificações sobre novos fluxos para assinaturas + Novos vídeos + Notificações sobre novos vídeos para assinaturas - %s fluxo novo - %s fluxos novos + %s vídeo novo + %s vídeos novos + %s vídeos novos Seja notificado Notificações são desativadas diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 50e0b4d48..24f023c81 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -663,7 +663,7 @@ Высокое качество (крупнее) Миниатюра над полосой прокрутки Автору видео понравилось это - Пометить как проигранные + Пометить проигранными Picasso: указать цветом источник изображений (красный — сеть, синий — диск, зелёный — память) Цветные метки на изображениях Серверные предложения поиска @@ -707,31 +707,31 @@ Вызвать сбой плеера Уведомление отчёта об ошибке Уведомления для отчётов об ошибках - NewPipe столкнулся с ошибкой, нажмите для отчёта + Ошибка NewPipe, нажмите для отчёта Произошла ошибка, подробнее в уведомлении - Показать ошибку снекбара - Создать уведомление об ошибке - Для этого действия не найдено подходящего файлового менеджера. -\nПожалуйста, установите файловый менеджер, или попробуйте отключить \'%s\' в настройках загрузок. - Для этого действия не найдено подходящего файлового менеджера. -\nПожалуйста, установите файловый менеджер, совместимый со Storage Access Framework (SAF). + Показать снэк-бар с ошибкой + Показать уведомление об ошибке + Не найден подходящий для этого действия файловый менеджер. +\nУстановите файловый менеджер, или попробуйте отключить \'%s\' в настройках загрузок + Не найден подходящий для этого действия файловый менеджер. +\nУстановите файловый менеджер, совместимый с Storage Access Framework Закреплённый комментарий LeakCanary недоступна Стандартное значение ExoPlayer - Изменить размер предварительной загрузки (сейчас %s). Меньшее значение может ускорить загрузку видео. При изменении требуется перезапуск плеера. - Загрузка деталей трансляции… - Проверить на наличие новых трансляций + Изменить размер предварительной загрузки (сейчас %s). Меньшее значение может ускорить загрузку видео. При изменении требуется перезапуск плеера + Загрузка сведений о трансляции… + Проверить наличие новых трансляций Удалить все загруженные файлы\? Уведомления плеера , Полутон Проценты - Выбранная трансляция не поддерживается внешними проигрывателями - Нет ни одного доступного видео потока для внешних проигрывателей + Выбранный поток не поддерживается внешними плеерами + Нет видеопотоков, доступных внешним плеерам Были скрыты трансляции, которые пока ещё не поддерживаются загрузчиком Неизвестный формат - Нет ни одного доступного аудио потока для внешних проигрывателей - Выберите качество для внешних проигрывателей + Нет аудиопотоков, доступных внешним плеерам + Выберите качество для внешних плееров Неизвестное качество Размер предварительной загрузки Показывать будущие видео diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 3d7888432..799ea4288 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -570,8 +570,8 @@ Akčné tlačidlo tri Akčné tlačidlo dva Akčné tlačidlo jedna - Zmeniť pomer strán videa zobrazovaného v miniatúre z 16:9 na 1:1 (čo môže spôsobovať skreslenie) - Zmenšiť pomer strán miniatúry na 1: 1 + Orezať pomer strán videa zobrazovaného v miniatúre z 16:9 na 1:1 + Orezať pomer strán miniatúry na 1: 1 Zobraziť memory leaks Zaradené do poradia Zaradiť do poradia @@ -681,7 +681,7 @@ Kontrolujú sa aktualizácie… Nové položky informačného kanála Pre túto akciu sa nenašiel vhodný správca súborov. -\nNainštalujte si správcu súborov alebo skúste vypnúť \'%s\' v nastaveniach sťahovania. +\nNainštalujte si správcu súborov alebo skúste vypnúť \'%s\' v nastaveniach sťahovania Zobraziť „zlyhať prehrávač“ LeakCanary nie je k dispozícii Pre túto akciu sa nenašiel vhodný správca súborov. @@ -696,7 +696,7 @@ Zobraziť krátke oznámenie chyby Oznámte chybu ExoPlayer preddefinovaný - Zmeniť interval načítania (aktuálne %s). Menšia hodnota môže zvýšiť rýchlosť prvotného načítania videa. Zmena vyžaduje reštart. + Zmeniť interval načítania (aktuálne %s). Menšia hodnota môže zvýšiť rýchlosť prvotného načítania videa. Zmena vyžaduje reštart Upozornenia Frekvencia kontroly Vymazať všetky stiahnuté súbory z disku\? @@ -730,4 +730,5 @@ Streamy nepodporované sťahovačom sa nezobrazujú Žiadne video streamy nie sú k dispozícií pre externé prehrávače Neznáma kvalita + Zobraziť budúce videá \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index daf2a4485..9a57d7b20 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -676,34 +676,34 @@ NewPipe stötte på ett fel, tryck för att rapportera Ett fel uppstod, se aviseringen Visa en fel snackbar - Skapa en fel avisering + Skapa en felavisering Ingen lämplig filhanterare hittades för denna åtgärd. -\nInstallera en filhanterare eller testa att inaktivera \'%s\' i nedladdningsinställningarna. +\nInstallera en filhanterare eller testa att inaktivera \'%s\' i nedladdningsinställningarna Ingen lämplig filhanterare hittades för denna åtgärd. -\nInstallera en filhanterare som är kompatibel med Storage Access Framework. +\nInstallera en filhanterare som är kompatibel med Storage Access Framework Fäst kommentar LeakCanary är inte tillgänglig ExoPlayer standard - Ändra inläsningsintervallets storlek (för närvarande %s). Ett lägre värde kan påskynda den första videoinläsningen. Ändringar kräver omstart av spelaren. - Validera frekvens - Kräver nätverksanslutning + Ändra inläsningsintervallets storlek (för närvarande %s). Ett lägre värde kan påskynda den första videoinläsningen. Ändringar kräver omstart av spelaren + Uppdateringsintervall + Nödvändig nätverksanslutning Alla nätverk Radera alla nedladdade filer från disken\? - Notifikationer är avstängda + Aviseringar är avstängda Bli meddelad Du har nu prenumenerat till denna kanalen - Notifikationer om nya strömmar för prenumenanter + Avisering om nya strömmar för prenumenanter %s Ny ström %s Nya strömmar Konfigurera meddelande om aktuell ström som spelas upp Kör leta efter nya strömmar - Meddela om nya strömmar från prenumeranter - Notifikationer + Meddela om nya strömmar från prenumerationer + Aviseringar Nya strömmar Laddar strömdetaljer… - Nya strömmnings notifikationer + Avisering om nya strömmar , Spelaravisering Växla alla diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 827da452a..694670be8 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -207,8 +207,8 @@ மூன்றாம் செயல் பொத்தான் இரண்டாம் செயல் பொத்தான் முதல் செயல் பொத்தான் - அறிவிப்பில் தெரியும் காணொளி சிறுபடத்தை 16: 9 முதல் 1: 1 அம்ச விகிதம் வரை அளவிடு (சிதைவுகளை அறிமுகப்படுத்தலாம்) - சிறுபடத்தை 1: 1 அம்ச விகிதத்திற்கு அளவிடு + அறிவிப்பில் தெரியும் காணொளி முகப்புபடத்தை 16: 9 முதல் 1: 1 அம்ச விகிதம் வரை அளவிட்டு பிரித்து எது + அட்டைப் படத்தை 1:1 விகிதத்தில் செதுக்கவும் %s :க்கான முடிவுகளைக் காட்டுகிறது இதனுடன் திற பற்றி @@ -359,4 +359,5 @@ சந்தாக்களைத் தேர்ந்தெடு செல்லா வரியுருக்கள் இம்மதிப்புடன் மாற்றீடுசெய்யப்படும் நயமான \'திற\' செயல் + பின்னணி சுழர்சி யின் கால அலவு \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 072b92193..a20a88181 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -675,15 +675,15 @@ Hata bildirimi oluştur Hata balonu göster Bu eyleme uygun dosya yönetici yok. -\nLütfen dosya yönetici kurun veya indirme ayarlarında \'%s\' devre dışı bırakın. +\nLütfen dosya yönetici kurun veya indirme ayarlarında \'%s\' devre dışı bırakın Bu eyleme uygun dosya yönetici bulunamadı. -\nLütfen Depolama Erişimi Çerçevesi uyumlu dosya yönetici kurun. +\nLütfen Depolama Erişimi Çerçevesi uyumlu dosya yönetici kurun Hata raporları için bildirimler Oynatıcı kullanırken çöktürme seçeneği gösterir Oynatıcıyı çöktür Sabitlenmiş yorum LeakCanary yok - Yükleme ara boyutunu değiştir (şu anda %s). Düşük bir değer videonun ilk yüklenişini hızlandırabilir. Değişiklikler oynatıcının yeniden başlatılmasını gerektirir. + Yükleme ara boyutunu değiştir (şu anda %s). Düşük bir değer videonun ilk yüklenişini hızlandırabilir. Değişiklikler oynatıcının yeniden başlatılmasını gerektirir ExoPlayer öntanımlısı Yeni akış bildirimleri Bildirimler diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 835c87f37..882b3acc8 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -692,15 +692,15 @@ Сталася помилка NewPipe, торкніться, щоб звітувати Сталася помилка. Перегляньте сповіщення Для цієї дії не знайдено відповідного файлового менеджера. -\nУстановіть файловий менеджер або спробуйте вимкнути «%s» у налаштуваннях завантажень. +\nУстановіть файловий менеджер або спробуйте вимкнути «%s» у налаштуваннях завантажень Для цієї дії не знайдено відповідного файлового менеджера. -\nУстановіть файловий менеджер, сумісний зі Storage Access Framework. +\nУстановіть файловий менеджер, сумісний зі Storage Access Framework Показати панель помилок Створити сповіщення про помилку Закріплений коментар LeakCanary недоступний Типовий ExoPlayer - Змінити розмір інтервалу завантаження (наразі %s). Менше значення може прискорити початкове завантаження відео. Зміни вимагають перезапуску програвача. + Змінити розмір інтервалу завантаження (наразі %s). Менше значення може прискорити початкове завантаження відео. Зміни вимагають перезапуску програвача Ви підписалися на цей канал , Сповіщення про нові трансляції для підписок diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 3a933ef98..0c2b7c726 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -6,7 +6,7 @@ Cài đặt Hủy Mở trong trình duyệt - Mở trong chế độ popup + Mở bằng chế độ popup Chia sẻ Tải về Tìm kiếm @@ -38,7 +38,7 @@ Chủ đề Sáng Tối - Đen + Đen (Amoled) Nhớ thuộc tính của popup Nhớ kích thước và vị trí lần trước của popup Đề xuất tìm kiếm @@ -47,7 +47,7 @@ Hiện video \"Tiếp theo\" và \"Tương tự\" URL không được hỗ trợ Hiển thị - Phát ở chế độ nền + Phát ở dưới nền Phát ở chế độ popup Nội dung Hiển thị nội dung bị giới hạn độ tuổi @@ -111,7 +111,7 @@ Trình phát video nhẹ và mã nguồn mở cho Android. Xem trên GitHub Giấy phép của NewPipe - Sự đóng góp của bạn luôn được hoan nghênh – kể cả khi bạn dịch, thay đổi giao diện, dọn code hay thay đổi những thứ khác, sự giúp đỡ của bạn vẫn đáng được trân trọng. Bạn càng làm nhiều, ứng dụng này sẽ càng tốt hơn bao giờ hết (Miễn đừng dịch vớ vẩn là được, nhé :]] )! + Sự đóng góp của bạn luôn được hoan nghênh – kể cả khi bạn dịch, thay đổi giao diện, dọn code hay thay đổi những thứ khác, sự giúp đỡ của bạn vẫn đáng được trân trọng. Bạn càng làm nhiều, ứng dụng này sẽ càng tốt hơn bao giờ hết (Miễn đừng dịch vớ vẩn là được, nhé) ! Đọc giấy phép Đóng góp Ngôn ngữ nội dung ưu tiên @@ -146,7 +146,7 @@ Theo dõi các video đã xem Tiếp tục phát Tiếp tục phát sau khi bị gián đoạn (ví dụ: cuộc gọi điện thoại) - Hiển thị mẹo \"Giữ để thêm vào hàng đợi\" + Hiển thị \"Giữ để thêm vào hàng đợi\" Hiển thị mẹo khi nhấn nút phát trong nền hoặc phát trên popup trong trang \"Chi tiết\" Quốc gia nội dung mặc định Phát @@ -172,11 +172,11 @@ Ghi đè lịch sử, kênh đăng ký, playlist hiện tại (và cài đặt, nếu có) của bạn Xuất lịch sử, danh sách đăng ký, playlist và cài đặt Xóa lịch sử xem - Xóa lịch sử các luồng đã phát và vị trí phát + Xóa lịch sử những video đã xem và vị trí phát Xóa toàn bộ lịch sử xem\? Lịch sử xem đã bị xóa Xóa lịch sử tìm kiếm - Xóa lịch sử của từ khóa tìm kiếm + Xóa lịch sử tìm kiếm mà bạn đã ghi Xóa toàn bộ lịch sử tìm kiếm\? Đã xóa lịch sử tìm kiếm Không thể phát video này @@ -217,12 +217,12 @@ Hầu hết các ký tự đặc biệt Không có ứng dụng nào được cài đặt để phát tệp này Đóng góp - NewPipe được phát triển bởi các tình nguyện viên dành thời gian và tâm huyết của mình để mang lại cho bạn trải nghiệm tốt nhất. Đóng góp một chai nước suối để giúp chúng tôi làm NewPipe tốt hơn nữa. - Ủng hộ một chai nước suối + NewPipe được phát triển bởi các tình nguyện viên dành thời gian và tâm huyết của mình để mang lại cho bạn trải nghiệm tốt nhất. Đóng góp một chút xiền để giúp chúng tôi làm NewPipe tốt hơn nữa (Nếu bạn muốn). + Đôn Nét Trang web - Truy cập trang web NewPipe để biết thêm thông tin và tin tức. + Truy cập website chính thức của NewPipe để biết thêm thông tin và tin tức. Chính sách bảo mật của NewPipe - Dự án NewPipe rất coi trọng quyền riêng tư của bạn. Do đó, ứng dụng không thu thập bất kỳ dữ liệu nào mà không có sự đồng ý của bạn. + NewPipe rất coi trọng quyền riêng tư của bạn. Do đó, ứng dụng không thu thập bất kỳ dữ liệu nào mà không có sự đồng ý của bạn. \nChính sách bảo mật của NewPipe giải thích chi tiết dữ liệu nào được gửi và lưu trữ khi bạn gửi báo cáo sự cố. Đọc chính sách bảo mật NewPipe là phần mềm miễn phí copyleft: Bạn có thể sử dụng, nghiên cứu, chia sẻ và cải thiện nó theo ý của bạn. Nói cụ thể hơn, bạn có thể phân phối lại và/hoặc sửa đổi nó theo các điều khoản trong Giấy phép Công cộng GNU (GPL) được xuất bản bởi Quỹ Phần mềm Tự do (FSF), theo phiên bản 3 hoặc bất kì phiên bản nào sau này của Giấy phép (tùy ý bạn). @@ -279,7 +279,7 @@ Thu phóng Tự động tạo ra Phụ đề - Sửa cỡ chữ và kiểu màu nền phụ đề. Yêu cầu khởi động lại ứng dụng để có hiệu lực + Sửa cỡ chữ, màu chữ và kiểu màu nền phụ đề. Hãy khởi động lại ứng dụng để áp dụng Theo dõi rò rỉ bộ nhớ có thể khiến ứng dụng trở nên không phản hồi khi đổ xô đống Báo các lỗi out-of-lifecycle Buộc báo cáo ngoại lệ Rx không thể gửi được bên ngoài vòng đời của mảnh hoặc hoạt động sau khi xử lý @@ -553,15 +553,15 @@ Chỉnh ảnh thu nhỏ thành tỉ lệ 1:1 Đang hiện kết quả cho: %s Thêm vào danh sách đang phát - Hiển thị nội dung không an toàn cho trẻ em vì có giới hạn độ tuổi (18+) + Hiển thị nội dung không phù hợp vì có giới hạn độ tuổi (18+) Hiện ảnh thu nhỏ (thumbnail) trên nền màn hình khóa và trong thông báo Xem hình thu nhỏ Kiểm tra bộ nhớ Đã thêm vào danh sách đang phát - Xoá Cookie mà NewPipe lưu trữ sau khi bạn vượt + Xoá Cookie mà NewPipe lưu trữ sau khi bạn hoàn thành nó Cookie reCAPTCHA đã được xóa Xóa bỏ Cookie của reCAPCHA - YouTube cung cấp \"Chế độ hạn chế\" để ẩn nội dung người lớn + YouTube cung cấp \"Chế độ hạn chế\" để ẩn nội dung không phụ hợp Yêu cầu Android tùy chỉnh màu của thông báo theo màu chính của ảnh thu nhỏ (lưu ý rằng việc này không khả dụng trên tất cả thiết bị) Tô màu thông báo Thiết bị của bạn không có ứng dụng để mở tệp này diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c7f91b7ad..d1c28c87a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -665,13 +665,13 @@ 显示错误警示SnackBar 创建一条错误通知 找不到适合此操作的文件管理器。 -\n请安装一文件管理器或尝试在下载设置中禁用“%s”。 +\n请安装一文件管理器或尝试在下载设置中禁用“%s” 找不到适合此操作的文件管理器。 -\n请安装与存储访问框架(SAF)兼容的文件管理器。 +\n请安装兼容存储访问框架(SAF)的文件管理器 NewPipe 遇到了一个错误,点击此处报告此错误 置顶评论 LeakCanary 不可用 - 更改加载间隔的大小(当前为 %s),较低的值可以加快视频的首次加载速度。更改需要重启播放器。 + 更改加载间隔的大小(当前为 %s),较低的值可以加快视频的首次加载速度。更改需要重启播放器 ExoPlayer 默认 配置当前正在播放的串流的通知 新串流通知 @@ -685,8 +685,7 @@ 通知 新的串流 - %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 9cf2f5ee1..17ed3fc29 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -85,7 +85,7 @@ 預設影片檔案格式 純黑 浮面播緊 - 所有 + 魂&我 App/界面閃退 經過:\\n請求:\\n內容語言:\\n內容國家:\\nApp 語言:\\n服務:\\nGMT 時間:\\n封裝:\\n版本:\\nOS 版本: @@ -308,7 +308,7 @@ 完成咗 無影片 影片播放器 - 僅限用 Wi-Fi 嘅時候 + 淨係用 Wi-Fi 嗰陣 高畫質 (大格啲) 無訂閱者 @@ -503,9 +503,9 @@ 顯示睇過嘅項目 %s 話理由如下: 搵唔到合適嘅檔案總管進行呢個動作。 -\n請安裝一個檔案管理程式,又或者試下喺下載設定度停用「%s」。 +\n請安裝一個檔案管理程式,又或者試下喺下載設定度停用「%s」 搵唔到合適嘅檔案總管進行呢個動作。 -\n請安裝一個與儲存空間存取框架兼容嘅檔案管理程式。 +\n請安裝一個與儲存空間存取框架兼容嘅檔案管理程式 呢部內容限區,喺您所在國家未有提供。 呢首 (至少喺您所在國家而言) 係 SoundCloud Go+ 單曲,因此 NewPipe 未能串流或下載。 呢部內容屬於私人,因此 NewPipe 未能串流或下載。 @@ -591,8 +591,8 @@ 載入緊串流詳細資料… 通知訂閱有新加串流 檢查頻率 - 須要網絡連線 - 不拘任何網絡 + 用咩網絡連線 + 咩網絡都無所謂 收取通知 您而家訂閱咗呢個頻道 全部切換 @@ -649,7 +649,7 @@ 係咪要全部剷走晒播放到邊個位? 百分比 半音 - 更改載入相距大細 (目前係 %s)。細啲或者會加快影片一開波嘅載入。更改要開過個播放器至生效。 + 更改載入斬件大細 (目前係 %s)。細啲或者可以等條片快啲開波。更改要開過個播放器至生效 問過先至將排隊播清零 目前播放器入面嘅排隊播將會清零 加一個站 @@ -662,7 +662,7 @@ 揀選一個站 轉換播放器嘅時候,排隊播可能會清零 NewPipe 係「著佐權」(copyleft) 自由軟件:您可以隨意使用、考究、分享同改進佢。具體而言,您可以依據自由軟件基金會發佈嘅《GNU 通用公眾特許條款》第 3 版或 (按您選擇) 之後任一版本之下嘅條款,重新分發及/或修改呢個軟件。 - 載入相距大細 + 載入斬件大細 互動頁面 預設嘅互動站 輸入 URL 或者您嘅 ID 去匯入 SoundCloud 個人檔案: diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index fb908e841..ba3cd2f98 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -664,15 +664,15 @@ 顯示錯誤 SnackBar 建立錯誤通知 找不到適合此動作的檔案管理程式。 -\n請安裝相容於儲存空間存取框架的檔案管理員。 +\n請安裝相容於儲存空間存取框架的檔案管理員 找不到適合此動作的檔案管理程式。 -\n請安裝檔案管理程式或在下載設定嘗試停用「%s」。 +\n請安裝檔案管理程式或在下載設定嘗試停用「%s」 NewPipe 遇到錯誤,點擊以回報 發生錯誤,請檢視通知 釘選的留言 LeakCanary 無法使用 ExoPlayer 預設值 - 變更載入間隔大小(目前為 %s)。較低的值可能會提昇初始影片載入速度。變更需要重新啟動播放器。 + 變更載入間隔大小(目前為 %s)。較低的值可能會提昇初始影片載入速度。變更需要重新啟動播放器 播放器通知 通知 正在載入串流詳細資訊…… diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8b4a2efcd..e685ca081 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -79,7 +79,7 @@ Inexact seek allows the player to seek to positions faster with reduced precision. Seeking for 5, 15 or 25 seconds doesn\'t work with this Fast-forward/-rewind seek duration Playback load interval size - Change the load interval size (currently %s). A lower value may speed up initial video loading. Changes require a player restart. + Change the load interval size (currently %s). A lower value may speed up initial video loading. Changes require a player restart Ask for confirmation before clearing a queue Switching from one player to another may replace your queue The active player queue will be replaced @@ -695,8 +695,10 @@ Recent Chapters No app on your device can open this - No appropriate file manager was found for this action.\nPlease install a file manager or try to disable \'%s\' in the download settings. - No appropriate file manager was found for this action.\nPlease install a Storage Access Framework compatible file manager. + No appropriate file manager was found for this action. +\nPlease install a file manager or try to disable \'%s\' in the download settings + No appropriate file manager was found for this action. +\nPlease install a Storage Access Framework compatible file manager This content is not available in your country. This is a SoundCloud Go+ track, at least in your country, so it cannot be streamed or downloaded by NewPipe. This content is private, so it cannot be streamed or downloaded by NewPipe. diff --git a/fastlane/metadata/android/cs/changelogs/990.txt b/fastlane/metadata/android/cs/changelogs/990.txt new file mode 100644 index 000000000..f3145df07 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/990.txt @@ -0,0 +1,14 @@ +V této verzi byla zrušena podpora Androidu 4.4 KitKat, nyní je minimální verzí Android 5 Lollipop! + +Nové +• Stahování z nabídky dlouhého stisknutí +• Skrytí nadcházejících videí ve zdroji +• Sdílení místních seznamů skladeb + +Vylepšení +• Přepracování kódu přehrávače na malé komponenty: menší využití RAM, méně chyb +• Vylepšení režimu měřítka miniatur +• Vektorizace zástupných symbolů obrázků + +Opravy +• Oprava různých problémů s oznámeními: neaktuální/chybějící informace o médiích, zkreslené miniatury diff --git a/fastlane/metadata/android/es/changelogs/69.txt b/fastlane/metadata/android/es/changelogs/69.txt new file mode 100644 index 000000000..a2d1c8395 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/69.txt @@ -0,0 +1,19 @@ +### Nuevo +- Mantén pulsado para borrar y/o compartir en Subscripciones #1516 +- Interfaz de Tablet y listas en forma de cuadrícula #1617 + +### Mejoras +- Guarda y usa la última relación de aspecto #1748 +- Activa las listas lineares en las Descargas con los nombres completos #1771 +- Guarda y comparte subscripciones directamente desde la pestaña de subscripciones #1516 +- Poner en cola un video hace que empiece a reproducirse si la cola ya ha acabado #1783 +- Ajustes para gestos separados del el brillo y el volumen #1644 +- Añadido soporte para traducciones #1792 + +### Arreglos +- Arreglada la obtención de fecha para el .format, de modo que NewPipe se puede usar en Finlandia. +- Arreglado el contador de subscriptores +- Añadido permiso para arrancar en primer plano para dispositivos con API +28 #1830 + +### Bugs Conocidos +- El estado de la reproducción no se puede guardar en Android P diff --git a/fastlane/metadata/android/es/changelogs/70.txt b/fastlane/metadata/android/es/changelogs/70.txt new file mode 100644 index 000000000..b5cd027a7 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/70.txt @@ -0,0 +1,25 @@ +ATENCIÓN: Esta versión quizá sea un festival de errores, como la anterior. Sin embargo, debido al cierre total desde la 17. una versión rota es mejor que ninguna versión. ¿Cierto? ¯\_(ツ)_/¯ + +### Mejorías +* los archivos descargados ahora pueden ser abiertos con un solo clic #1879 +* descenso de soporte para android 4.1 - 4.3 #1884 +* eliminar el reproductor antiguo #1884 +* eliminar los flujos de la cola de reproducción actual deslizándolos hacia la derecha #1915 +* eliminar cola de reproducción automática cuando se pone en cola una nueva secuencia manualmente #1878 +* Posprocesamiento para descargas e implementación de características faltantes #1759 por @kapodamy + * Infraestructura de posprocesamiento + * Infraestructura de manejo de errores adecuada (para el descargador) + * Cola en lugar de descargas múltiples +* Mover las descargas pendientes serializadas (archivos `.giga`) hacia datos de aplicación + * Implementar el reintento máximo de descarga + * Pausa adecuada de descargas multihilo +* Detener las descargas cuando se cambia hacia red móvil (nunca funciona, ver 2º punto) +* Guardar el conteo de hilos para las próximas descargas + * Un montón de incoherencias corregidas + +### Corregidos +* Arreglado el fallo con la resolución por defecto ajustada a la mejor y resolución de datos móviles limitada #1835 +* Arreglado el fallo del reproductor emergente #1874 +* NPE al intentar abrir el reproductor en segundo plano #1901 +* Corrección de la inserción de nuevos flujos cuando la cola automática está activada #1878 +* Corregido el problema de descifrado de shuttown diff --git a/fastlane/metadata/android/es/changelogs/740.txt b/fastlane/metadata/android/es/changelogs/740.txt new file mode 100644 index 000000000..6d89a2246 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/740.txt @@ -0,0 +1,23 @@ +

    Mejorías

    +
      +
    • hacer cliqueables enlaces en comentarios, aumentar el tamaño del texto
    • +
    • buscar al hacer clic en enlaces de marca de tiempo en comentarios
    • +
    • mostrar pestaña preferida según el estado seleccionado recientemente
    • +
    • añadir lista de reproducción a cola cuando se hace un clic largo en 'Fondo' en ventana de lista de reproducción
    • +
    • buscar texto compartido cuando no es una URL
    • +
    • añadir botón "compartir en el momento actual" al reproductor de vídeo principal
    • +
    • añadir botón de cierre a reproductor principal cuando la cola de vídeo haya terminado
    • +
    • añadir "Reproducir directamente en segundo plano" a menú de pulsación larga para elementos lista de vídeos
    • +
    • mejorar traducciones a inglés de comandos Reproducir/PonerEnCola
    • +
    • pequeñas mejorías de rendimiento
    • +
    • eliminar archivos no utilizados
    • +
    • actualizar ExoPlayer a 2.9.6
    • +
    • añadir soporte para enlaces Invidious
    • +
    +

    Arreglado

    +
      +
    • arreglado desplazamiento con comentarios y flujos relacionados desactivados
    • +
    • arreglado que TareaBuscarNuevaVersiónDeApp se ejecute cuando no debería
    • +
    • corregida la importación de suscripciones a YouTube: ignorar las que tienen URL inválida y mantener las que tienen el título vacío
    • +arreglar URL inválida de YouTube: nombre de etiqueta de firma no es siempre "firma", lo que impide cargar flujos +
    diff --git a/fastlane/metadata/android/es/changelogs/810.txt b/fastlane/metadata/android/es/changelogs/810.txt new file mode 100644 index 000000000..2f569dc5b --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/810.txt @@ -0,0 +1,19 @@ +Nuevo +- Mostrar la miniatura del vídeo en la pantalla de bloqueo cuando se reproduce en segundo plano + +Mejorado +- Añadir la lista de reproducción local a la cola cuando se hace una pulsación larga en el botón de fondo / emergente +- Hacer que las pestañas de la página principal se puedan desplazar y ocultar cuando sólo hay una pestaña +- Limitar la cantidad de actualizaciones de miniaturas de notificación en el reproductor de fondo +- Añadir una miniatura ficticia para listas de reproducción locales vacías +- Usar la extensión de archivos *.opus en lugar de *.webm y mostrar "opus" en etiqueta de formato en lugar de "WebM Opus" en menú desplegable de descargas +- Añadir un botón para eliminar archivos descargados o el historial de descargas en "Descargas" +- YouTube] Añadir soporte a los enlaces de canal /c/shortened_url + +Corregidos +- Corregidos múltiples problemas al compartir un video a NewPipe y al descargar sus secuencias directamente +- Corregido el acceso al reproductor fuera de su hilo de creación +- Corregida la paginación de resultados de búsqueda +- YouTube] Corregido el cambio a nulo que causaba NPE +- YouTube] Corregida la visualización de comentarios al abrir una url de invidio.us +- SoundCloud] Actualizado client_id diff --git a/fastlane/metadata/android/es/changelogs/840.txt b/fastlane/metadata/android/es/changelogs/840.txt new file mode 100644 index 000000000..37c0628a8 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/840.txt @@ -0,0 +1,24 @@ +Nuevo +- Se ha añadido un selector de idioma para cambiar el idioma de la aplicación +- Se ha añadido el botón de enviar a Kodi al menú desplegable del reproductor +- Se ha añadido la posibilidad de copiar comentarios con una pulsación larga + +Mejorado +- Se ha corregido la actividad de ReCaptcha y se han guardado correctamente las cookies obtenidas +- Menú de puntos eliminado en favor de cajón y botón del historial ocultado cuando no está habilitado el historial de reloj en ajustes +- Pedir permiso de visualización sobre otras aplicaciones en ajustes correctamente en Android 6 y posteriores +- Cambiar nombre de lista de reproducción local haciendo un clic largo en MarcadorDePáginasFragmentos +- Varias mejorías en PeerTube +- Mejoría de varias cadenas de origen en inglés + +Corregido +Corregido que reproductor se reinicie aunque esté en pausa con opción "minimizar al cambiar de app" activada y NewPipe está minimizado +- Corregido el valor de brillo inicial para el gesto +- Corregida la descarga de subtítulos .srt que no contienen todos los saltos de línea +- Corregida descarga a tarjeta SD que falla porque algunos dispositivos Android 5 no son compatibles con CTF +- Corregida la descarga en Android KitKat +- Arreglado el archivo de vídeo .mp4 corrupto que era reconocido como archivo de audio +- Corregidos múltiples problemas de localización, incluyendo códigos de idioma chino erróneos +- YouTube] Las marcas de tiempo en la descripción vuelven a ser cliqueables + +Traducción realizada con la versión gratuita del traductor www.DeepL.com/Translator diff --git a/fastlane/metadata/android/es/changelogs/900.txt b/fastlane/metadata/android/es/changelogs/900.txt new file mode 100644 index 000000000..6ae175a17 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/900.txt @@ -0,0 +1,14 @@ +Nuevo +- Grupos de suscriptores y feeds ordenados +- Botón de silencio en reproductores + +Mejora de +- Permitir la apertura de enlaces music.youtube.com y media.ccc.de en NewPipe +- Reubicar dos ajustes de Apariencia a Contenido +- Ocultar opciones de búsqueda de 5, 15 y 25 seg. si activada búsqueda inexacta + +Corregido +- algunos vídeos WebM no pueden ser buscados +- copia de seguridad de base de datos en Android P +- caída al compartir un archivo descargado +- montones de problema de extracción de YouTube y más ... diff --git a/fastlane/metadata/android/es/changelogs/930.txt b/fastlane/metadata/android/es/changelogs/930.txt new file mode 100644 index 000000000..65cd48b98 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/930.txt @@ -0,0 +1,19 @@ +Nuevo +- Búsqueda en YouTube Music +- Soporte básico de Android TV + +Mejorías de +- Añadida la opción de borrar todos los vídeos vistos de lista de reproducción local +- Mostrar mensaje cuando el contenido aún no es compatible en lugar de caída +- Mejora del tamaño del reproductor emergente con gestos de pellizco +- Puesta en cola de flujos con presión prolongada de botones de fondo y emergentes en el canal +- Mejora de la gestión del tamaño del título de la cabecera de cajón + +Corregido +- Arreglado ajuste restringido de contenido por edad que no funciona +- Corregidos ciertos tipos de reCAPTCHAs +- Fallo corregido al abrir marcadores mientras lista de reproducción es "nula". +- Corregida la detección de excepciones relacionadas con la red +- Corregida visibilidad de botón de clasificación de grupos en fragmento de suscripciones + +y más diff --git a/fastlane/metadata/android/es/changelogs/940.txt b/fastlane/metadata/android/es/changelogs/940.txt new file mode 100644 index 000000000..41a09c1a0 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/940.txt @@ -0,0 +1,16 @@ +Nuevos +- Añadir soporte para los comentarios de SoundCloud +- Añadir la configuración del modo restringido de YouTube +- Mostrar los detalles del canal padre de PeerTube + +Mejora de +- Mostrar el botón Kore sólo para los servicios compatibles +- Bloquear gestos de reproductor que inician en NavigationBar o StatusBar +- Cambio color fondo de botones reintento y suscripción según color de servicio + +Arreglados +- Corregido el congelamiento del diálogo de descarga +- El botón "Abrir en navegador" ahora se abre realmente en navegador +- Arreglo de colapso al abrir vídeos y "No se pudo reproducir este flujo" + +y más diff --git a/fastlane/metadata/android/es/changelogs/964.txt b/fastlane/metadata/android/es/changelogs/964.txt new file mode 100644 index 000000000..8de16d21a --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/964.txt @@ -0,0 +1,8 @@ +- Soporte añadido para capítulos en controles del reproductor +- [PeerTube] Añadida la búsqueda de Sepia +- Botón compartir reañadido en vista detalles de vídeo y descripción de secuencia movida a diseño de pestaña +- Desactivar restauración de brillo si gesto de brillo desactivado +- Añadido el elemento de lista para reproducir vídeo en kodi +- Fallo corregido si no hay navegador por defecto en ciertos dispositivos, diálogos compartir mejorados +- Alternar reproducción/pausa con botón de espacio de hardware en reproductor de pantalla completa +- [media.ccc.de] Varias correcciones y mejoras diff --git a/fastlane/metadata/android/es/changelogs/966.txt b/fastlane/metadata/android/es/changelogs/966.txt new file mode 100644 index 000000000..0cb20696c --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/966.txt @@ -0,0 +1,14 @@ +Nuevo: +- Añadir un nuevo servicio: Bandcamp + +Mejorados: +- Añadir una opción para que la app siga el tema del dispositivo +- Evitar algunos colapsos mostrando un panel de error mejorado +- Mostrar más información sobre razón de contenido no disponible +- Botón de espacio de hardware activa reproducción/pausa +- Mostrar brindis de "Descarga iniciada" + +Corregidos: +- Arreglar miniatura chica en detalles de vídeo durante reproducción de fondo +- Arreglar título vacío en el reproductor minimizado +- Arreglar último modo de redimensionamiento no restaurable correctamente diff --git a/fastlane/metadata/android/es/changelogs/968.txt b/fastlane/metadata/android/es/changelogs/968.txt new file mode 100644 index 000000000..6ef82f8d9 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/968.txt @@ -0,0 +1,7 @@ +Opción de detalles de canal añadida a menú de pulsación larga. +Función añadida de cambiar Nombre de Lista de Reproducción desde su interfaz. +Permitir al usuario pausar video almacenando en memoria intermedia. +Se ha pulido el tema blanco. +Solapamiento corregido de fuentes al usar un tamaño mayor de fuente. +Ausencia de video corregida en dispositivos Formuler y Zephier. +Se han corregido varios fallos. diff --git a/fastlane/metadata/android/es/changelogs/969.txt b/fastlane/metadata/android/es/changelogs/969.txt new file mode 100644 index 000000000..7714b1aef --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/969.txt @@ -0,0 +1,8 @@ +- Permitir instalación en almacenamiento externo +- Bandcamp] Soporte añadido para mostrar 3 primeros comentarios en una secuencia +- Sólo mostrar brindis de "descarga iniciada" cuando la descarga inicia +- No establecer la cookie reCaptcha si no hay cookies almacenadas +- [Reproductor] Mejorar el rendimiento de la caché +- [Reproductor] Arreglado reproductor sin reproducción automática +- Descartar Snackbars anteriores al borrar descargas +- Corregido intento de eliminar objetos fuera de lista diff --git a/fastlane/metadata/android/es/changelogs/970.txt b/fastlane/metadata/android/es/changelogs/970.txt new file mode 100644 index 000000000..39bb2e23e --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/970.txt @@ -0,0 +1,11 @@ +Nuevos +- Mostrar metadatos de contenido (etiquetas, categorías, licencia, ...) bajo descripción +- Opción añadida "Mostrar detalles de canal" en listas de reproducción remotas (no locales) +- Opción añadida "Abrir en navegador" en menú de pulsación larga + +Corregidos +- Fallo corregido de rotación en la página de detalles de vídeo +- Botón corregido "Reproducir con Kodi" en reproductor que siempre pide instalar Kore +- Corregido y mejorado ajuste de rutas de importación y exportación +- [YouTube] Corregido el recuento de comentarios preferidos +Y mucho más diff --git a/fastlane/metadata/android/es/changelogs/973.txt b/fastlane/metadata/android/es/changelogs/973.txt new file mode 100644 index 000000000..09576bc99 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/973.txt @@ -0,0 +1,4 @@ +Corrección en caliente +- Corrección de miniaturas y títulos recortados en diseño de cuadrícula, por cálculo erróneo de cuántos vídeos caben en 1 fila +- Corrección de diálogo de descarga que desaparece sin hacer nada si se abre desde menú compartir +- Actualización de biblioteca relacionada con apertura de actividades externas, como selector de archivos de Marco de Acceso a Almacenamiento diff --git a/fastlane/metadata/android/es/changelogs/980.txt b/fastlane/metadata/android/es/changelogs/980.txt new file mode 100644 index 000000000..b33810479 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/980.txt @@ -0,0 +1,13 @@ +Nuevos +- Opción añadida "Añadir a lista de reproducción" a menú compartir +- Soporte añadido para enlaces cortos de y2u.be y PeerTube + +Mejorados +- Controles de velocidad de reproducción más compactos +- Feed destaca ahora nuevos elementos +- Ahora se guarda la opción "Mostrar elementos vistos" en feed + +Corregidos +- Extracción corregida de "likes" y "dislikes" de YouTube +- Repetición automática corregida después de volver del fondo +Y mucho más diff --git a/fastlane/metadata/android/es/changelogs/982.txt b/fastlane/metadata/android/es/changelogs/982.txt new file mode 100644 index 000000000..e38ba90c9 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/982.txt @@ -0,0 +1 @@ +Solución a YouTube no reproduciendo flujos. diff --git a/fastlane/metadata/android/es/changelogs/984.txt b/fastlane/metadata/android/es/changelogs/984.txt new file mode 100644 index 000000000..3460b9754 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/984.txt @@ -0,0 +1,7 @@ +Carga suficientes elementos iniciales en listas para llenar pantalla entera y arreglo desplazamiento en tabletas y televisores +Arreglar fallos aleatorios al desplazarse por las listas +Hacer que arco de superposición de búsqueda rápida de reproductor vaya bajo la IU de sistema +Revertir cambios en cortes al reproducir en multiventana, que causan regresión de reproductor desubicado en teléfonos +Aumentar compileSdk de 30 a 31 +Actualizar la biblioteca de informes de errores +Refactorizar algunos códigos en reproductor diff --git a/fastlane/metadata/android/es/changelogs/985.txt b/fastlane/metadata/android/es/changelogs/985.txt new file mode 100644 index 000000000..80b4efa55 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/985.txt @@ -0,0 +1 @@ +Arreglo en YouTube no reproduciendo flujos diff --git a/fastlane/metadata/android/es/changelogs/986.txt b/fastlane/metadata/android/es/changelogs/986.txt new file mode 100644 index 000000000..1631b5d81 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/986.txt @@ -0,0 +1,16 @@ +Nuevos +- Notificaciones de nuevos flujos +- Transición perfecta entre fondo y reproductores de vídeo +- Cambio de tono por semitonos +- Añadir cola de reproductor principal a lista de reproducción + +Mejorías +- Recordar el tamaño del paso de velocidad/tono +- Mitigar el largo buffering inicial en el reproductor de vídeo +- Mejor interfaz de usuario de reproductor para Android TV +- Confirmar antes de borrar todos los archivos descargados + +Corregidos +- Arreglar botón multimedia no oculta controles de reproductor +- Corregir reinicio reproducción al cambiar tipo de reproductor +- Arreglar rotación de diálogo de lista de reproducción diff --git a/fastlane/metadata/android/es/changelogs/987.txt b/fastlane/metadata/android/es/changelogs/987.txt new file mode 100644 index 000000000..bdad4e10d --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/987.txt @@ -0,0 +1,12 @@ +Nuevos +- Soporta métodos de entrega distintos a HTTP progresivo: tiempo más rápido de carga de reproducción, arreglos PeerTube y SoundCloud, reproducción de livestreams YouTube recién terminados +- Botón para añadir una lista de reproducción remota a una local +- Vista previa de imagen en hoja de compartir de Android 10+ + +Mejorías +- Mejorar diálogo de parámetros de reproducción +- Mover botones de importación/exportación de suscripciones a menú de 3 puntos + +Arreglados +- Arreglar eliminación de vídeos totalmente vistos de lista de reproducción +- Tema corregido de menú compartir y entrada "añadir a lista de reproducción diff --git a/fastlane/metadata/android/es/changelogs/988.txt b/fastlane/metadata/android/es/changelogs/988.txt new file mode 100644 index 000000000..0e1a88e6f --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Arreglo error "No se pudo obtener flujo" al intentar reproducir videos +[YouTube] Arreglo el mensaje "Siguiente contenido no disponible en esta aplicación" en lugar de video solicitado diff --git a/fastlane/metadata/android/es/changelogs/989.txt b/fastlane/metadata/android/es/changelogs/989.txt new file mode 100644 index 000000000..72f3a8098 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/989.txt @@ -0,0 +1,3 @@ +- YouTube] Arreglo carga infinita al tratar de reproducir videos +- YouTube] Arreglo de ralentización de algunos vídeos +- Actualización de biblioteca jsoup a versión 1.15.3, con un arreglo de seguridad diff --git a/fastlane/metadata/android/es/changelogs/990.txt b/fastlane/metadata/android/es/changelogs/990.txt new file mode 100644 index 000000000..217ceaaa9 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/990.txt @@ -0,0 +1,15 @@ +Esta versión deja de soportar Android 4.4 KitKat, ¡ahora la versión mínima es Android 5 Lollipop! + +Nuevos +- Descarga desde menú de pulsación larga +- Ocultar futuros vídeos en feed +- Compartir listas de reproducción locales + +Mejorados +- Refactorización de código de reproductor en componentes pequeños: menos RAM usada, menos errores +- Mejorar el modo de escala de miniaturas +- Vectorizar marcadores de posición de imágenes + +Corregidos +- Arreglos varios con notificación de reproductor: antigua/falta información de medios, miniatura distorsionada +- Arreglo pantalla completa usa 1/4 de pantalla diff --git a/fastlane/metadata/android/fr/changelogs/989.txt b/fastlane/metadata/android/fr/changelogs/989.txt new file mode 100644 index 000000000..6d41c1b78 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/989.txt @@ -0,0 +1,3 @@ +• [YouTube] Correction du chargement infini lors de la lecture d'une vidéo +• [YouTube] Correction de l'accélération de certaines vidéos +• Mise à jour 1.15.3 de la bibliothèque jsoup contenant une correction de sécurité diff --git a/fastlane/metadata/android/fr/changelogs/990.txt b/fastlane/metadata/android/fr/changelogs/990.txt new file mode 100644 index 000000000..fcab79c3c --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/990.txt @@ -0,0 +1,15 @@ +Cette mise à jour abandonne la prise en charge d'Android 4.4 KitKat, la nouvelle version minimum est Android 5 Lollipop ! + +Nouveautés +• Télécharger depuis le menu d'appuis long +• Cacher les futures vidéos dans le flux +• Partager des listes de lecture locales + +Améliorations +• Réusinage du code du lecteur en petits composants : moins de mémoire vive utilisée, moins de bogues +• Meilleur redimension des miniatures +• Vectorisation des emplacements des images + +Corrections +• Notifications +• Plein écran diff --git a/fastlane/metadata/android/hi/short_description.txt b/fastlane/metadata/android/hi/short_description.txt index 9e2aff567..3d76fa533 100644 --- a/fastlane/metadata/android/hi/short_description.txt +++ b/fastlane/metadata/android/hi/short_description.txt @@ -1 +1 @@ -एंड्रॉयड के लिए एक मुफ्त हल्का यूट्यूब फ्रंटएंड। +एंड्रॉयड के लिए एक मुफ्त लाइट यूट्यूब फ्रंटएंड। diff --git a/fastlane/metadata/android/pl/changelogs/990.txt b/fastlane/metadata/android/pl/changelogs/990.txt new file mode 100644 index 000000000..4c292432b --- /dev/null +++ b/fastlane/metadata/android/pl/changelogs/990.txt @@ -0,0 +1,15 @@ +To wydanie znosi wsparcie dla Androida 4.4 KitKat, teraz min. wersja to Android 5 Lollipop! + +Nowe +• Pobier. z menu długiego naciśnięcia +• Ukryw. przyszłych wideo w kanale +• Udostęp. lokalnych playlist + +Ulepszone +• Refaktor. kodu odtwarzacza: mniejsze zużycie RAM-u, mniej błędów +• Skalowanie miniatur +• Wektoryzacja obrazków zastępczych + +Naprawione +• Różne problemy z powiadomieniem odtwarzacza: nieaktualne/brakujące info o multimediach, zniekształcona miniatura +• Pełny ekran zajmujący 1/4 ekranu diff --git a/fastlane/metadata/android/pt/changelogs/65.txt b/fastlane/metadata/android/pt/changelogs/65.txt new file mode 100644 index 000000000..89a006829 --- /dev/null +++ b/fastlane/metadata/android/pt/changelogs/65.txt @@ -0,0 +1,26 @@ +### Melhorias + +- Desativar a animação do ícone do burgermenu #1486 +- Desfazer a eliminação de descarregamentos #1472 +- Opção de descarregamento no menu de partilha #1498 +- Opção de partilha adicionada ao menu de toque longo #1454 +- Minimize o jogador principal na saída #1354 +- Atualização da versão da biblioteca e correção de cópia de segurança da base de dados #1510 +- ExoPlayer 2.8.2 Atualização #1392 +- Retrabalhado a caixa de diálogo de controlo de velocidade de reprodução para suportar diferentes tamanhos de etapa para uma mudança de velocidade mais rápida. +- Adicionado uma alternância para avanço rápido durante silêncios no controle de velocidade de reprodução. Isso deve ser útil para audiolivros e certos géneros musicais, e pode trazer uma experiência verdadeiramente perfeita (e pode quebrar uma música com muitos silêncios =\\). +- Resolução de fonte de média ré fatorada para permitir a passagem de metadados junto com a média internamente no reprodutor, em vez de fazê-lo manualmente. Agora temos uma única fonte de metadados e está disponível diretamente quando a reprodução é iniciada. +- Correção de metadados de listas de reprodução remotas que não são atualizadas quando novos metadados estão disponíveis quando o fragmento da lista de reprodução é aberta. +- Várias correções de interface do utilizador : #1383, controles de notificação do reprodutor em segundo plano agora sempre brancos, mais fácil de desligar o reprodutor pop-up por meio de arremesso +- Use novo extrator com arquitetura ré fatorada para multisserviço + +### Conserta + +- Correção #1440 Layout de informações de vídeo quebrado #1491 +-Ver correção de histórico #1497 +- #1495, atualizando os metadados (miniatura, título e contagem de vídeos) assim que o usuário acessar a lista de reprodução. +- #1475, registando uma visualização na base de dados quando o utilizador inicia um vídeo no reprodutor externo no fragmento de detalhes. +- Correção de tempo limite de criação em caso de modo pop-up. #1463 (Corrigido #640) +- Correção do reprodutor de vídeo principal #1509 +- [#1412] Corrigido o modo de repetição causando NPE do reprodutor quando uma nova intenção é recebida enquanto a atividade do reprodutor está em segundo plano. +- Corrigida a minimização de reprodutor para pop-up não destrói o reprodutor quando a permissão de pop-up não é concedida. diff --git a/fastlane/metadata/android/sk/changelogs/987.txt b/fastlane/metadata/android/sk/changelogs/987.txt index b291ba0d5..45b2d85dd 100644 --- a/fastlane/metadata/android/sk/changelogs/987.txt +++ b/fastlane/metadata/android/sk/changelogs/987.txt @@ -1,12 +1,12 @@ Novinky -• Podpora spôsobov poskytovania iných ako progresívnych HTTP: rýchlejší čas načítania prehrávania, opravy pre PeerTube a SoundCloud, prehrávanie nedávno ukončených priamych prenosov YouTube -• Tlačidlo Pridať na pridanie vzdialeného zoznamu skladieb k lokálnemu -• Ukážka obrázka v zdieľanom hárku so systémom Android 10+ +• Poskytovanie iné než progresívne HTTP: zrýchlené načítanie prehrávania, opravy PeerTube a SoundCloud, prehrávanie nedávno ukončených livestreamov YouTube +• Tlačidlo na pridanie vzdialeného zoznamu k lokálnemu +• Náhľad obrázka v hárku zdieľania v Android 10+ Vylepšenia -• Vylepšené dialógové okno parametrov prehrávania -• Presunuté tlačidlá importu/exportu odberov do ponuky (tri bodky) +• Dialógové okno parametrov prehrávania +• Presunuté tlačidlá import/export odberov do ○○○ menu Opravy -• Oprava odstraňovania kompletne prehraných videí zo zoznamu videí -• Oprava témy v ponuke zdieľania a pri položke „pridať do zoznamu skladieb“ +• Odstraňovanie dokončených videí zo zoznamu videí +• Téma v menu zdieľania a položky „pridať do zoznamu skladieb“ diff --git a/fastlane/metadata/android/sv/changelogs/65.txt b/fastlane/metadata/android/sv/changelogs/65.txt index 89c12c7d2..74d0a2d70 100644 --- a/fastlane/metadata/android/sv/changelogs/65.txt +++ b/fastlane/metadata/android/sv/changelogs/65.txt @@ -2,11 +2,23 @@ - Stängde av burgarmeny ikonens animation #1486 - Ångra radering av nedladdningar #1472 - Nedladdningsalternativ i delningsmenyn #1498 -- La till delningsalternativet i menyn för långa tryckningar #1454 -- Och mer... +- Lade till delningsalternativet i menyn för långa tryckningar #1454 +- Minimerar huvudsplearen vid avslut #1354 +- Uppdatering av biblioteksversion samt åtgärd av databasbackup #1510 +- Uppdatering av ExoPlayer 2.8.2 #1392 + - Omarbetad kontroll för uppspelningshastighet för att stödja olika stegstorlekar för snabbare hastighetsändring. + - Lade till växelkontroll för att snabbspola vid tystnad i uppspelningens hastighetskontroll. Detta borde vara underlätta vid uppspelning av ljudböcker och vissa musikgenres och kan bidra till en sömlös upplevelse ( och kan pajja en låt med massa tystnad =\\). + - Omskrivning av källmedias upplösning för att tillåta samtidig rörelse av metadata internt i spelaren, hellre än att utföra detta manuellt. Nu finns endast en källa för metadata som är omedelbart tillgängig så snart uppspelning sker. + - Åtgärdat att fjärrspellistors metadata inte uppdateras när nytt metadata är tillgänligt vid öppning av spelliststdelar. + - Diverse åtgärder av användargränssnitt: #1383, aviseringar för bakgrundsspelaren är nu alltid vita, lättare att stänga popup-spelare via "flinging" +- Nyttja ny extraherare med omskriven arkitektur för stöd av flera tjänster -### Fixade -- Fixade #1440 Trasig layout för videoinformation #1491 +### Åtgärdade +- Åtgärdade #1440 Trasig layout för videoinformation #1491 - Visningshistorik fix #1497 -- #1495, genom att uppdatera metadata (miniatyrbild, titel och videoantal) så snart användaren får tillgång till spellistan. - #1475 -- Och mer... +- #1495, genom att uppdatera metadata (miniatyrbild, titel och videoantal) så snart användaren får tillgång till spellistan. + - #1475, genom att skapa en vy i databasen när användaren startar en video i extern spelare för detaljfragment. +- Åtgärdade tidsgräns för fönster som är i popup-läge. #1463 (Fixed #640) +- Åtgärd av primär videospelare #1509 + - [#1412] Åtgärdade upprepningsläge vilket orsakade "null-pointer-exception" i spelaren när ny avsikt mottas för spelare som arbetar i bakgrunden. + - Åtgärdade att spelare utan popup-behörighet inte kraschar vid minimering till popupstorlek av fönstret. diff --git a/fastlane/metadata/android/sv/changelogs/969.txt b/fastlane/metadata/android/sv/changelogs/969.txt index a9ecf6b67..32725cc53 100644 --- a/fastlane/metadata/android/sv/changelogs/969.txt +++ b/fastlane/metadata/android/sv/changelogs/969.txt @@ -1,13 +1,8 @@ -• Tillåt installation på extern lagring - +• Tillåt installation på extern lagringsenhet • [Bandcamp] Stöd för att visa de tre första kommentarerna i en stream har lagts till. - • Visa endast "nedladdning har börjat" när nedladdningen har påbörjats. - • Ställ inte in reCaptcha-cookie när det inte finns någon cookie lagrad. - -• Player] Förbättra prestanda för cache - +• [Player] Förbättra prestanda för cache +• [Player] Åtgärdat ej automatisk uppspelning • Avskaffa tidigare Snackbars när nedladdningar raderas - -• Fixat att försöka radera objekt som inte finns i listan +• Åtgärdat försök att radera objekt som inte finns i listan diff --git a/fastlane/metadata/android/sv/changelogs/985.txt b/fastlane/metadata/android/sv/changelogs/985.txt index 4c434af54..35f298dbf 100644 --- a/fastlane/metadata/android/sv/changelogs/985.txt +++ b/fastlane/metadata/android/sv/changelogs/985.txt @@ -1 +1 @@ -Fixade att YouTube inte spelade någon stream. +Åtgärdat att YouTube inte spelar någon stream diff --git a/fastlane/metadata/android/uk/changelogs/990.txt b/fastlane/metadata/android/uk/changelogs/990.txt new file mode 100644 index 000000000..bc1d0a12c --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/990.txt @@ -0,0 +1,15 @@ +Припинено підтримку Android 4.4 KitKat, тепер найнижча версія — Android 5 Lollipop! + +Нове +• Завантаження з меню при затисненні +• Ховання майбутніх відео в стрічці +• Поширення локальних добірок + +Поліпшено +• Код поділено на компоненти: менше використання пам'яті, менше вад +• Удосконалено режим масштабування мініатюр +• Замінено картинки-заглушки на векторні + +Виправлено +• Сповіщення: застарілі/відсутні дані про медіафайл, викривлену мініатюру +• Використання повноекранним режимом лише чверті екрана diff --git a/fastlane/metadata/android/zh-Hans/changelogs/990.txt b/fastlane/metadata/android/zh-Hans/changelogs/990.txt new file mode 100644 index 000000000..2b3886a0a --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/990.txt @@ -0,0 +1,15 @@ +此版本移除了对 Android 4.4 KitKat 的支持,现在支持的最低版本是 Android 5 Lollipop! + +新增 +• 在长按菜单中进行下载 +• 隐藏 Feed 中的未来视频 +• 分享本地播放列表 + +改进 +• 重构播放器代码成多个小组件:使用的内存更少了,BUG 更少了 +• 改进了缩略图的缩放方式 +• 矢量化了占位图片 + +修复 +• 修复播放器通知相关的几个问题:媒体信息过期或缺失,缩略图扭曲变形 +• 修复全屏模式仅使用了 1/4 屏幕 diff --git a/fastlane/metadata/android/zh-Hant/changelogs/989.txt b/fastlane/metadata/android/zh-Hant/changelogs/989.txt new file mode 100644 index 000000000..fdf05181a --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/989.txt @@ -0,0 +1,3 @@ +• [YouTube] 修正嘗試播放任何影片時無盡載入 +• [YouTube] 修正部分影片限速 +• 升級 jsoup 程式庫至 1.15.3,當中包含一項安全修正 diff --git a/fastlane/metadata/android/zh-Hant/changelogs/990.txt b/fastlane/metadata/android/zh-Hant/changelogs/990.txt new file mode 100644 index 000000000..3260f395e --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/990.txt @@ -0,0 +1,15 @@ +是次發布終止支援 Android 4.4 KitKat,最低版本現為 Android 5 Lollipop! + +新增 +• 長按功能表中的下載選項 +• 摘要隱藏未到時候的影片 +• 分享本機播放清單 + +改進 +• 重構播放器程式碼為若干小元件:佔用較少 RAM,出現較少錯誤 +• 改進縮圖的縮放模式 +• 向量化預留位置影像 + +修正 +• 修正播放器通知的若干問題:媒體資訊過時/欠奉、縮圖失真 +• 修正全螢幕僅佔用 1/4 畫面 diff --git a/fastlane/metadata/android/zh_Hant_HK/changelogs/989.txt b/fastlane/metadata/android/zh_Hant_HK/changelogs/989.txt new file mode 100644 index 000000000..296c2ada4 --- /dev/null +++ b/fastlane/metadata/android/zh_Hant_HK/changelogs/989.txt @@ -0,0 +1,3 @@ +• [YouTube] 執返好播咩片都係噉轉 lo 唔到 +• [YouTube] 執返好有啲片窒下窒下 +• 將 jsoup 程式庫升級做 1.15.3,包括修正一個保安問題 diff --git a/fastlane/metadata/android/zh_Hant_HK/changelogs/990.txt b/fastlane/metadata/android/zh_Hant_HK/changelogs/990.txt new file mode 100644 index 000000000..a91ea0e67 --- /dev/null +++ b/fastlane/metadata/android/zh_Hant_HK/changelogs/990.txt @@ -0,0 +1,15 @@ +今次版本要扔低 Android 4.4 KitKat 啦,而家起最起碼要 Android 5 Lollipop 至裝到呢個 app! + +新嘢 +• 撳實有得揀下載 +• 摘要飛起未夠鐘上畫嘅片 +• 分享本機嘅播放清單 + +進步 +• 翻新播放器程式碼劏做幾部細件:用少啲 RAM、冇咁多 bug +• 錶起啲縮圖嘅時候擺得靚仔啲 +• 啲楔位公仔轉做向量圖 + +執漏 +• 修正播放器通知嘅問題:多媒體資訊過時/留空、縮圖鬆郁矇 +• 修正全螢幕用得 1/4 個螢幕 From aa9cd8c88f500b0ef3801f976757c0351fbacf14 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 25 Sep 2022 13:33:49 +0200 Subject: [PATCH 152/152] Update NewPipeExtractor again --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2387881cf..9d941d5a7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -187,7 +187,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:a99af9bb6e755997f40c975b1a212d243f051389' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:5c710da160f488bb40ab2cf4469bec9bd4cefd38' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/
BitcoinBitcoin QR code16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh
Liberapay Visit NewPipe at liberapay.com Donate via Liberapay
BitcoinBitcoin QR code16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh
Bountysource Visit NewPipe at bountysource.com