From c0ff1e86b93bd47761aae2cf3e5fcd3ef3053822 Mon Sep 17 00:00:00 2001
From: Atemu
Date: Fri, 20 Nov 2020 21:43:05 +0100
Subject: [PATCH 001/131] VideoDetailFragment: Don't exit fullscreen on
rotation in tablet UI
Fixes https://github.com/TeamNewPipe/NewPipe/issues/4936
Going from portrait to landscape doesn't toggle fullscreen in tablet mode, so
the reverse action shouldn't do it either.
---
.../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 4 +++-
1 file changed, 3 insertions(+), 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 772a291b1..2f1c3e586 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
@@ -274,7 +274,9 @@ 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 (player.isFullscreen() && !player.isVerticalVideo()
+ // 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();
From f341f43427ca048f762c8df6e37f5d70f23c1bdf Mon Sep 17 00:00:00 2001
From: mhmdanas <32234660+mhmdanas@users.noreply.github.com>
Date: Sun, 6 Dec 2020 16:52:03 +0300
Subject: [PATCH 002/131] Update "Updates" to account for F-Droid bug
Pun not intended (oh really?).
---
README.md | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 2568b7624..b29e2c758 100644
--- a/README.md
+++ b/README.md
@@ -82,12 +82,13 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
## Updates
When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can:
- 1. 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.
- 2. Add our custom repo to F-Droid and install it from there as soon as we publish a release. The instructions are here: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
- 3. Download the APK from [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it as soon as we publish a release.
- 4. 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.
-We recommend method 2 for most users. APKs installed using method 2 or 3 are compatible with each other, but not with those installed using method 4. This is due to the same signing key (ours) being used for 2 and 3, but a different signing key (F-Droid's) being used for 4. Building a debug APK using method 1 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app.
+ 1. Add our custom repo to F-Droid and install it from there as soon as we publish a release. The instructions are here: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
+ 2. Download the APK from [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it as soon as we publish a release.
+ 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. (**IMPORTANT**: as of the time of writing, an F-Droid bug is preventing updates later than 0.20.1 from being published. Thus, till this bug is solved, if you want to use install updates from F-Droid, we recommend method 1.)
+ 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.
+
+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.
In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality was broken and F-Droid doesn't have the 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
From 2b8837609bed291969961f351e5d128fb90bb65f Mon Sep 17 00:00:00 2001
From: bopol
Date: Sat, 19 Dec 2020 14:48:03 +0100
Subject: [PATCH 003/131] dynamically get package name
it fixes issues with forks or debug builds, e.g. when you open two newpipe apps (with debug or fork apps), close one notification, it closes all newpipe notifications
fixes https://github.com/TeamNewPipe/NewPipe/issues/4653
---
app/src/main/java/org/schabi/newpipe/App.java | 4 +++-
.../fragments/detail/VideoDetailFragment.java | 18 ++++++++--------
.../local/feed/service/FeedLoadService.kt | 3 ++-
.../services/SubscriptionsExportService.java | 3 ++-
.../services/SubscriptionsImportService.java | 3 ++-
.../org/schabi/newpipe/player/MainPlayer.java | 21 ++++++++++---------
6 files changed, 29 insertions(+), 23 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index e6dce4d67..7e3466f67 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -67,8 +67,10 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins;
public class App extends MultiDexApplication {
protected static final String TAG = App.class.toString();
private static App app;
+ public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
- @Nullable private Disposable disposable = null;
+ @Nullable
+ private Disposable disposable = null;
@NonNull
public static App getApp() {
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 427cff06e..d0a1584a9 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
@@ -145,15 +145,15 @@ public final class VideoDetailFragment
private static final float MAX_PLAYER_HEIGHT = 0.7f;
public static final String ACTION_SHOW_MAIN_PLAYER =
- "org.schabi.newpipe.VideoDetailFragment.ACTION_SHOW_MAIN_PLAYER";
+ App.PACKAGE_NAME + ".VideoDetailFragment.ACTION_SHOW_MAIN_PLAYER";
public static final String ACTION_HIDE_MAIN_PLAYER =
- "org.schabi.newpipe.VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER";
+ App.PACKAGE_NAME + ".VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER";
public static final String ACTION_PLAYER_STARTED =
- "org.schabi.newpipe.VideoDetailFragment.ACTION_PLAYER_STARTED";
+ App.PACKAGE_NAME + ".VideoDetailFragment.ACTION_PLAYER_STARTED";
public static final String ACTION_VIDEO_FRAGMENT_RESUMED =
- "org.schabi.newpipe.VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED";
+ App.PACKAGE_NAME + ".VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED";
public static final String ACTION_VIDEO_FRAGMENT_STOPPED =
- "org.schabi.newpipe.VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED";
+ App.PACKAGE_NAME + ".VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED";
private static final String COMMENTS_TAB_TAG = "COMMENTS";
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
@@ -494,10 +494,10 @@ public final class VideoDetailFragment
final PlaylistAppendDialog d = PlaylistAppendDialog.fromStreamInfo(currentInfo);
disposables.add(
- PlaylistAppendDialog.onPlaylistFound(getContext(),
- () -> d.show(getFM(), TAG),
- () -> PlaylistCreationDialog.newInstance(d).show(getFM(), TAG)
- )
+ PlaylistAppendDialog.onPlaylistFound(getContext(),
+ () -> d.show(getFM(), TAG),
+ () -> PlaylistCreationDialog.newInstance(d).show(getFM(), TAG)
+ )
);
}
break;
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
index 2a0aa1c90..45e8855e7 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
@@ -43,6 +43,7 @@ import io.reactivex.rxjava3.processors.PublishProcessor
import io.reactivex.rxjava3.schedulers.Schedulers
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
+import org.schabi.newpipe.App
import org.schabi.newpipe.MainActivity.DEBUG
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
@@ -68,7 +69,7 @@ class FeedLoadService : Service() {
companion object {
private val TAG = FeedLoadService::class.java.simpleName
private const val NOTIFICATION_ID = 7293450
- private const val ACTION_CANCEL = "org.schabi.newpipe.local.feed.service.FeedLoadService.CANCEL"
+ private const val ACTION_CANCEL = App.PACKAGE_NAME + ".local.feed.service.FeedLoadService.CANCEL"
/**
* How often the notification will be updated.
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java
index 982701d1f..5dfb1bfe5 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java
@@ -27,6 +27,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
+import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.subscription.SubscriptionItem;
@@ -50,7 +51,7 @@ public class SubscriptionsExportService extends BaseImportExportService {
* A {@link LocalBroadcastManager local broadcast} will be made with this action
* when the export is successfully completed.
*/
- public static final String EXPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription"
+ public static final String EXPORT_COMPLETE_ACTION = App.PACKAGE_NAME + ".local.subscription"
+ ".services.SubscriptionsExportService.EXPORT_COMPLETE";
private Subscription subscription;
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
index b1c67719c..90d0afe37 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
@@ -29,6 +29,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
+import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.NewPipe;
@@ -66,7 +67,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
* A {@link LocalBroadcastManager local broadcast} will be made with this action
* when the import is successfully completed.
*/
- public static final String IMPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription"
+ public static final String IMPORT_COMPLETE_ACTION = App.PACKAGE_NAME + ".local.subscription"
+ ".services.SubscriptionsImportService.IMPORT_COMPLETE";
/**
diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
index 63f6a400e..49c836346 100644
--- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
@@ -33,6 +33,7 @@ import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
+import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
@@ -64,25 +65,25 @@ public final class MainPlayer extends Service {
//////////////////////////////////////////////////////////////////////////*/
static final String ACTION_CLOSE
- = "org.schabi.newpipe.player.MainPlayer.CLOSE";
+ = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE";
static final String ACTION_PLAY_PAUSE
- = "org.schabi.newpipe.player.MainPlayer.PLAY_PAUSE";
+ = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE";
static final String ACTION_OPEN_CONTROLS
- = "org.schabi.newpipe.player.MainPlayer.OPEN_CONTROLS";
+ = App.PACKAGE_NAME + ".player.MainPlayer.OPEN_CONTROLS";
static final String ACTION_REPEAT
- = "org.schabi.newpipe.player.MainPlayer.REPEAT";
+ = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT";
static final String ACTION_PLAY_NEXT
- = "org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT";
+ = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT";
static final String ACTION_PLAY_PREVIOUS
- = "org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS";
+ = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS";
static final String ACTION_FAST_REWIND
- = "org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND";
+ = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND";
static final String ACTION_FAST_FORWARD
- = "org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD";
+ = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD";
static final String ACTION_SHUFFLE
- = "org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE";
+ = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE";
public static final String ACTION_RECREATE_NOTIFICATION
- = "org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION";
+ = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION";
/*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle
From b5bf0d7e1d2e47fbac39333e194671eafb3997f3 Mon Sep 17 00:00:00 2001
From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com>
Date: Tue, 22 Dec 2020 16:58:29 +0000
Subject: [PATCH 004/131] Export -> Import
---
README.ko.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.ko.md b/README.ko.md
index a86eae8d9..460a90dc1 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -98,7 +98,7 @@ NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로
1. 당신의 기록, 구독, 그리고 재생목록을 유지할 수 있도록 Settings > Content > Export Database 를 통해 데이터를 백업하십시오.
2. NewPipe를 삭제하십시오.
3. 새로운 소스에서 APK를 다운로드하고 이것을 설치하십시오.
-4. Step 1의 Settings > Content > Export Database 을 통해 데이터를 불러오십시오.
+4. Step 1의 Settings > Content > Import Database 을 통해 데이터를 불러오십시오.
## Contribution
당신이 아이디어, 번역, 디자인 변경, 코드 정리, 또는 정말 큰 코드 수정에 대한 의견이 있다면, 도움은 항상 환영합니다.
From 81bbef04dc7f46e5e2cf94e572d7713ddd2772c2 Mon Sep 17 00:00:00 2001
From: bopol
Date: Wed, 23 Dec 2020 15:14:26 +0100
Subject: [PATCH 005/131] [peertube] implement sepia search
---
app/build.gradle | 2 +-
.../newpipe/fragments/list/search/SearchFragment.java | 10 +++++++++-
.../java/org/schabi/newpipe/util/ServiceHelper.java | 1 +
3 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 3ac8ff525..0d3cf61a8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -179,7 +179,7 @@ dependencies {
// NewPipe dependencies
// You can use a local version by uncommenting a few lines in settings.gradle
- implementation 'com.github.TeamNewPipe:NewPipeExtractor:85fa006214b003f21eacb76c445a167732f19981'
+ implementation 'com.github.B0pol:NewPipeExtractor:3ae924a7f18d5ee5b4aa0bd13d7179922cc094fa'
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
implementation "org.jsoup:jsoup:1.13.1"
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 02dbf176b..4b7b1c3db 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
@@ -45,6 +45,8 @@ import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.search.SearchInfo;
+import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory;
+import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@@ -419,12 +421,18 @@ public class SearchFragment extends BaseListFragment
Date: Sat, 26 Dec 2020 21:35:07 +0300
Subject: [PATCH 006/131] Remove APK testing section from PR template
This is because the our workflows build debug APKs now.
---
.github/PULL_REQUEST_TEMPLATE.md | 4 ----
1 file changed, 4 deletions(-)
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index c3022d93f..5e5cfd08d 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -20,9 +20,5 @@
-
-#### APK testing
-
-debug.zip
-
#### Due diligence
- [ ] I read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md).
From d45ce19b04f175cbb2fd22fd01636e5e2bdb5ca9 Mon Sep 17 00:00:00 2001
From: urlordjames
Date: Sat, 26 Dec 2020 12:47:08 -0500
Subject: [PATCH 007/131] Fix #4481
---
.../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 4 ++++
1 file changed, 4 insertions(+)
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 6560ab404..0da79d4c2 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
@@ -2048,6 +2048,10 @@ public final class VideoDetailFragment
// Apply system brightness when the player is not in fullscreen
restoreDefaultBrightness();
} else {
+ // Do not restore if user has disabled brightness gesture
+ if (!PlayerHelper.isBrightnessGestureEnabled(activity)) {
+ return;
+ }
// Restore already saved brightness level
final float brightnessLevel = PlayerHelper.getScreenBrightness(activity);
if (brightnessLevel == lp.screenBrightness) {
From 0a05534c848a5b32d67ce3df7060a037ce686c1c Mon Sep 17 00:00:00 2001
From: Eric Lemieux
Date: Mon, 28 Dec 2020 15:20:17 -0500
Subject: [PATCH 008/131] Fix null pointer exception in play button method
When the play queue was null, and this method was called a null pointer
exception would be thrown. This change adds an additional check to see
if the play queue is not null before making additional changes.
---
.../newpipe/player/VideoPlayerImpl.java | 22 +++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
index a304b4430..06cbcd780 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
@@ -1071,11 +1071,25 @@ public class VideoPlayerImpl extends VideoPlayer
private void animatePlayButtons(final boolean show, final int duration) {
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
- if (playQueue.getIndex() > 0 || !show) {
- animateView(playPreviousButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
+
+ boolean showQueueButtons = show;
+ if (playQueue == null) {
+ showQueueButtons = false;
}
- if (playQueue.getIndex() + 1 < playQueue.getStreams().size() || !show) {
- animateView(playNextButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
+
+ if (!showQueueButtons || playQueue.getIndex() > 0) {
+ animateView(
+ playPreviousButton,
+ AnimationUtils.Type.SCALE_AND_ALPHA,
+ showQueueButtons,
+ duration);
+ }
+ if (!showQueueButtons || playQueue.getIndex() + 1 < playQueue.getStreams().size()) {
+ animateView(
+ playNextButton,
+ AnimationUtils.Type.SCALE_AND_ALPHA,
+ showQueueButtons,
+ duration);
}
}
From 83faf119a90ac1adb02d62fee2ec31d54f18e669 Mon Sep 17 00:00:00 2001
From: khimaros
Date: Tue, 29 Dec 2020 18:17:36 -0800
Subject: [PATCH 009/131] add list item to play video on kodi
closes: #5157
---
.../newpipe/fragments/list/BaseListFragment.java | 7 +++++++
.../fragments/list/playlist/PlaylistFragment.java | 8 ++++++++
.../local/history/StatisticsPlaylistFragment.java | 8 ++++++++
.../newpipe/local/playlist/LocalPlaylistFragment.java | 8 ++++++++
.../org/schabi/newpipe/util/StreamDialogEntry.java | 10 ++++++++++
5 files changed, 41 insertions(+)
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 5252024c2..ecfaaf80e 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
@@ -31,6 +31,7 @@ import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.report.ErrorActivity;
+import org.schabi.newpipe.util.KoreUtil;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StateSaver;
@@ -359,6 +360,12 @@ public abstract class BaseListFragment extends BaseStateFragment
StreamDialogEntry.share
));
}
+ final boolean enableKodiEntry = KoreUtil.isServiceSupportedByKore(item.getServiceId())
+ && PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
+ if (enableKodiEntry) {
+ entries.add(StreamDialogEntry.play_on_kodi);
+ }
StreamDialogEntry.setEnabledEntries(entries);
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),
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 6fa7eb700..a4ccd9c78 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
@@ -17,6 +17,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
+import androidx.preference.PreferenceManager;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
@@ -42,6 +43,7 @@ import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
+import org.schabi.newpipe.util.KoreUtil;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
@@ -174,6 +176,12 @@ public class PlaylistFragment extends BaseListInfoFragment {
StreamDialogEntry.share
));
}
+ final boolean enableKodiEntry = KoreUtil.isServiceSupportedByKore(item.getServiceId())
+ && PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
+ if (enableKodiEntry) {
+ entries.add(StreamDialogEntry.play_on_kodi);
+ }
StreamDialogEntry.setEnabledEntries(entries);
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItem) ->
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 9750d9820..59537f1a8 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
@@ -17,6 +17,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
+import androidx.preference.PreferenceManager;
import com.google.android.material.snackbar.Snackbar;
@@ -37,6 +38,7 @@ import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
+import org.schabi.newpipe.util.KoreUtil;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StreamDialogEntry;
@@ -413,6 +415,12 @@ public class StatisticsPlaylistFragment
StreamDialogEntry.share
));
}
+ final boolean enableKodiEntry = KoreUtil.isServiceSupportedByKore(infoItem.getServiceId())
+ && PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
+ if (enableKodiEntry) {
+ entries.add(StreamDialogEntry.play_on_kodi);
+ }
StreamDialogEntry.setEnabledEntries(entries);
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) ->
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 a8974e018..c6314f557 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
@@ -20,6 +20,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
+import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
@@ -41,6 +42,7 @@ import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
+import org.schabi.newpipe.util.KoreUtil;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
@@ -781,6 +783,12 @@ public class LocalPlaylistFragment extends BaseLocalListFragment
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
index 34ff637ad..d85f5ae57 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
@@ -1,6 +1,7 @@
package org.schabi.newpipe.util;
import android.content.Context;
+import android.net.Uri;
import androidx.fragment.app.Fragment;
@@ -70,6 +71,15 @@ public enum StreamDialogEntry {
}
}),
+ play_on_kodi(R.string.play_with_kodi_title, (fragment, item) -> {
+ final Uri videoUrl = Uri.parse(item.getUrl());
+ try {
+ NavigationHelper.playWithKore(fragment.getContext(), videoUrl);
+ } catch (final Exception e) {
+ KoreUtil.showInstallKoreDialog(fragment.getActivity());
+ }
+ }),
+
share(R.string.share, (fragment, item) ->
ShareUtils.shareUrl(fragment.getContext(), item.getName(), item.getUrl()));
From 1f15368b7bd53ed06e149c6eca81c1e6a91bed5c Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Wed, 30 Dec 2020 21:07:30 +0100
Subject: [PATCH 010/131] Fix urls with timestamps not being played
Else path is now executed, when a timestamp (item.getRecoveryPosition) is present
---
.../org/schabi/newpipe/player/BasePlayer.java | 80 +++++++++----------
1 file changed, 37 insertions(+), 43 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
index d5ad9cb6f..8eae33de6 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
@@ -19,6 +19,12 @@
package org.schabi.newpipe.player;
+import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
+import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
+import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
+import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -30,11 +36,9 @@ import android.media.AudioManager;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
-
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
@@ -53,7 +57,12 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
-
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.disposables.SerialDisposable;
+import java.io.IOException;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
@@ -75,20 +84,6 @@ import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.SerializedCache;
-import java.io.IOException;
-
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.disposables.SerialDisposable;
-
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
/**
* Base for the players, joining the common properties.
*
@@ -342,37 +337,36 @@ public abstract class BasePlayer implements
simpleExoPlayer.setPlayWhenReady(playWhenReady);
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
- && isPlaybackResumeEnabled()
- && !samePlayQueue) {
- final PlayQueueItem item = queue.getItem();
- if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
- stateLoader = recordManager.loadStreamState(item)
- .observeOn(AndroidSchedulers.mainThread())
- // Do not place initPlayback() in doFinally() because
- // it restarts playback after destroy()
- //.doFinally()
- .subscribe(
- state -> {
- queue.setRecovery(queue.getIndex(), state.getProgressTime());
- initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
- playbackSkipSilence, playWhenReady, isMuted);
- },
- error -> {
- if (DEBUG) {
- error.printStackTrace();
- }
- // In case any error we can start playback without history
- initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
- playbackSkipSilence, playWhenReady, isMuted);
- },
- () -> {
- // Completed but not found in history
+ && isPlaybackResumeEnabled()
+ && !samePlayQueue
+ && !queue.isEmpty()
+ && queue.getItem().getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
+ stateLoader = recordManager.loadStreamState(queue.getItem())
+ .observeOn(AndroidSchedulers.mainThread())
+ // Do not place initPlayback() in doFinally() because
+ // it restarts playback after destroy()
+ //.doFinally()
+ .subscribe(
+ state -> {
+ queue.setRecovery(queue.getIndex(), state.getProgressTime());
+ initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
+ playbackSkipSilence, playWhenReady, isMuted);
+ },
+ error -> {
+ if (DEBUG) {
+ error.printStackTrace();
+ }
+ // In case any error we can start playback without history
+ initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
+ playbackSkipSilence, playWhenReady, isMuted);
+ },
+ () -> {
+ // Completed but not found in history
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, playWhenReady, isMuted);
}
);
databaseUpdateReactor.add(stateLoader);
- }
} else {
// Good to go...
// In a case of equal PlayQueues we can re-init old one but only when it is disposed
From ebd589c9cbe7c73f4a2ba174438b533529860668 Mon Sep 17 00:00:00 2001
From: TacoTheDank
Date: Wed, 30 Dec 2020 17:10:57 -0500
Subject: [PATCH 011/131] Use AndroidX Media compat in AudioReactor
---
app/build.gradle | 1 +
.../newpipe/player/helper/AudioReactor.java | 39 ++++++-------------
2 files changed, 13 insertions(+), 27 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 15561b2d2..15b0668c6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -203,6 +203,7 @@ dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
+ implementation 'androidx.media:media:1.2.1'
implementation 'androidx.webkit:webkit:1.4.0'
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
index ffe19599d..13ee24e16 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
@@ -5,14 +5,14 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.Intent;
-import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.audiofx.AudioEffect;
-import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
+import androidx.media.AudioFocusRequestCompat;
+import androidx.media.AudioManagerCompat;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.analytics.AnalyticsListener;
@@ -21,20 +21,17 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
private static final String TAG = "AudioFocusReactor";
- private static final boolean SHOULD_BUILD_FOCUS_REQUEST =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
-
private static final int DUCK_DURATION = 1500;
private static final float DUCK_AUDIO_TO = .2f;
- private static final int FOCUS_GAIN_TYPE = AudioManager.AUDIOFOCUS_GAIN;
+ private static final int FOCUS_GAIN_TYPE = AudioManagerCompat.AUDIOFOCUS_GAIN;
private static final int STREAM_TYPE = AudioManager.STREAM_MUSIC;
private final SimpleExoPlayer player;
private final Context context;
private final AudioManager audioManager;
- private final AudioFocusRequest request;
+ private final AudioFocusRequestCompat request;
public AudioReactor(@NonNull final Context context,
@NonNull final SimpleExoPlayer player) {
@@ -43,15 +40,11 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
this.audioManager = ContextCompat.getSystemService(context, AudioManager.class);
player.addAnalyticsListener(this);
- if (SHOULD_BUILD_FOCUS_REQUEST) {
- request = new AudioFocusRequest.Builder(FOCUS_GAIN_TYPE)
- .setAcceptsDelayedFocusGain(true)
- .setWillPauseWhenDucked(true)
- .setOnAudioFocusChangeListener(this)
- .build();
- } else {
- request = null;
- }
+ request = new AudioFocusRequestCompat.Builder(FOCUS_GAIN_TYPE)
+ //.setAcceptsDelayedFocusGain(true)
+ .setWillPauseWhenDucked(true)
+ .setOnAudioFocusChangeListener(this)
+ .build();
}
public void dispose() {
@@ -64,19 +57,11 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
//////////////////////////////////////////////////////////////////////////*/
public void requestAudioFocus() {
- if (SHOULD_BUILD_FOCUS_REQUEST) {
- audioManager.requestAudioFocus(request);
- } else {
- audioManager.requestAudioFocus(this, STREAM_TYPE, FOCUS_GAIN_TYPE);
- }
+ AudioManagerCompat.requestAudioFocus(audioManager, request);
}
public void abandonAudioFocus() {
- if (SHOULD_BUILD_FOCUS_REQUEST) {
- audioManager.abandonAudioFocusRequest(request);
- } else {
- audioManager.abandonAudioFocus(this);
- }
+ AudioManagerCompat.abandonAudioFocusRequest(audioManager, request);
}
public int getVolume() {
@@ -88,7 +73,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
}
public int getMaxVolume() {
- return audioManager.getStreamMaxVolume(STREAM_TYPE);
+ return AudioManagerCompat.getStreamMaxVolume(audioManager, STREAM_TYPE);
}
/*//////////////////////////////////////////////////////////////////////////
From 48a5107296c4f62fa762dafe4e999337f2b23e62 Mon Sep 17 00:00:00 2001
From: khimaros
Date: Wed, 30 Dec 2020 14:40:21 -0800
Subject: [PATCH 012/131] address pull request feedback
---
.../schabi/newpipe/fragments/list/BaseListFragment.java | 7 ++-----
.../newpipe/fragments/list/playlist/PlaylistFragment.java | 8 ++------
.../newpipe/local/history/StatisticsPlaylistFragment.java | 8 ++------
.../newpipe/local/playlist/LocalPlaylistFragment.java | 8 ++------
app/src/main/java/org/schabi/newpipe/util/KoreUtil.java | 8 ++++++++
.../java/org/schabi/newpipe/util/StreamDialogEntry.java | 2 +-
6 files changed, 17 insertions(+), 24 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 ecfaaf80e..10527d189 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
@@ -360,11 +360,8 @@ public abstract class BaseListFragment extends BaseStateFragment
StreamDialogEntry.share
));
}
- final boolean enableKodiEntry = KoreUtil.isServiceSupportedByKore(item.getServiceId())
- && PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
- if (enableKodiEntry) {
- entries.add(StreamDialogEntry.play_on_kodi);
+ if (KoreUtil.shouldShowPlayWithKodi(context, item)) {
+ entries.add(StreamDialogEntry.play_with_kodi);
}
StreamDialogEntry.setEnabledEntries(entries);
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 a4ccd9c78..0eed5f4b0 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
@@ -17,7 +17,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
-import androidx.preference.PreferenceManager;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
@@ -176,11 +175,8 @@ public class PlaylistFragment extends BaseListInfoFragment {
StreamDialogEntry.share
));
}
- final boolean enableKodiEntry = KoreUtil.isServiceSupportedByKore(item.getServiceId())
- && PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
- if (enableKodiEntry) {
- entries.add(StreamDialogEntry.play_on_kodi);
+ if (KoreUtil.shouldShowPlayWithKodi(context, item)) {
+ entries.add(StreamDialogEntry.play_with_kodi);
}
StreamDialogEntry.setEnabledEntries(entries);
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 59537f1a8..a6a73e05f 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
@@ -17,7 +17,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
-import androidx.preference.PreferenceManager;
import com.google.android.material.snackbar.Snackbar;
@@ -415,11 +414,8 @@ public class StatisticsPlaylistFragment
StreamDialogEntry.share
));
}
- final boolean enableKodiEntry = KoreUtil.isServiceSupportedByKore(infoItem.getServiceId())
- && PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
- if (enableKodiEntry) {
- entries.add(StreamDialogEntry.play_on_kodi);
+ if (KoreUtil.shouldShowPlayWithKodi(context, infoItem)) {
+ entries.add(StreamDialogEntry.play_with_kodi);
}
StreamDialogEntry.setEnabledEntries(entries);
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 c6314f557..94f76cc75 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
@@ -20,7 +20,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
-import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
@@ -783,11 +782,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment {
+ play_with_kodi(R.string.play_with_kodi_title, (fragment, item) -> {
final Uri videoUrl = Uri.parse(item.getUrl());
try {
NavigationHelper.playWithKore(fragment.getContext(), videoUrl);
From 71add5a7c28945682ad9e45b108baa47f1928353 Mon Sep 17 00:00:00 2001
From: TacoTheDank
Date: Thu, 31 Dec 2020 19:26:41 -0500
Subject: [PATCH 013/131] Update displayed licenses
---
app/src/main/assets/epl1.html | 245 ++++++++++++++++++
.../schabi/newpipe/about/AboutActivity.java | 62 +++--
.../newpipe/about/StandardLicenses.java | 2 +
3 files changed, 283 insertions(+), 26 deletions(-)
create mode 100644 app/src/main/assets/epl1.html
diff --git a/app/src/main/assets/epl1.html b/app/src/main/assets/epl1.html
new file mode 100644
index 000000000..7123552dd
--- /dev/null
+++ b/app/src/main/assets/epl1.html
@@ -0,0 +1,245 @@
+
+
+
+
+
+
+ Eclipse Public License - Version 1.0
+
+
+
+
+
+
Eclipse Public License - v 1.0
+
+
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+ PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
+ DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
+ AGREEMENT.
+
+
1. DEFINITIONS
+
+
"Contribution" means:
+
+
a) in the case of the initial Contributor, the initial
+ code and documentation distributed under this Agreement, and
+
b) in the case of each subsequent Contributor:
+
i) changes to the Program, and
+
ii) additions to the Program;
+
where such changes and/or additions to the Program
+ originate from and are distributed by that particular Contributor. A
+ Contribution 'originates' from a Contributor if it was added to the
+ Program by such Contributor itself or anyone acting on such
+ Contributor's behalf. Contributions do not include additions to the
+ Program which: (i) are separate modules of software distributed in
+ conjunction with the Program under their own license agreement, and (ii)
+ are not derivative works of the Program.
+
+
"Contributor" means any person or entity that distributes
+ the Program.
+
+
"Licensed Patents" mean patent claims licensable by a
+ Contributor which are necessarily infringed by the use or sale of its
+ Contribution alone or when combined with the Program.
+
+
"Program" means the Contributions distributed in accordance
+ with this Agreement.
+
+
"Recipient" means anyone who receives the Program under
+ this Agreement, including all Contributors.
+
+
2. GRANT OF RIGHTS
+
+
a) Subject to the terms of this Agreement, each
+ Contributor hereby grants Recipient a non-exclusive, worldwide,
+ royalty-free copyright license to reproduce, prepare derivative works
+ of, publicly display, publicly perform, distribute and sublicense the
+ Contribution of such Contributor, if any, and such derivative works, in
+ source code and object code form.
+
+
b) Subject to the terms of this Agreement, each
+ Contributor hereby grants Recipient a non-exclusive, worldwide,
+ royalty-free patent license under Licensed Patents to make, use, sell,
+ offer to sell, import and otherwise transfer the Contribution of such
+ Contributor, if any, in source code and object code form. This patent
+ license shall apply to the combination of the Contribution and the
+ Program if, at the time the Contribution is added by the Contributor,
+ such addition of the Contribution causes such combination to be covered
+ by the Licensed Patents. The patent license shall not apply to any other
+ combinations which include the Contribution. No hardware per se is
+ licensed hereunder.
+
+
c) Recipient understands that although each Contributor
+ grants the licenses to its Contributions set forth herein, no assurances
+ are provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity. Each
+ Contributor disclaims any liability to Recipient for claims brought by
+ any other entity based on infringement of intellectual property rights
+ or otherwise. As a condition to exercising the rights and licenses
+ granted hereunder, each Recipient hereby assumes sole responsibility to
+ secure any other intellectual property rights needed, if any. For
+ example, if a third party patent license is required to allow Recipient
+ to distribute the Program, it is Recipient's responsibility to acquire
+ that license before distributing the Program.
+
+
d) Each Contributor represents that to its knowledge it
+ has sufficient copyright rights in its Contribution, if any, to grant
+ the copyright license set forth in this Agreement.
+
+
3. REQUIREMENTS
+
+
A Contributor may choose to distribute the Program in object code
+ form under its own license agreement, provided that:
+
+
a) it complies with the terms and conditions of this
+ Agreement; and
+
+
b) its license agreement:
+
+
i) effectively disclaims on behalf of all Contributors
+ all warranties and conditions, express and implied, including warranties
+ or conditions of title and non-infringement, and implied warranties or
+ conditions of merchantability and fitness for a particular purpose;
+
+
ii) effectively excludes on behalf of all Contributors
+ all liability for damages, including direct, indirect, special,
+ incidental and consequential damages, such as lost profits;
+
+
iii) states that any provisions which differ from this
+ Agreement are offered by that Contributor alone and not by any other
+ party; and
+
+
iv) states that source code for the Program is available
+ from such Contributor, and informs licensees how to obtain it in a
+ reasonable manner on or through a medium customarily used for software
+ exchange.
+
+
When the Program is made available in source code form:
+
+
a) it must be made available under this Agreement; and
+
+
b) a copy of this Agreement must be included with each
+ copy of the Program.
+
+
Contributors may not remove or alter any copyright notices contained
+ within the Program.
+
+
Each Contributor must identify itself as the originator of its
+ Contribution, if any, in a manner that reasonably allows subsequent
+ Recipients to identify the originator of the Contribution.
+
+
4. COMMERCIAL DISTRIBUTION
+
+
Commercial distributors of software may accept certain
+ responsibilities with respect to end users, business partners and the
+ like. While this license is intended to facilitate the commercial use of
+ the Program, the Contributor who includes the Program in a commercial
+ product offering should do so in a manner which does not create
+ potential liability for other Contributors. Therefore, if a Contributor
+ includes the Program in a commercial product offering, such Contributor
+ ("Commercial Contributor") hereby agrees to defend and
+ indemnify every other Contributor ("Indemnified Contributor")
+ against any losses, damages and costs (collectively "Losses")
+ arising from claims, lawsuits and other legal actions brought by a third
+ party against the Indemnified Contributor to the extent caused by the
+ acts or omissions of such Commercial Contributor in connection with its
+ distribution of the Program in a commercial product offering. The
+ obligations in this section do not apply to any claims or Losses
+ relating to any actual or alleged intellectual property infringement. In
+ order to qualify, an Indemnified Contributor must: a) promptly notify
+ the Commercial Contributor in writing of such claim, and b) allow the
+ Commercial Contributor to control, and cooperate with the Commercial
+ Contributor in, the defense and any related settlement negotiations. The
+ Indemnified Contributor may participate in any such claim at its own
+ expense.
+
+
For example, a Contributor might include the Program in a commercial
+ product offering, Product X. That Contributor is then a Commercial
+ Contributor. If that Commercial Contributor then makes performance
+ claims, or offers warranties related to Product X, those performance
+ claims and warranties are such Commercial Contributor's responsibility
+ alone. Under this section, the Commercial Contributor would have to
+ defend claims against the other Contributors related to those
+ performance claims and warranties, and if a court requires any other
+ Contributor to pay any damages as a result, the Commercial Contributor
+ must pay those damages.
+
+
5. NO WARRANTY
+
+
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+ PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
+ ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+ OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+ responsible for determining the appropriateness of using and
+ distributing the Program and assumes all risks associated with its
+ exercise of rights under this Agreement , including but not limited to
+ the risks and costs of program errors, compliance with applicable laws,
+ damage to or loss of data, programs or equipment, and unavailability or
+ interruption of operations.
+
+
6. DISCLAIMER OF LIABILITY
+
+
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
+ NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+ WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+ DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+ HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+
7. GENERAL
+
+
If any provision of this Agreement is invalid or unenforceable under
+ applicable law, it shall not affect the validity or enforceability of
+ the remainder of the terms of this Agreement, and without further action
+ by the parties hereto, such provision shall be reformed to the minimum
+ extent necessary to make such provision valid and enforceable.
+
+
If Recipient institutes patent litigation against any entity
+ (including a cross-claim or counterclaim in a lawsuit) alleging that the
+ Program itself (excluding combinations of the Program with other
+ software or hardware) infringes such Recipient's patent(s), then such
+ Recipient's rights granted under Section 2(b) shall terminate as of the
+ date such litigation is filed.
+
+
All Recipient's rights under this Agreement shall terminate if it
+ fails to comply with any of the material terms or conditions of this
+ Agreement and does not cure such failure in a reasonable period of time
+ after becoming aware of such noncompliance. If all Recipient's rights
+ under this Agreement terminate, Recipient agrees to cease use and
+ distribution of the Program as soon as reasonably practicable. However,
+ Recipient's obligations under this Agreement and any licenses granted by
+ Recipient relating to the Program shall continue and survive.
+
+
Everyone is permitted to copy and distribute copies of this
+ Agreement, but in order to avoid inconsistency the Agreement is
+ copyrighted and may only be modified in the following manner. The
+ Agreement Steward reserves the right to publish new versions (including
+ revisions) of this Agreement from time to time. No one other than the
+ Agreement Steward has the right to modify this Agreement. The Eclipse
+ Foundation is the initial Agreement Steward. The Eclipse Foundation may
+ assign the responsibility to serve as the Agreement Steward to a
+ suitable separate entity. Each new version of the Agreement will be
+ given a distinguishing version number. The Program (including
+ Contributions) may always be distributed subject to the version of the
+ Agreement under which it was received. In addition, after a new version
+ of the Agreement is published, Contributor may elect to distribute the
+ Program (including its Contributions) under the new version. Except as
+ expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
+ rights or licenses to the intellectual property of any Contributor under
+ this Agreement, whether expressly, by implication, estoppel or
+ otherwise. All rights in the Program not expressly granted under this
+ Agreement are reserved.
+
+
This Agreement is governed by the laws of the State of New York and
+ the intellectual property laws of the United States of America. No party
+ to this Agreement will bring a legal action under this Agreement more
+ than one year after the cause of action arose. Each party waives its
+ rights to a jury trial in any resulting litigation.
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
index 6ff691561..2569f4a94 100644
--- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
@@ -29,37 +29,46 @@ public class AboutActivity extends AppCompatActivity {
* List of all software components.
*/
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = {
- new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai",
+ new SoftwareComponent("ACRA", "2013", "Kevin Gaudin",
+ "https://github.com/ACRA/acra", StandardLicenses.APACHE2),
+ new SoftwareComponent("AndroidX", "2005 - 2011", "The Android Open Source Project",
+ "https://developer.android.com/jetpack", StandardLicenses.APACHE2),
+ new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof",
+ "https://github.com/hdodenhof/CircleImageView",
+ StandardLicenses.APACHE2),
+ new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google, Inc.",
+ "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
+ new SoftwareComponent("GigaGet", "2014 - 2015", "Peter Cai",
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3),
+ new SoftwareComponent("Groupie", "2016", "Lisa Wray",
+ "https://github.com/lisawray/groupie", StandardLicenses.MIT),
+ new SoftwareComponent("Icepick", "2015", "Frankie Sardo",
+ "https://github.com/frankiesardo/icepick", StandardLicenses.EPL1),
+ new SoftwareComponent("Jsoup", "2009 - 2020", "Jonathan Hedley",
+ "https://github.com/jhy/jsoup", StandardLicenses.MIT),
+ new SoftwareComponent("Markwon", "2019", "Dimitry Ivanov",
+ "https://github.com/noties/Markwon", StandardLicenses.APACHE2),
+ new SoftwareComponent("Material Components for Android", "2016 - 2020", "Google, Inc.",
+ "https://github.com/material-components/material-components-android",
+ StandardLicenses.APACHE2),
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
"https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
- new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley",
- "https://github.com/jhy/jsoup", StandardLicenses.MIT),
- new SoftwareComponent("Rhino", "2015", "Mozilla",
- "https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
- new SoftwareComponent("ACRA", "2013", "Kevin Gaudin",
- "http://www.acra.ch", StandardLicenses.APACHE2),
+ new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
+ "https://github.com/spacecowboy/NoNonsense-FilePicker",
+ StandardLicenses.MPL2),
+ new SoftwareComponent("OkHttp", "2019", "Square, Inc.",
+ "https://square.github.io/okhttp/", StandardLicenses.APACHE2),
+ new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
+ "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
+ new SoftwareComponent("RxAndroid", "2015", "The RxAndroid authors",
+ "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
+ new SoftwareComponent("RxBinding", "2015", "Jake Wharton",
+ "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
+ new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors",
+ "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
"https://github.com/nostra13/Android-Universal-Image-Loader",
StandardLicenses.APACHE2),
- new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof",
- "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
- new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
- "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
- new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc",
- "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
- new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors",
- "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
- new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors",
- "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
- new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton",
- "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
- new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
- "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
- new SoftwareComponent("Markwon", "2017 - 2020", "Noties",
- "https://github.com/noties/Markwon", StandardLicenses.APACHE2),
- new SoftwareComponent("Groupie", "2016", "Lisa Wray",
- "https://github.com/lisawray/groupie", StandardLicenses.MIT)
};
private static final int POS_ABOUT = 0;
@@ -115,7 +124,8 @@ public class AboutActivity extends AppCompatActivity {
* A placeholder fragment containing a simple view.
*/
public static class AboutFragment extends Fragment {
- public AboutFragment() { }
+ public AboutFragment() {
+ }
/**
* Created a new instance of this fragment for the given section number.
diff --git a/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java b/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java
index 50ee5ebc3..60b1e168c 100644
--- a/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java
+++ b/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java
@@ -12,6 +12,8 @@ public final class StandardLicenses {
= new License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html");
public static final License MIT
= new License("MIT License", "MIT", "mit.html");
+ public static final License EPL1
+ = new License("Eclipse Public License, Version 1.0", "EPL 1.0", "epl1.html");
private StandardLicenses() { }
}
From 31899d2ab9dee7a6a41f17b818297d4170408152 Mon Sep 17 00:00:00 2001
From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com>
Date: Fri, 1 Jan 2021 18:40:20 +0000
Subject: [PATCH 014/131] Checklist is compulsory: bug report template
---
.github/ISSUE_TEMPLATE/bug_report.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index f9201f948..e74a5a761 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -15,7 +15,7 @@ Oh no, a bug! It happens. Thanks for reporting an issue with NewPipe. To make it
### Checklist
-
+
- [x] I am using the latest version - x.xx.x
- [ ] I checked, but didn't find any duplicates (open OR closed) of this issue in the repo.
From 24c1cfbf721bc8df5f36005c180cfec653a82bdf Mon Sep 17 00:00:00 2001
From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com>
Date: Fri, 1 Jan 2021 18:42:07 +0000
Subject: [PATCH 015/131] Checklist is compulsory: feature request template
---
.github/ISSUE_TEMPLATE/feature_request.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index c4d378d14..361c8057f 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -11,7 +11,7 @@ assignees: ''
### Checklist
-
+
- [x] I checked, but didn't find any duplicates (open OR closed) of this issue in the repo.
- [ ] I have read the contribution guidelines given at https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md.
From 14a21710359f2ae688288087a661bd9f27a16772 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Wed, 23 Dec 2020 07:50:43 +0530
Subject: [PATCH 016/131] Use Objects.requireNonNull().
---
.../org/schabi/newpipe/about/LicenseFragment.java | 8 +++-----
.../newpipe/settings/DownloadSettingsFragment.java | 5 +----
.../java/us/shandian/giga/get/DownloadMission.java | 5 +++--
.../us/shandian/giga/get/DownloadRunnable.java | 4 ++--
.../giga/get/sqlite/FinishedMissionStore.java | 14 +++++---------
.../us/shandian/giga/io/CircularFileWriter.java | 5 ++---
6 files changed, 16 insertions(+), 25 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java
index 8367a75dc..6e48a0e14 100644
--- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java
@@ -19,6 +19,7 @@ import org.schabi.newpipe.util.ShareUtils;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.Objects;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
@@ -35,12 +36,9 @@ public class LicenseFragment extends Fragment {
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
- if (softwareComponents == null) {
- throw new NullPointerException("softwareComponents is null");
- }
- final LicenseFragment fragment = new LicenseFragment();
final Bundle bundle = new Bundle();
- bundle.putParcelableArray(ARG_COMPONENTS, softwareComponents);
+ bundle.putParcelableArray(ARG_COMPONENTS, Objects.requireNonNull(softwareComponents));
+ final LicenseFragment fragment = new LicenseFragment();
fragment.setArguments(bundle);
return fragment;
}
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 a4b29fc49..8742f0937 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java
@@ -246,10 +246,7 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
// revoke permissions on the old save path (required for SAF only)
- final Context context = getContext();
- if (context == null) {
- throw new NullPointerException("getContext()");
- }
+ final Context context = requireContext();
forgetSAFTree(context, defaultPreferences.getString(key, ""));
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 d7c586083..2b3faa3e0 100644
--- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java
+++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java
@@ -22,6 +22,7 @@ import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.channels.ClosedByInterruptException;
+import java.util.Objects;
import javax.net.ssl.SSLException;
@@ -154,8 +155,8 @@ public class DownloadMission extends Mission {
public transient Thread init = null;
public DownloadMission(String[] urls, StoredFileHelper storage, char kind, Postprocessing psInstance) {
- if (urls == null) throw new NullPointerException("urls is null");
- if (urls.length < 1) throw new IllegalArgumentException("urls is empty");
+ if (Objects.requireNonNull(urls).length < 1)
+ throw new IllegalArgumentException("urls array is empty");
this.urls = urls;
this.kind = kind;
this.offsets = new long[urls.length];
diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java
index 7fb12d088..6f504cea3 100644
--- a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java
+++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java
@@ -8,6 +8,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.nio.channels.ClosedByInterruptException;
+import java.util.Objects;
import us.shandian.giga.get.DownloadMission.Block;
import us.shandian.giga.get.DownloadMission.HttpError;
@@ -29,8 +30,7 @@ public class DownloadRunnable extends Thread {
private HttpURLConnection mConn;
DownloadRunnable(DownloadMission mission, int id) {
- if (mission == null) throw new NullPointerException("mission is null");
- mMission = mission;
+ mMission = Objects.requireNonNull(mission);
mId = id;
}
diff --git a/app/src/main/java/us/shandian/giga/get/sqlite/FinishedMissionStore.java b/app/src/main/java/us/shandian/giga/get/sqlite/FinishedMissionStore.java
index 1d1dca0df..15c45c6fd 100644
--- a/app/src/main/java/us/shandian/giga/get/sqlite/FinishedMissionStore.java
+++ b/app/src/main/java/us/shandian/giga/get/sqlite/FinishedMissionStore.java
@@ -12,6 +12,7 @@ import androidx.annotation.NonNull;
import java.io.File;
import java.util.ArrayList;
+import java.util.Objects;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.FinishedMission;
@@ -140,9 +141,7 @@ public class FinishedMissionStore extends SQLiteOpenHelper {
}
private FinishedMission getMissionFromCursor(Cursor cursor) {
- if (cursor == null) throw new NullPointerException("cursor is null");
-
- String kind = cursor.getString(cursor.getColumnIndex(KEY_KIND));
+ String kind = Objects.requireNonNull(cursor).getString(cursor.getColumnIndex(KEY_KIND));
if (kind == null || kind.isEmpty()) kind = "?";
String path = cursor.getString(cursor.getColumnIndexOrThrow(KEY_PATH));
@@ -186,15 +185,13 @@ public class FinishedMissionStore extends SQLiteOpenHelper {
}
public void addFinishedMission(DownloadMission downloadMission) {
- if (downloadMission == null) throw new NullPointerException("downloadMission is null");
+ ContentValues values = getValuesOfMission(Objects.requireNonNull(downloadMission));
SQLiteDatabase database = getWritableDatabase();
- ContentValues values = getValuesOfMission(downloadMission);
database.insert(FINISHED_TABLE_NAME, null, values);
}
public void deleteMission(Mission mission) {
- if (mission == null) throw new NullPointerException("mission is null");
- String ts = String.valueOf(mission.timestamp);
+ String ts = String.valueOf(Objects.requireNonNull(mission).timestamp);
SQLiteDatabase database = getWritableDatabase();
@@ -212,9 +209,8 @@ public class FinishedMissionStore extends SQLiteOpenHelper {
}
public void updateMission(Mission mission) {
- if (mission == null) throw new NullPointerException("mission is null");
+ ContentValues values = getValuesOfMission(Objects.requireNonNull(mission));
SQLiteDatabase database = getWritableDatabase();
- ContentValues values = getValuesOfMission(mission);
String ts = String.valueOf(mission.timestamp);
int rowsAffected;
diff --git a/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java b/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java
index 4d62ab200..dbceeb091 100644
--- a/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java
+++ b/app/src/main/java/us/shandian/giga/io/CircularFileWriter.java
@@ -7,6 +7,7 @@ import org.schabi.newpipe.streams.io.SharpStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.Objects;
public class CircularFileWriter extends SharpStream {
@@ -27,9 +28,7 @@ public class CircularFileWriter extends SharpStream {
private BufferedFile aux;
public CircularFileWriter(SharpStream target, File temp, OffsetChecker checker) throws IOException {
- if (checker == null) {
- throw new NullPointerException("checker is null");
- }
+ Objects.requireNonNull(checker);
if (!temp.exists()) {
if (!temp.createNewFile()) {
From aeca8dc5b2fb9f612d1d26b797d2160977247c40 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Sat, 2 Jan 2021 14:39:31 +0100
Subject: [PATCH 017/131] Disable sending metrics to Google in Android System
Webview
Fixes TeamNewPipe/NewPipe#5335
---
app/src/main/AndroidManifest.xml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d240d123f..3509f2d13 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -318,6 +318,8 @@
android:name=".RouterActivity$FetcherService"
android:exported="false" />
+
+
From ac59382b84cc047483e29df1ba40a2a71848d5be Mon Sep 17 00:00:00 2001
From: khimaros
Date: Sat, 2 Jan 2021 11:24:33 -0800
Subject: [PATCH 018/131] pass serviceId instead of item, reduce duplication
---
.../org/schabi/newpipe/fragments/list/BaseListFragment.java | 3 +--
.../newpipe/fragments/list/playlist/PlaylistFragment.java | 2 +-
.../newpipe/local/history/StatisticsPlaylistFragment.java | 2 +-
.../newpipe/local/playlist/LocalPlaylistFragment.java | 2 +-
.../java/org/schabi/newpipe/player/VideoPlayerImpl.java | 4 +---
app/src/main/java/org/schabi/newpipe/util/KoreUtil.java | 6 ++----
6 files changed, 7 insertions(+), 12 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 10527d189..a40ff1bf3 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
@@ -333,7 +333,6 @@ public abstract class BaseListFragment extends BaseStateFragment
}
}
-
protected void showStreamDialog(final StreamInfoItem item) {
final Context context = getContext();
final Activity activity = getActivity();
@@ -360,7 +359,7 @@ public abstract class BaseListFragment extends BaseStateFragment
StreamDialogEntry.share
));
}
- if (KoreUtil.shouldShowPlayWithKodi(context, item)) {
+ if (KoreUtil.shouldShowPlayWithKodi(context, item.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi);
}
StreamDialogEntry.setEnabledEntries(entries);
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 0eed5f4b0..8f7d6128b 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
@@ -175,7 +175,7 @@ public class PlaylistFragment extends BaseListInfoFragment {
StreamDialogEntry.share
));
}
- if (KoreUtil.shouldShowPlayWithKodi(context, item)) {
+ if (KoreUtil.shouldShowPlayWithKodi(context, item.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi);
}
StreamDialogEntry.setEnabledEntries(entries);
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 a6a73e05f..0d549ecf9 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
@@ -414,7 +414,7 @@ public class StatisticsPlaylistFragment
StreamDialogEntry.share
));
}
- if (KoreUtil.shouldShowPlayWithKodi(context, infoItem)) {
+ if (KoreUtil.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi);
}
StreamDialogEntry.setEnabledEntries(entries);
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 94f76cc75..17f7a4ff9 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
@@ -782,7 +782,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment
Date: Sat, 19 Dec 2020 11:22:25 +0100
Subject: [PATCH 019/131] Extract import database logic in
ContentSettingsManager
---
.../settings/ContentSettingsFragment.java | 52 ++---------
.../settings/ContentSettingsManager.kt | 92 ++++++++++++++++++-
2 files changed, 95 insertions(+), 49 deletions(-)
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 d7766f7b0..1d9b8e102 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -224,33 +224,24 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private void importDatabase(final String filePath) {
// check if file is supported
- try (ZipFile zipFile = new ZipFile(filePath)) {
- } catch (final IOException ioe) {
+ if (!manager.isValidZipFile(filePath)) {
Toast.makeText(getContext(), R.string.no_valid_zip_file, Toast.LENGTH_SHORT)
- .show();
+ .show();
return;
}
try {
- if (!databasesDir.exists() && !databasesDir.mkdir()) {
+ if (!manager.ensureDbDirectoryExists()) {
throw new Exception("Could not create databases dir");
}
- final boolean isDbFileExtracted = ZipHelper.extractFileFromZip(filePath,
- newpipeDb.getPath(), "newpipe.db");
-
- if (isDbFileExtracted) {
- newpipeDbJournal.delete();
- newpipeDbWal.delete();
- newpipeDbShm.delete();
- } else {
+ if (!manager.extractDb(filePath)) {
Toast.makeText(getContext(), R.string.could_not_import_all_files, Toast.LENGTH_LONG)
- .show();
+ .show();
}
//If settings file exist, ask if it should be imported.
- if (ZipHelper.extractFileFromZip(filePath, newpipeSettings.getPath(),
- "newpipe.settings")) {
+ if (manager.containSettings(filePath)) {
final AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
alert.setTitle(R.string.import_settings);
@@ -261,7 +252,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
});
alert.setPositiveButton(getString(R.string.finish), (dialog, which) -> {
dialog.dismiss();
- loadSharedPreferences(newpipeSettings);
+ manager.loadSharedPreferences(PreferenceManager
+ .getDefaultSharedPreferences(requireContext()));
// restart app to properly load db
System.exit(0);
});
@@ -275,34 +267,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
}
}
- private void loadSharedPreferences(final File src) {
- try (ObjectInputStream input = new ObjectInputStream(new FileInputStream(src))) {
- final SharedPreferences.Editor prefEdit = PreferenceManager
- .getDefaultSharedPreferences(requireContext()).edit();
- prefEdit.clear();
- final Map entries = (Map) input.readObject();
- for (final Map.Entry entry : entries.entrySet()) {
- final Object v = entry.getValue();
- final String key = entry.getKey();
-
- if (v instanceof Boolean) {
- prefEdit.putBoolean(key, (Boolean) v);
- } else if (v instanceof Float) {
- prefEdit.putFloat(key, (Float) v);
- } else if (v instanceof Integer) {
- prefEdit.putInt(key, (Integer) v);
- } else if (v instanceof Long) {
- prefEdit.putLong(key, (Long) v);
- } else if (v instanceof String) {
- prefEdit.putString(key, (String) v);
- }
- }
- prefEdit.commit();
- } catch (final IOException | ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
-
/*//////////////////////////////////////////////////////////////////////////
// Error
//////////////////////////////////////////////////////////////////////////*/
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 2682ac5e0..2a2c4df20 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
@@ -1,23 +1,34 @@
package org.schabi.newpipe.settings
import android.content.SharedPreferences
+import androidx.preference.PreferenceManager
import org.schabi.newpipe.util.ZipHelper
import java.io.BufferedOutputStream
import java.io.File
+import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
+import java.io.ObjectInputStream
import java.io.ObjectOutputStream
-import java.lang.Exception
+import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
class ContentSettingsManager(
- private val newpipeDb: File,
- private val newpipeSettings: File
+ private val databasesDir: File,
+ private val newpipeDb: File,
+ private val newpipeDbJournal: File,
+ private var newpipeDbShm: File,
+ private val newpipeDbWal: File,
+ private val newpipeSettings: File,
) {
constructor(homeDir: File) : this(
- File(homeDir, "databases/newpipe.db"),
- File(homeDir, "databases/newpipe.settings")
+ File(homeDir, "/databases"),
+ File(homeDir, "/databases/newpipe.db"),
+ File(homeDir, "/databases/newpipe.db-journal"),
+ File(homeDir, "/databases/newpipe.db-shm"),
+ File(homeDir, "/databases/newpipe.db-wal"),
+ File(homeDir, "/databases/newpipe.settings")
)
/**
@@ -42,4 +53,75 @@ class ContentSettingsManager(
ZipHelper.addFileToZip(outZip, newpipeSettings.path, "newpipe.settings")
}
}
+
+ fun isValidZipFile(filePath: String): Boolean {
+ try {
+ ZipFile(filePath).use {
+ return@isValidZipFile true
+ }
+ } catch (ioe: IOException) {
+ return false
+ }
+ }
+
+ /**
+ * Tries to create database directory if it does not exist.
+ *
+ * @return Whether the directory exists afterwards.
+ */
+ fun ensureDbDirectoryExists(): Boolean {
+ return !databasesDir.exists() && !databasesDir.mkdir()
+ }
+
+ fun extractDb(filePath: String): Boolean {
+ val success = ZipHelper.extractFileFromZip(filePath, newpipeDb.path, "newpipe.db")
+ if (success) {
+ newpipeDbJournal.delete()
+ newpipeDbWal.delete()
+ newpipeDbShm.delete()
+ }
+
+ return success
+ }
+
+ fun containSettings(filePath: String): Boolean {
+ return ZipHelper
+ .extractFileFromZip(filePath, newpipeSettings.path, "newpipe.settings")
+ }
+
+ fun loadSharedPreferences(preferences: SharedPreferences) {
+ try {
+ val preferenceEditor = preferences.edit()
+
+ ObjectInputStream(FileInputStream(newpipeSettings)).use { input ->
+ preferenceEditor.clear()
+ val entries = input.readObject() as Map
+ for ((key, value) in entries) {
+ when (value) {
+ is Boolean -> {
+ preferenceEditor.putBoolean(key, value)
+ }
+ is Float -> {
+ preferenceEditor.putFloat(key, value)
+ }
+ is Int -> {
+ preferenceEditor.putInt(key, value)
+ }
+ is Long -> {
+ preferenceEditor.putLong(key, value)
+ }
+ is String -> {
+ preferenceEditor.putString(key, value)
+ }
+ }
+ }
+ preferenceEditor.commit()
+ }
+ } catch (e: IOException) {
+ e.printStackTrace()
+ } catch (e: ClassNotFoundException) {
+ e.printStackTrace()
+ }
+ }
+
}
From cef791ba1baa639476ebc00a8060d5f4ac1c07a1 Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 19 Dec 2020 14:02:34 +0100
Subject: [PATCH 020/131] Introduce NewPipeFileLocator class
It handles locating specific NewPipe files based on the home directory of the app.
---
.../settings/ContentSettingsFragment.java | 2 +-
.../settings/ContentSettingsManager.kt | 68 ++++++++-----------
.../newpipe/settings/NewPipeFileLocator.kt | 22 ++++++
3 files changed, 51 insertions(+), 41 deletions(-)
create mode 100644 app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt
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 1d9b8e102..f783df85e 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -129,7 +129,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
newpipeSettings = new File(homeDir, "/databases/newpipe.settings");
newpipeSettings.delete();
- manager = new ContentSettingsManager(homeDir);
+ manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir));
addPreferencesFromResource(R.xml.content_settings);
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 2a2c4df20..5e92f59bf 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
@@ -1,10 +1,8 @@
package org.schabi.newpipe.settings
import android.content.SharedPreferences
-import androidx.preference.PreferenceManager
import org.schabi.newpipe.util.ZipHelper
import java.io.BufferedOutputStream
-import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
@@ -13,23 +11,7 @@ import java.io.ObjectOutputStream
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
-class ContentSettingsManager(
- private val databasesDir: File,
- private val newpipeDb: File,
- private val newpipeDbJournal: File,
- private var newpipeDbShm: File,
- private val newpipeDbWal: File,
- private val newpipeSettings: File,
-) {
-
- constructor(homeDir: File) : this(
- File(homeDir, "/databases"),
- File(homeDir, "/databases/newpipe.db"),
- File(homeDir, "/databases/newpipe.db-journal"),
- File(homeDir, "/databases/newpipe.db-shm"),
- File(homeDir, "/databases/newpipe.db-wal"),
- File(homeDir, "/databases/newpipe.settings")
- )
+class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
/**
* Exports given [SharedPreferences] to the file in given outputPath.
@@ -38,19 +20,19 @@ class ContentSettingsManager(
@Throws(Exception::class)
fun exportDatabase(preferences: SharedPreferences, outputPath: String) {
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputPath)))
- .use { outZip ->
- ZipHelper.addFileToZip(outZip, newpipeDb.path, "newpipe.db")
+ .use { outZip ->
+ ZipHelper.addFileToZip(outZip, fileLocator.dbDir.path, "newpipe.db")
- try {
- ObjectOutputStream(FileOutputStream(newpipeSettings)).use { output ->
- output.writeObject(preferences.all)
- output.flush()
+ try {
+ ObjectOutputStream(FileOutputStream(fileLocator.settings)).use { output ->
+ output.writeObject(preferences.all)
+ output.flush()
+ }
+ } catch (e: IOException) {
+ e.printStackTrace()
}
- } catch (e: IOException) {
- e.printStackTrace()
- }
- ZipHelper.addFileToZip(outZip, newpipeSettings.path, "newpipe.settings")
+ ZipHelper.addFileToZip(outZip, fileLocator.settings.path, "newpipe.settings")
}
}
@@ -70,30 +52,37 @@ class ContentSettingsManager(
* @return Whether the directory exists afterwards.
*/
fun ensureDbDirectoryExists(): Boolean {
- return !databasesDir.exists() && !databasesDir.mkdir()
+ return !fileLocator.dbDir.exists() && !fileLocator.dbDir.mkdir()
}
- fun extractDb(filePath: String): Boolean {
- val success = ZipHelper.extractFileFromZip(filePath, newpipeDb.path, "newpipe.db")
+
+ fun extractDb(
+ filePath: String,
+ ): Boolean {
+ val success = ZipHelper.extractFileFromZip(filePath, fileLocator.db.path, "newpipe.db")
if (success) {
- newpipeDbJournal.delete()
- newpipeDbWal.delete()
- newpipeDbShm.delete()
+ fileLocator.dbJournal.delete()
+ fileLocator.dbWal.delete()
+ fileLocator.dbShm.delete()
}
return success
}
- fun containSettings(filePath: String): Boolean {
+ fun containSettings(
+ filePath: String,
+ ): Boolean {
return ZipHelper
- .extractFileFromZip(filePath, newpipeSettings.path, "newpipe.settings")
+ .extractFileFromZip(filePath, fileLocator.settings.path, "newpipe.settings")
}
- fun loadSharedPreferences(preferences: SharedPreferences) {
+ fun loadSharedPreferences(
+ preferences: SharedPreferences,
+ ) {
try {
val preferenceEditor = preferences.edit()
- ObjectInputStream(FileInputStream(newpipeSettings)).use { input ->
+ ObjectInputStream(FileInputStream(fileLocator.settings)).use { input ->
preferenceEditor.clear()
val entries = input.readObject() as Map
for ((key, value) in entries) {
@@ -123,5 +112,4 @@ class ContentSettingsManager(
e.printStackTrace()
}
}
-
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt b/app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt
new file mode 100644
index 000000000..1fe2fffa0
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt
@@ -0,0 +1,22 @@
+package org.schabi.newpipe.settings
+
+import java.io.File
+
+/**
+ * Locates specific files of NewPipe based on the home directory of the app.
+ */
+class NewPipeFileLocator(private val homeDir: File) {
+
+ val dbDir by lazy { File(homeDir, "/databases") }
+
+ val db by lazy { File(homeDir, "/databases/newpipe.db") }
+
+ val dbJournal by lazy { File(homeDir, "/databases/newpipe.db-journal") }
+
+ val dbShm by lazy { File(homeDir, "/databases/newpipe.db-shm") }
+
+ val dbWal by lazy { File(homeDir, "/databases/newpipe.db-wal") }
+
+ val settings by lazy { File(homeDir, "/databases/newpipe.settings") }
+
+}
From ea91a62c89e70fc392c811af5dbcea34a9064035 Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 19 Dec 2020 14:10:10 +0100
Subject: [PATCH 021/131] Adjust ExportTest to new DI with FileLocator
---
app/build.gradle | 4 +++-
.../org/schabi/newpipe/settings/ContentSettingsManager.kt | 2 +-
.../schabi/newpipe/settings/ContentSettingsManagerTest.kt | 8 +++++++-
3 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 9fb9cf85f..b3c6f6bad 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,6 +102,7 @@ ext {
groupieVersion = '2.8.1'
markwonVersion = '4.6.0'
googleAutoServiceVersion = '1.0-rc7'
+ mockitoVersion = '3.6.0'
}
configurations {
@@ -235,7 +236,8 @@ dependencies {
implementation "org.ocpsoft.prettytime:prettytime:4.0.6.Final"
testImplementation 'junit:junit:4.13.1'
- testImplementation 'org.mockito:mockito-core:3.6.0'
+ testImplementation "org.mockito:mockito-core:${mockitoVersion}"
+ testImplementation "org.mockito:mockito-inline:${mockitoVersion}"
androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
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 5e92f59bf..f278c6b24 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
@@ -21,7 +21,7 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
fun exportDatabase(preferences: SharedPreferences, outputPath: String) {
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputPath)))
.use { outZip ->
- ZipHelper.addFileToZip(outZip, fileLocator.dbDir.path, "newpipe.db")
+ ZipHelper.addFileToZip(outZip, fileLocator.db.path, "newpipe.db")
try {
ObjectOutputStream(FileOutputStream(fileLocator.settings)).use { output ->
diff --git a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
index 5ac132f7f..67895fc7c 100644
--- a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
@@ -1,6 +1,7 @@
package org.schabi.newpipe.settings
import android.content.SharedPreferences
+import com.nononsenseapps.filepicker.NewFolderFragment
import org.junit.Assert
import org.junit.Assume
import org.junit.Before
@@ -24,6 +25,7 @@ class ContentSettingsManagerTest {
class ExportTest {
companion object {
+ private lateinit var fileLocator: NewPipeFileLocator
private lateinit var newpipeDb: File
private lateinit var newpipeSettings: File
@@ -37,6 +39,10 @@ class ContentSettingsManagerTest {
newpipeDb = File(dbPath!!)
newpipeSettings = File(settingsPath!!)
+
+ fileLocator = Mockito.mock(NewPipeFileLocator::class.java, Mockito.withSettings().stubOnly())
+ `when`(fileLocator.db).thenReturn(newpipeDb)
+ `when`(fileLocator.settings).thenReturn(newpipeSettings)
}
}
@@ -52,7 +58,7 @@ class ContentSettingsManagerTest {
val expectedPreferences = mapOf("such pref" to "much wow")
`when`(preferences.all).thenReturn(expectedPreferences)
- val manager = ContentSettingsManager(newpipeDb, newpipeSettings)
+ val manager = ContentSettingsManager(fileLocator)
val output = File.createTempFile("newpipe_", "")
manager.exportDatabase(preferences, output.absolutePath)
From 19cd3a17df4f3b17b72604c5887f3aea7ab3fbb2 Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 19 Dec 2020 14:14:48 +0100
Subject: [PATCH 022/131] Move isValidZipFile to ZipHelper
---
.../newpipe/settings/ContentSettingsFragment.java | 2 +-
.../schabi/newpipe/settings/ContentSettingsManager.kt | 10 ----------
.../main/java/org/schabi/newpipe/util/ZipHelper.java | 10 ++++++++++
3 files changed, 11 insertions(+), 11 deletions(-)
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 f783df85e..2339de53a 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -224,7 +224,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private void importDatabase(final String filePath) {
// check if file is supported
- if (!manager.isValidZipFile(filePath)) {
+ if (!ZipHelper.isValidZipFile(filePath)) {
Toast.makeText(getContext(), R.string.no_valid_zip_file, Toast.LENGTH_SHORT)
.show();
return;
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 f278c6b24..3ab3dc8f3 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
@@ -36,16 +36,6 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
}
}
- fun isValidZipFile(filePath: String): Boolean {
- try {
- ZipFile(filePath).use {
- return@isValidZipFile true
- }
- } catch (ioe: IOException) {
- return false
- }
- }
-
/**
* Tries to create database directory if it does not exist.
*
diff --git a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java
index f9a950d2b..e2b766bb0 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java
@@ -4,7 +4,9 @@ import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@@ -99,4 +101,12 @@ public final class ZipHelper {
return found;
}
}
+
+ public static boolean isValidZipFile(final String filePath) {
+ try (ZipFile ignored = new ZipFile(filePath)) {
+ return true;
+ } catch (final IOException ioe) {
+ return false;
+ }
+ }
}
From f778c4892359ba5bcb3b411637a530713066267f Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 19 Dec 2020 15:15:38 +0100
Subject: [PATCH 023/131] Add basic tests for settings import
---
.../settings/ContentSettingsManagerTest.kt | 110 +++++++++++++++++-
app/src/test/resources/settings/empty.zip | Bin 0 -> 22 bytes
app/src/test/resources/settings/newpipe.zip | Bin 0 -> 8317 bytes
3 files changed, 107 insertions(+), 3 deletions(-)
create mode 100644 app/src/test/resources/settings/empty.zip
create mode 100644 app/src/test/resources/settings/newpipe.zip
diff --git a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
index 67895fc7c..111fd7c09 100644
--- a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
@@ -1,8 +1,10 @@
package org.schabi.newpipe.settings
import android.content.SharedPreferences
-import com.nononsenseapps.filepicker.NewFolderFragment
import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Assume
import org.junit.Before
import org.junit.BeforeClass
@@ -11,10 +13,16 @@ import org.junit.runner.RunWith
import org.junit.runners.Suite
import org.mockito.Mockito
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.withSettings
import org.mockito.junit.MockitoJUnitRunner
import org.schabi.newpipe.settings.ContentSettingsManagerTest.ExportTest
import java.io.File
import java.io.ObjectInputStream
+import java.nio.file.Files
import java.util.zip.ZipFile
@RunWith(Suite::class)
@@ -40,7 +48,7 @@ class ContentSettingsManagerTest {
newpipeDb = File(dbPath!!)
newpipeSettings = File(settingsPath!!)
- fileLocator = Mockito.mock(NewPipeFileLocator::class.java, Mockito.withSettings().stubOnly())
+ fileLocator = Mockito.mock(NewPipeFileLocator::class.java, withSettings().stubOnly())
`when`(fileLocator.db).thenReturn(newpipeDb)
`when`(fileLocator.settings).thenReturn(newpipeSettings)
}
@@ -50,7 +58,7 @@ class ContentSettingsManagerTest {
@Before
fun setupMocks() {
- preferences = Mockito.mock(SharedPreferences::class.java, Mockito.withSettings().stubOnly())
+ preferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly())
}
@Test
@@ -79,4 +87,100 @@ class ContentSettingsManagerTest {
}
}
}
+
+ @RunWith(MockitoJUnitRunner::class)
+ class ImportTest {
+ companion object {
+ private lateinit var fileLocator: NewPipeFileLocator
+ private lateinit var zip: File
+ private lateinit var emptyZip: File
+ private lateinit var db: File
+ private lateinit var dbJournal: File
+ private lateinit var dbWal: File
+ private lateinit var dbShm: File
+ private lateinit var settings: File
+
+ @JvmStatic
+ @BeforeClass
+ fun setupReadOnlyFiles() {
+ val zipPath = ImportTest::class.java.classLoader?.getResource("settings/newpipe.zip")?.file
+ val emptyZipPath = ImportTest::class.java.classLoader?.getResource("settings/empty.zip")?.file
+ Assume.assumeNotNull(zipPath)
+ Assume.assumeNotNull(emptyZipPath)
+
+ zip = File(zipPath!!)
+ emptyZip = File(emptyZipPath!!)
+ }
+ }
+
+ @Before
+ fun setupWriteFiles() {
+ db = File.createTempFile("newpipe_", "")
+ dbJournal = File.createTempFile("newpipe_", "")
+ dbWal = File.createTempFile("newpipe_", "")
+ dbShm = File.createTempFile("newpipe_", "")
+ settings = File.createTempFile("newpipe_", "")
+
+ fileLocator = Mockito.mock(NewPipeFileLocator::class.java, withSettings().stubOnly())
+ `when`(fileLocator.db).thenReturn(db)
+ `when`(fileLocator.dbJournal).thenReturn(dbJournal)
+ `when`(fileLocator.dbShm).thenReturn(dbShm)
+ `when`(fileLocator.dbWal).thenReturn(dbWal)
+ `when`(fileLocator.settings).thenReturn(settings)
+ }
+
+ @Test
+ fun `The database must be extracted from the zip file`() {
+ val success = ContentSettingsManager(fileLocator).extractDb(zip.path)
+
+ assertTrue(success)
+ assertFalse(dbJournal.exists())
+ assertFalse(dbWal.exists())
+ assertFalse(dbShm.exists())
+ assertTrue("database file size is zero", Files.size(db.toPath()) > 0)
+ }
+
+ @Test
+ fun `Extracting the database from an empty zip must not work`() {
+ val success = ContentSettingsManager(fileLocator).extractDb(emptyZip.path)
+
+ assertFalse(success)
+ assertTrue(dbJournal.exists())
+ assertTrue(dbWal.exists())
+ assertTrue(dbShm.exists())
+ assertEquals(0, Files.size(db.toPath()))
+ }
+
+ @Test
+ fun `Contain setting must return true, if a settings file exists in the zip`() {
+ val contains = ContentSettingsManager(fileLocator).containSettings(zip.path)
+
+ assertTrue(contains)
+ }
+
+ @Test
+ fun `Contain setting must return false, if a no settings file exists in the zip`() {
+ val contains = ContentSettingsManager(fileLocator).containSettings(emptyZip.path)
+
+ assertFalse(contains)
+ }
+
+ @Test
+ fun `Preferences must be set from the settings file`() {
+ val preferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly())
+ val editor = Mockito.mock(SharedPreferences.Editor::class.java)
+ `when`(preferences.edit()).thenReturn(editor)
+
+
+ val manager = ContentSettingsManager(fileLocator)
+ manager.containSettings(zip.path)
+ manager.loadSharedPreferences(preferences)
+
+ verify(editor, atLeastOnce()).putBoolean(anyString(), anyBoolean())
+ }
+
+
+ }
+
+
}
diff --git a/app/src/test/resources/settings/empty.zip b/app/src/test/resources/settings/empty.zip
new file mode 100644
index 0000000000000000000000000000000000000000..15cb0ecb3e219d1701294bfdf0fe3f5cb5d208e7
GIT binary patch
literal 22
NcmWIWW@Tf*000g10H*)|
literal 0
HcmV?d00001
diff --git a/app/src/test/resources/settings/newpipe.zip b/app/src/test/resources/settings/newpipe.zip
new file mode 100644
index 0000000000000000000000000000000000000000..1ce8431feb3d4f2b14e770ac7f8a3b7efac07436
GIT binary patch
literal 8317
zcmZ{Jby!s0_b-Z+gmj0*(A|w99Yc#qN!QTbAl)xRcej9mGz{IPl0yvLjRHf?_50qR
ze!cfTcmHvov(AcVpR+&vbM{(GT^acqArd+|I#Rb+k_OUW(834=_kZ~;)UxX)>lo8KJn0OlvfdIP@}
z>8q=P&%42I1s?@AR!#c$!?CWjw<7C#&sDb{mP85uyjF*EZiXi6d3dvBAxuzMX)qxv
za3ccsw;HmkLXm9sW0iZ2=Jz9{Y!1uKe^BafK<}v#YPwi(wExj`Bl#h+oqM^ja!Ia|
zN@>t_8KOv5>pp)437$5J=z%f!s3&Gg_MQ*ampA=$={VYMV{AW5VXz2#zhpgOH#Q97
z>-}7>V2Nu;%ZJ4!xu~M=vdort6J6|3%<&D=$g%00`IyFJ7nN~EScQkpSlyRaoz>VH
zZHgZJMwsOfQV8ugot`Gp2|omHj2YV_q!}
zKcHYE#+)D#{FM<9|4BMHarwTMTXkToqs>%9`j
z*Av)Y3Rsm9YQDlo6&PqQW~PO`x7JUoASWQ(Wy0juVgp-{?G7C>%W9d2;FB^mrf7;(
zxWIWn0pe3-wY3F1LaY`0d6cJ#I*J5-#z&FbQlB74k)xvtt18y37tbhIvDUw2t?kPC
zvIH|}SbxRHoXNt&AMb;lDB>8aqT9ka;wqboUn0Ns4lf~C@KDNjR>^rminzj5~jC|r4LW6(=*#7I>atuRLt2$
zAcleh{}?#U#o|oGZ=QEuNX<~tJ-*KPL3utgiTOvLjpx`Enq*a$yo(TGx({bL?9T%#
z^eZPx$@!TdXeF&?-YmHJT)1kj@}0X**Q#cgAzW@VD!P~!GPowbSh4r-mPv9qh(53p
zJSv8#XS#JkX;A)+-BAE4mhy>QMJ#n@$d4k*c5H^Dgl6j9G2Cj0t;mcQrQ-FG$J5Gse
zwvdhk4vXR@rmUsR@7HAFu;El=SPJWMaShh2d)e6hqN4o$MraL|xO|#4WXnU4ih4dJ
z7-}xzY-d~hPeT0uDDG>;FYC>1X3%0i-EbubmMMqDQJg4>Sx?}u$AMc7XjN}Ke$UP_
z^Ke937!MnSrrO4vygBl&vhMBL;jD@OY&*CN)VRF3h${3Yh)?{A2H7h)cx6YZS^wV1
zP3_?>u&-6<4*%)z^&Q=Tr7;H!$J};I1-2fPN`(
zyN$8_Kt_ndJ=@0)UQS$1@w!ABb^6q=xj*HBrO0cpV3|P6DJO4
z{F(MCLuppu;CWUlg>a`yTm1B4shx1AWn1)gRVkZrr)gX4^cW*1kfT&txYIndZ$EpV
z4#<^h=`3p37Br1n`a@X4Q8Z%OmGNm=s10fvU#U$tFssx_)FQKgzu4w!89}LYc6`>L
zlc*){<_w5@V87hvSs6Ve0nqv5w8=Cs@ci@)-g0q|U^LMhL>SXUr0)UeyZw3V-w;+&
zY6M-c@1LK?cpV|M`V;VJ7y{D0uoLbnp2=&YM=uO+KBD6>z+~As$NzjYGCo`0cnU|D
zHj|J;dD7wh>`0^C!PbF}yUTX8Xse%I&O7#lt?vMLUZFA|r;#>OL&L#VFN(X%V~41S
zCol&$R5T+)gK2vDz}!|TM+I1!6oui=D_H{^9Gr=QegewL&yEa7it=R4jGTQIB_rp}
z7^I0qRH1I@_X|ZLWc|Y6Z2wsb+DGJbrhXc{Jb>gIzi7DohHjKGqa@HT8BVsr6cza9
z9{rpv>YAVy?-X!_p0Va^`Fs9I;-=)(LNIMGg_4V*VhziP4xqJ=AuXl<*Cp8;sL07W
z@(FC94*sF!0#rl?WN3hG02!KKML>o+*cm{kh2H>%kF){Zg>+$2#>zfUnQH0a4dmCW
z6Q5;gG?O}l4zfcp{NilqNj30)(BYF~#a*cIaA7v~4G_%i1^Taq{AaIr^juMBS&l8O
zI{3N2E_^P)p!Brxz+0oRxMrU3QTuBa2AHgt3!j0;nndDdUf|DM7Ri@=bt>G1Dv5Po
z;jtFFu~)wc&YE}mjPOA!hFz)+sS*Ci!r(-gWJcgJOY`z!*W?GzY9
zuy|ASw0&2N29=%?^O?{HTA7u9KH+1&uZ`#M&I^d$GAJWY2qI^l(6ipQa>{r|9RbNY
z_GDnrd>j8pLjvTViWw=NoLnZ7k&?UdDre#1&}Z2wo3yciHx54twFM&I
z1A7Q~t_$aBoJ)w!;OMa%eAvzn39P5_687BSK7+m!&N%knQ?s0*_R^5xmybyaVzC?V
z5;YOjuQB8c2-I=i8t5DG>N2Z#P@JPSn&6@BgsO;B_T3cryp!|&AYgGVc5-lH`7z7T
zn?bI!00xbqOBX(yEZ*vPkJVq|!PhftTg>Xj=_yVIhvx~YapwxO)Pt!FmJXz>OY*(D&6$Qb
z*0;+$mZJhRq~|j{T7*y-eyLx_v2%kQcF{YWl^2#|ZIa^zS_&
z(ords>%yN`w8tcm(vopmckI0MTX=NHqfDo;>f7V5*>$IsR0jeLW{)RbwTaq&X_%U(
zr}u?hJK7Jwx!+2tqA}!y03`w4PVCtjzVwOJeHlI7_W=jt(0U5~ldeJ=q*>u)6yQ#K
zX5Br`$(Jp)qg|Ur*nww>cV?U?`RKIEPci8qwGcn->(q~f*PUJlFQ}6cUo3=fXX3Ry
ziW>Hub#s{J3u_i#zSK#2tIcH>xwlu1pFiT%Kso>ipp)D35^_I-gxi^03n}}3)jku)
zyOgRHGxzp(vyzEn49#2ur=c^iol7R2%OY{6!!Pec64EA5yAGqFE_Y4mFWm|+sJq&(
zTB2``4bu|1nigBJ9^hBNI~yQpE(vEA2TWx1u`%h};k1i^KWKq^3IC_W=IJ{*4z9_Z
z_kfups)1z(
z{n1RTQ|`K_0ZUX~o{%cc+T-?X8Zpm=#)OFDm6f|2|BH`MushlHL*|;)?aPFdQnxnz
z1YmxEGS`(DewkybtHN!y>&D4Kz(ohNw>1_mNo{eUD5HhAl5fWSu3FSrX(b?9HSdv(
z-#dA$Ip{xo3h-d%ftC2
zRkbXRW+xF9-DA82Bix6>k!EPGyMT#*X0`YB9YX6(9^d%=`p;8Uf76RT0vbxk*s$?k
z`KgS@)3dx6{*!P=Y^m#=_(7dFQ=L#>B-jDYq}l}O;NJ35gpqJM6-0v9D3QU)Z>U$g
z0opOYW3I@V@5`@iZ2iAa*@{M&sl1v?NfY*Ar~J4hg{b%nc(kd-3r*T43%q(}m682i
z1HRWM_MylZRl3+%c&lb@$&~!HL*LW7@(`F-+n5v&A4ndrS2{jx##C6d
zXj0-_Oifc%qAP-_@=}eP_tvT3HVLwtCEXSlvRH6yBu0-Il(*;`s9BmSV|5PIx)vyj
zyscy!JH5|Z6(dV|IHk}nG@Yj+$}Li+HFgop!l~7SGuYDAkJq82(K#UkbQ|G@{!hMw
zoVcx2Si10{_$g-_$vNO*_ESxLd3vy-Il(s*Ka3wHa&6a1>79_SCUVe)b19!#CSCSd
zS9D5ClK0UF9{~yYx$^h0_w~eE_1gr-N>wYI+6=yh9`m2%
z>@__*qw*OR&-7R7K)fOPX}CJN!17SzidQHZ6^iCj1jEm@n8*@;%5^TmV|&+?2i}-R
zvus+HbvtXSEz4!!Zj`e4b6aI}WyEYReH*+5LI~}wB=%dhRg=rri*p$QHXfAC6K!L1
zoH{$sPN}I4$aE&_>rbq8T$H^%rr&xfEVI`9+Q)%QXP;tAd?le!)e8}M(pQqbyy((T;3kv$U@g+A1uGvGQF_mrH+HX<#wa*5I1qvM
zhF%FI-e%U2wp20QTJLYgLy>y27DJ>ZZyV}lSPkY~bzh;L^LG9Ya(nq2-Rq#PL*800
zHC^47=QBiee8B}08lmbn>S~PW&>{%^etiLNv-fE`ywY@)&_ib`3_i|ZOp8JhIwLJ8
z|I**-Pqs|Ao`jjR_Z=(H$k>`+7Mqbj%Eg1io}Kd;b>
z0fy?O)#KWF&&_(-;ZDl$#7(E9Lw-UvIZ0MVGQn!oNdZfB*7o?O#&xIbFea)r
z_Q^nh4)cPSq|T?>#Z4XCZ){@~93#ew$=y2((gK0OPe7PwVAYdMK;+
z=-aXCYI`S>dS)l_1Pb>O%g^PCw**jMrh~y@uKk!0%l42~YQ5{{_B@>mCFKUt6q_>_
z+KvOGs!^6MPtaw=u@a@I$v<7Ui+#vh8W?@PVkYmX$$T|Q7wiLE+Xqf%uQMak931JW
zqau2jS|0q2yyafGlz7)8ES6S$p}Ds$5N?}MDiwmI_Y*DC6CW5?@j=vr#4r;IztS8-
z*f~Q);8n;_nmt8SEn%<8giTi0F!Lq?W10ku9x?JWEMrO~5OjdXlo54GmJr90@VI%#
z^`g^s3p=BXCOYo%s9W9>3F%A*ZHH`|Zf$-=DzdE?5a?_#G$oL5intdhR#M(*DuQv{wT{dxD1uC
z!-b!4gzqb=Wf*~SeRUsL{eRI$Gwq;&=t}s2Mg5dhpoUeLC@iAqSv9e#*;0lQ3%G!Q
z;*!0Km~*`cYO6WQAfNY8T@(=VCJtuOY_fC8`{D)Xq>V#v=kdrl{f1-ykCQ=Foc1ip
z3v}|~ifz5lbZrTJ$qMbm;R+7KEyNjePeab^?EPXz?{qRXQ3>*TmGtX{41yI}N4Jgx
z=2jLJ81`EnJ^Pe9?z>1DP4w>P@+8F5A?2ZfN-_37HI=foT|0D7E?LF3Md6)GK{5~v
zQ@Rg+^onO*ps}$ye-@33I4M(?RwMNMYfGZFS5Z8bBO`}bxdF2Q}UgpvfJ#bkCsb%RtEJIgzI)~{bm+T=SUTFgb5$?<}j@TO$2zPm;IxKp;O
zdOM-t4(L4%%{Lu^PC=@K2ScT_$)MPk17CY5=O4`XkiFX&q92cPR|03g{N
zZg*TheXfTc__>BF>ln_QEcnKz@V9(HwgSAz*K$Q(GsLpOj8lt-f>|zh>~YrOpLH`o
zi1!Y}ab@J6rB6ZyzW6s!792YVhg7^8aq8bLqKE^T2sPL=?CO`4bY5xNdp75Gv|jYU
z&Q$`|jV!U+JH?z^BowOR@7%T@9oYT_qHwo=oqPK5urHfc>
zxC@{j#I2yVL>mipwQPy=N*A>n4r%IDbpRV4UY+E_xX>B$04C}a2
zsMo<2$GHZIoKQ7zQX3BYN()ImDE6g8LFqReGh8YG)^t+2WUg5g(_N32*Big`=pdo)
zo=ys--2;g>FOHTJlj>*lMjck$ZOwAC#@WZ
z25@%b!N{#8NNKgV>LsuZrL3^d9hbgS^XP%%8{@#^^3}DOpA=hNHy67!)CR3J-mfZ(
zgfbg%U3y+AqWkPo0@j7$A~mIw7xL0k1P6r5^p}YURvk9?lMW256?Z$EJ;zHFMXi|+
zZAIQ{FIn`mCKib|T^2!LE{*cJ)TW1H^&fth%|4Ftv4*~F8mC~6AYx?>8gLR$)>5hD
zS0Fb{kNQ63?kqmP)a-5k0!arSrcmu(+=3+_5TfQGEf_R`
zr&A-Vy-+(put3b(_fpU_EC~0ky&+LWxY93JnCOCq{=;QMsYR|XwdF0Fc6Imy?*SyQ
z^K;b?WxSyQEZZ6UVM5Yyu=uiz--t2j_!CJ*Hq|Wt_PJ+3u@Glpnt(C(H@vjcEgLR@
zR9#b8wHwu}?Ocm{2wsZsqDoo8Nn6rS8-I#I7Jc4;M=N*kN@bhtLk&26xT
zVC2&aj+yqD#SQ2$HM)Eb4()Rs&(Dk$r9JmXHb>GWUEYGTJ=K-x58EseoPjGDzkSRO
zC}LBe+QUpPUG90ty60X~&7)Y^+99pT=F-WslqzWMIiZ?n6*vpi%6ODgS4Kl0;^@hk
zBmY%-`S27;{r}qy!~JFOyBp?e;pS%JVCCws)8$AhOGu4qc_zl-Y&z5v&iv{LKb*5)
zh8SD;Z6T=%!2mJJgt^1@%CS>B-(iN(H9g96Y{D;JHPqF25|A{s)W53J`dBBHdo?up
z!s6mMZi;iff9zzP`kwkuJ(_aZEu>8KtAm7-_Xm+pk|YwFClQa+Q&kCpIc5#lP}53T
zKVzBl2~Pd){vcI1@`NAPQXd{q&-YMn{gn`&iJd0&iBIJxjuQz`TgGMb0*G|UE!5x&45xGFp0xU>dOD61xrT^@Gd1(tZHxUB
zv*HNvE_;HU=dVr3E|g@Du%ukp
zx*aczN1hj7^0!VJ0!4U-+dj0Us0HmVzNP&!%!wh-^+L}U?R6AqBDl=-JJGiweL{5c
z6k>Za7m1V%ncV%F;_i3(Ym@k<=JUOi=lFOP*8Z0_#lgk+!#-2HJg?gE)^&!9WJotq
zvFgcy3Wn6axO7u?3blh@2{>i*RLUj^f!UD(MoJ&Xht&SGGo9h^XPbX5=
zaDeoHTZeH8`YruK5ef}eIzWUaF+UWEJD+OI&5+{(|}!k@{aFeFNysH`(tyS=xt=@9x}AqZ25
z_ZAH~_9X8g*Eo_9b6W{-xUpUDq1sS_(Grn}?CnRUDmSQql{a0HZ)Fy6PPq`*iOxx`XGY+Az$Y$sf
zlfjlo+Sqhzx}x|5ha8olwcRW+m0>y8JUZxcxH+aa?ev_;w>G1%qvIdle}2h}A13a#
zN_E5!t9aS&lG_b
z0&%lWBS!xb+T`Fa3gPKXEHtr^nSO<&107@$60|=Shra(O83tGM`Ua_J+6@4yByvB*
zR2T}lISVv;8~cQ*Yi313cJ_9)WiR0WQrw8)&~(W&B&0YDq+b;G~|F_@YG5G{81rT+!Ia@nE)
literal 0
HcmV?d00001
From 8fceffd6fd0afd882ff8ae27a00b53c899b2b11b Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 19 Dec 2020 15:16:22 +0100
Subject: [PATCH 024/131] Introduce NewPipeFileLocator class
---
.../newpipe/settings/ContentSettingsManager.kt | 12 +++---------
.../newpipe/settings/ContentSettingsManagerTest.kt | 11 ++++++-----
2 files changed, 9 insertions(+), 14 deletions(-)
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 3ab3dc8f3..0e779edbf 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
@@ -46,9 +46,7 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
}
- fun extractDb(
- filePath: String,
- ): Boolean {
+ fun extractDb(filePath: String): Boolean {
val success = ZipHelper.extractFileFromZip(filePath, fileLocator.db.path, "newpipe.db")
if (success) {
fileLocator.dbJournal.delete()
@@ -59,16 +57,12 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
return success
}
- fun containSettings(
- filePath: String,
- ): Boolean {
+ fun containSettings(filePath: String): Boolean {
return ZipHelper
.extractFileFromZip(filePath, fileLocator.settings.path, "newpipe.settings")
}
- fun loadSharedPreferences(
- preferences: SharedPreferences,
- ) {
+ fun loadSharedPreferences(preferences: SharedPreferences) {
try {
val preferenceEditor = preferences.edit()
diff --git a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
index 111fd7c09..e139dad7b 100644
--- a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
@@ -19,14 +19,14 @@ import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.Mockito.withSettings
import org.mockito.junit.MockitoJUnitRunner
-import org.schabi.newpipe.settings.ContentSettingsManagerTest.ExportTest
+import org.schabi.newpipe.settings.ContentSettingsManagerTest.*
import java.io.File
import java.io.ObjectInputStream
import java.nio.file.Files
import java.util.zip.ZipFile
@RunWith(Suite::class)
-@Suite.SuiteClasses(ExportTest::class)
+@Suite.SuiteClasses(ExportTest::class, ImportTest::class)
class ContentSettingsManagerTest {
@RunWith(MockitoJUnitRunner::class)
@@ -73,23 +73,24 @@ class ContentSettingsManagerTest {
val zipFile = ZipFile(output.absoluteFile)
val entries = zipFile.entries().toList()
- Assert.assertEquals(2, entries.size)
+ assertEquals(2, entries.size)
zipFile.getInputStream(entries.first { it.name == "newpipe.db" }).use { actual ->
newpipeDb.inputStream().use { expected ->
- Assert.assertEquals(expected.reader().readText(), actual.reader().readText())
+ assertEquals(expected.reader().readText(), actual.reader().readText())
}
}
zipFile.getInputStream(entries.first { it.name == "newpipe.settings" }).use { actual ->
val actualPreferences = ObjectInputStream(actual).readObject()
- Assert.assertEquals(expectedPreferences, actualPreferences)
+ assertEquals(expectedPreferences, actualPreferences)
}
}
}
@RunWith(MockitoJUnitRunner::class)
class ImportTest {
+
companion object {
private lateinit var fileLocator: NewPipeFileLocator
private lateinit var zip: File
From 122e80fae9afc2e5bc75aa3cd73af027391358d0 Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 19 Dec 2020 16:28:10 +0100
Subject: [PATCH 025/131] Remove subclasses from ContentSettingsManagerTest
ExportTest provides no value.
ImportTest creates temporary files even if not needed.
---
.../settings/ContentSettingsFragment.java | 7 +-
.../settings/ContentSettingsManager.kt | 2 +-
.../settings/ContentSettingsManagerTest.kt | 251 ++++++++----------
.../test/resources/settings/newpipe.settings | Bin 0 -> 2445 bytes
4 files changed, 106 insertions(+), 154 deletions(-)
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 2339de53a..18d49b507 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -32,14 +32,9 @@ import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.ZipHelper;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
-import java.util.Map;
-import java.util.zip.ZipFile;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@@ -241,7 +236,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
}
//If settings file exist, ask if it should be imported.
- if (manager.containSettings(filePath)) {
+ if (manager.extractSettings(filePath)) {
final AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
alert.setTitle(R.string.import_settings);
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 0e779edbf..bab29a30a 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
@@ -57,7 +57,7 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
return success
}
- fun containSettings(filePath: String): Boolean {
+ fun extractSettings(filePath: String): Boolean {
return ZipHelper
.extractFileFromZip(filePath, fileLocator.settings.path, "newpipe.settings")
}
diff --git a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
index e139dad7b..875ef758a 100644
--- a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
@@ -1,187 +1,144 @@
package org.schabi.newpipe.settings
import android.content.SharedPreferences
-import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
-import org.junit.Assume
import org.junit.Before
-import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Suite
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyString
import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.Mockito.withSettings
import org.mockito.junit.MockitoJUnitRunner
-import org.schabi.newpipe.settings.ContentSettingsManagerTest.*
import java.io.File
import java.io.ObjectInputStream
import java.nio.file.Files
import java.util.zip.ZipFile
-@RunWith(Suite::class)
-@Suite.SuiteClasses(ExportTest::class, ImportTest::class)
+@RunWith(MockitoJUnitRunner::class)
class ContentSettingsManagerTest {
- @RunWith(MockitoJUnitRunner::class)
- class ExportTest {
+ companion object {
+ private val classloader = ContentSettingsManager::class.java.classLoader!!
+ }
- companion object {
- private lateinit var fileLocator: NewPipeFileLocator
- private lateinit var newpipeDb: File
- private lateinit var newpipeSettings: File
+ private lateinit var fileLocator: NewPipeFileLocator
- @JvmStatic
- @BeforeClass
- fun setupFiles() {
- val dbPath = ExportTest::class.java.classLoader?.getResource("settings/newpipe.db")?.file
- val settingsPath = ExportTest::class.java.classLoader?.getResource("settings/newpipe.settings")?.path
- Assume.assumeNotNull(dbPath)
- Assume.assumeNotNull(settingsPath)
+ @Before
+ fun setupFileLocator() {
+ fileLocator = Mockito.mock(NewPipeFileLocator::class.java, withSettings().stubOnly())
+ }
- newpipeDb = File(dbPath!!)
- newpipeSettings = File(settingsPath!!)
+ @Test
+ fun `The settings must be exported successfully in the correct format`() {
+ val db = File(classloader.getResource("settings/newpipe.db")!!.file)
+ val newpipeSettings = File.createTempFile("newpipe_", "")
+ `when`(fileLocator.db).thenReturn(db)
+ `when`(fileLocator.settings).thenReturn(newpipeSettings)
- fileLocator = Mockito.mock(NewPipeFileLocator::class.java, withSettings().stubOnly())
- `when`(fileLocator.db).thenReturn(newpipeDb)
- `when`(fileLocator.settings).thenReturn(newpipeSettings)
+ val expectedPreferences = mapOf("such pref" to "much wow")
+ val sharedPreferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly())
+ `when`(sharedPreferences.all).thenReturn(expectedPreferences)
+
+ val output = File.createTempFile("newpipe_", "")
+ ContentSettingsManager(fileLocator).exportDatabase(sharedPreferences, output.absolutePath)
+
+ val zipFile = ZipFile(output)
+ val entries = zipFile.entries().toList()
+ assertEquals(2, entries.size)
+
+ zipFile.getInputStream(entries.first { it.name == "newpipe.db" }).use { actual ->
+ db.inputStream().use { expected ->
+ assertEquals(expected.reader().readText(), actual.reader().readText())
}
}
- private lateinit var preferences: SharedPreferences
-
- @Before
- fun setupMocks() {
- preferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly())
- }
-
- @Test
- fun `The settings must be exported successfully in the correct format`() {
- val expectedPreferences = mapOf("such pref" to "much wow")
- `when`(preferences.all).thenReturn(expectedPreferences)
-
- val manager = ContentSettingsManager(fileLocator)
-
- val output = File.createTempFile("newpipe_", "")
- manager.exportDatabase(preferences, output.absolutePath)
-
- val zipFile = ZipFile(output.absoluteFile)
- val entries = zipFile.entries().toList()
- assertEquals(2, entries.size)
-
- zipFile.getInputStream(entries.first { it.name == "newpipe.db" }).use { actual ->
- newpipeDb.inputStream().use { expected ->
- assertEquals(expected.reader().readText(), actual.reader().readText())
- }
- }
-
- zipFile.getInputStream(entries.first { it.name == "newpipe.settings" }).use { actual ->
- val actualPreferences = ObjectInputStream(actual).readObject()
- assertEquals(expectedPreferences, actualPreferences)
- }
+ zipFile.getInputStream(entries.first { it.name == "newpipe.settings" }).use { actual ->
+ val actualPreferences = ObjectInputStream(actual).readObject()
+ assertEquals(expectedPreferences, actualPreferences)
}
}
- @RunWith(MockitoJUnitRunner::class)
- class ImportTest {
-
- companion object {
- private lateinit var fileLocator: NewPipeFileLocator
- private lateinit var zip: File
- private lateinit var emptyZip: File
- private lateinit var db: File
- private lateinit var dbJournal: File
- private lateinit var dbWal: File
- private lateinit var dbShm: File
- private lateinit var settings: File
-
- @JvmStatic
- @BeforeClass
- fun setupReadOnlyFiles() {
- val zipPath = ImportTest::class.java.classLoader?.getResource("settings/newpipe.zip")?.file
- val emptyZipPath = ImportTest::class.java.classLoader?.getResource("settings/empty.zip")?.file
- Assume.assumeNotNull(zipPath)
- Assume.assumeNotNull(emptyZipPath)
-
- zip = File(zipPath!!)
- emptyZip = File(emptyZipPath!!)
- }
- }
-
- @Before
- fun setupWriteFiles() {
- db = File.createTempFile("newpipe_", "")
- dbJournal = File.createTempFile("newpipe_", "")
- dbWal = File.createTempFile("newpipe_", "")
- dbShm = File.createTempFile("newpipe_", "")
- settings = File.createTempFile("newpipe_", "")
-
- fileLocator = Mockito.mock(NewPipeFileLocator::class.java, withSettings().stubOnly())
- `when`(fileLocator.db).thenReturn(db)
- `when`(fileLocator.dbJournal).thenReturn(dbJournal)
- `when`(fileLocator.dbShm).thenReturn(dbShm)
- `when`(fileLocator.dbWal).thenReturn(dbWal)
- `when`(fileLocator.settings).thenReturn(settings)
- }
-
- @Test
- fun `The database must be extracted from the zip file`() {
- val success = ContentSettingsManager(fileLocator).extractDb(zip.path)
-
- assertTrue(success)
- assertFalse(dbJournal.exists())
- assertFalse(dbWal.exists())
- assertFalse(dbShm.exists())
- assertTrue("database file size is zero", Files.size(db.toPath()) > 0)
- }
-
- @Test
- fun `Extracting the database from an empty zip must not work`() {
- val success = ContentSettingsManager(fileLocator).extractDb(emptyZip.path)
-
- assertFalse(success)
- assertTrue(dbJournal.exists())
- assertTrue(dbWal.exists())
- assertTrue(dbShm.exists())
- assertEquals(0, Files.size(db.toPath()))
- }
-
- @Test
- fun `Contain setting must return true, if a settings file exists in the zip`() {
- val contains = ContentSettingsManager(fileLocator).containSettings(zip.path)
-
- assertTrue(contains)
- }
-
- @Test
- fun `Contain setting must return false, if a no settings file exists in the zip`() {
- val contains = ContentSettingsManager(fileLocator).containSettings(emptyZip.path)
-
- assertFalse(contains)
- }
-
- @Test
- fun `Preferences must be set from the settings file`() {
- val preferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly())
- val editor = Mockito.mock(SharedPreferences.Editor::class.java)
- `when`(preferences.edit()).thenReturn(editor)
-
-
- val manager = ContentSettingsManager(fileLocator)
- manager.containSettings(zip.path)
- manager.loadSharedPreferences(preferences)
-
- verify(editor, atLeastOnce()).putBoolean(anyString(), anyBoolean())
- }
+ @Test
+ fun `The database must be extracted from the zip file`() {
+ val db = File.createTempFile("newpipe_", "")
+ val dbJournal = File.createTempFile("newpipe_", "")
+ val dbWal = File.createTempFile("newpipe_", "")
+ val dbShm = File.createTempFile("newpipe_", "")
+ `when`(fileLocator.db).thenReturn(db)
+ `when`(fileLocator.dbJournal).thenReturn(dbJournal)
+ `when`(fileLocator.dbShm).thenReturn(dbShm)
+ `when`(fileLocator.dbWal).thenReturn(dbWal)
+ val zip = File(classloader.getResource("settings/newpipe.zip")?.file!!)
+ val success = ContentSettingsManager(fileLocator).extractDb(zip.path)
+ assertTrue(success)
+ assertFalse(dbJournal.exists())
+ assertFalse(dbWal.exists())
+ assertFalse(dbShm.exists())
+ assertTrue("database file size is zero", Files.size(db.toPath()) > 0)
}
+ @Test
+ fun `Extracting the database from an empty zip must not work`() {
+ val db = File.createTempFile("newpipe_", "")
+ val dbJournal = File.createTempFile("newpipe_", "")
+ val dbWal = File.createTempFile("newpipe_", "")
+ val dbShm = File.createTempFile("newpipe_", "")
+ `when`(fileLocator.db).thenReturn(db)
+ val emptyZip = File(classloader.getResource("settings/empty.zip")?.file!!)
+ val success = ContentSettingsManager(fileLocator).extractDb(emptyZip.path)
+
+ assertFalse(success)
+ assertTrue(dbJournal.exists())
+ assertTrue(dbWal.exists())
+ assertTrue(dbShm.exists())
+ assertEquals(0, Files.size(db.toPath()))
+ }
+
+ @Test
+ fun `Contains setting must return true if a settings file exists in the zip`() {
+ val settings = File.createTempFile("newpipe_", "")
+ `when`(fileLocator.settings).thenReturn(settings)
+
+ val zip = File(classloader.getResource("settings/newpipe.zip")?.file!!)
+ val contains = ContentSettingsManager(fileLocator).extractSettings(zip.path)
+
+ assertTrue(contains)
+ }
+
+ @Test
+ fun `Contains setting must return false if a no settings file exists in the zip`() {
+ val settings = File.createTempFile("newpipe_", "")
+ `when`(fileLocator.settings).thenReturn(settings)
+
+ val emptyZip = File(classloader.getResource("settings/empty.zip")?.file!!)
+ val contains = ContentSettingsManager(fileLocator).extractSettings(emptyZip.path)
+
+ assertFalse(contains)
+ }
+
+ @Test
+ fun `Preferences must be set from the settings file`() {
+ val settings = File(classloader.getResource("settings/newpipe.settings")!!.path)
+ `when`(fileLocator.settings).thenReturn(settings)
+
+ val preferences = Mockito.mock(SharedPreferences::class.java, withSettings().stubOnly())
+ val editor = Mockito.mock(SharedPreferences.Editor::class.java)
+ `when`(preferences.edit()).thenReturn(editor)
+
+ ContentSettingsManager(fileLocator).loadSharedPreferences(preferences)
+
+ verify(editor, atLeastOnce()).putBoolean(anyString(), anyBoolean())
+ verify(editor, atLeastOnce()).putString(anyString(), anyString())
+ verify(editor, atLeastOnce()).putInt(anyString(), anyInt())
+ }
}
diff --git a/app/src/test/resources/settings/newpipe.settings b/app/src/test/resources/settings/newpipe.settings
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..56e6c5d5dd53ba66b0144215b928f6137f67fc30 100644
GIT binary patch
literal 2445
zcmaJ@zi%8x7#-VjLL8FV`Qea26rcba>tWC@AwsfT$@bXs8f9v`AFEZ+6$YOH59>lXiFZn>X*h?|t{^FSQuFTKUxM
znYEg2zINM$?2f4xkNp1AMo9T~PD4)KczDddnp2{FXY(nWvDsJRMi>zNh5#VDub*
zAL4tR)oUR!Icat}?AFGUwuKqux74L$JFtmi>&mddYi`Qn+I8o0GUYEX`-i`M_wV2H
zYVH%YxM%X3#?>6F3)rzPNV`&-I<-y@Tv7);G`0-8>T|WASe>bZ>_TJU)PZP6@6bdZ
z9%l6-@Q$>=Kq2-vVM=x4N~V(AB`+la;Pq}zHOZra^+iNFU8ZtXU7UAfw;Se;JYs1z#%U6?Ueu%DpT+pJEuT!Of|jCT)*Nnk2iU7A*1hui^(
zD=HDGC;QI&U<;BA$)Ec3`yc-K;;T*gV5{?iSIg0*(Jyyv5#Y;5-(EcS%^zRSD>bg*
zKDgjyR2nW|y=PN$t-8x{N_n#u6JT3b`v}iIK30IkWq}DhEse*!hq^MHv3f&jeP_L0
zxK2f#gT!>bw|>X%SqkfSX>`w4w9e{^t8Q-p483ZdsB*vt+ynCUU$oh43)KX42h
z!4@eLKP2YDYl(LuXn0`c{EqGrcCQ_eRY9_--rYPe-2qm}mnLthiEbp{GPG*)96gee
zsdQQwl`V%;ws?k$m!pbO;ErW9c(hCS))im5}2_^Pw<68fK2z8dISMv0Drk&AjoMs#>b9it&?BwVX2r$E{;3Vfb9jr!2~|;gdEOfJ
zRg-wLmebHAlWVyrlTL29au110>3z4cwz<}8C4S|ZE8h>UY!@zahID4Nlp3^hdvU(<
zG$8MR!&QWtmk5#u=!(flW)dW#+6HDd+uY5B6oU7g`vA$VNV({1Y1^Z4NS7l(@kHy2
zpk0{7RK@(2>B#D6b6HdytS;sTafKF5b%USEgn|Y=0qEttZ%)w|ni~KpkK%laVjRSU^
zjkP0)wMJ5$@#E%1cYR0S!jG8J9etsl{&8xzDqo6d(dI)d@yix%W`<6)%&E>_
Date: Sat, 19 Dec 2020 16:35:30 +0100
Subject: [PATCH 026/131] Extract settings file deletion
---
.../settings/ContentSettingsFragment.java | 17 +---------------
.../settings/ContentSettingsManager.kt | 5 ++++-
.../settings/ContentSettingsManagerTest.kt | 20 +++++++++++++++++++
3 files changed, 25 insertions(+), 17 deletions(-)
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 18d49b507..c0639131c 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -44,13 +44,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private ContentSettingsManager manager;
- private File databasesDir;
- private File newpipeDb;
- private File newpipeDbJournal;
- private File newpipeDbShm;
- private File newpipeDbWal;
- private File newpipeSettings;
-
private String thumbnailLoadToggleKey;
private String youtubeRestrictedModeEnabledKey;
@@ -115,16 +108,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
final File homeDir = ContextCompat.getDataDir(requireContext());
- databasesDir = new File(homeDir, "/databases");
- newpipeDb = new File(homeDir, "/databases/newpipe.db");
- newpipeDbJournal = new File(homeDir, "/databases/newpipe.db-journal");
- newpipeDbShm = new File(homeDir, "/databases/newpipe.db-shm");
- newpipeDbWal = new File(homeDir, "/databases/newpipe.db-wal");
-
- newpipeSettings = new File(homeDir, "/databases/newpipe.settings");
- newpipeSettings.delete();
-
manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir));
+ manager.deleteSettingsFile();
addPreferencesFromResource(R.xml.content_settings);
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 bab29a30a..d461ac61b 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
@@ -8,7 +8,6 @@ import java.io.FileOutputStream
import java.io.IOException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
-import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
@@ -36,6 +35,10 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
}
}
+ fun deleteSettingsFile() {
+ fileLocator.settings.delete()
+ }
+
/**
* Tries to create database directory if it does not exist.
*
diff --git a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
index 875ef758a..088b7e1d1 100644
--- a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
@@ -65,6 +65,26 @@ class ContentSettingsManagerTest {
}
}
+ @Test
+ fun `Settings file must be deleted`() {
+ val settings = File.createTempFile("newpipe_", "")
+ `when`(fileLocator.settings).thenReturn(settings)
+
+ ContentSettingsManager(fileLocator).deleteSettingsFile()
+
+ assertFalse(settings.exists())
+ }
+
+ @Test
+ fun `Deleting settings file must do nothing if none exist`() {
+ val settings = File("non_existent")
+ `when`(fileLocator.settings).thenReturn(settings)
+
+ ContentSettingsManager(fileLocator).deleteSettingsFile()
+
+ assertFalse(settings.exists())
+ }
+
@Test
fun `The database must be extracted from the zip file`() {
val db = File.createTempFile("newpipe_", "")
From fcfdcd1025a6fc005c11e82a0ace8a5fe3fba1a3 Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 19 Dec 2020 16:53:11 +0100
Subject: [PATCH 027/131] Fix ensureDbDirectoryExists
---
.../settings/ContentSettingsManager.kt | 3 +--
.../settings/ContentSettingsManagerTest.kt | 20 +++++++++++++++++++
2 files changed, 21 insertions(+), 2 deletions(-)
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 d461ac61b..49d35d984 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
@@ -45,10 +45,9 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
* @return Whether the directory exists afterwards.
*/
fun ensureDbDirectoryExists(): Boolean {
- return !fileLocator.dbDir.exists() && !fileLocator.dbDir.mkdir()
+ return fileLocator.dbDir.exists() || fileLocator.dbDir.mkdir()
}
-
fun extractDb(filePath: String): Boolean {
val success = ZipHelper.extractFileFromZip(filePath, fileLocator.db.path, "newpipe.db")
if (success) {
diff --git a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
index 088b7e1d1..a21ad7cb1 100644
--- a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
@@ -4,6 +4,7 @@ import android.content.SharedPreferences
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
+import org.junit.Assume
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -85,6 +86,25 @@ class ContentSettingsManagerTest {
assertFalse(settings.exists())
}
+ @Test
+ fun `Ensuring db directory existence must work`() {
+ val dir = Files.createTempDirectory("newpipe_").toFile()
+ Assume.assumeTrue(dir.delete())
+ `when`(fileLocator.dbDir).thenReturn(dir)
+
+ ContentSettingsManager(fileLocator).ensureDbDirectoryExists()
+ assertTrue(dir.exists())
+ }
+
+ @Test
+ fun `Ensuring db directory existence must work when the directory already exists`() {
+ val dir = Files.createTempDirectory("newpipe_").toFile()
+ `when`(fileLocator.dbDir).thenReturn(dir)
+
+ ContentSettingsManager(fileLocator).ensureDbDirectoryExists()
+ assertTrue(dir.exists())
+ }
+
@Test
fun `The database must be extracted from the zip file`() {
val db = File.createTempFile("newpipe_", "")
From 716d79597050a24b315d5b384e744b4a3b47983b Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 19 Dec 2020 16:54:42 +0100
Subject: [PATCH 028/131] cleanup
---
.../settings/ContentSettingsManager.kt | 23 ++++++++++---------
.../newpipe/settings/NewPipeFileLocator.kt | 1 -
.../settings/ContentSettingsManagerTest.kt | 2 +-
3 files changed, 13 insertions(+), 13 deletions(-)
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 49d35d984..1730a230e 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
@@ -19,19 +19,19 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
@Throws(Exception::class)
fun exportDatabase(preferences: SharedPreferences, outputPath: String) {
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputPath)))
- .use { outZip ->
- ZipHelper.addFileToZip(outZip, fileLocator.db.path, "newpipe.db")
+ .use { outZip ->
+ ZipHelper.addFileToZip(outZip, fileLocator.db.path, "newpipe.db")
- try {
- ObjectOutputStream(FileOutputStream(fileLocator.settings)).use { output ->
- output.writeObject(preferences.all)
- output.flush()
- }
- } catch (e: IOException) {
- e.printStackTrace()
+ try {
+ ObjectOutputStream(FileOutputStream(fileLocator.settings)).use { output ->
+ output.writeObject(preferences.all)
+ output.flush()
}
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
- ZipHelper.addFileToZip(outZip, fileLocator.settings.path, "newpipe.settings")
+ ZipHelper.addFileToZip(outZip, fileLocator.settings.path, "newpipe.settings")
}
}
@@ -61,7 +61,7 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
fun extractSettings(filePath: String): Boolean {
return ZipHelper
- .extractFileFromZip(filePath, fileLocator.settings.path, "newpipe.settings")
+ .extractFileFromZip(filePath, fileLocator.settings.path, "newpipe.settings")
}
fun loadSharedPreferences(preferences: SharedPreferences) {
@@ -70,6 +70,7 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) {
ObjectInputStream(FileInputStream(fileLocator.settings)).use { input ->
preferenceEditor.clear()
+ @Suppress("UNCHECKED_CAST")
val entries = input.readObject() as Map
for ((key, value) in entries) {
when (value) {
diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt b/app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt
index 1fe2fffa0..c2f93d15f 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeFileLocator.kt
@@ -18,5 +18,4 @@ class NewPipeFileLocator(private val homeDir: File) {
val dbWal by lazy { File(homeDir, "/databases/newpipe.db-wal") }
val settings by lazy { File(homeDir, "/databases/newpipe.settings") }
-
}
diff --git a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
index a21ad7cb1..3c59c29b3 100644
--- a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt
@@ -10,9 +10,9 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyString
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.Mockito.withSettings
From 50a026183d56b379f9d0f20a8f420106a00aaae6 Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Wed, 6 Jan 2021 14:48:34 +0100
Subject: [PATCH 029/131] Make Localization.relativeTime testable
Problem is global state in static variable prettyTime. But for performance reasons on Android that is preferred.
Now allow injecting prettyTime dependency by making init function public.
---
app/src/main/java/org/schabi/newpipe/App.java | 37 ++++++++----------
.../java/org/schabi/newpipe/MainActivity.java | 16 +++-----
.../org/schabi/newpipe/util/Localization.java | 27 ++++++-------
.../schabi/newpipe/util/LocalizationTest.kt | 39 +++++++++++++++++++
4 files changed, 73 insertions(+), 46 deletions(-)
create mode 100644 app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index 7e3466f67..062a033e9 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -6,16 +6,26 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.multidex.MultiDexApplication;
import androidx.preference.PreferenceManager;
-
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
-
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.CompositeException;
+import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
+import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
+import io.reactivex.rxjava3.exceptions.UndeliverableException;
+import io.reactivex.rxjava3.functions.Consumer;
+import io.reactivex.rxjava3.plugins.RxJavaPlugins;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.SocketException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import org.acra.ACRA;
import org.acra.config.ACRAConfigurationException;
import org.acra.config.CoreConfiguration;
@@ -31,21 +41,6 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.net.SocketException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.exceptions.CompositeException;
-import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
-import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
-import io.reactivex.rxjava3.exceptions.UndeliverableException;
-import io.reactivex.rxjava3.functions.Consumer;
-import io.reactivex.rxjava3.plugins.RxJavaPlugins;
-
/*
* Copyright (C) Hans-Christoph Steiner 2016
* App.java is part of NewPipe.
@@ -93,9 +88,9 @@ public class App extends MultiDexApplication {
SettingsActivity.initSettings(this);
NewPipe.init(getDownloader(),
- Localization.getPreferredLocalization(this),
- Localization.getPreferredContentCountry(this));
- Localization.init(getApplicationContext());
+ Localization.getPreferredLocalization(this),
+ Localization.getPreferredContentCountry(this));
+ Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext()));
StateSaver.init(this);
initNotificationChannels();
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 0c784e9d5..1d1f87f6f 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -20,6 +20,8 @@
package org.schabi.newpipe;
+import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -41,7 +43,6 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.Spinner;
-
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
@@ -52,9 +53,10 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;
-
import com.google.android.material.bottomsheet.BottomSheetBehavior;
-
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
import org.schabi.newpipe.databinding.ActivityMainBinding;
import org.schabi.newpipe.databinding.DrawerHeaderBinding;
import org.schabi.newpipe.databinding.DrawerLayoutBinding;
@@ -87,12 +89,6 @@ import org.schabi.newpipe.util.TLSSocketFactoryCompat;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
-
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
@@ -468,7 +464,7 @@ public class MainActivity extends AppCompatActivity {
protected void onResume() {
assureCorrectAppLanguage(this);
// Change the date format to match the selected language on resume
- Localization.init(getApplicationContext());
+ Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext()));
super.onResume();
// Close drawer on return, and don't show animation,
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 978f558c4..3fdab9a12 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -9,19 +9,10 @@ import android.icu.text.CompactDecimalFormat;
import android.os.Build;
import android.text.TextUtils;
import android.util.DisplayMetrics;
-
import androidx.annotation.NonNull;
import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
-
-import org.ocpsoft.prettytime.PrettyTime;
-import org.ocpsoft.prettytime.units.Decade;
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.ListExtractor;
-import org.schabi.newpipe.extractor.localization.ContentCountry;
-import org.schabi.newpipe.ktx.OffsetDateTimeKt;
-
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
@@ -33,6 +24,12 @@ import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
+import org.ocpsoft.prettytime.PrettyTime;
+import org.ocpsoft.prettytime.units.Decade;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.ListExtractor;
+import org.schabi.newpipe.extractor.localization.ContentCountry;
+import org.schabi.newpipe.ktx.OffsetDateTimeKt;
/*
@@ -62,10 +59,6 @@ public final class Localization {
private Localization() { }
- public static void init(final Context context) {
- initPrettyTime(context);
- }
-
@NonNull
public static String concatenateStrings(final String... strings) {
return concatenateStrings(Arrays.asList(strings));
@@ -307,12 +300,16 @@ public final class Localization {
// Pretty Time
//////////////////////////////////////////////////////////////////////////*/
- private static void initPrettyTime(final Context context) {
- prettyTime = new PrettyTime(getAppLocale(context));
+ public static void initPrettyTime(final PrettyTime time) {
+ prettyTime = time;
// Do not use decades as YouTube doesn't either.
prettyTime.removeUnit(Decade.class);
}
+ public static PrettyTime resolvePrettyTime(final Context context) {
+ return new PrettyTime(getAppLocale(context));
+ }
+
public static String relativeTime(final OffsetDateTime offsetDateTime) {
return relativeTime(OffsetDateTimeKt.toCalendar(offsetDateTime));
}
diff --git a/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt b/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt
new file mode 100644
index 000000000..d0a8e7a40
--- /dev/null
+++ b/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt
@@ -0,0 +1,39 @@
+package org.schabi.newpipe.util
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.ocpsoft.prettytime.PrettyTime
+import java.text.SimpleDateFormat
+import java.time.OffsetDateTime
+import java.time.ZoneOffset
+import java.util.GregorianCalendar
+import java.util.Locale
+
+class LocalizationTest {
+
+ @Test
+ fun `After initializing pretty time relativeTime() with a Calendar must work`() {
+ val reference = SimpleDateFormat("yyyy/MM/dd").parse("2021/1/1")
+ Localization.initPrettyTime(PrettyTime(reference, Locale.ENGLISH))
+
+ val actual = Localization.relativeTime(GregorianCalendar(2021, 1, 6))
+
+ assertEquals("1 month from now", actual)
+ }
+
+ @Test(expected = NullPointerException::class)
+ fun `relativeTime() must fail without initializing pretty time`() {
+ Localization.relativeTime(GregorianCalendar(2021, 1, 6))
+ }
+
+ @Test
+ fun `relativeTime() with a OffsetDateTime must work`() {
+ val reference = SimpleDateFormat("yyyy/MM/dd").parse("2021/1/1")
+ Localization.initPrettyTime(PrettyTime(reference, Locale.ENGLISH))
+
+ val offset = OffsetDateTime.of(2021, 1, 6, 0, 0, 0, 0, ZoneOffset.UTC)
+ val actual = Localization.relativeTime(offset)
+
+ assertEquals("5 days from now", actual)
+ }
+}
From 907106156fa2887e1714a0c8ff25e326e67433c4 Mon Sep 17 00:00:00 2001
From: Mikhail Barashkov
Date: Fri, 1 Jan 2021 23:23:59 +0200
Subject: [PATCH 030/131] When in Fullscreen playback mode, toggle play/pause
with the hardware Space button.
---
.../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 2 ++
.../main/java/org/schabi/newpipe/player/VideoPlayerImpl.java | 5 +++++
2 files changed, 7 insertions(+)
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 470de3aa9..aeb51f63a 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
@@ -1891,8 +1891,10 @@ public final class VideoDetailFragment
if (fullscreen) {
hideSystemUiIfNeeded();
+ viewPager.setVisibility(View.GONE);
} else {
showSystemUi();
+ viewPager.setVisibility(View.VISIBLE);
}
if (relatedStreamsLayout != null) {
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
index 06cbcd780..10887790b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
@@ -505,6 +505,11 @@ public class VideoPlayerImpl extends VideoPlayer
switch (keyCode) {
default:
break;
+ case KeyEvent.KEYCODE_SPACE:
+ if (isFullscreen) {
+ onPlayPause();
+ }
+ break;
case KeyEvent.KEYCODE_BACK:
if (DeviceUtils.isTv(service) && isControlsVisible()) {
hideControls(0, 0);
From 0ff7170ab18cef623a281a3beb1984e6f4bc60e4 Mon Sep 17 00:00:00 2001
From: nadiration <59365236+nadiration@users.noreply.github.com>
Date: Sat, 9 Jan 2021 18:39:43 +0300
Subject: [PATCH 031/131] Update settings_keys.xml
Changed the Somali language name from Af-Soomaali to Soomaali which is common and more user friendly when users are looking for Somali language in the list (since they aren't expecting it starts with A as in Af-Soomaali).
I contributed the language to the project on Weblate and I think this is name is better.
---
app/src/main/res/values/settings_keys.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index 44e75c10c..e9d951520 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -1145,7 +1145,7 @@
sarduSlovenčinaSlovenščina
- Af Soomaali
+ SoomaaliShqipСрпскиSvenska
From 43e4dc817034bbf6df6eb43ff09083539e960eac Mon Sep 17 00:00:00 2001
From: bopol
Date: Sun, 10 Jan 2021 15:54:01 +0100
Subject: [PATCH 032/131] update extractor version
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index 9fb9cf85f..f2c846442 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -179,7 +179,7 @@ dependencies {
// NewPipe dependencies
// You can use a local version by uncommenting a few lines in settings.gradle
- implementation 'com.github.TeamNewPipe:NewPipeExtractor:deb9af7bf53b3f8fd9d32322adae02df78d985ea'
+ implementation 'com.github.TeamNewPipe:NewPipeExtractor:2d93b237236b8dce98943fd5dced9b8e645a2e0a'
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
implementation "org.jsoup:jsoup:1.13.1"
From 486e720e00a6c589997821529f0e77967b6598d7 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 21 Nov 2020 15:15:42 +0530
Subject: [PATCH 033/131] Rewrite ExceptionUtils methods as extension
functions.
---
app/src/main/java/org/schabi/newpipe/App.java | 2 +-
.../newpipe/fragments/BaseStateFragment.java | 2 +-
.../fragments/list/search/SearchFragment.java | 2 +-
.../java/org/schabi/newpipe/ktx/Throwable.kt | 65 ++++++++++++++
.../local/feed/service/FeedLoadService.kt | 4 +-
.../services/BaseImportExportService.java | 2 +-
.../services/SubscriptionsImportService.java | 2 +-
.../org/schabi/newpipe/util/ExceptionUtils.kt | 86 -------------------
.../schabi/newpipe/util/ExtractorHelper.java | 1 +
.../newpipe/ktx/ThrowableExtensionsTest.kt | 67 +++++++++++++++
.../schabi/newpipe/util/ExceptionUtilsTest.kt | 69 ---------------
11 files changed, 140 insertions(+), 162 deletions(-)
create mode 100644 app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt
delete mode 100644 app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt
create mode 100644 app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt
delete mode 100644 app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index 7e3466f67..ea6c23028 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -22,11 +22,11 @@ import org.acra.config.CoreConfiguration;
import org.acra.config.CoreConfigurationBuilder;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader;
+import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
-import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
index 0d25765a4..9f1f57998 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
@@ -22,10 +22,10 @@ import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
+import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
-import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.InfoCache;
import java.util.Collections;
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 3b48d9c84..fb05ed51c 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
@@ -50,6 +50,7 @@ import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearch
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
+import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
@@ -57,7 +58,6 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
-import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
diff --git a/app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt b/app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt
new file mode 100644
index 000000000..e05a55f3f
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt
@@ -0,0 +1,65 @@
+@file:JvmName("ExceptionUtils")
+
+package org.schabi.newpipe.ktx
+
+import java.io.IOException
+import java.io.InterruptedIOException
+
+/**
+ * @return if throwable is related to Interrupted exceptions, or one of its causes is.
+ */
+val Throwable.isInterruptedCaused: Boolean
+ get() = hasExactCause(InterruptedIOException::class.java, InterruptedException::class.java)
+
+/**
+ * @return if throwable is related to network issues, or one of its causes is.
+ */
+val Throwable.isNetworkRelated: Boolean
+ get() = hasAssignableCause(IOException::class.java)
+
+/**
+ * Calls [hasCause] with the `checkSubtypes` parameter set to false.
+ */
+fun Throwable.hasExactCause(vararg causesToCheck: Class<*>) = hasCause(false, *causesToCheck)
+
+/**
+ * Calls [hasCause] with the `checkSubtypes` parameter set to true.
+ */
+fun Throwable?.hasAssignableCause(vararg causesToCheck: Class<*>) = hasCause(true, *causesToCheck)
+
+/**
+ * Check if the throwable has some cause from the causes to check, or is itself in it.
+ *
+ * If `checkIfAssignable` is true, not only the exact type will be considered equals, but also its subtypes.
+ *
+ * @param checkSubtypes if subtypes are also checked.
+ * @param causesToCheck an array of causes to check.
+ *
+ * @see Class.isAssignableFrom
+ */
+tailrec fun Throwable?.hasCause(checkSubtypes: Boolean, vararg causesToCheck: Class<*>): Boolean {
+ if (this == null) {
+ return false
+ }
+
+ // Check if throwable is a subtype of any of the causes to check
+ causesToCheck.forEach { causeClass ->
+ if (checkSubtypes) {
+ if (causeClass.isAssignableFrom(this.javaClass)) {
+ return true
+ }
+ } else {
+ if (causeClass == this.javaClass) {
+ return true
+ }
+ }
+ }
+
+ val currentCause: Throwable? = cause
+ // Check if cause is not pointing to the same instance, to avoid infinite loops.
+ if (this !== currentCause) {
+ return currentCause.hasCause(checkSubtypes, *causesToCheck)
+ }
+
+ return false
+}
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
index 45e8855e7..5ed7998d2 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
@@ -50,13 +50,13 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.extractor.ListInfo
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.extractor.stream.StreamInfoItem
+import org.schabi.newpipe.ktx.isNetworkRelated
import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
import org.schabi.newpipe.local.subscription.SubscriptionManager
-import org.schabi.newpipe.util.ExceptionUtils
import org.schabi.newpipe.util.ExtractorHelper
import java.io.IOException
import java.time.OffsetDateTime
@@ -344,7 +344,7 @@ class FeedLoadService : Service() {
error is IOException -> throw error
cause is IOException -> throw cause
- ExceptionUtils.isNetworkRelated(error) -> throw IOException(error)
+ error.isNetworkRelated -> throw IOException(error)
}
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java
index 34543b565..f573f4679 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java
@@ -36,11 +36,11 @@ import androidx.core.app.ServiceCompat;
import org.reactivestreams.Publisher;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
+import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
-import org.schabi.newpipe.util.ExceptionUtils;
import java.io.FileNotFoundException;
import java.util.Collections;
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
index 90d0afe37..af94934b2 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
@@ -35,8 +35,8 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.subscription.SubscriptionItem;
+import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.util.Constants;
-import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import java.io.File;
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt b/app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt
deleted file mode 100644
index 0addb26fb..000000000
--- a/app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.schabi.newpipe.util
-
-import java.io.IOException
-import java.io.InterruptedIOException
-
-class ExceptionUtils {
- companion object {
- /**
- * @return if throwable is related to Interrupted exceptions, or one of its causes is.
- */
- @JvmStatic
- fun isInterruptedCaused(throwable: Throwable): Boolean {
- return hasExactCause(
- throwable,
- InterruptedIOException::class.java,
- InterruptedException::class.java
- )
- }
-
- /**
- * @return if throwable is related to network issues, or one of its causes is.
- */
- @JvmStatic
- fun isNetworkRelated(throwable: Throwable): Boolean {
- return hasAssignableCause(
- throwable,
- IOException::class.java
- )
- }
-
- /**
- * Calls [hasCause] with the `checkSubtypes` parameter set to false.
- */
- @JvmStatic
- fun hasExactCause(throwable: Throwable, vararg causesToCheck: Class<*>): Boolean {
- return hasCause(throwable, false, *causesToCheck)
- }
-
- /**
- * Calls [hasCause] with the `checkSubtypes` parameter set to true.
- */
- @JvmStatic
- fun hasAssignableCause(throwable: Throwable?, vararg causesToCheck: Class<*>): Boolean {
- return hasCause(throwable, true, *causesToCheck)
- }
-
- /**
- * Check if throwable has some cause from the causes to check, or is itself in it.
- *
- * If `checkIfAssignable` is true, not only the exact type will be considered equals, but also its subtypes.
- *
- * @param throwable throwable that will be checked.
- * @param checkSubtypes if subtypes are also checked.
- * @param causesToCheck an array of causes to check.
- *
- * @see Class.isAssignableFrom
- */
- @JvmStatic
- tailrec fun hasCause(throwable: Throwable?, checkSubtypes: Boolean, vararg causesToCheck: Class<*>): Boolean {
- if (throwable == null) {
- return false
- }
-
- // Check if throwable is a subtype of any of the causes to check
- causesToCheck.forEach { causeClass ->
- if (checkSubtypes) {
- if (causeClass.isAssignableFrom(throwable.javaClass)) {
- return true
- }
- } else {
- if (causeClass == throwable.javaClass) {
- return true
- }
- }
- }
-
- val currentCause: Throwable? = throwable.cause
- // Check if cause is not pointing to the same instance, to avoid infinite loops.
- if (throwable !== currentCause) {
- return hasCause(currentCause, checkSubtypes, *causesToCheck)
- }
-
- return false
- }
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 1f1b94545..103d9a72b 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -58,6 +58,7 @@ import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExt
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
+import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
diff --git a/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt b/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt
new file mode 100644
index 000000000..8132b8fbc
--- /dev/null
+++ b/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt
@@ -0,0 +1,67 @@
+package org.schabi.newpipe.ktx
+
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import java.io.IOException
+import java.io.InterruptedIOException
+import java.net.SocketException
+import javax.net.ssl.SSLException
+
+class ThrowableExtensionsTest {
+ @Test fun `assignable causes`() {
+ assertTrue(Throwable().hasAssignableCause(Throwable::class.java))
+ assertTrue(Exception().hasAssignableCause(Exception::class.java))
+ assertTrue(IOException().hasAssignableCause(Exception::class.java))
+
+ assertTrue(IOException().hasAssignableCause(IOException::class.java))
+ assertTrue(Exception(SocketException()).hasAssignableCause(IOException::class.java))
+ assertTrue(Exception(IllegalStateException()).hasAssignableCause(RuntimeException::class.java))
+ assertTrue(Exception(Exception(IOException())).hasAssignableCause(IOException::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(IOException()))).hasAssignableCause(IOException::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(SocketException()))).hasAssignableCause(IOException::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(SSLException("IO")))).hasAssignableCause(IOException::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(IOException::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(RuntimeException::class.java))
+
+ assertTrue(IllegalStateException().hasAssignableCause(Throwable::class.java))
+ assertTrue(IllegalStateException().hasAssignableCause(Exception::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(InterruptedIOException::class.java))
+ }
+
+ @Test fun `no assignable causes`() {
+ assertFalse(Throwable().hasAssignableCause(Exception::class.java))
+ assertFalse(Exception().hasAssignableCause(IOException::class.java))
+ assertFalse(Exception(IllegalStateException()).hasAssignableCause(IOException::class.java))
+ assertFalse(Exception(NullPointerException()).hasAssignableCause(IOException::class.java))
+ assertFalse(Exception(IllegalStateException(Exception(Exception()))).hasAssignableCause(IOException::class.java))
+ assertFalse(Exception(IllegalStateException(Exception(SocketException()))).hasAssignableCause(InterruptedIOException::class.java))
+ assertFalse(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(InterruptedException::class.java))
+ }
+
+ @Test fun `exact causes`() {
+ assertTrue(Throwable().hasExactCause(Throwable::class.java))
+ assertTrue(Exception().hasExactCause(Exception::class.java))
+
+ assertTrue(IOException().hasExactCause(IOException::class.java))
+ assertTrue(Exception(SocketException()).hasExactCause(SocketException::class.java))
+ assertTrue(Exception(Exception(IOException())).hasExactCause(IOException::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(IOException()))).hasExactCause(IOException::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(SocketException()))).hasExactCause(SocketException::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(SSLException("IO")))).hasExactCause(SSLException::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause(InterruptedIOException::class.java))
+ assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause(IllegalStateException::class.java))
+ }
+
+ @Test fun `no exact causes`() {
+ assertFalse(Throwable().hasExactCause(Exception::class.java))
+ assertFalse(Exception().hasExactCause(Throwable::class.java))
+
+ assertFalse(SocketException().hasExactCause(IOException::class.java))
+ assertFalse(IllegalStateException().hasExactCause(RuntimeException::class.java))
+ assertFalse(Exception(SocketException()).hasExactCause(IOException::class.java))
+ assertFalse(Exception(IllegalStateException(Exception(IOException()))).hasExactCause(RuntimeException::class.java))
+ assertFalse(Exception(IllegalStateException(Exception(SocketException()))).hasExactCause(IOException::class.java))
+ assertFalse(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause(IOException::class.java))
+ }
+}
diff --git a/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt b/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt
deleted file mode 100644
index 2d897e379..000000000
--- a/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package org.schabi.newpipe.util
-
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import org.schabi.newpipe.util.ExceptionUtils.Companion.hasAssignableCause
-import org.schabi.newpipe.util.ExceptionUtils.Companion.hasExactCause
-import java.io.IOException
-import java.io.InterruptedIOException
-import java.net.SocketException
-import javax.net.ssl.SSLException
-
-class ExceptionUtilsTest {
- @Test fun `assignable causes`() {
- assertTrue(hasAssignableCause(Throwable(), Throwable::class.java))
- assertTrue(hasAssignableCause(Exception(), Exception::class.java))
- assertTrue(hasAssignableCause(IOException(), Exception::class.java))
-
- assertTrue(hasAssignableCause(IOException(), IOException::class.java))
- assertTrue(hasAssignableCause(Exception(SocketException()), IOException::class.java))
- assertTrue(hasAssignableCause(Exception(IllegalStateException()), RuntimeException::class.java))
- assertTrue(hasAssignableCause(Exception(Exception(IOException())), IOException::class.java))
- assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(IOException()))), IOException::class.java))
- assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(SocketException()))), IOException::class.java))
- assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(SSLException("IO")))), IOException::class.java))
- assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), IOException::class.java))
- assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), RuntimeException::class.java))
-
- assertTrue(hasAssignableCause(IllegalStateException(), Throwable::class.java))
- assertTrue(hasAssignableCause(IllegalStateException(), Exception::class.java))
- assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), InterruptedIOException::class.java))
- }
-
- @Test fun `no assignable causes`() {
- assertFalse(hasAssignableCause(Throwable(), Exception::class.java))
- assertFalse(hasAssignableCause(Exception(), IOException::class.java))
- assertFalse(hasAssignableCause(Exception(IllegalStateException()), IOException::class.java))
- assertFalse(hasAssignableCause(Exception(NullPointerException()), IOException::class.java))
- assertFalse(hasAssignableCause(Exception(IllegalStateException(Exception(Exception()))), IOException::class.java))
- assertFalse(hasAssignableCause(Exception(IllegalStateException(Exception(SocketException()))), InterruptedIOException::class.java))
- assertFalse(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), InterruptedException::class.java))
- }
-
- @Test fun `exact causes`() {
- assertTrue(hasExactCause(Throwable(), Throwable::class.java))
- assertTrue(hasExactCause(Exception(), Exception::class.java))
-
- assertTrue(hasExactCause(IOException(), IOException::class.java))
- assertTrue(hasExactCause(Exception(SocketException()), SocketException::class.java))
- assertTrue(hasExactCause(Exception(Exception(IOException())), IOException::class.java))
- assertTrue(hasExactCause(Exception(IllegalStateException(Exception(IOException()))), IOException::class.java))
- assertTrue(hasExactCause(Exception(IllegalStateException(Exception(SocketException()))), SocketException::class.java))
- assertTrue(hasExactCause(Exception(IllegalStateException(Exception(SSLException("IO")))), SSLException::class.java))
- assertTrue(hasExactCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), InterruptedIOException::class.java))
- assertTrue(hasExactCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), IllegalStateException::class.java))
- }
-
- @Test fun `no exact causes`() {
- assertFalse(hasExactCause(Throwable(), Exception::class.java))
- assertFalse(hasExactCause(Exception(), Throwable::class.java))
-
- assertFalse(hasExactCause(SocketException(), IOException::class.java))
- assertFalse(hasExactCause(IllegalStateException(), RuntimeException::class.java))
- assertFalse(hasExactCause(Exception(SocketException()), IOException::class.java))
- assertFalse(hasExactCause(Exception(IllegalStateException(Exception(IOException()))), RuntimeException::class.java))
- assertFalse(hasExactCause(Exception(IllegalStateException(Exception(SocketException()))), IOException::class.java))
- assertFalse(hasExactCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), IOException::class.java))
- }
-}
From 50dcf308a266296c92be92e7186299eac20f46d9 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 15 Dec 2020 16:45:34 +0530
Subject: [PATCH 034/131] Add extension functions that accept reified types.
---
.../java/org/schabi/newpipe/ktx/Throwable.kt | 12 ++-
.../newpipe/ktx/ThrowableExtensionsTest.kt | 80 +++++++++----------
2 files changed, 51 insertions(+), 41 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt b/app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt
index e05a55f3f..b95f46fd4 100644
--- a/app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt
+++ b/app/src/main/java/org/schabi/newpipe/ktx/Throwable.kt
@@ -15,18 +15,28 @@ val Throwable.isInterruptedCaused: Boolean
* @return if throwable is related to network issues, or one of its causes is.
*/
val Throwable.isNetworkRelated: Boolean
- get() = hasAssignableCause(IOException::class.java)
+ get() = hasAssignableCause()
/**
* Calls [hasCause] with the `checkSubtypes` parameter set to false.
*/
fun Throwable.hasExactCause(vararg causesToCheck: Class<*>) = hasCause(false, *causesToCheck)
+/**
+ * Calls [hasCause] with a reified [Throwable] type.
+ */
+inline fun Throwable.hasExactCause() = hasExactCause(T::class.java)
+
/**
* Calls [hasCause] with the `checkSubtypes` parameter set to true.
*/
fun Throwable?.hasAssignableCause(vararg causesToCheck: Class<*>) = hasCause(true, *causesToCheck)
+/**
+ * Calls [hasCause] with a reified [Throwable] type.
+ */
+inline fun Throwable?.hasAssignableCause() = hasAssignableCause(T::class.java)
+
/**
* Check if the throwable has some cause from the causes to check, or is itself in it.
*
diff --git a/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt b/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt
index 8132b8fbc..ca2d04c85 100644
--- a/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt
@@ -10,58 +10,58 @@ import javax.net.ssl.SSLException
class ThrowableExtensionsTest {
@Test fun `assignable causes`() {
- assertTrue(Throwable().hasAssignableCause(Throwable::class.java))
- assertTrue(Exception().hasAssignableCause(Exception::class.java))
- assertTrue(IOException().hasAssignableCause(Exception::class.java))
+ assertTrue(Throwable().hasAssignableCause())
+ assertTrue(Exception().hasAssignableCause())
+ assertTrue(IOException().hasAssignableCause())
- assertTrue(IOException().hasAssignableCause(IOException::class.java))
- assertTrue(Exception(SocketException()).hasAssignableCause(IOException::class.java))
- assertTrue(Exception(IllegalStateException()).hasAssignableCause(RuntimeException::class.java))
- assertTrue(Exception(Exception(IOException())).hasAssignableCause(IOException::class.java))
- assertTrue(Exception(IllegalStateException(Exception(IOException()))).hasAssignableCause(IOException::class.java))
- assertTrue(Exception(IllegalStateException(Exception(SocketException()))).hasAssignableCause(IOException::class.java))
- assertTrue(Exception(IllegalStateException(Exception(SSLException("IO")))).hasAssignableCause(IOException::class.java))
- assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(IOException::class.java))
- assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(RuntimeException::class.java))
+ assertTrue(IOException().hasAssignableCause())
+ assertTrue(Exception(SocketException()).hasAssignableCause())
+ assertTrue(Exception(IllegalStateException()).hasAssignableCause())
+ assertTrue(Exception(Exception(IOException())).hasAssignableCause())
+ assertTrue(Exception(IllegalStateException(Exception(IOException()))).hasAssignableCause())
+ assertTrue(Exception(IllegalStateException(Exception(SocketException()))).hasAssignableCause())
+ assertTrue(Exception(IllegalStateException(Exception(SSLException("IO")))).hasAssignableCause())
+ assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause())
+ assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause())
- assertTrue(IllegalStateException().hasAssignableCause(Throwable::class.java))
- assertTrue(IllegalStateException().hasAssignableCause(Exception::class.java))
- assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(InterruptedIOException::class.java))
+ assertTrue(IllegalStateException().hasAssignableCause())
+ assertTrue(IllegalStateException().hasAssignableCause())
+ assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause())
}
@Test fun `no assignable causes`() {
- assertFalse(Throwable().hasAssignableCause(Exception::class.java))
- assertFalse(Exception().hasAssignableCause(IOException::class.java))
- assertFalse(Exception(IllegalStateException()).hasAssignableCause(IOException::class.java))
- assertFalse(Exception(NullPointerException()).hasAssignableCause(IOException::class.java))
- assertFalse(Exception(IllegalStateException(Exception(Exception()))).hasAssignableCause(IOException::class.java))
- assertFalse(Exception(IllegalStateException(Exception(SocketException()))).hasAssignableCause(InterruptedIOException::class.java))
- assertFalse(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause(InterruptedException::class.java))
+ assertFalse(Throwable().hasAssignableCause())
+ assertFalse(Exception().hasAssignableCause())
+ assertFalse(Exception(IllegalStateException()).hasAssignableCause())
+ assertFalse(Exception(NullPointerException()).hasAssignableCause())
+ assertFalse(Exception(IllegalStateException(Exception(Exception()))).hasAssignableCause())
+ assertFalse(Exception(IllegalStateException(Exception(SocketException()))).hasAssignableCause())
+ assertFalse(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasAssignableCause())
}
@Test fun `exact causes`() {
- assertTrue(Throwable().hasExactCause(Throwable::class.java))
- assertTrue(Exception().hasExactCause(Exception::class.java))
+ assertTrue(Throwable().hasExactCause())
+ assertTrue(Exception().hasExactCause())
- assertTrue(IOException().hasExactCause(IOException::class.java))
- assertTrue(Exception(SocketException()).hasExactCause(SocketException::class.java))
- assertTrue(Exception(Exception(IOException())).hasExactCause(IOException::class.java))
- assertTrue(Exception(IllegalStateException(Exception(IOException()))).hasExactCause(IOException::class.java))
- assertTrue(Exception(IllegalStateException(Exception(SocketException()))).hasExactCause(SocketException::class.java))
- assertTrue(Exception(IllegalStateException(Exception(SSLException("IO")))).hasExactCause(SSLException::class.java))
- assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause(InterruptedIOException::class.java))
- assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause(IllegalStateException::class.java))
+ assertTrue(IOException().hasExactCause())
+ assertTrue(Exception(SocketException()).hasExactCause())
+ assertTrue(Exception(Exception(IOException())).hasExactCause())
+ assertTrue(Exception(IllegalStateException(Exception(IOException()))).hasExactCause())
+ assertTrue(Exception(IllegalStateException(Exception(SocketException()))).hasExactCause())
+ assertTrue(Exception(IllegalStateException(Exception(SSLException("IO")))).hasExactCause())
+ assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause())
+ assertTrue(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause())
}
@Test fun `no exact causes`() {
- assertFalse(Throwable().hasExactCause(Exception::class.java))
- assertFalse(Exception().hasExactCause(Throwable::class.java))
+ assertFalse(Throwable().hasExactCause())
+ assertFalse(Exception().hasExactCause())
- assertFalse(SocketException().hasExactCause(IOException::class.java))
- assertFalse(IllegalStateException().hasExactCause(RuntimeException::class.java))
- assertFalse(Exception(SocketException()).hasExactCause(IOException::class.java))
- assertFalse(Exception(IllegalStateException(Exception(IOException()))).hasExactCause(RuntimeException::class.java))
- assertFalse(Exception(IllegalStateException(Exception(SocketException()))).hasExactCause(IOException::class.java))
- assertFalse(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause(IOException::class.java))
+ assertFalse(SocketException().hasExactCause())
+ assertFalse(IllegalStateException().hasExactCause())
+ assertFalse(Exception(SocketException()).hasExactCause())
+ assertFalse(Exception(IllegalStateException(Exception(IOException()))).hasExactCause())
+ assertFalse(Exception(IllegalStateException(Exception(SocketException()))).hasExactCause())
+ assertFalse(Exception(IllegalStateException(Exception(InterruptedIOException()))).hasExactCause())
}
}
From fee1fed0a160a26af3f98f1a565ae4ee41bc072f Mon Sep 17 00:00:00 2001
From: nadiration <59365236+nadiration@users.noreply.github.com>
Date: Tue, 12 Jan 2021 15:16:54 +0300
Subject: [PATCH 035/131] Created Somali Readme (#5383)
* Created Somali Readme
---
README.so.md | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 136 insertions(+)
create mode 100644 README.so.md
diff --git a/README.so.md b/README.so.md
new file mode 100644
index 000000000..adbeafc37
--- /dev/null
+++ b/README.so.md
@@ -0,0 +1,136 @@
+
+
NewPipe
+
App bilaash ah oo fudud looguna talagalay in aaladaha nidaamka Android-ka ku shaqeeya wax loogu daawado.
-*Read this in other languages: [English](README.md), [한국어](README.ko.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md).*
WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.
From 5108bf17422f9082307b8ef3343df23aa5364b6c Mon Sep 17 00:00:00 2001
From: nadiration <59365236+nadiration@users.noreply.github.com>
Date: Tue, 12 Jan 2021 13:23:52 +0300
Subject: [PATCH 037/131] Linked with Somali Readme
---
README.ko.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.ko.md b/README.ko.md
index 1ca7be1be..8af84d3df 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -16,7 +16,7 @@
-*Read this in other languages: [English](README.md), [한국어](README.ko.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md).*
경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.
From 4c26e597e4a91a4b834e12deca8c8b84c90620aa Mon Sep 17 00:00:00 2001
From: David BrazSan <60136669+DavidBrazSan@users.noreply.github.com>
Date: Tue, 12 Jan 2021 12:55:28 -0300
Subject: [PATCH 038/131] Created Brazilian Portuguese Readme
---
README.pt.br.md | 140 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
create mode 100644 README.pt.br.md
diff --git a/README.pt.br.md b/README.pt.br.md
new file mode 100644
index 000000000..4b2662cef
--- /dev/null
+++ b/README.pt.br.md
@@ -0,0 +1,140 @@
+
+
NewPipe
+
Uma interface de streaming leve e gratuita para Android.
+
+
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt.br.md).*
+
+AVISO: ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.
+
+COLOCAR NEWPIPE OU QUALQUER FORK DELE NA GOOGLE PLAY STORE VIOLA SEUS TERMOS E CONDIÇÕES.
+
+## Screenshots
+
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
+[](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
+[](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
+
+## Descrição
+
+O NewPipe não usa nenhuma biblioteca de framework do Google, nem a API do YouTube. Os sites são apenas analisados para obter informações necessárias, para que este aplicativo possa ser usado em dispositivos sem os serviços do Google instalados. Além disso, você não precisa de uma conta no YouTube para usar o NewPipe, que é um software livre com copyleft.
+
+### Características
+
+* Procurar vídeos
+* Exibir informações gerais sobre vídeos
+* Assista aos vídeos do YouTube
+* Ouça vídeos do YouTube
+* Modo popup (player flutuante)
+* Selecione o player para assistir streaming
+* Baixar vídeos
+* Baixar somente áudio
+* Abrir vídeo no Kodi
+* Mostrar vídeos próximos/relacionados
+* Pesquise no YouTube em um idioma específico
+* Assistir/Bloquear material restrito
+* Exibir informações gerais sobre canais
+* Pesquisar canais
+* Assista a vídeos de um canal
+* Suporte Orbot/Tor (ainda não diretamente)
+* Suporte 1080p/2K/4K
+* Ver histórico
+* Inscreva-se nos canais
+* Procurar histórico
+* Porcurar/Assistir playlists
+* Assistir playlists em fila
+* Vídeos em fila
+* Playlists Local
+* Legenda
+* Suporte a live
+* Mostrar comentários
+
+### Serviços Suportados
+
+O NewPipe suporta vários serviços. Nosso [documentação](https://teamnewpipe.github.io/documentation/) fornecer mais informações sobre como um novo serviço pode ser adicionado ao aplicativo e ao extrator. Por favor, entre em contato conosco se você pretende adicionar um novo. Atualmente, os serviços suportados são:
+
+* YouTube
+* SoundCloud \[beta\]
+* media.ccc.de \[beta\]
+* PeerTube instances \[beta\]
+
+## Atualizações
+Quando uma alteração no código NewPipe (devido à adição de recursos ou fixação de bugs), eventualmente ocorrerá uma versão. Estes estão no formato x.xx.x . A fim de obter esta nova versão, você pode:
+ 1. Construa um APK de depuração você mesmo. Esta é a maneira mais rápida de obter novos recursos em seu dispositivo, mas é muito mais complicado, por isso recomendamos usar um dos outros métodos.
+ 2. Adicione nosso repo personalizado ao F-Droid e instale-o a partir daí assim que publicarmos um lançamento. As instruções estão aqui.: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
+ 3. Baixe o APK do [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) e instalá-lo assim que publicarmos um lançamento.
+ 4. Atualização via F-droid. Este é o método mais lento para obter atualizações, pois o F-Droid deve reconhecer alterações, construir o próprio APK, assiná-lo e, em seguida, enviar a atualização para os usuários.
+
+Recomendamos o método 2 para a maioria dos usuários. Os APKs instalados usando o método 2 ou 3 são compatíveis entre si, mas não com aqueles instalados usando o método 4. Isso se deve à mesma chave de assinatura (nossa) sendo usada para 2 e 3, mas uma chave de assinatura diferente (F-Droid's) está sendo usada para 4. Construir um APK depuração usando o método 1 exclui totalmente uma chave. Assinar chaves ajudam a garantir que um usuário não seja enganado para instalar uma atualização maliciosa em um aplicativo.
+
+Enquanto isso, se você quiser trocar de fontes por algum motivo (por exemplo, a funcionalidade principal do NewPipe foi quebrada e o F-Droid ainda não tem a atualização), recomendamos seguir este procedimento:
+1. Back up your data via Settings > Content > Export Database so you keep your history, subscriptions, and playlistsFaça backup de seus dados através de Configurações > Conteúdo > Exportar Base de Dados para que você mantenha seu histórico, inscrições e playlists
+2. Desinstale o NewPipe
+3. Baixe o APK da nova fonte e instale-o
+4. Importe os dados da etapa 1 via Configurações > Conteúdo > Inportar Banco de Dados
+
+## Contribuição
+Se você tem ideias, traduções, alterações de design, limpeza de códigos ou mudanças reais de código, a ajuda é sempre bem-vinda.
+Quanto mais for feito, melhor fica!
+
+Se você quiser se envolver, verifique nossa [notas de contribuição](.github/CONTRIBUTING.md).
+
+
+
+
+
+## Doar
+Se você gosta de NewPipe, ficaríamos felizes com uma doação. Você pode enviar bitcoin ou doar via Bountysource ou Liberapay. Para obter mais informações sobre como doar para a NewPipe, visite nosso [site](https://newpipe.net/donate).
+
+
+
+
+
+
16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Política de Privacidade
+
+O projeto NewPipe tem como objetivo proporcionar uma experiência privada e anônima para o uso de serviços web de mídia.
+Portanto, o aplicativo não coleta nenhum dado sem o seu consentimento. A política de privacidade da NewPipe explica em detalhes quais dados são enviados e armazenados quando você envia um relatório de erro ou comenta em nosso blog. Você pode encontrar o documento [aqui](https://newpipe.net/legal/privacy/).
+
+## Licença
+[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
+
+NewPipe é Software Livre: Você pode usar, estudar compartilhamento e melhorá-lo à sua vontade.
+ Especificamente, você pode redistribuir e/ou modificá-lo sob os termos do
+[GNU General Public License](https://www.gnu.org/licenses/gpl.html) publicado pela Free Software Foundation, seja a versão 3 da Licença, ou
+(a sua opção) qualquer versão posterior.
From 031585be3fd8374464357925d5e5640eb41e00e8 Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Wed, 13 Jan 2021 17:25:00 +0100
Subject: [PATCH 039/131] Add comment about unexpected assertion
---
app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt b/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt
index d0a8e7a40..9f7e74649 100644
--- a/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt
@@ -18,6 +18,7 @@ class LocalizationTest {
val actual = Localization.relativeTime(GregorianCalendar(2021, 1, 6))
+ // yes this assertion is true, even if it should be 5 days, it works as it is. Future research required.
assertEquals("1 month from now", actual)
}
From 0c86a4e608f7f07c686643b8b2f8c17d2763f59a Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Wed, 23 Dec 2020 06:25:57 +0530
Subject: [PATCH 040/131] Use view binding (PlayerBinding) in VideoPlayer.
---
.../org/schabi/newpipe/player/BasePlayer.java | 7 -
.../org/schabi/newpipe/player/MainPlayer.java | 7 +-
.../schabi/newpipe/player/VideoPlayer.java | 384 +++++--------
.../newpipe/player/VideoPlayerImpl.java | 529 ++++++++----------
.../player/event/PlayerGestureListener.java | 4 +-
5 files changed, 385 insertions(+), 546 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
index 8eae33de6..ea16574e9 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
@@ -225,13 +225,6 @@ public abstract class BasePlayer implements
this.renderFactory = new DefaultRenderersFactory(context);
}
- public void setup() {
- if (simpleExoPlayer == null) {
- initPlayer(true);
- }
- initListeners();
- }
-
public void initPlayer(final boolean playOnReady) {
if (DEBUG) {
Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]");
diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
index 49c836346..fc9f110e6 100644
--- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
@@ -26,6 +26,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -33,8 +34,8 @@ import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
+import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.App;
-import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@@ -102,10 +103,10 @@ public final class MainPlayer extends Service {
}
private void createView() {
- final View layout = View.inflate(this, R.layout.player, null);
+ final PlayerBinding binding = PlayerBinding.inflate(LayoutInflater.from(this));
playerImpl = new VideoPlayerImpl(this);
- playerImpl.setup(layout);
+ playerImpl.setup(binding);
playerImpl.shouldUpdateOnProgress = true;
NotificationUtil.getInstance().createNotificationAndStartForeground(playerImpl, this);
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
index 67ea673c3..8894646c0 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
@@ -33,21 +33,18 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.os.Build;
import android.os.Handler;
-import androidx.preference.PreferenceManager;
import android.util.Log;
-
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.PopupMenu;
-import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
+import androidx.preference.PreferenceManager;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackParameters;
@@ -62,6 +59,7 @@ import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.exoplayer2.video.VideoListener;
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;
@@ -84,7 +82,7 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
*
* @author mauriciocolli
*/
-@SuppressWarnings({"WeakerAccess", "unused"})
+@SuppressWarnings({"WeakerAccess"})
public abstract class VideoPlayer extends BasePlayer
implements VideoListener,
SeekBar.OnSeekBarChangeListener,
@@ -117,34 +115,11 @@ public abstract class VideoPlayer extends BasePlayer
// Views
//////////////////////////////////////////////////////////////////////////*/
- private View rootView;
+ protected PlayerBinding binding;
- private ExpandableSurfaceView surfaceView;
- private View surfaceForeground;
-
- private View loadingPanel;
- private ImageView endScreen;
- private ImageView controlAnimationView;
-
- private View controlsRoot;
- private TextView currentDisplaySeek;
- private View playerTopShadow;
- private View playerBottomShadow;
-
- private View bottomControlsRoot;
- private SeekBar playbackSeekBar;
- private TextView playbackCurrentTime;
- private TextView playbackEndTime;
- private TextView playbackLiveSync;
- private TextView playbackSpeedTextView;
-
- private LinearLayout topControlsRoot;
- private TextView qualityTextView;
-
- private SubtitleView subtitleView;
-
- private TextView resizeView;
- private TextView captionTextView;
+ protected SeekBar playbackSeekBar;
+ protected TextView qualityTextView;
+ protected TextView playbackSpeed;
private ValueAnimator controlViewAnimator;
private final Handler controlsVisibilityHandler = new Handler();
@@ -162,7 +137,7 @@ public abstract class VideoPlayer extends BasePlayer
///////////////////////////////////////////////////////////////////////////
- public VideoPlayer(final String debugTag, final Context context) {
+ protected VideoPlayer(final String debugTag, final Context context) {
super(context);
this.TAG = debugTag;
this.resolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
@@ -178,54 +153,37 @@ public abstract class VideoPlayer extends BasePlayer
return false;
}
- public void setup(final View view) {
- initViews(view);
- setup();
+ public void setup(@NonNull final PlayerBinding playerBinding) {
+ initViews(playerBinding);
+ if (simpleExoPlayer == null) {
+ initPlayer(true);
+ }
+ initListeners();
}
- public void initViews(final View view) {
- this.rootView = view;
- this.surfaceView = view.findViewById(R.id.surfaceView);
- this.surfaceForeground = view.findViewById(R.id.surfaceForeground);
- this.loadingPanel = view.findViewById(R.id.loading_panel);
- this.endScreen = view.findViewById(R.id.endScreen);
- this.controlAnimationView = view.findViewById(R.id.controlAnimationView);
- this.controlsRoot = view.findViewById(R.id.playbackControlRoot);
- this.currentDisplaySeek = view.findViewById(R.id.currentDisplaySeek);
- this.playerTopShadow = view.findViewById(R.id.playerTopShadow);
- this.playerBottomShadow = view.findViewById(R.id.playerBottomShadow);
- this.playbackSeekBar = view.findViewById(R.id.playbackSeekBar);
- this.playbackCurrentTime = view.findViewById(R.id.playbackCurrentTime);
- this.playbackEndTime = view.findViewById(R.id.playbackEndTime);
- this.playbackLiveSync = view.findViewById(R.id.playbackLiveSync);
- this.playbackSpeedTextView = view.findViewById(R.id.playbackSpeed);
- this.bottomControlsRoot = view.findViewById(R.id.bottomControls);
- this.topControlsRoot = view.findViewById(R.id.topControls);
- this.qualityTextView = view.findViewById(R.id.qualityTextView);
-
- this.subtitleView = view.findViewById(R.id.subtitleView);
+ public void initViews(@NonNull final PlayerBinding playerBinding) {
+ binding = playerBinding;
+ playbackSeekBar = (SeekBar) binding.playbackSeekBar;
+ qualityTextView = (TextView) binding.qualityTextView;
+ playbackSpeed = (TextView) binding.playbackSpeed;
final float captionScale = PlayerHelper.getCaptionScale(context);
final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context);
- setupSubtitleView(subtitleView, captionScale, captionStyle);
+ setupSubtitleView(binding.subtitleView, captionScale, captionStyle);
- this.resizeView = view.findViewById(R.id.resizeTextView);
- resizeView.setText(PlayerHelper
- .resizeTypeOf(context, getSurfaceView().getResizeMode()));
+ ((TextView) binding.resizeTextView).setText(PlayerHelper.resizeTypeOf(context,
+ binding.surfaceView.getResizeMode()));
- this.captionTextView = view.findViewById(R.id.captionTextView);
+ playbackSeekBar.getThumb().setColorFilter(new PorterDuffColorFilter(Color.RED,
+ PorterDuff.Mode.SRC_IN));
+ playbackSeekBar.getProgressDrawable().setColorFilter(new PorterDuffColorFilter(Color.RED,
+ PorterDuff.Mode.MULTIPLY));
- playbackSeekBar.getThumb()
- .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
- this.playbackSeekBar.getProgressDrawable()
- .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY));
+ qualityPopupMenu = new PopupMenu(context, qualityTextView);
+ playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeed);
+ captionPopupMenu = new PopupMenu(context, binding.captionTextView);
- this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
- this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView);
- this.captionPopupMenu = new PopupMenu(context, captionTextView);
-
- ((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel))
- .getIndeterminateDrawable()
+ binding.progressBarLoadingPanel.getIndeterminateDrawable()
.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY));
}
@@ -235,11 +193,11 @@ public abstract class VideoPlayer extends BasePlayer
@Override
public void initListeners() {
playbackSeekBar.setOnSeekBarChangeListener(this);
- playbackSpeedTextView.setOnClickListener(this);
- qualityTextView.setOnClickListener(this);
- captionTextView.setOnClickListener(this);
- resizeView.setOnClickListener(this);
- playbackLiveSync.setOnClickListener(this);
+ binding.playbackSpeed.setOnClickListener(this);
+ binding.qualityTextView.setOnClickListener(this);
+ binding.captionTextView.setOnClickListener(this);
+ binding.resizeTextView.setOnClickListener(this);
+ binding.playbackLiveSync.setOnClickListener(this);
}
@Override
@@ -247,11 +205,11 @@ public abstract class VideoPlayer extends BasePlayer
super.initPlayer(playOnReady);
// Setup video view
- simpleExoPlayer.setVideoSurfaceView(surfaceView);
+ simpleExoPlayer.setVideoSurfaceView(binding.surfaceView);
simpleExoPlayer.addVideoListener(this);
// Setup subtitle view
- simpleExoPlayer.addTextOutput(cues -> subtitleView.onCues(cues));
+ simpleExoPlayer.addTextOutput(binding.subtitleView);
// Setup audio session with onboard equalizer
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@@ -305,7 +263,7 @@ public abstract class VideoPlayer extends BasePlayer
playbackSpeedPopupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE,
formatSpeed(PLAYBACK_SPEEDS[i]));
}
- playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
+ playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
playbackSpeedPopupMenu.setOnMenuItemClickListener(this);
playbackSpeedPopupMenu.setOnDismissListener(this);
}
@@ -385,29 +343,29 @@ public abstract class VideoPlayer extends BasePlayer
final MediaSourceTag tag = getCurrentMetadata();
final StreamInfo metadata = tag.getMetadata();
- qualityTextView.setVisibility(View.GONE);
- playbackSpeedTextView.setVisibility(View.GONE);
+ binding.qualityTextView.setVisibility(View.GONE);
+ binding.playbackSpeed.setVisibility(View.GONE);
- playbackEndTime.setVisibility(View.GONE);
- playbackLiveSync.setVisibility(View.GONE);
+ binding.playbackEndTime.setVisibility(View.GONE);
+ binding.playbackLiveSync.setVisibility(View.GONE);
switch (metadata.getStreamType()) {
case AUDIO_STREAM:
- surfaceView.setVisibility(View.GONE);
- endScreen.setVisibility(View.VISIBLE);
- playbackEndTime.setVisibility(View.VISIBLE);
+ binding.surfaceView.setVisibility(View.GONE);
+ binding.endScreen.setVisibility(View.VISIBLE);
+ binding.playbackEndTime.setVisibility(View.VISIBLE);
break;
case AUDIO_LIVE_STREAM:
- surfaceView.setVisibility(View.GONE);
- endScreen.setVisibility(View.VISIBLE);
- playbackLiveSync.setVisibility(View.VISIBLE);
+ binding.surfaceView.setVisibility(View.GONE);
+ binding.endScreen.setVisibility(View.VISIBLE);
+ binding.playbackLiveSync.setVisibility(View.VISIBLE);
break;
case LIVE_STREAM:
- surfaceView.setVisibility(View.VISIBLE);
- endScreen.setVisibility(View.GONE);
- playbackLiveSync.setVisibility(View.VISIBLE);
+ binding.surfaceView.setVisibility(View.VISIBLE);
+ binding.endScreen.setVisibility(View.GONE);
+ binding.playbackLiveSync.setVisibility(View.VISIBLE);
break;
case VIDEO_STREAM:
@@ -420,16 +378,16 @@ public abstract class VideoPlayer extends BasePlayer
selectedStreamIndex = tag.getSelectedVideoStreamIndex();
buildQualityMenu();
- qualityTextView.setVisibility(View.VISIBLE);
- surfaceView.setVisibility(View.VISIBLE);
+ binding.qualityTextView.setVisibility(View.VISIBLE);
+ binding.surfaceView.setVisibility(View.VISIBLE);
default:
- endScreen.setVisibility(View.GONE);
- playbackEndTime.setVisibility(View.VISIBLE);
+ binding.endScreen.setVisibility(View.GONE);
+ binding.playbackEndTime.setVisibility(View.VISIBLE);
break;
}
buildPlaybackSpeedMenu();
- playbackSpeedTextView.setVisibility(View.VISIBLE);
+ binding.playbackSpeed.setVisibility(View.VISIBLE);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -438,6 +396,7 @@ public abstract class VideoPlayer extends BasePlayer
protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver();
+ @Override
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
super.onMetadataChanged(tag);
updateStreamRelatedViews();
@@ -458,15 +417,15 @@ public abstract class VideoPlayer extends BasePlayer
super.onBlocked();
controlsVisibilityHandler.removeCallbacksAndMessages(null);
- animateView(controlsRoot, false, DEFAULT_CONTROLS_DURATION);
+ animateView(binding.playbackControlRoot, false, DEFAULT_CONTROLS_DURATION);
playbackSeekBar.setEnabled(false);
- playbackSeekBar.getThumb()
- .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
+ playbackSeekBar.getThumb().setColorFilter(new PorterDuffColorFilter(Color.RED,
+ PorterDuff.Mode.SRC_IN));
- loadingPanel.setBackgroundColor(Color.BLACK);
- animateView(loadingPanel, true, 0);
- animateView(surfaceForeground, true, 100);
+ binding.loadingPanel.setBackgroundColor(Color.BLACK);
+ animateView(binding.loadingPanel, true, 0);
+ animateView(binding.surfaceForeground, true, 100);
}
@Override
@@ -478,12 +437,13 @@ public abstract class VideoPlayer extends BasePlayer
showAndAnimateControl(-1, true);
playbackSeekBar.setEnabled(true);
- playbackSeekBar.getThumb()
- .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
+ playbackSeekBar.getThumb().setColorFilter(new PorterDuffColorFilter(Color.RED,
+ PorterDuff.Mode.SRC_IN));
- loadingPanel.setVisibility(View.GONE);
+ binding.loadingPanel.setVisibility(View.GONE);
- animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
+ animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false,
+ 200);
}
@Override
@@ -491,7 +451,7 @@ public abstract class VideoPlayer extends BasePlayer
if (DEBUG) {
Log.d(TAG, "onBuffering() called");
}
- loadingPanel.setBackgroundColor(Color.TRANSPARENT);
+ binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT);
}
@Override
@@ -500,7 +460,7 @@ public abstract class VideoPlayer extends BasePlayer
Log.d(TAG, "onPaused() called");
}
showControls(400);
- loadingPanel.setVisibility(View.GONE);
+ binding.loadingPanel.setVisibility(View.GONE);
}
@Override
@@ -516,10 +476,11 @@ public abstract class VideoPlayer extends BasePlayer
super.onCompleted();
showControls(500);
- animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
- loadingPanel.setVisibility(View.GONE);
+ animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false,
+ 200);
+ binding.loadingPanel.setVisibility(View.GONE);
- animateView(surfaceForeground, true, 100);
+ animateView(binding.surfaceForeground, true, 100);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -527,16 +488,16 @@ public abstract class VideoPlayer extends BasePlayer
//////////////////////////////////////////////////////////////////////////*/
@Override
- public void onTracksChanged(final TrackGroupArray trackGroups,
- final TrackSelectionArray trackSelections) {
+ public void onTracksChanged(@NonNull final TrackGroupArray trackGroups,
+ @NonNull final TrackSelectionArray trackSelections) {
super.onTracksChanged(trackGroups, trackSelections);
onTextTrackUpdate();
}
@Override
- public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
+ public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) {
super.onPlaybackParametersChanged(playbackParameters);
- playbackSpeedTextView.setText(formatSpeed(playbackParameters.speed));
+ playbackSpeed.setText(formatSpeed(playbackParameters.speed));
}
@Override
@@ -550,12 +511,12 @@ public abstract class VideoPlayer extends BasePlayer
+ "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
+ "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
}
- getSurfaceView().setAspectRatio(((float) width) / height);
+ binding.surfaceView.setAspectRatio(((float) width) / height);
}
@Override
public void onRenderedFirstFrame() {
- animateView(surfaceForeground, false, 100);
+ animateView(binding.surfaceForeground, false, 100);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -565,12 +526,12 @@ public abstract class VideoPlayer extends BasePlayer
private void onTextTrackUpdate() {
final int textRenderer = getRendererIndex(C.TRACK_TYPE_TEXT);
- if (captionTextView == null) {
+ if (binding == null) {
return;
}
if (trackSelector.getCurrentMappedTrackInfo() == null
|| textRenderer == RENDERER_UNAVAILABLE) {
- captionTextView.setVisibility(View.GONE);
+ binding.captionTextView.setVisibility(View.GONE);
return;
}
@@ -593,11 +554,12 @@ public abstract class VideoPlayer extends BasePlayer
if (trackSelector.getParameters().getRendererDisabled(textRenderer)
|| preferredLanguage == null || (!availableLanguages.contains(preferredLanguage)
&& !containsCaseInsensitive(availableLanguages, preferredLanguage))) {
- captionTextView.setText(R.string.caption_none);
+ binding.captionTextView.setText(R.string.caption_none);
} else {
- captionTextView.setText(preferredLanguage);
+ binding.captionTextView.setText(preferredLanguage);
}
- captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE : View.VISIBLE);
+ binding.captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE
+ : View.VISIBLE);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -611,8 +573,8 @@ public abstract class VideoPlayer extends BasePlayer
}
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
- playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
- playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
+ binding.playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
+ playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
super.onPrepared(playWhenReady);
}
@@ -620,8 +582,8 @@ public abstract class VideoPlayer extends BasePlayer
@Override
public void destroy() {
super.destroy();
- if (endScreen != null) {
- endScreen.setImageBitmap(null);
+ if (binding != null) {
+ binding.endScreen.setImageBitmap(null);
}
}
@@ -633,14 +595,14 @@ public abstract class VideoPlayer extends BasePlayer
}
if (duration != playbackSeekBar.getMax()) {
- playbackEndTime.setText(getTimeString(duration));
+ binding.playbackEndTime.setText(getTimeString(duration));
playbackSeekBar.setMax(duration);
}
if (currentState != STATE_PAUSED) {
if (currentState != STATE_PAUSED_SEEK) {
playbackSeekBar.setProgress(currentProgress);
}
- playbackCurrentTime.setText(getTimeString(currentProgress));
+ binding.playbackCurrentTime.setText(getTimeString(currentProgress));
}
if (simpleExoPlayer.isLoading() || bufferPercent > 90) {
playbackSeekBar.setSecondaryProgress(
@@ -652,7 +614,7 @@ public abstract class VideoPlayer extends BasePlayer
+ "currentProgress = [" + currentProgress + "], "
+ "duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]");
}
- playbackLiveSync.setClickable(!isLiveEdge());
+ binding.playbackLiveSync.setClickable(!isLiveEdge());
}
@Override
@@ -660,7 +622,7 @@ public abstract class VideoPlayer extends BasePlayer
final Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
if (loadedImage != null) {
- endScreen.setImageBitmap(loadedImage);
+ binding.endScreen.setImageBitmap(loadedImage);
}
}
@@ -689,15 +651,15 @@ public abstract class VideoPlayer extends BasePlayer
if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
- if (v.getId() == qualityTextView.getId()) {
+ if (v.getId() == binding.qualityTextView.getId()) {
onQualitySelectorClicked();
- } else if (v.getId() == playbackSpeedTextView.getId()) {
+ } else if (v.getId() == binding.playbackSpeed.getId()) {
onPlaybackSpeedClicked();
- } else if (v.getId() == resizeView.getId()) {
+ } else if (v.getId() == binding.resizeTextView.getId()) {
onResizeClicked();
- } else if (v.getId() == captionTextView.getId()) {
+ } else if (v.getId() == binding.captionTextView.getId()) {
onCaptionClicked();
- } else if (v.getId() == playbackLiveSync.getId()) {
+ } else if (v.getId() == binding.playbackLiveSync.getId()) {
seekToDefault();
}
}
@@ -732,7 +694,7 @@ public abstract class VideoPlayer extends BasePlayer
final float speed = PLAYBACK_SPEEDS[speedIndex];
setPlaybackSpeed(speed);
- playbackSpeedTextView.setText(formatSpeed(speed));
+ playbackSpeed.setText(formatSpeed(speed));
}
return false;
@@ -786,16 +748,17 @@ public abstract class VideoPlayer extends BasePlayer
}
void onResizeClicked() {
- if (getSurfaceView() != null) {
- final int currentResizeMode = getSurfaceView().getResizeMode();
+ if (binding != null) {
+ final int currentResizeMode = binding.surfaceView.getResizeMode();
final int newResizeMode = nextResizeMode(currentResizeMode);
setResizeMode(newResizeMode);
}
}
protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
- getSurfaceView().setResizeMode(resizeMode);
- getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode));
+ binding.surfaceView.setResizeMode(resizeMode);
+ ((TextView) binding.resizeTextView).setText(PlayerHelper.resizeTypeOf(context,
+ resizeMode));
}
protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode);
@@ -811,9 +774,8 @@ public abstract class VideoPlayer extends BasePlayer
Log.d(TAG, "onProgressChanged() called with: "
+ "seekBar = [" + seekBar + "], progress = [" + progress + "]");
}
- //if (fromUser) playbackCurrentTime.setText(getTimeString(progress));
if (fromUser) {
- currentDisplaySeek.setText(getTimeString(progress));
+ binding.currentDisplaySeek.setText(getTimeString(progress));
}
}
@@ -832,7 +794,7 @@ public abstract class VideoPlayer extends BasePlayer
}
showControls(0);
- animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, true,
+ animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, true,
DEFAULT_CONTROLS_DURATION);
}
@@ -847,8 +809,9 @@ public abstract class VideoPlayer extends BasePlayer
simpleExoPlayer.setPlayWhenReady(true);
}
- playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
- animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
+ binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
+ animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false,
+ 200);
if (getCurrentState() == STATE_PAUSED_SEEK) {
changeState(STATE_BUFFERING);
@@ -877,7 +840,8 @@ public abstract class VideoPlayer extends BasePlayer
}
public boolean isControlsVisible() {
- return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE;
+ return binding != null
+ && binding.playbackControlRoot.getVisibility() == View.VISIBLE;
}
/**
@@ -900,8 +864,9 @@ public abstract class VideoPlayer extends BasePlayer
}
if (drawableId == -1) {
- if (controlAnimationView.getVisibility() == View.VISIBLE) {
- controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
+ if (binding.controlAnimationView.getVisibility() == View.VISIBLE) {
+ controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(
+ binding.controlAnimationView,
PropertyValuesHolder.ofFloat(View.ALPHA, 1.0f, 0.0f),
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1.0f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1.0f)
@@ -909,7 +874,7 @@ public abstract class VideoPlayer extends BasePlayer
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(final Animator animation) {
- controlAnimationView.setVisibility(View.GONE);
+ binding.controlAnimationView.setVisibility(View.GONE);
}
});
controlViewAnimator.start();
@@ -923,7 +888,8 @@ public abstract class VideoPlayer extends BasePlayer
final float alphaTo = goneOnEnd ? 0f : 1f;
- controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
+ controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(
+ binding.controlAnimationView,
PropertyValuesHolder.ofFloat(View.ALPHA, alphaFrom, alphaTo),
PropertyValuesHolder.ofFloat(View.SCALE_X, scaleFrom, scaleTo),
PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleFrom, scaleTo)
@@ -932,17 +898,14 @@ public abstract class VideoPlayer extends BasePlayer
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(final Animator animation) {
- if (goneOnEnd) {
- controlAnimationView.setVisibility(View.GONE);
- } else {
- controlAnimationView.setVisibility(View.VISIBLE);
- }
+ binding.controlAnimationView.setVisibility(goneOnEnd ? View.GONE
+ : View.VISIBLE);
}
});
-
- controlAnimationView.setVisibility(View.VISIBLE);
- controlAnimationView.setImageDrawable(AppCompatResources.getDrawable(context, drawableId));
+ binding.controlAnimationView.setVisibility(View.VISIBLE);
+ binding.controlAnimationView.setImageDrawable(AppCompatResources.getDrawable(context,
+ drawableId));
controlViewAnimator.start();
}
@@ -955,12 +918,12 @@ public abstract class VideoPlayer extends BasePlayer
Log.d(TAG, "showControlsThenHide() called");
}
- final int hideTime = controlsRoot.isInTouchMode()
+ final int hideTime = binding.playbackControlRoot.isInTouchMode()
? DEFAULT_CONTROLS_HIDE_TIME
: DPAD_CONTROLS_HIDE_TIME;
showHideShadow(true, DEFAULT_CONTROLS_DURATION, 0);
- animateView(controlsRoot, true, DEFAULT_CONTROLS_DURATION, 0,
+ animateView(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, 0,
() -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime));
}
@@ -970,17 +933,18 @@ public abstract class VideoPlayer extends BasePlayer
}
controlsVisibilityHandler.removeCallbacksAndMessages(null);
showHideShadow(true, duration, 0);
- animateView(controlsRoot, true, duration);
+ animateView(binding.playbackControlRoot, true, duration);
}
public void safeHideControls(final long duration, final long delay) {
if (DEBUG) {
Log.d(TAG, "safeHideControls() called with: delay = [" + delay + "]");
}
- if (rootView.isInTouchMode()) {
+ if (binding.getRoot().isInTouchMode()) {
controlsVisibilityHandler.removeCallbacksAndMessages(null);
controlsVisibilityHandler.postDelayed(
- () -> animateView(controlsRoot, false, duration), delay);
+ () -> animateView(binding.playbackControlRoot, false, duration),
+ delay);
}
}
@@ -991,7 +955,7 @@ public abstract class VideoPlayer extends BasePlayer
controlsVisibilityHandler.removeCallbacksAndMessages(null);
controlsVisibilityHandler.postDelayed(() -> {
showHideShadow(false, duration, 0);
- animateView(controlsRoot, false, duration);
+ animateView(binding.playbackControlRoot, false, duration);
}, delay);
}
@@ -1007,13 +971,13 @@ public abstract class VideoPlayer extends BasePlayer
private Runnable hideControlsAndButtonHandler(final long duration, final View videoPlayPause) {
return () -> {
videoPlayPause.setVisibility(View.INVISIBLE);
- animateView(controlsRoot, false, duration);
+ animateView(binding.playbackControlRoot, false, duration);
};
}
void showHideShadow(final boolean show, final long duration, final long delay) {
- animateView(playerTopShadow, show, duration, delay, null);
- animateView(playerBottomShadow, show, duration, delay, null);
+ animateView(binding.playerTopShadow, show, duration, delay, null);
+ animateView(binding.playerBottomShadow, show, duration, delay, null);
}
public abstract void hideSystemUIIfNeeded();
@@ -1032,7 +996,7 @@ public abstract class VideoPlayer extends BasePlayer
}
public ExpandableSurfaceView getSurfaceView() {
- return surfaceView;
+ return binding.surfaceView;
}
public boolean wasPlaying() {
@@ -1050,83 +1014,23 @@ public abstract class VideoPlayer extends BasePlayer
return controlsVisibilityHandler;
}
+ @NonNull
public View getRootView() {
- return rootView;
- }
-
- public void setRootView(final View rootView) {
- this.rootView = rootView;
+ return binding.getRoot();
}
+ @NonNull
public View getLoadingPanel() {
- return loadingPanel;
+ return binding.loadingPanel;
}
- public ImageView getEndScreen() {
- return endScreen;
- }
-
- public ImageView getControlAnimationView() {
- return controlAnimationView;
- }
-
- public View getControlsRoot() {
- return controlsRoot;
- }
-
- public View getBottomControlsRoot() {
- return bottomControlsRoot;
- }
-
- public SeekBar getPlaybackSeekBar() {
- return playbackSeekBar;
- }
-
- public TextView getPlaybackCurrentTime() {
- return playbackCurrentTime;
- }
-
- public TextView getPlaybackEndTime() {
- return playbackEndTime;
- }
-
- public LinearLayout getTopControlsRoot() {
- return topControlsRoot;
- }
-
- public TextView getQualityTextView() {
- return qualityTextView;
- }
-
- public PopupMenu getQualityPopupMenu() {
- return qualityPopupMenu;
- }
-
- public TextView getPlaybackSpeedTextView() {
- return playbackSpeedTextView;
- }
-
- public PopupMenu getPlaybackSpeedPopupMenu() {
- return playbackSpeedPopupMenu;
- }
-
- public View getSurfaceForeground() {
- return surfaceForeground;
+ @NonNull
+ public View getPlaybackControlRoot() {
+ return binding.playbackControlRoot;
}
+ @NonNull
public TextView getCurrentDisplaySeek() {
- return currentDisplaySeek;
- }
-
- public SubtitleView getSubtitleView() {
- return subtitleView;
- }
-
- public TextView getResizeView() {
- return resizeView;
- }
-
- public TextView getCaptionTextView() {
- return captionTextView;
+ return binding.currentDisplaySeek;
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
index ce40efb5f..a968ddc91 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
@@ -77,6 +77,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
@@ -145,45 +146,11 @@ public class VideoPlayerImpl extends VideoPlayer
private static final float MAX_GESTURE_LENGTH = 0.75f;
- private TextView titleTextView;
- private TextView channelTextView;
- private RelativeLayout volumeRelativeLayout;
- private ProgressBar volumeProgressBar;
- private ImageView volumeImageView;
- private RelativeLayout brightnessRelativeLayout;
- private ProgressBar brightnessProgressBar;
- private ImageView brightnessImageView;
- private TextView resizingIndicator;
- private ImageButton queueButton;
- private ImageButton repeatButton;
- private ImageButton shuffleButton;
- private ImageButton playWithKodi;
- private ImageButton openInBrowser;
- private ImageButton fullscreenButton;
- private ImageButton playerCloseButton;
- private ImageButton screenRotationButton;
- private ImageButton muteButton;
-
- private ImageButton playPauseButton;
- private ImageButton playPreviousButton;
- private ImageButton playNextButton;
-
- private RelativeLayout queueLayout;
- private ImageButton itemsListCloseButton;
- private RecyclerView itemsList;
private ItemTouchHelper itemTouchHelper;
- private RelativeLayout playerOverlays;
-
private boolean queueVisible;
private MainPlayer.PlayerType playerType = MainPlayer.PlayerType.VIDEO;
- private ImageButton moreOptionsButton;
- private ImageButton shareButton;
-
- private View primaryControls;
- private View secondaryControls;
-
private int maxGestureLength;
private boolean audioOnly = false;
@@ -205,7 +172,6 @@ public class VideoPlayerImpl extends VideoPlayer
private WindowManager.LayoutParams popupLayoutParams;
public WindowManager windowManager;
- private View closingOverlayView;
private View closeOverlayView;
private FloatingActionButton closeOverlayButton;
@@ -251,13 +217,13 @@ public class VideoPlayerImpl extends VideoPlayer
getRootView().setVisibility(View.VISIBLE);
initPopup();
initPopupCloseOverlay();
- playPauseButton.requestFocus();
+ binding.playPauseButton.requestFocus();
} else {
getRootView().setVisibility(View.VISIBLE);
initVideoPlayer();
onQueueClosed();
// Android TV: without it focus will frame the whole player
- playPauseButton.requestFocus();
+ binding.playPauseButton.requestFocus();
if (simpleExoPlayer.getPlayWhenReady()) {
onPlay();
@@ -279,49 +245,14 @@ public class VideoPlayerImpl extends VideoPlayer
@SuppressLint("ClickableViewAccessibility")
@Override
- public void initViews(final View view) {
- super.initViews(view);
- this.titleTextView = view.findViewById(R.id.titleTextView);
- this.channelTextView = view.findViewById(R.id.channelTextView);
- this.volumeRelativeLayout = view.findViewById(R.id.volumeRelativeLayout);
- this.volumeProgressBar = view.findViewById(R.id.volumeProgressBar);
- this.volumeImageView = view.findViewById(R.id.volumeImageView);
- this.brightnessRelativeLayout = view.findViewById(R.id.brightnessRelativeLayout);
- this.brightnessProgressBar = view.findViewById(R.id.brightnessProgressBar);
- this.brightnessImageView = view.findViewById(R.id.brightnessImageView);
- this.resizingIndicator = view.findViewById(R.id.resizing_indicator);
- this.queueButton = view.findViewById(R.id.queueButton);
- this.repeatButton = view.findViewById(R.id.repeatButton);
- this.shuffleButton = view.findViewById(R.id.shuffleButton);
- this.playWithKodi = view.findViewById(R.id.playWithKodi);
- this.openInBrowser = view.findViewById(R.id.openInBrowser);
- this.fullscreenButton = view.findViewById(R.id.fullScreenButton);
- this.screenRotationButton = view.findViewById(R.id.screenRotationButton);
- this.playerCloseButton = view.findViewById(R.id.playerCloseButton);
- this.muteButton = view.findViewById(R.id.switchMute);
+ public void initViews(@NonNull final PlayerBinding binding) {
+ super.initViews(binding);
- this.playPauseButton = view.findViewById(R.id.playPauseButton);
- this.playPreviousButton = view.findViewById(R.id.playPreviousButton);
- this.playNextButton = view.findViewById(R.id.playNextButton);
-
- this.moreOptionsButton = view.findViewById(R.id.moreOptionsButton);
- this.primaryControls = view.findViewById(R.id.primaryControls);
- this.secondaryControls = view.findViewById(R.id.secondaryControls);
- this.shareButton = view.findViewById(R.id.share);
-
- this.queueLayout = view.findViewById(R.id.playQueuePanel);
- this.itemsListCloseButton = view.findViewById(R.id.playQueueClose);
- this.itemsList = view.findViewById(R.id.playQueue);
-
- this.playerOverlays = view.findViewById(R.id.player_overlays);
-
- closingOverlayView = view.findViewById(R.id.closingOverlay);
-
- titleTextView.setSelected(true);
- channelTextView.setSelected(true);
+ binding.titleTextView.setSelected(true);
+ binding.channelTextView.setSelected(true);
// Prevent hiding of bottom sheet via swipe inside queue
- this.itemsList.setNestedScrollingEnabled(false);
+ binding.playQueue.setNestedScrollingEnabled(false);
}
@Override
@@ -349,58 +280,60 @@ public class VideoPlayerImpl extends VideoPlayer
*/
private void setupElementsVisibility() {
if (popupPlayerSelected()) {
- fullscreenButton.setVisibility(View.VISIBLE);
- screenRotationButton.setVisibility(View.GONE);
- getResizeView().setVisibility(View.GONE);
- getRootView().findViewById(R.id.metadataView).setVisibility(View.GONE);
- queueButton.setVisibility(View.GONE);
- moreOptionsButton.setVisibility(View.GONE);
- getTopControlsRoot().setOrientation(LinearLayout.HORIZONTAL);
- primaryControls.getLayoutParams().width = LinearLayout.LayoutParams.WRAP_CONTENT;
- secondaryControls.setAlpha(1.0f);
- secondaryControls.setVisibility(View.VISIBLE);
- secondaryControls.setTranslationY(0);
- shareButton.setVisibility(View.GONE);
- playWithKodi.setVisibility(View.GONE);
- openInBrowser.setVisibility(View.GONE);
- muteButton.setVisibility(View.GONE);
- playerCloseButton.setVisibility(View.GONE);
- getTopControlsRoot().bringToFront();
- getTopControlsRoot().setClickable(false);
- getTopControlsRoot().setFocusable(false);
- getBottomControlsRoot().bringToFront();
+ binding.fullScreenButton.setVisibility(View.VISIBLE);
+ binding.screenRotationButton.setVisibility(View.GONE);
+ binding.resizeTextView.setVisibility(View.GONE);
+ binding.metadataView.setVisibility(View.GONE);
+ binding.queueButton.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();
onQueueClosed();
} else {
- fullscreenButton.setVisibility(View.GONE);
+ binding.fullScreenButton.setVisibility(View.GONE);
setupScreenRotationButton();
- getResizeView().setVisibility(View.VISIBLE);
- getRootView().findViewById(R.id.metadataView).setVisibility(View.VISIBLE);
- moreOptionsButton.setVisibility(View.VISIBLE);
- getTopControlsRoot().setOrientation(LinearLayout.VERTICAL);
- primaryControls.getLayoutParams().width = LinearLayout.LayoutParams.MATCH_PARENT;
- secondaryControls.setVisibility(View.INVISIBLE);
- moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(service,
+ binding.resizeTextView.setVisibility(View.VISIBLE);
+ binding.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(service,
R.drawable.ic_expand_more_white_24dp));
- shareButton.setVisibility(View.VISIBLE);
+ binding.share.setVisibility(View.VISIBLE);
showHideKodiButton();
- openInBrowser.setVisibility(View.VISIBLE);
- muteButton.setVisibility(View.VISIBLE);
- playerCloseButton.setVisibility(isFullscreen ? View.GONE : 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)
- getTopControlsRoot().setClickable(true);
- getTopControlsRoot().setFocusable(true);
+ binding.topControls.setClickable(true);
+ binding.topControls.setFocusable(true);
}
if (!isFullscreen()) {
- titleTextView.setVisibility(View.GONE);
- channelTextView.setVisibility(View.GONE);
+ binding.titleTextView.setVisibility(View.GONE);
+ binding.channelTextView.setVisibility(View.GONE);
} else {
- titleTextView.setVisibility(View.VISIBLE);
- channelTextView.setVisibility(View.VISIBLE);
+ binding.titleTextView.setVisibility(View.VISIBLE);
+ binding.channelTextView.setVisibility(View.VISIBLE);
}
- setMuteButton(muteButton, isMuted());
+ setMuteButton(binding.switchMute, isMuted());
- animateRotation(moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0);
+ animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0);
}
/**
@@ -413,15 +346,15 @@ public class VideoPlayerImpl extends VideoPlayer
.getDimensionPixelSize(R.dimen.player_popup_controls_padding);
final int buttonsPadding = service.getResources()
.getDimensionPixelSize(R.dimen.player_popup_buttons_padding);
- getTopControlsRoot().setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
- getBottomControlsRoot().setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
- getQualityTextView().setPadding(
- buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
- getPlaybackSpeedTextView().setPadding(
- buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
- getCaptionTextView().setPadding(
- buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
- getPlaybackSpeedTextView().setMinimumWidth(0);
+ binding.topControls.setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
+ binding.bottomControls.setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
+ binding.qualityTextView.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
+ buttonsPadding);
+ binding.playbackSpeed.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
+ buttonsPadding);
+ binding.captionTextView.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
+ buttonsPadding);
+ binding.playbackSpeed.setMinimumWidth(0);
} else if (videoPlayerSelected()) {
final int buttonsMinWidth = service.getResources()
.getDimensionPixelSize(R.dimen.player_main_buttons_min_width);
@@ -431,16 +364,16 @@ public class VideoPlayerImpl extends VideoPlayer
.getDimensionPixelSize(R.dimen.player_main_controls_padding);
final int buttonsPadding = service.getResources()
.getDimensionPixelSize(R.dimen.player_main_buttons_padding);
- getTopControlsRoot().setPaddingRelative(
- controlsPadding, playerTopPadding, controlsPadding, 0);
- getBottomControlsRoot().setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
- getQualityTextView().setPadding(
- buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
- getPlaybackSpeedTextView().setPadding(
- buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
- getPlaybackSpeedTextView().setMinimumWidth(buttonsMinWidth);
- getCaptionTextView().setPadding(
- buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
+ binding.topControls.setPaddingRelative(controlsPadding, playerTopPadding,
+ controlsPadding, 0);
+ binding.bottomControls.setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
+ binding.qualityTextView.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
+ buttonsPadding);
+ binding.playbackSpeed.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
+ buttonsPadding);
+ binding.playbackSpeed.setMinimumWidth(buttonsMinWidth);
+ binding.captionTextView.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
+ buttonsPadding);
}
}
@@ -452,23 +385,23 @@ public class VideoPlayerImpl extends VideoPlayer
gestureDetector = new GestureDetector(context, listener);
getRootView().setOnTouchListener(listener);
- queueButton.setOnClickListener(this);
- repeatButton.setOnClickListener(this);
- shuffleButton.setOnClickListener(this);
+ binding.queueButton.setOnClickListener(this);
+ binding.repeatButton.setOnClickListener(this);
+ binding.shuffleButton.setOnClickListener(this);
- playPauseButton.setOnClickListener(this);
- playPreviousButton.setOnClickListener(this);
- playNextButton.setOnClickListener(this);
+ binding.playPauseButton.setOnClickListener(this);
+ binding.playPreviousButton.setOnClickListener(this);
+ binding.playNextButton.setOnClickListener(this);
- moreOptionsButton.setOnClickListener(this);
- moreOptionsButton.setOnLongClickListener(this);
- shareButton.setOnClickListener(this);
- fullscreenButton.setOnClickListener(this);
- screenRotationButton.setOnClickListener(this);
- playWithKodi.setOnClickListener(this);
- openInBrowser.setOnClickListener(this);
- playerCloseButton.setOnClickListener(this);
- muteButton.setOnClickListener(this);
+ binding.moreOptionsButton.setOnClickListener(this);
+ binding.moreOptionsButton.setOnLongClickListener(this);
+ binding.share.setOnClickListener(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
@@ -481,24 +414,25 @@ public class VideoPlayerImpl extends VideoPlayer
settingsContentObserver);
getRootView().addOnLayoutChangeListener(this);
- ViewCompat.setOnApplyWindowInsetsListener(queueLayout, (view, windowInsets) -> {
- final DisplayCutoutCompat cutout = windowInsets.getDisplayCutout();
- if (cutout != null) {
- view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
- cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
- }
- return windowInsets;
- });
+ ViewCompat.setOnApplyWindowInsetsListener(binding.playQueuePanel,
+ (view, windowInsets) -> {
+ final DisplayCutoutCompat cutout = windowInsets.getDisplayCutout();
+ if (cutout != null) {
+ view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
+ cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
+ }
+ return windowInsets;
+ });
// PlaybackControlRoot already consumed window insets but we should pass them to
// player_overlays too. Without it they will be off-centered
- getControlsRoot().addOnLayoutChangeListener((v, left, top, right, bottom,
- oldLeft, oldTop, oldRight, oldBottom) ->
- playerOverlays.setPadding(
- v.getPaddingLeft(),
- v.getPaddingTop(),
- v.getPaddingRight(),
- v.getPaddingBottom()));
+ binding.playbackControlRoot.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ binding.playerOverlays.setPadding(
+ v.getPaddingLeft(),
+ v.getPaddingTop(),
+ v.getPaddingRight(),
+ v.getPaddingBottom()));
}
public boolean onKeyDown(final int keyCode) {
@@ -521,7 +455,7 @@ public class VideoPlayerImpl extends VideoPlayer
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_CENTER:
- if (getRootView().hasFocus() && !getControlsRoot().hasFocus()) {
+ if (getRootView().hasFocus() && !binding.playbackControlRoot.hasFocus()) {
// do not interfere with focus in playlist etc.
return false;
}
@@ -532,7 +466,7 @@ public class VideoPlayerImpl extends VideoPlayer
if (!isControlsVisible()) {
if (!queueVisible) {
- playPauseButton.requestFocus();
+ binding.playPauseButton.requestFocus();
}
showControlsThenHide();
showSystemUIPartially();
@@ -548,13 +482,12 @@ public class VideoPlayerImpl extends VideoPlayer
public AppCompatActivity getParentActivity() {
// ! instanceof ViewGroup means that view was added via windowManager for Popup
- if (getRootView() == null
- || getRootView().getParent() == null
- || !(getRootView().getParent() instanceof ViewGroup)) {
+ if (binding == null || binding.getRoot().getParent() == null
+ || !(binding.getRoot().getParent() instanceof ViewGroup)) {
return null;
}
- final ViewGroup parent = (ViewGroup) getRootView().getParent();
+ final ViewGroup parent = (ViewGroup) binding.getRoot().getParent();
return (AppCompatActivity) parent.getContext();
}
@@ -644,13 +577,14 @@ public class VideoPlayerImpl extends VideoPlayer
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true);
}
+ @Override
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
super.onMetadataChanged(tag);
showHideKodiButton();
- titleTextView.setText(tag.getMetadata().getName());
- channelTextView.setText(tag.getMetadata().getUploaderName());
+ binding.titleTextView.setText(tag.getMetadata().getName());
+ binding.channelTextView.setText(tag.getMetadata().getUploaderName());
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
updateMetadata();
@@ -668,7 +602,7 @@ public class VideoPlayerImpl extends VideoPlayer
public void onMuteUnmuteButtonClicked() {
super.onMuteUnmuteButtonClicked();
updatePlayback();
- setMuteButton(muteButton, isMuted());
+ setMuteButton(binding.switchMute, isMuted());
}
@Override
@@ -747,7 +681,7 @@ public class VideoPlayerImpl extends VideoPlayer
if (!isFullscreen) {
// Apply window insets because Android will not do it when orientation changes
// from landscape to portrait (open vertical video to reproduce)
- getControlsRoot().setPadding(0, 0, 0, 0);
+ getPlaybackControlRoot().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.
@@ -757,13 +691,14 @@ public class VideoPlayerImpl extends VideoPlayer
fragmentListener.onFullscreenStateChanged(isFullscreen());
if (!isFullscreen()) {
- titleTextView.setVisibility(View.GONE);
- channelTextView.setVisibility(View.GONE);
- playerCloseButton.setVisibility(videoPlayerSelected() ? View.VISIBLE : View.GONE);
+ binding.titleTextView.setVisibility(View.GONE);
+ binding.channelTextView.setVisibility(View.GONE);
+ binding.playerCloseButton.setVisibility(videoPlayerSelected()
+ ? View.VISIBLE : View.GONE);
} else {
- titleTextView.setVisibility(View.VISIBLE);
- channelTextView.setVisibility(View.VISIBLE);
- playerCloseButton.setVisibility(View.GONE);
+ binding.titleTextView.setVisibility(View.VISIBLE);
+ binding.channelTextView.setVisibility(View.VISIBLE);
+ binding.playerCloseButton.setVisibility(View.GONE);
}
setupScreenRotationButton();
}
@@ -771,34 +706,34 @@ public class VideoPlayerImpl extends VideoPlayer
@Override
public void onClick(final View v) {
super.onClick(v);
- if (v.getId() == playPauseButton.getId()) {
+ if (v.getId() == binding.playPauseButton.getId()) {
onPlayPause();
- } else if (v.getId() == playPreviousButton.getId()) {
+ } else if (v.getId() == binding.playPreviousButton.getId()) {
onPlayPrevious();
- } else if (v.getId() == playNextButton.getId()) {
+ } else if (v.getId() == binding.playNextButton.getId()) {
onPlayNext();
- } else if (v.getId() == queueButton.getId()) {
+ } else if (v.getId() == binding.queueButton.getId()) {
onQueueClicked();
return;
- } else if (v.getId() == repeatButton.getId()) {
+ } else if (v.getId() == binding.repeatButton.getId()) {
onRepeatClicked();
return;
- } else if (v.getId() == shuffleButton.getId()) {
+ } else if (v.getId() == binding.shuffleButton.getId()) {
onShuffleClicked();
return;
- } else if (v.getId() == moreOptionsButton.getId()) {
+ } else if (v.getId() == binding.moreOptionsButton.getId()) {
onMoreOptionsClicked();
- } else if (v.getId() == shareButton.getId()) {
+ } else if (v.getId() == binding.share.getId()) {
onShareClicked();
- } else if (v.getId() == playWithKodi.getId()) {
+ } else if (v.getId() == binding.playWithKodi.getId()) {
onPlayWithKodiClicked();
- } else if (v.getId() == openInBrowser.getId()) {
+ } else if (v.getId() == binding.openInBrowser.getId()) {
onOpenInBrowserClicked();
- } else if (v.getId() == fullscreenButton.getId()) {
+ } else if (v.getId() == binding.fullScreenButton.getId()) {
setRecovery();
NavigationHelper.playOnMainPlayer(context, getPlayQueue(), true);
return;
- } else if (v.getId() == screenRotationButton.getId()) {
+ } 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
@@ -807,32 +742,34 @@ public class VideoPlayerImpl extends VideoPlayer
} else {
toggleFullscreen();
}
- } else if (v.getId() == muteButton.getId()) {
+ } else if (v.getId() == binding.switchMute.getId()) {
onMuteUnmuteButtonClicked();
- } else if (v.getId() == playerCloseButton.getId()) {
+ } else if (v.getId() == binding.playerCloseButton.getId()) {
service.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER));
}
if (getCurrentState() != STATE_COMPLETED) {
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
showHideShadow(true, DEFAULT_CONTROLS_DURATION, 0);
- animateView(getControlsRoot(), true, DEFAULT_CONTROLS_DURATION, 0, () -> {
- if (getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible()) {
- if (v.getId() == playPauseButton.getId()
- // Hide controls in fullscreen immediately
- || (v.getId() == screenRotationButton.getId() && isFullscreen)) {
- hideControls(0, 0);
- } else {
- hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
- }
- }
- });
+ animateView(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, 0,
+ () -> {
+ if (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() == moreOptionsButton.getId() && isFullscreen()) {
+ if (v.getId() == binding.moreOptionsButton.getId() && isFullscreen()) {
fragmentListener.onMoreOptionsLongClicked();
hideControls(0, 0);
hideSystemUIIfNeeded();
@@ -848,11 +785,11 @@ public class VideoPlayerImpl extends VideoPlayer
updatePlaybackButtons();
hideControls(0, 0);
- queueLayout.requestFocus();
- animateView(queueLayout, SLIDE_AND_ALPHA, true,
+ binding.playQueuePanel.requestFocus();
+ animateView(binding.playQueuePanel, SLIDE_AND_ALPHA, true,
DEFAULT_CONTROLS_DURATION);
- itemsList.scrollToPosition(playQueue.getIndex());
+ binding.playQueue.scrollToPosition(playQueue.getIndex());
}
public void onQueueClosed() {
@@ -860,14 +797,15 @@ public class VideoPlayerImpl extends VideoPlayer
return;
}
- animateView(queueLayout, SLIDE_AND_ALPHA, false,
+ animateView(binding.playQueuePanel, SLIDE_AND_ALPHA, false,
DEFAULT_CONTROLS_DURATION, 0, () -> {
// Even when queueLayout is GONE it receives touch events
// and ruins normal behavior of the app. This line fixes it
- queueLayout.setTranslationY(-queueLayout.getHeight() * 5);
+ binding.playQueuePanel
+ .setTranslationY(-binding.playQueuePanel.getHeight() * 5);
});
queueVisible = false;
- playPauseButton.requestFocus();
+ binding.playPauseButton.requestFocus();
}
private void onMoreOptionsClicked() {
@@ -875,18 +813,19 @@ public class VideoPlayerImpl extends VideoPlayer
Log.d(TAG, "onMoreOptionsClicked() called");
}
- final boolean isMoreControlsVisible = secondaryControls.getVisibility() == View.VISIBLE;
+ final boolean isMoreControlsVisible =
+ binding.secondaryControls.getVisibility() == View.VISIBLE;
- animateRotation(moreOptionsButton, DEFAULT_CONTROLS_DURATION,
+ animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION,
isMoreControlsVisible ? 0 : 180);
- animateView(secondaryControls, SLIDE_AND_ALPHA, !isMoreControlsVisible,
+ animateView(binding.secondaryControls, SLIDE_AND_ALPHA, !isMoreControlsVisible,
DEFAULT_CONTROLS_DURATION, 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) {
- secondaryControls.setVisibility(View.INVISIBLE);
+ binding.secondaryControls.setVisibility(View.INVISIBLE);
}
});
showControls(DEFAULT_CONTROLS_DURATION);
@@ -896,7 +835,7 @@ public class VideoPlayerImpl extends VideoPlayer
// share video at the current time (youtube.com/watch?v=ID&t=SECONDS)
// Timestamp doesn't make sense in a live stream so drop it
- final int ts = getPlaybackSeekBar().getProgress() / 1000;
+ final int ts = playbackSeekBar.getProgress() / 1000;
final MediaSourceTag metadata = getCurrentMetadata();
String videoUrl = getVideoUrl();
if (!isLive() && ts >= 0 && metadata != null
@@ -938,18 +877,18 @@ public class VideoPlayerImpl extends VideoPlayer
// show kodi button if it supports the current service and it is enabled in settings
final boolean showKodiButton = playQueue != null && playQueue.getItem() != null
&& KoreUtil.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId());
- playWithKodi.setVisibility(videoPlayerSelected() && kodiEnabled && showKodiButton
- ? View.VISIBLE : View.GONE);
+ binding.playWithKodi.setVisibility(videoPlayerSelected() && kodiEnabled
+ && showKodiButton ? View.VISIBLE : View.GONE);
}
private void setupScreenRotationButton() {
final boolean orientationLocked = PlayerHelper.globalScreenOrientationLocked(service);
final boolean showButton = videoPlayerSelected()
&& (orientationLocked || isVerticalVideo || DeviceUtils.isTablet(service));
- screenRotationButton.setVisibility(showButton ? View.VISIBLE : View.GONE);
- screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(service, isFullscreen()
- ? R.drawable.ic_fullscreen_exit_white_24dp
- : R.drawable.ic_fullscreen_white_24dp));
+ binding.screenRotationButton.setVisibility(showButton ? View.VISIBLE : View.GONE);
+ binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(service,
+ isFullscreen() ? R.drawable.ic_fullscreen_exit_white_24dp
+ : R.drawable.ic_fullscreen_white_24dp));
}
private void prepareOrientation() {
@@ -1009,11 +948,12 @@ public class VideoPlayerImpl extends VideoPlayer
Log.d(TAG, "maxGestureLength = " + maxGestureLength);
}
- volumeProgressBar.setMax(maxGestureLength);
- brightnessProgressBar.setMax(maxGestureLength);
+ binding.volumeProgressBar.setMax(maxGestureLength);
+ binding.brightnessProgressBar.setMax(maxGestureLength);
setInitialGestureValues();
- queueLayout.getLayoutParams().height = height - queueLayout.getTop();
+ binding.playQueuePanel.getLayoutParams().height = height
+ - binding.playQueuePanel.getTop();
}
}
@@ -1073,7 +1013,8 @@ public class VideoPlayerImpl extends VideoPlayer
//////////////////////////////////////////////////////////////////////////*/
private void animatePlayButtons(final boolean show, final int duration) {
- animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
+ animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, show,
+ duration);
boolean showQueueButtons = show;
if (playQueue == null) {
@@ -1082,19 +1023,18 @@ public class VideoPlayerImpl extends VideoPlayer
if (!showQueueButtons || playQueue.getIndex() > 0) {
animateView(
- playPreviousButton,
+ binding.playPreviousButton,
AnimationUtils.Type.SCALE_AND_ALPHA,
showQueueButtons,
duration);
}
if (!showQueueButtons || playQueue.getIndex() + 1 < playQueue.getStreams().size()) {
animateView(
- playNextButton,
+ binding.playNextButton,
AnimationUtils.Type.SCALE_AND_ALPHA,
showQueueButtons,
duration);
}
-
}
@Override
@@ -1106,7 +1046,7 @@ public class VideoPlayerImpl extends VideoPlayer
@Override
public void onBlocked() {
super.onBlocked();
- playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
+ binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
animatePlayButtons(false, 100);
getRootView().setKeepScreenOn(false);
@@ -1126,13 +1066,14 @@ public class VideoPlayerImpl extends VideoPlayer
@Override
public void onPlaying() {
super.onPlaying();
- animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
- playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
- animatePlayButtons(true, 200);
- if (!queueVisible) {
- playPauseButton.requestFocus();
- }
- });
+ animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false,
+ 80, 0, () -> {
+ binding.playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
+ animatePlayButtons(true, 200);
+ if (!queueVisible) {
+ binding.playPauseButton.requestFocus();
+ }
+ });
updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
checkLandscape();
@@ -1144,13 +1085,15 @@ public class VideoPlayerImpl extends VideoPlayer
@Override
public void onPaused() {
super.onPaused();
- animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
- playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
- animatePlayButtons(true, 200);
- if (!queueVisible) {
- playPauseButton.requestFocus();
- }
- });
+ animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA,
+ false, 80, 0, () -> {
+ binding.playPauseButton
+ .setImageResource(R.drawable.ic_play_arrow_white_24dp);
+ animatePlayButtons(true, 200);
+ if (!queueVisible) {
+ binding.playPauseButton.requestFocus();
+ }
+ });
updateWindowFlags(IDLE_WINDOW_FLAGS);
@@ -1177,10 +1120,11 @@ public class VideoPlayerImpl extends VideoPlayer
@Override
public void onCompleted() {
- animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, () -> {
- playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
- animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
- });
+ animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false,
+ 0, 0, () -> {
+ binding.playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
+ animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
+ });
getRootView().setKeepScreenOn(false);
updateWindowFlags(IDLE_WINDOW_FLAGS);
@@ -1355,8 +1299,8 @@ public class VideoPlayerImpl extends VideoPlayer
if (getAudioReactor() != null) {
final float currentVolumeNormalized = (float) getAudioReactor()
.getVolume() / getAudioReactor().getMaxVolume();
- volumeProgressBar.setProgress(
- (int) (volumeProgressBar.getMax() * currentVolumeNormalized));
+ binding.volumeProgressBar.setProgress(
+ (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized));
}
}
@@ -1453,14 +1397,15 @@ public class VideoPlayerImpl extends VideoPlayer
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
getControlsVisibilityHandler().postDelayed(() -> {
showHideShadow(false, duration, 0);
- animateView(getControlsRoot(), false, duration, 0, this::hideSystemUIIfNeeded);
+ animateView(binding.playbackControlRoot, false, duration, 0,
+ this::hideSystemUIIfNeeded);
}, delay
);
}
@Override
public void safeHideControls(final long duration, final long delay) {
- if (getControlsRoot().isInTouchMode()) {
+ if (binding.playbackControlRoot.isInTouchMode()) {
hideControls(duration, delay);
}
}
@@ -1474,12 +1419,12 @@ public class VideoPlayerImpl extends VideoPlayer
final boolean showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size();
final boolean showQueue = playQueue.getStreams().size() > 1 && !popupPlayerSelected();
- playPreviousButton.setVisibility(showPrev ? View.VISIBLE : View.INVISIBLE);
- playPreviousButton.setAlpha(showPrev ? 1.0f : 0.0f);
- playNextButton.setVisibility(showNext ? View.VISIBLE : View.INVISIBLE);
- playNextButton.setAlpha(showNext ? 1.0f : 0.0f);
- queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE);
- queueButton.setAlpha(showQueue ? 1.0f : 0.0f);
+ 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);
}
private void showSystemUIPartially() {
@@ -1524,15 +1469,12 @@ public class VideoPlayerImpl extends VideoPlayer
}
private void updatePlaybackButtons() {
- if (repeatButton == null
- || shuffleButton == null
- || simpleExoPlayer == null
- || playQueue == null) {
+ if (binding == null || simpleExoPlayer == null || playQueue == null) {
return;
}
- setRepeatModeButton(repeatButton, getRepeatMode());
- setShuffleButton(shuffleButton, playQueue.isShuffled());
+ setRepeatModeButton(binding.repeatButton, getRepeatMode());
+ setShuffleButton(binding.shuffleButton, playQueue.isShuffled());
}
public void checkLandscape() {
@@ -1553,19 +1495,19 @@ public class VideoPlayerImpl extends VideoPlayer
}
private void buildQueue() {
- itemsList.setAdapter(playQueueAdapter);
- itemsList.setClickable(true);
- itemsList.setLongClickable(true);
+ binding.playQueue.setAdapter(playQueueAdapter);
+ binding.playQueue.setClickable(true);
+ binding.playQueue.setLongClickable(true);
- itemsList.clearOnScrollListeners();
- itemsList.addOnScrollListener(getQueueScrollListener());
+ binding.playQueue.clearOnScrollListeners();
+ binding.playQueue.addOnScrollListener(getQueueScrollListener());
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
- itemTouchHelper.attachToRecyclerView(itemsList);
+ itemTouchHelper.attachToRecyclerView(binding.playQueue);
playQueueAdapter.setSelectedListener(getOnSelectedListener());
- itemsListCloseButton.setOnClickListener(view -> onQueueClosed());
+ binding.playQueueClose.setOnClickListener(view -> onQueueClosed());
}
public void useVideoSource(final boolean video) {
@@ -1589,8 +1531,8 @@ public class VideoPlayerImpl extends VideoPlayer
public void onScrolledDown(final RecyclerView recyclerView) {
if (playQueue != null && !playQueue.isComplete()) {
playQueue.fetch();
- } else if (itemsList != null) {
- itemsList.clearOnScrollListeners();
+ } else if (binding != null) {
+ binding.playQueue.clearOnScrollListeners();
}
}
};
@@ -1682,8 +1624,8 @@ public class VideoPlayerImpl extends VideoPlayer
checkPopupPositionBounds();
- getLoadingPanel().setMinimumWidth(popupLayoutParams.width);
- getLoadingPanel().setMinimumHeight(popupLayoutParams.height);
+ binding.loadingPanel.setMinimumWidth(popupLayoutParams.width);
+ binding.loadingPanel.setMinimumHeight(popupLayoutParams.height);
service.removeViewFromParent();
windowManager.addView(getRootView(), popupLayoutParams);
@@ -1933,10 +1875,9 @@ public class VideoPlayerImpl extends VideoPlayer
}
private boolean popupHasParent() {
- final View root = getRootView();
- return root != null
- && root.getLayoutParams() instanceof WindowManager.LayoutParams
- && root.getParent() != null;
+ return binding != null
+ && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams
+ && binding.getRoot().getParent() != null;
}
///////////////////////////////////////////////////////////////////////////
@@ -1949,9 +1890,9 @@ public class VideoPlayerImpl extends VideoPlayer
// Apply window insets because Android will not do it when orientation changes
// from landscape to portrait
if (!isFullscreen) {
- getControlsRoot().setPadding(0, 0, 0, 0);
+ binding.playbackControlRoot.setPadding(0, 0, 0, 0);
}
- queueLayout.setPadding(0, 0, 0, 0);
+ binding.playQueuePanel.setPadding(0, 0, 0, 0);
updateQueue();
updateMetadata();
updatePlayback();
@@ -2050,31 +1991,31 @@ public class VideoPlayerImpl extends VideoPlayer
///////////////////////////////////////////////////////////////////////////
public RelativeLayout getVolumeRelativeLayout() {
- return volumeRelativeLayout;
+ return binding.volumeRelativeLayout;
}
public ProgressBar getVolumeProgressBar() {
- return volumeProgressBar;
+ return binding.volumeProgressBar;
}
public ImageView getVolumeImageView() {
- return volumeImageView;
+ return binding.volumeImageView;
}
public RelativeLayout getBrightnessRelativeLayout() {
- return brightnessRelativeLayout;
+ return binding.brightnessRelativeLayout;
}
public ProgressBar getBrightnessProgressBar() {
- return brightnessProgressBar;
+ return binding.brightnessProgressBar;
}
public ImageView getBrightnessImageView() {
- return brightnessImageView;
+ return binding.brightnessImageView;
}
public ImageButton getPlayPauseButton() {
- return playPauseButton;
+ return binding.playPauseButton;
}
public int getMaxGestureLength() {
@@ -2082,7 +2023,7 @@ public class VideoPlayerImpl extends VideoPlayer
}
public TextView getResizingIndicator() {
- return resizingIndicator;
+ return binding.resizingIndicator;
}
public GestureDetector getGestureDetector() {
@@ -2125,8 +2066,8 @@ public class VideoPlayerImpl extends VideoPlayer
return closeOverlayButton;
}
- public View getClosingOverlayView() {
- return closingOverlayView;
+ public View getClosingOverlay() {
+ return binding.closingOverlay;
}
public boolean isVerticalVideo() {
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
index ab1330f7b..b6916e620 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
+++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
@@ -123,7 +123,7 @@ public class PlayerGestureListener
}
} else /* MainPlayer.PlayerType.POPUP */ {
- final View closingOverlayView = playerImpl.getClosingOverlayView();
+ final View closingOverlayView = playerImpl.getClosingOverlay();
if (playerImpl.isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
animateView(closingOverlayView, true, 250);
@@ -240,7 +240,7 @@ public class PlayerGestureListener
if (playerImpl.isInsideClosingRadius(event)) {
playerImpl.closePopup();
} else {
- animateView(playerImpl.getClosingOverlayView(), false, 0);
+ animateView(playerImpl.getClosingOverlay(), false, 0);
if (!playerImpl.isPopupClosing) {
animateView(playerImpl.getCloseOverlayButton(), false, 200);
From fa75c79d34e79e2a71a304bde3e1629d0e1c6194 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Wed, 23 Dec 2020 06:47:36 +0530
Subject: [PATCH 041/131] Use view binding (PlayerPopupCloseOverlayBinding) in
VideoPlayerImpl.
---
.../newpipe/player/VideoPlayerImpl.java | 48 +++++++++----------
.../player/event/BasePlayerGestureListener.kt | 2 +-
.../player/event/PlayerGestureListener.java | 2 +-
3 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
index a968ddc91..949b11374 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
@@ -39,6 +39,7 @@ import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -73,11 +74,11 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.SubtitleView;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.PlayerBinding;
+import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
@@ -172,8 +173,7 @@ public class VideoPlayerImpl extends VideoPlayer
private WindowManager.LayoutParams popupLayoutParams;
public WindowManager windowManager;
- private View closeOverlayView;
- private FloatingActionButton closeOverlayButton;
+ private PlayerPopupCloseOverlayBinding closeOverlayBinding;
public boolean isPopupClosing = false;
@@ -1341,10 +1341,10 @@ public class VideoPlayerImpl extends VideoPlayer
}
private int distanceFromCloseButton(final MotionEvent popupMotionEvent) {
- final int closeOverlayButtonX = closeOverlayButton.getLeft()
- + closeOverlayButton.getWidth() / 2;
- final int closeOverlayButtonY = closeOverlayButton.getTop()
- + closeOverlayButton.getHeight() / 2;
+ 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();
@@ -1354,7 +1354,7 @@ public class VideoPlayerImpl extends VideoPlayer
}
private float getClosingRadius() {
- final int buttonRadius = closeOverlayButton.getWidth() / 2;
+ final int buttonRadius = closeOverlayBinding.closeButton.getWidth() / 2;
// 20% wider than the button itself
return buttonRadius * 1.2f;
}
@@ -1641,12 +1641,11 @@ public class VideoPlayerImpl extends VideoPlayer
}
// closeOverlayView is already added to windowManager
- if (closeOverlayView != null) {
+ if (closeOverlayBinding != null) {
return;
}
- closeOverlayView = View.inflate(service, R.layout.player_popup_close_overlay, null);
- closeOverlayButton = closeOverlayView.findViewById(R.id.closeButton);
+ closeOverlayBinding = PlayerPopupCloseOverlayBinding.inflate(LayoutInflater.from(service));
final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
@@ -1661,8 +1660,8 @@ public class VideoPlayerImpl extends VideoPlayer
closeOverlayLayoutParams.softInputMode =
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
- closeOverlayButton.setVisibility(View.GONE);
- windowManager.addView(closeOverlayView, closeOverlayLayoutParams);
+ closeOverlayBinding.closeButton.setVisibility(View.GONE);
+ windowManager.addView(closeOverlayBinding.getRoot(), closeOverlayLayoutParams);
}
private void initVideoPlayer() {
@@ -1835,22 +1834,23 @@ public class VideoPlayerImpl extends VideoPlayer
}
public void removePopupFromView() {
- final boolean isCloseOverlayHasParent = closeOverlayView != null
- && closeOverlayView.getParent() != null;
+ final boolean isCloseOverlayHasParent = closeOverlayBinding != null
+ && closeOverlayBinding.getRoot().getParent() != null;
if (popupHasParent()) {
windowManager.removeView(getRootView());
}
if (isCloseOverlayHasParent) {
- windowManager.removeView(closeOverlayView);
+ windowManager.removeView(closeOverlayBinding.getRoot());
}
}
private void animateOverlayAndFinishService() {
- final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight()
- - closeOverlayButton.getY());
+ final int targetTranslationY =
+ (int) (closeOverlayBinding.closeButton.getRootView().getHeight()
+ - closeOverlayBinding.closeButton.getY());
- closeOverlayButton.animate().setListener(null).cancel();
- closeOverlayButton.animate()
+ closeOverlayBinding.closeButton.animate().setListener(null).cancel();
+ closeOverlayBinding.closeButton.animate()
.setInterpolator(new AnticipateInterpolator())
.translationY(targetTranslationY)
.setDuration(400)
@@ -1866,8 +1866,8 @@ public class VideoPlayerImpl extends VideoPlayer
}
private void end() {
- windowManager.removeView(closeOverlayView);
- closeOverlayView = null;
+ windowManager.removeView(closeOverlayBinding.getRoot());
+ closeOverlayBinding = null;
service.onDestroy();
}
@@ -2062,8 +2062,8 @@ public class VideoPlayerImpl extends VideoPlayer
popupHeight = height;
}
- public View getCloseOverlayButton() {
- return closeOverlayButton;
+ public View getCloseButton() {
+ return closeOverlayBinding.closeButton;
}
public View getClosingOverlay() {
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
index 043e7f31d..d34746ca5 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt
+++ b/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt
@@ -371,7 +371,7 @@ abstract class BasePlayerGestureListener(
}
if (!isMovingInPopup) {
- AnimationUtils.animateView(playerImpl.closeOverlayButton, true, 200)
+ AnimationUtils.animateView(playerImpl.closeButton, true, 200)
}
isMovingInPopup = true
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
index b6916e620..8f9514781 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
+++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
@@ -243,7 +243,7 @@ public class PlayerGestureListener
animateView(playerImpl.getClosingOverlay(), false, 0);
if (!playerImpl.isPopupClosing) {
- animateView(playerImpl.getCloseOverlayButton(), false, 200);
+ animateView(playerImpl.getCloseButton(), false, 200);
}
}
}
From 98be89a20ac833bf9ec7af55e8d71ea8c0477766 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 09:47:27 +0530
Subject: [PATCH 042/131] Return ViewBinding instead of View in
BaseLocalListFragment's getListHeader() and getListFooter() methods.
---
.../newpipe/local/BaseLocalListFragment.java | 37 ++++----
.../history/StatisticsPlaylistFragment.java | 66 +++++++--------
.../local/playlist/LocalPlaylistFragment.java | 84 ++++++++-----------
.../main/res/layout/local_playlist_header.xml | 5 +-
.../res/layout/statistic_playlist_control.xml | 4 +-
5 files changed, 93 insertions(+), 103 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
index 8e88ceaed..38ecc1c63 100644
--- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
@@ -4,6 +4,8 @@ import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
+
+import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
@@ -15,8 +17,10 @@ import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewbinding.ViewBinding;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.PignateFooterBinding;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.list.ListViewContract;
@@ -42,8 +46,8 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
//////////////////////////////////////////////////////////////////////////*/
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
- private View headerRootView;
- private View footerRootView;
+ private ViewBinding headerRootBinding;
+ private ViewBinding footerRootBinding;
protected LocalItemListAdapter itemListAdapter;
protected RecyclerView itemsList;
private int updateFlags = 0;
@@ -86,12 +90,13 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
// Lifecycle - View
//////////////////////////////////////////////////////////////////////////*/
- protected View getListHeader() {
+ @Nullable
+ protected ViewBinding getListHeader() {
return null;
}
- protected View getListFooter() {
- return activity.getLayoutInflater().inflate(R.layout.pignate_footer, itemsList, false);
+ protected ViewBinding getListFooter() {
+ return PignateFooterBinding.inflate(activity.getLayoutInflater(), itemsList, false);
}
protected RecyclerView.LayoutManager getGridLayoutManager() {
@@ -120,10 +125,12 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setUseGridVariant(useGrid);
- headerRootView = getListHeader();
- itemListAdapter.setHeader(headerRootView);
- footerRootView = getListFooter();
- itemListAdapter.setFooter(footerRootView);
+ headerRootBinding = getListHeader();
+ if (headerRootBinding != null) {
+ itemListAdapter.setHeader(headerRootBinding.getRoot());
+ }
+ footerRootBinding = getListFooter();
+ itemListAdapter.setFooter(footerRootBinding.getRoot());
itemsList.setAdapter(itemListAdapter);
}
@@ -180,8 +187,8 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
if (itemsList != null) {
animateView(itemsList, false, 200);
}
- if (headerRootView != null) {
- animateView(headerRootView, false, 200);
+ if (headerRootBinding != null) {
+ animateView(headerRootBinding.getRoot(), false, 200);
}
}
@@ -191,8 +198,8 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
if (itemsList != null) {
animateView(itemsList, true, 200);
}
- if (headerRootView != null) {
- animateView(headerRootView, true, 200);
+ if (headerRootBinding != null) {
+ animateView(headerRootBinding.getRoot(), true, 200);
}
}
@@ -204,8 +211,8 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
if (itemsList != null) {
animateView(itemsList, false, 200);
}
- if (headerRootView != null) {
- animateView(headerRootView, false, 200);
+ if (headerRootBinding != null) {
+ animateView(headerRootBinding.getRoot(), false, 200);
}
}
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 0d549ecf9..f9aa38054 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
@@ -10,13 +10,12 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
+import androidx.viewbinding.ViewBinding;
import com.google.android.material.snackbar.Snackbar;
@@ -26,6 +25,8 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.stream.model.StreamEntity;
+import org.schabi.newpipe.databinding.PlaylistControlBinding;
+import org.schabi.newpipe.databinding.StatisticPlaylistControlBinding;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemDialog;
@@ -60,13 +61,10 @@ public class StatisticsPlaylistFragment
@State
Parcelable itemsListState;
private StatisticSortMode sortMode = StatisticSortMode.LAST_PLAYED;
- private View headerPlayAllButton;
- private View headerPopupButton;
- private View headerBackgroundButton;
- private View playlistCtrl;
- private View sortButton;
- private ImageView sortButtonIcon;
- private TextView sortButtonText;
+
+ private StatisticPlaylistControlBinding headerBinding;
+ private PlaylistControlBinding playlistControlBinding;
+
/* Used for independent events */
private Subscription databaseSubscription;
private HistoryRecordManager recordManager;
@@ -131,17 +129,12 @@ public class StatisticsPlaylistFragment
}
@Override
- protected View getListHeader() {
- final View headerRootLayout = activity.getLayoutInflater()
- .inflate(R.layout.statistic_playlist_control, itemsList, false);
- playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
- headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
- headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
- headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
- sortButton = headerRootLayout.findViewById(R.id.sortButton);
- sortButtonIcon = headerRootLayout.findViewById(R.id.sortButtonIcon);
- sortButtonText = headerRootLayout.findViewById(R.id.sortButtonText);
- return headerRootLayout;
+ protected ViewBinding getListHeader() {
+ headerBinding = StatisticPlaylistControlBinding.inflate(activity.getLayoutInflater(),
+ itemsList, false);
+ playlistControlBinding = headerBinding.playlistControl;
+
+ return headerBinding;
}
@Override
@@ -245,14 +238,13 @@ public class StatisticsPlaylistFragment
if (itemListAdapter != null) {
itemListAdapter.unsetSelectedListener();
}
- if (headerBackgroundButton != null) {
- headerBackgroundButton.setOnClickListener(null);
- }
- if (headerPlayAllButton != null) {
- headerPlayAllButton.setOnClickListener(null);
- }
- if (headerPopupButton != null) {
- headerPopupButton.setOnClickListener(null);
+ if (playlistControlBinding != null) {
+ playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(null);
+ playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(null);
+ playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(null);
+
+ headerBinding = null;
+ playlistControlBinding = null;
}
if (databaseSubscription != null) {
@@ -311,7 +303,7 @@ public class StatisticsPlaylistFragment
return;
}
- playlistCtrl.setVisibility(View.VISIBLE);
+ playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
itemListAdapter.clearStreamItemList();
@@ -326,13 +318,13 @@ public class StatisticsPlaylistFragment
itemsListState = null;
}
- headerPlayAllButton.setOnClickListener(view ->
+ playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
- headerPopupButton.setOnClickListener(view ->
+ playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
- headerBackgroundButton.setOnClickListener(view ->
+ playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
- sortButton.setOnClickListener(view -> toggleSortMode());
+ headerBinding.sortButton.setOnClickListener(view -> toggleSortMode());
hideLoading();
}
@@ -368,15 +360,15 @@ public class StatisticsPlaylistFragment
if (sortMode == StatisticSortMode.LAST_PLAYED) {
sortMode = StatisticSortMode.MOST_PLAYED;
setTitle(getString(R.string.title_most_played));
- sortButtonIcon.setImageResource(
+ headerBinding.sortButtonIcon.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_history));
- sortButtonText.setText(R.string.title_last_played);
+ headerBinding.sortButtonText.setText(R.string.title_last_played);
} else {
sortMode = StatisticSortMode.LAST_PLAYED;
setTitle(getString(R.string.title_last_played));
- sortButtonIcon.setImageResource(
+ headerBinding.sortButtonIcon.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_filter_list));
- sortButtonText.setText(R.string.title_most_played);
+ headerBinding.sortButtonText.setText(R.string.title_most_played);
}
startLoading(true);
}
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 17f7a4ff9..08b7101e6 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
@@ -14,7 +14,6 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
-import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -22,6 +21,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewbinding.ViewBinding;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
@@ -32,6 +32,8 @@ import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
+import org.schabi.newpipe.databinding.LocalPlaylistHeaderBinding;
+import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemDialog;
@@ -77,13 +79,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment createRenameDialog());
+ headerBinding.playlistTitleView.setOnClickListener(view -> createRenameDialog());
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
itemTouchHelper.attachToRecyclerView(itemsList);
@@ -210,22 +200,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment
+ playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
- headerPopupButton.setOnClickListener(view ->
+ playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
- headerBackgroundButton.setOnClickListener(view ->
+ playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
- headerPopupButton.setOnLongClickListener(view -> {
+ playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true);
return true;
});
- headerBackgroundButton.setOnLongClickListener(view -> {
+ playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true);
return true;
});
@@ -806,8 +791,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment
-
+
+
diff --git a/app/src/main/res/layout/statistic_playlist_control.xml b/app/src/main/res/layout/statistic_playlist_control.xml
index ef090807c..c8eff8fd3 100644
--- a/app/src/main/res/layout/statistic_playlist_control.xml
+++ b/app/src/main/res/layout/statistic_playlist_control.xml
@@ -38,6 +38,8 @@
tools:ignore="RtlHardcoded" />
-
+
From 979102a2d932cd17ce58d367244c6c906dcc81a8 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 10:49:19 +0530
Subject: [PATCH 043/131] Return ViewBinding instead of View in
BaseListFragment's getListHeader() and getListFooter() methods.
---
.../fragments/list/BaseListFragment.java | 18 ++-
.../list/channel/ChannelFragment.java | 148 +++++++++---------
.../list/playlist/PlaylistFragment.java | 89 +++++------
.../list/videos/RelatedVideosFragment.java | 46 +++---
app/src/main/res/layout/channel_header.xml | 5 +-
app/src/main/res/layout/playlist_header.xml | 4 +-
6 files changed, 153 insertions(+), 157 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 a40ff1bf3..02c7a4818 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
@@ -12,13 +12,16 @@ import android.view.MenuInflater;
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewbinding.ViewBinding;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.PignateFooterBinding;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
@@ -215,12 +218,13 @@ public abstract class BaseListFragment extends BaseStateFragment
// Init
//////////////////////////////////////////////////////////////////////////*/
- protected View getListHeader() {
+ @Nullable
+ protected ViewBinding getListHeader() {
return null;
}
- protected View getListFooter() {
- return activity.getLayoutInflater().inflate(R.layout.pignate_footer, itemsList, false);
+ protected ViewBinding getListFooter() {
+ return PignateFooterBinding.inflate(activity.getLayoutInflater(), itemsList, false);
}
protected RecyclerView.LayoutManager getListLayoutManager() {
@@ -247,8 +251,12 @@ public abstract class BaseListFragment extends BaseStateFragment
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setUseGridVariant(useGrid);
- infoListAdapter.setFooter(getListFooter());
- infoListAdapter.setHeader(getListHeader());
+ infoListAdapter.setFooter(getListFooter().getRoot());
+
+ final ViewBinding listHeader = getListHeader();
+ if (listHeader != null) {
+ infoListAdapter.setHeader(listHeader.getRoot());
+ }
itemsList.setAdapter(infoListAdapter);
}
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 0342bb99c..b97ef9acc 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
@@ -14,8 +14,6 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -23,11 +21,14 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
+import androidx.viewbinding.ViewBinding;
import com.jakewharton.rxbinding4.view.RxView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
+import org.schabi.newpipe.databinding.ChannelHeaderBinding;
+import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
@@ -78,18 +79,10 @@ public class ChannelFragment extends BaseListInfoFragment
//////////////////////////////////////////////////////////////////////////*/
private SubscriptionManager subscriptionManager;
- private View headerRootLayout;
- private ImageView headerChannelBanner;
- private ImageView headerAvatarView;
- private TextView headerTitleView;
- private ImageView headerSubChannelAvatarView;
- private TextView headerSubChannelTitleView;
- private TextView headerSubscribersTextView;
- private Button headerSubscribeButton;
- private View playlistCtrl;
- private LinearLayout headerPlayAllButton;
- private LinearLayout headerPopupButton;
- private LinearLayout headerBackgroundButton;
+
+ private ChannelHeaderBinding headerBinding;
+ private PlaylistControlBinding playlistControlBinding;
+
private MenuItem menuRssButton;
private TextView contentNotSupportedTextView;
private TextView kaomojiTextView;
@@ -140,45 +133,38 @@ public class ChannelFragment extends BaseListInfoFragment
@Override
public void onDestroy() {
super.onDestroy();
- if (disposables != null) {
- disposables.clear();
- }
+ disposables.clear();
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
}
+ @Override
+ public void onDestroyView() {
+ headerBinding = null;
+ playlistControlBinding = null;
+ super.onDestroyView();
+ }
+
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
- protected View getListHeader() {
- headerRootLayout = activity.getLayoutInflater()
- .inflate(R.layout.channel_header, itemsList, false);
- headerChannelBanner = headerRootLayout.findViewById(R.id.channel_banner_image);
- headerAvatarView = headerRootLayout.findViewById(R.id.channel_avatar_view);
- headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view);
- headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
- headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
- playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
- headerSubChannelAvatarView =
- headerRootLayout.findViewById(R.id.sub_channel_avatar_view);
- headerSubChannelTitleView =
- headerRootLayout.findViewById(R.id.sub_channel_title_view);
+ @Override
+ protected ViewBinding getListHeader() {
+ headerBinding = ChannelHeaderBinding
+ .inflate(activity.getLayoutInflater(), itemsList, false);
+ playlistControlBinding = headerBinding.playlistControl;
- headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
- headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
- headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
-
- return headerRootLayout;
+ return headerBinding;
}
@Override
protected void initListeners() {
super.initListeners();
- headerSubChannelTitleView.setOnClickListener(this);
- headerSubChannelAvatarView.setOnClickListener(this);
+ headerBinding.subChannelTitleView.setOnClickListener(this);
+ headerBinding.subChannelAvatarView.setOnClickListener(this);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -241,7 +227,7 @@ public class ChannelFragment extends BaseListInfoFragment
private void monitorSubscription(final ChannelInfo info) {
final Consumer onError = (Throwable throwable) -> {
- animateView(headerSubscribeButton, false, 100);
+ animateView(headerBinding.channelSubscribeButton, false, 100);
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(currentInfo.getServiceId()),
"Get subscription status", 0);
@@ -351,15 +337,15 @@ public class ChannelFragment extends BaseListInfoFragment
info.getAvatarUrl(),
info.getDescription(),
info.getSubscriberCount());
- subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton,
- mapOnSubscribe(channel, info));
+ subscribeButtonMonitor = monitorSubscribeButton(
+ headerBinding.channelSubscribeButton, mapOnSubscribe(channel, info));
} else {
if (DEBUG) {
Log.d(TAG, "Found subscription to this channel!");
}
final SubscriptionEntity subscription = subscriptionEntities.get(0);
- subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton,
- mapOnUnsubscribe(subscription));
+ subscribeButtonMonitor = monitorSubscribeButton(
+ headerBinding.channelSubscribeButton, mapOnUnsubscribe(subscription));
}
};
}
@@ -370,7 +356,8 @@ public class ChannelFragment extends BaseListInfoFragment
+ "isSubscribed = [" + isSubscribed + "]");
}
- final boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
+ final boolean isButtonVisible = headerBinding.channelSubscribeButton.getVisibility()
+ == View.VISIBLE;
final int backgroundDuration = isButtonVisible ? 300 : 0;
final int textDuration = isButtonVisible ? 200 : 0;
@@ -382,18 +369,21 @@ public class ChannelFragment extends BaseListInfoFragment
final int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
if (!isSubscribed) {
- headerSubscribeButton.setText(R.string.subscribe_button_title);
- animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribedBackground,
- subscribeBackground);
- animateTextColor(headerSubscribeButton, textDuration, subscribedText, subscribeText);
+ headerBinding.channelSubscribeButton.setText(R.string.subscribe_button_title);
+ animateBackgroundColor(headerBinding.channelSubscribeButton, backgroundDuration,
+ subscribedBackground, subscribeBackground);
+ animateTextColor(headerBinding.channelSubscribeButton, textDuration, subscribedText,
+ subscribeText);
} else {
- headerSubscribeButton.setText(R.string.subscribed_button_title);
- animateBackgroundColor(headerSubscribeButton, backgroundDuration, subscribeBackground,
- subscribedBackground);
- animateTextColor(headerSubscribeButton, textDuration, subscribeText, subscribedText);
+ headerBinding.channelSubscribeButton.setText(R.string.subscribed_button_title);
+ animateBackgroundColor(headerBinding.channelSubscribeButton, backgroundDuration,
+ subscribeBackground, subscribedBackground);
+ animateTextColor(headerBinding.channelSubscribeButton, textDuration, subscribeText,
+ subscribedText);
}
- animateView(headerSubscribeButton, AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, true, 100);
+ animateView(headerBinding.channelSubscribeButton, AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA,
+ true, 100);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -446,48 +436,49 @@ public class ChannelFragment extends BaseListInfoFragment
public void showLoading() {
super.showLoading();
- IMAGE_LOADER.cancelDisplayTask(headerChannelBanner);
- IMAGE_LOADER.cancelDisplayTask(headerAvatarView);
- IMAGE_LOADER.cancelDisplayTask(headerSubChannelAvatarView);
- animateView(headerSubscribeButton, false, 100);
+ IMAGE_LOADER.cancelDisplayTask(headerBinding.channelBannerImage);
+ IMAGE_LOADER.cancelDisplayTask(headerBinding.channelAvatarView);
+ IMAGE_LOADER.cancelDisplayTask(headerBinding.subChannelAvatarView);
+ animateView(headerBinding.channelSubscribeButton, false, 100);
}
@Override
public void handleResult(@NonNull final ChannelInfo result) {
super.handleResult(result);
- headerRootLayout.setVisibility(View.VISIBLE);
- IMAGE_LOADER.displayImage(result.getBannerUrl(), headerChannelBanner,
+ headerBinding.getRoot().setVisibility(View.VISIBLE);
+ IMAGE_LOADER.displayImage(result.getBannerUrl(), headerBinding.channelBannerImage,
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
- IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView,
+ IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerBinding.channelAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
- IMAGE_LOADER.displayImage(result.getParentChannelAvatarUrl(), headerSubChannelAvatarView,
+ IMAGE_LOADER.displayImage(result.getParentChannelAvatarUrl(),
+ headerBinding.subChannelAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
- headerSubscribersTextView.setVisibility(View.VISIBLE);
+ headerBinding.channelSubscriberView.setVisibility(View.VISIBLE);
if (result.getSubscriberCount() >= 0) {
- headerSubscribersTextView.setText(Localization
+ headerBinding.channelSubscriberView.setText(Localization
.shortSubscriberCount(activity, result.getSubscriberCount()));
} else {
- headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
+ headerBinding.channelSubscriberView.setText(R.string.subscribers_count_not_available);
}
if (!TextUtils.isEmpty(currentInfo.getParentChannelName())) {
- headerSubChannelTitleView.setText(String.format(
+ headerBinding.subChannelTitleView.setText(String.format(
getString(R.string.channel_created_by),
currentInfo.getParentChannelName())
);
- headerSubChannelTitleView.setVisibility(View.VISIBLE);
- headerSubChannelAvatarView.setVisibility(View.VISIBLE);
+ headerBinding.subChannelTitleView.setVisibility(View.VISIBLE);
+ headerBinding.subChannelAvatarView.setVisibility(View.VISIBLE);
} else {
- headerSubChannelTitleView.setVisibility(View.GONE);
+ headerBinding.subChannelTitleView.setVisibility(View.GONE);
}
if (menuRssButton != null) {
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
}
- playlistCtrl.setVisibility(View.VISIBLE);
+ playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
final List errors = new ArrayList<>(result.getErrors());
if (!errors.isEmpty()) {
@@ -516,19 +507,22 @@ public class ChannelFragment extends BaseListInfoFragment
updateSubscription(result);
monitorSubscription(result);
- headerPlayAllButton.setOnClickListener(view -> NavigationHelper
- .playOnMainPlayer(activity, getPlayQueue()));
- headerPopupButton.setOnClickListener(view -> NavigationHelper
- .playOnPopupPlayer(activity, getPlayQueue(), false));
- headerBackgroundButton.setOnClickListener(view -> NavigationHelper
- .playOnBackgroundPlayer(activity, getPlayQueue(), false));
+ playlistControlBinding.playlistCtrlPlayAllButton
+ .setOnClickListener(view -> NavigationHelper
+ .playOnMainPlayer(activity, getPlayQueue()));
+ playlistControlBinding.playlistCtrlPlayPopupButton
+ .setOnClickListener(view -> NavigationHelper
+ .playOnPopupPlayer(activity, getPlayQueue(), false));
+ playlistControlBinding.playlistCtrlPlayBgButton
+ .setOnClickListener(view -> NavigationHelper
+ .playOnBackgroundPlayer(activity, getPlayQueue(), false));
- headerPopupButton.setOnLongClickListener(view -> {
+ playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true);
return true;
});
- headerBackgroundButton.setOnLongClickListener(view -> {
+ playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true);
return true;
});
@@ -596,7 +590,7 @@ public class ChannelFragment extends BaseListInfoFragment
public void setTitle(final String title) {
super.setTitle(title);
if (!useAsFrontPage) {
- headerTitleView.setText(title);
+ headerBinding.channelTitleView.setText(title);
}
}
}
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 8f7d6128b..6e723a686 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
@@ -11,18 +11,20 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
+import androidx.viewbinding.ViewBinding;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
+import org.schabi.newpipe.databinding.PlaylistControlBinding;
+import org.schabi.newpipe.databinding.PlaylistHeaderBinding;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
@@ -53,7 +55,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
-import de.hdodenhof.circleimageview.CircleImageView;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Single;
@@ -74,17 +75,8 @@ public class PlaylistFragment extends BaseListInfoFragment {
// Views
//////////////////////////////////////////////////////////////////////////*/
- private View headerRootLayout;
- private TextView headerTitleView;
- private View headerUploaderLayout;
- private TextView headerUploaderName;
- private CircleImageView headerUploaderAvatar;
- private TextView headerStreamCount;
- private View playlistCtrl;
-
- private View headerPlayAllButton;
- private View headerPopupButton;
- private View headerBackgroundButton;
+ private PlaylistHeaderBinding headerBinding;
+ private PlaylistControlBinding playlistControlBinding;
private MenuItem playlistBookmarkButton;
@@ -119,22 +111,13 @@ public class PlaylistFragment extends BaseListInfoFragment {
// Init
//////////////////////////////////////////////////////////////////////////*/
- protected View getListHeader() {
- headerRootLayout = activity.getLayoutInflater()
- .inflate(R.layout.playlist_header, itemsList, false);
- headerTitleView = headerRootLayout.findViewById(R.id.playlist_title_view);
- headerUploaderLayout = headerRootLayout.findViewById(R.id.uploader_layout);
- headerUploaderName = headerRootLayout.findViewById(R.id.uploader_name);
- headerUploaderAvatar = headerRootLayout.findViewById(R.id.uploader_avatar_view);
- headerStreamCount = headerRootLayout.findViewById(R.id.playlist_stream_count);
- playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
+ @Override
+ protected ViewBinding getListHeader() {
+ headerBinding = PlaylistHeaderBinding
+ .inflate(activity.getLayoutInflater(), itemsList, false);
+ playlistControlBinding = headerBinding.playlistControl;
- headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
- headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
- headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
-
-
- return headerRootLayout;
+ return headerBinding;
}
@Override
@@ -203,6 +186,9 @@ public class PlaylistFragment extends BaseListInfoFragment {
@Override
public void onDestroyView() {
+ headerBinding = null;
+ playlistControlBinding = null;
+
super.onDestroyView();
if (isBookmarkButtonReady != null) {
isBookmarkButtonReady.set(false);
@@ -275,25 +261,25 @@ public class PlaylistFragment extends BaseListInfoFragment {
@Override
public void showLoading() {
super.showLoading();
- animateView(headerRootLayout, false, 200);
+ animateView(headerBinding.getRoot(), false, 200);
animateView(itemsList, false, 100);
- IMAGE_LOADER.cancelDisplayTask(headerUploaderAvatar);
- animateView(headerUploaderLayout, false, 200);
+ IMAGE_LOADER.cancelDisplayTask(headerBinding.uploaderAvatarView);
+ animateView(headerBinding.uploaderLayout, false, 200);
}
@Override
public void handleResult(@NonNull final PlaylistInfo result) {
super.handleResult(result);
- animateView(headerRootLayout, true, 100);
- animateView(headerUploaderLayout, true, 300);
- headerUploaderLayout.setOnClickListener(null);
+ animateView(headerBinding.getRoot(), true, 100);
+ animateView(headerBinding.uploaderLayout, true, 300);
+ headerBinding.uploaderLayout.setOnClickListener(null);
// If we have an uploader put them into the UI
if (!TextUtils.isEmpty(result.getUploaderName())) {
- headerUploaderName.setText(result.getUploaderName());
+ headerBinding.uploaderName.setText(result.getUploaderName());
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
- headerUploaderLayout.setOnClickListener(v -> {
+ headerBinding.uploaderLayout.setOnClickListener(v -> {
try {
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
result.getUploaderUrl(), result.getUploaderName());
@@ -303,28 +289,29 @@ public class PlaylistFragment extends BaseListInfoFragment {
});
}
} else { // Otherwise say we have no uploader
- headerUploaderName.setText(R.string.playlist_no_uploader);
+ headerBinding.uploaderName.setText(R.string.playlist_no_uploader);
}
- playlistCtrl.setVisibility(View.VISIBLE);
+ playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
final String avatarUrl = result.getUploaderAvatarUrl();
if (result.getServiceId() == ServiceList.YouTube.getServiceId()
&& (YoutubeParsingHelper.isYoutubeMixId(result.getId())
|| YoutubeParsingHelper.isYoutubeMusicMixId(result.getId()))) {
// this is an auto-generated playlist (e.g. Youtube mix), so a radio is shown
- headerUploaderAvatar.setDisableCircularTransformation(true);
- headerUploaderAvatar.setBorderColor(
+ headerBinding.uploaderAvatarView.setDisableCircularTransformation(true);
+ headerBinding.uploaderAvatarView.setBorderColor(
getResources().getColor(R.color.transparent_background_color));
- headerUploaderAvatar.setImageDrawable(AppCompatResources.getDrawable(requireContext(),
- resolveResourceIdFromAttr(requireContext(), R.attr.ic_radio)));
-
+ headerBinding.uploaderAvatarView.setImageDrawable(
+ AppCompatResources.getDrawable(requireContext(),
+ resolveResourceIdFromAttr(requireContext(), R.attr.ic_radio))
+ );
} else {
- IMAGE_LOADER.displayImage(avatarUrl, headerUploaderAvatar,
+ IMAGE_LOADER.displayImage(avatarUrl, headerBinding.uploaderAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
}
- headerStreamCount.setText(Localization
+ headerBinding.playlistStreamCount.setText(Localization
.localizeStreamCount(getContext(), result.getStreamCount()));
if (!result.getErrors().isEmpty()) {
@@ -338,19 +325,19 @@ public class PlaylistFragment extends BaseListInfoFragment {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistBookmarkSubscriber());
- headerPlayAllButton.setOnClickListener(view ->
+ playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
- headerPopupButton.setOnClickListener(view ->
+ playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
- headerBackgroundButton.setOnClickListener(view ->
+ playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
- headerPopupButton.setOnLongClickListener(view -> {
+ playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true);
return true;
});
- headerBackgroundButton.setOnLongClickListener(view -> {
+ playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true);
return true;
});
@@ -459,7 +446,7 @@ public class PlaylistFragment extends BaseListInfoFragment {
@Override
public void setTitle(final String title) {
super.setTitle(title);
- headerTitleView.setText(title);
+ headerBinding.playlistTitleView.setText(title);
}
private void onBookmarkClicked() {
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java
index 907615b45..7fb41f0e5 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java
@@ -8,13 +8,14 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.Switch;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
+import androidx.viewbinding.ViewBinding;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.RelatedStreamsHeaderBinding;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.stream.StreamInfo;
@@ -38,8 +39,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment
+ headerBinding.autoplaySwitch.setChecked(autoplay);
+ headerBinding.autoplaySwitch.setOnCheckedChangeListener((compoundButton, b) ->
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
.putBoolean(getString(R.string.auto_queue_key), b).apply());
- return headerRootLayout;
+ return headerBinding;
} else {
return null;
}
@@ -107,8 +111,8 @@ public class RelatedVideosFragment extends BaseListInfoFragment
-
+
+
diff --git a/app/src/main/res/layout/playlist_header.xml b/app/src/main/res/layout/playlist_header.xml
index d27c86872..a5960472f 100644
--- a/app/src/main/res/layout/playlist_header.xml
+++ b/app/src/main/res/layout/playlist_header.xml
@@ -83,7 +83,9 @@
android:layout_height="wrap_content"
android:layout_below="@id/playlist_meta">
-
+
From 910d22daa6dcccdb6b21213ab02da1eed1842047 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 09:24:07 +0530
Subject: [PATCH 044/131] Use view binding in MainFragment.
---
.../newpipe/fragments/MainFragment.java | 35 ++++++++++---------
1 file changed, 19 insertions(+), 16 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
index a77109f86..9487fa385 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
@@ -19,12 +19,12 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
import androidx.preference.PreferenceManager;
-import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.FragmentMainBinding;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
@@ -34,15 +34,13 @@ import org.schabi.newpipe.settings.tabs.TabsManager;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
-import org.schabi.newpipe.views.ScrollableTabLayout;
import java.util.ArrayList;
import java.util.List;
public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener {
- private ViewPager viewPager;
+ private FragmentMainBinding binding;
private SelectedTabsPagerAdapter pagerAdapter;
- private ScrollableTabLayout tabLayout;
private final List tabsList = new ArrayList<>();
private TabsManager tabsManager;
@@ -90,13 +88,12 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
- tabLayout = rootView.findViewById(R.id.main_tab_layout);
- viewPager = rootView.findViewById(R.id.pager);
+ binding = FragmentMainBinding.bind(rootView);
- tabLayout.setTabIconTint(ColorStateList.valueOf(
+ binding.mainTabLayout.setTabIconTint(ColorStateList.valueOf(
ThemeHelper.resolveColorFromAttr(requireContext(), R.attr.colorAccent)));
- tabLayout.setupWithViewPager(viewPager);
- tabLayout.addOnTabSelectedListener(this);
+ binding.mainTabLayout.setupWithViewPager(binding.pager);
+ binding.mainTabLayout.addOnTabSelectedListener(this);
setupTabs();
}
@@ -120,8 +117,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public void onDestroy() {
super.onDestroy();
tabsManager.unsetSavedTabsListener();
- if (viewPager != null) {
- viewPager.setAdapter(null);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ if (binding != null) {
+ binding.pager.setAdapter(null);
+ binding = null;
}
}
@@ -172,19 +175,19 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
getChildFragmentManager(), tabsList);
}
- viewPager.setAdapter(null);
- viewPager.setOffscreenPageLimit(tabsList.size());
- viewPager.setAdapter(pagerAdapter);
+ binding.pager.setAdapter(null);
+ binding.pager.setOffscreenPageLimit(tabsList.size());
+ binding.pager.setAdapter(pagerAdapter);
updateTabsIconAndDescription();
- updateTitleForTab(viewPager.getCurrentItem());
+ updateTitleForTab(binding.pager.getCurrentItem());
hasTabsChanged = false;
}
private void updateTabsIconAndDescription() {
for (int i = 0; i < tabsList.size(); i++) {
- final TabLayout.Tab tabToSet = tabLayout.getTabAt(i);
+ final TabLayout.Tab tabToSet = binding.mainTabLayout.getTabAt(i);
if (tabToSet != null) {
final Tab tab = tabsList.get(i);
tabToSet.setIcon(tab.getTabIconRes(requireContext()));
From 7c581ec108e84fa4c15b720520a0004dd3ed88b7 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 09:52:51 +0530
Subject: [PATCH 045/131] Use view binding in LicenseFragment.
---
.../schabi/newpipe/about/LicenseFragment.java | 34 +++++++++----------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java
index 6e48a0e14..f5bf4df19 100644
--- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java
@@ -7,13 +7,14 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.FragmentLicensesBinding;
+import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding;
import org.schabi.newpipe.util.ShareUtils;
import java.io.Serializable;
@@ -67,43 +68,42 @@ public class LicenseFragment extends Fragment {
@Nullable
@Override
- public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
+ public View onCreateView(@NonNull final LayoutInflater inflater,
+ @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
- final View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
- final ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
+ final FragmentLicensesBinding binding = FragmentLicensesBinding
+ .inflate(inflater, container, false);
- final View licenseLink = rootView.findViewById(R.id.app_read_license);
- licenseLink.setOnClickListener(v -> {
+ binding.appReadLicense.setOnClickListener(v -> {
activeLicense = StandardLicenses.GPL3;
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
StandardLicenses.GPL3));
});
for (final SoftwareComponent component : softwareComponents) {
- final View componentView = inflater
- .inflate(R.layout.item_software_component, container, false);
- final TextView softwareName = componentView.findViewById(R.id.name);
- final TextView copyright = componentView.findViewById(R.id.copyright);
- softwareName.setText(component.getName());
- copyright.setText(getString(R.string.copyright,
+ final ItemSoftwareComponentBinding componentBinding = ItemSoftwareComponentBinding
+ .inflate(inflater, container, false);
+ componentBinding.name.setText(component.getName());
+ componentBinding.copyright.setText(getString(R.string.copyright,
component.getYears(),
component.getCopyrightOwner(),
component.getLicense().getAbbreviation()));
- componentView.setTag(component);
- componentView.setOnClickListener(v -> {
+ final View root = componentBinding.getRoot();
+ root.setTag(component);
+ root.setOnClickListener(v -> {
activeLicense = component.getLicense();
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
component.getLicense()));
});
- softwareComponentsView.addView(componentView);
- registerForContextMenu(componentView);
+ binding.softwareComponents.addView(root);
+ registerForContextMenu(root);
}
if (activeLicense != null) {
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
activeLicense));
}
- return rootView;
+ return binding.getRoot();
}
@Override
From 7682ebd245a55261e2c5737f305d35b2ab37d441 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 10:00:12 +0530
Subject: [PATCH 046/131] Use view binding in DownloadDialog.
---
.../newpipe/download/DownloadDialog.java | 102 +++++++++---------
1 file changed, 48 insertions(+), 54 deletions(-)
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 e80a0ab21..68bcf3cc1 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -16,12 +16,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
-import android.widget.EditText;
-import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SeekBar;
-import android.widget.Spinner;
-import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.IdRes;
@@ -40,6 +36,7 @@ import com.nononsenseapps.filepicker.Utils;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.RouterActivity;
+import org.schabi.newpipe.databinding.DownloadDialogBinding;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.localization.Localization;
@@ -116,11 +113,7 @@ public class DownloadDialog extends DialogFragment
private final CompositeDisposable disposables = new CompositeDisposable();
- private EditText nameEditText;
- private Spinner streamsSpinner;
- private RadioGroup radioStreamsGroup;
- private TextView threadsCountTextView;
- private SeekBar threadsSeekBar;
+ private DownloadDialogBinding dialogBinding;
private SharedPreferences prefs;
@@ -277,38 +270,35 @@ public class DownloadDialog extends DialogFragment
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- nameEditText = view.findViewById(R.id.file_name);
- nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
+ dialogBinding = DownloadDialogBinding.bind(view);
+
+ dialogBinding.fileName.setText(FilenameUtils.createFilename(getContext(),
+ currentInfo.getName()));
selectedAudioIndex = ListHelper
.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams());
selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());
- streamsSpinner = view.findViewById(R.id.quality_spinner);
- streamsSpinner.setOnItemSelectedListener(this);
+ dialogBinding.qualitySpinner.setOnItemSelectedListener(this);
- threadsCountTextView = view.findViewById(R.id.threads_count);
- threadsSeekBar = view.findViewById(R.id.threads);
+ dialogBinding.videoAudioGroup.setOnCheckedChangeListener(this);
- radioStreamsGroup = view.findViewById(R.id.video_audio_group);
- radioStreamsGroup.setOnCheckedChangeListener(this);
-
- initToolbar(view.findViewById(R.id.toolbar));
+ initToolbar(dialogBinding.toolbarLayout.toolbar);
setupDownloadOptions();
prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
final int threads = prefs.getInt(getString(R.string.default_download_threads), 3);
- threadsCountTextView.setText(String.valueOf(threads));
- threadsSeekBar.setProgress(threads - 1);
- threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ dialogBinding.threadsCount.setText(String.valueOf(threads));
+ dialogBinding.threads.setProgress(threads - 1);
+ dialogBinding.threads.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(final SeekBar seekbar, final int progress,
final boolean fromUser) {
final int newProgress = progress + 1;
prefs.edit().putInt(getString(R.string.default_download_threads), newProgress)
.apply();
- threadsCountTextView.setText(String.valueOf(newProgress));
+ dialogBinding.threadsCount.setText(String.valueOf(newProgress));
}
@Override
@@ -326,19 +316,19 @@ public class DownloadDialog extends DialogFragment
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams)
.subscribe(result -> {
- if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.video_button) {
+ if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.video_button) {
setupVideoSpinner();
}
}));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams)
.subscribe(result -> {
- if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) {
+ if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) {
setupAudioSpinner();
}
}));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams)
.subscribe(result -> {
- if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
+ if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
setupSubtitleSpinner();
}
}));
@@ -350,6 +340,12 @@ public class DownloadDialog extends DialogFragment
disposables.clear();
}
+ @Override
+ public void onDestroyView() {
+ dialogBinding = null;
+ super.onDestroyView();
+ }
+
/*//////////////////////////////////////////////////////////////////////////
// Radio group Video&Audio options - Listener
//////////////////////////////////////////////////////////////////////////*/
@@ -429,8 +425,8 @@ public class DownloadDialog extends DialogFragment
return;
}
- streamsSpinner.setAdapter(audioStreamsAdapter);
- streamsSpinner.setSelection(selectedAudioIndex);
+ dialogBinding.qualitySpinner.setAdapter(audioStreamsAdapter);
+ dialogBinding.qualitySpinner.setSelection(selectedAudioIndex);
setRadioButtonsState(true);
}
@@ -439,8 +435,8 @@ public class DownloadDialog extends DialogFragment
return;
}
- streamsSpinner.setAdapter(videoStreamsAdapter);
- streamsSpinner.setSelection(selectedVideoIndex);
+ dialogBinding.qualitySpinner.setAdapter(videoStreamsAdapter);
+ dialogBinding.qualitySpinner.setSelection(selectedVideoIndex);
setRadioButtonsState(true);
}
@@ -449,8 +445,8 @@ public class DownloadDialog extends DialogFragment
return;
}
- streamsSpinner.setAdapter(subtitleStreamsAdapter);
- streamsSpinner.setSelection(selectedSubtitleIndex);
+ dialogBinding.qualitySpinner.setAdapter(subtitleStreamsAdapter);
+ dialogBinding.qualitySpinner.setSelection(selectedSubtitleIndex);
setRadioButtonsState(true);
}
@@ -475,7 +471,7 @@ public class DownloadDialog extends DialogFragment
break;
}
- threadsSeekBar.setEnabled(flag);
+ dialogBinding.threads.setEnabled(flag);
}
@Override
@@ -486,7 +482,7 @@ public class DownloadDialog extends DialogFragment
+ "parent = [" + parent + "], view = [" + view + "], "
+ "position = [" + position + "], id = [" + id + "]");
}
- switch (radioStreamsGroup.getCheckedRadioButtonId()) {
+ switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
selectedAudioIndex = position;
break;
@@ -506,16 +502,14 @@ public class DownloadDialog extends DialogFragment
protected void setupDownloadOptions() {
setRadioButtonsState(false);
- final RadioButton audioButton = radioStreamsGroup.findViewById(R.id.audio_button);
- final RadioButton videoButton = radioStreamsGroup.findViewById(R.id.video_button);
- final RadioButton subtitleButton = radioStreamsGroup.findViewById(R.id.subtitle_button);
final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0;
final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0;
final boolean isSubtitleStreamsAvailable = subtitleStreamsAdapter.getCount() > 0;
- audioButton.setVisibility(isAudioStreamsAvailable ? View.VISIBLE : View.GONE);
- videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
- subtitleButton.setVisibility(isSubtitleStreamsAvailable ? View.VISIBLE : View.GONE);
+ dialogBinding.audioButton.setVisibility(isAudioStreamsAvailable ? View.VISIBLE : View.GONE);
+ dialogBinding.videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
+ dialogBinding.subtitleButton.setVisibility(isSubtitleStreamsAvailable
+ ? View.VISIBLE : View.GONE);
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
final String defaultMedia = prefs.getString(getString(R.string.last_used_download_type),
@@ -523,24 +517,24 @@ public class DownloadDialog extends DialogFragment
if (isVideoStreamsAvailable
&& (defaultMedia.equals(getString(R.string.last_download_type_video_key)))) {
- videoButton.setChecked(true);
+ dialogBinding.videoButton.setChecked(true);
setupVideoSpinner();
} else if (isAudioStreamsAvailable
&& (defaultMedia.equals(getString(R.string.last_download_type_audio_key)))) {
- audioButton.setChecked(true);
+ dialogBinding.audioButton.setChecked(true);
setupAudioSpinner();
} else if (isSubtitleStreamsAvailable
&& (defaultMedia.equals(getString(R.string.last_download_type_subtitle_key)))) {
- subtitleButton.setChecked(true);
+ dialogBinding.subtitleButton.setChecked(true);
setupSubtitleSpinner();
} else if (isVideoStreamsAvailable) {
- videoButton.setChecked(true);
+ dialogBinding.videoButton.setChecked(true);
setupVideoSpinner();
} else if (isAudioStreamsAvailable) {
- audioButton.setChecked(true);
+ dialogBinding.audioButton.setChecked(true);
setupAudioSpinner();
} else if (isSubtitleStreamsAvailable) {
- subtitleButton.setChecked(true);
+ dialogBinding.subtitleButton.setChecked(true);
setupSubtitleSpinner();
} else {
Toast.makeText(getContext(), R.string.no_streams_available_download,
@@ -550,9 +544,9 @@ public class DownloadDialog extends DialogFragment
}
private void setRadioButtonsState(final boolean enabled) {
- radioStreamsGroup.findViewById(R.id.audio_button).setEnabled(enabled);
- radioStreamsGroup.findViewById(R.id.video_button).setEnabled(enabled);
- radioStreamsGroup.findViewById(R.id.subtitle_button).setEnabled(enabled);
+ dialogBinding.audioButton.setEnabled(enabled);
+ dialogBinding.videoButton.setEnabled(enabled);
+ dialogBinding.subtitleButton.setEnabled(enabled);
}
private int getSubtitleIndexBy(final List streams) {
@@ -582,7 +576,7 @@ public class DownloadDialog extends DialogFragment
}
private String getNameEditText() {
- final String str = nameEditText.getText().toString().trim();
+ final String str = dialogBinding.fileName.getText().toString().trim();
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
}
@@ -619,7 +613,7 @@ public class DownloadDialog extends DialogFragment
String filename = getNameEditText().concat(".");
- switch (radioStreamsGroup.getCheckedRadioButtonId()) {
+ switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
selectedMediaType = getString(R.string.last_download_type_audio_key);
mainStorage = mainStorageAudio;
@@ -669,7 +663,7 @@ public class DownloadDialog extends DialogFragment
filename, mime);
} else {
File initialSavePath;
- if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) {
+ if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) {
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MUSIC);
} else {
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES);
@@ -862,7 +856,7 @@ public class DownloadDialog extends DialogFragment
final Stream selectedStream;
Stream secondaryStream = null;
final char kind;
- int threads = threadsSeekBar.getProgress() + 1;
+ int threads = dialogBinding.threads.getProgress() + 1;
final String[] urls;
final MissionRecoveryInfo[] recoveryInfo;
String psName = null;
@@ -870,7 +864,7 @@ public class DownloadDialog extends DialogFragment
long nearLength = 0;
// more download logic: select muxer, subtitle converter, etc.
- switch (radioStreamsGroup.getCheckedRadioButtonId()) {
+ switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
kind = 'a';
selectedStream = audioStreamsAdapter.getItem(selectedAudioIndex);
From 6039484a02bda6eeef9633c46fd5edfc4eb6311d Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 10:56:29 +0530
Subject: [PATCH 047/131] Use view binding in ChannelFragment.
---
.../list/channel/ChannelFragment.java | 19 ++++++++-----------
1 file changed, 8 insertions(+), 11 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 b97ef9acc..003893517 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
@@ -14,7 +14,6 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
-import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -28,6 +27,7 @@ import com.jakewharton.rxbinding4.view.RxView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.databinding.ChannelHeaderBinding;
+import org.schabi.newpipe.databinding.FragmentChannelBinding;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
@@ -80,13 +80,11 @@ public class ChannelFragment extends BaseListInfoFragment
private SubscriptionManager subscriptionManager;
+ private FragmentChannelBinding channelBinding;
private ChannelHeaderBinding headerBinding;
private PlaylistControlBinding playlistControlBinding;
private MenuItem menuRssButton;
- private TextView contentNotSupportedTextView;
- private TextView kaomojiTextView;
- private TextView noVideosTextView;
public static ChannelFragment getInstance(final int serviceId, final String url,
final String name) {
@@ -125,9 +123,7 @@ public class ChannelFragment extends BaseListInfoFragment
@Override
public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
- contentNotSupportedTextView = rootView.findViewById(R.id.error_content_not_supported);
- kaomojiTextView = rootView.findViewById(R.id.channel_kaomoji);
- noVideosTextView = rootView.findViewById(R.id.channel_no_videos);
+ channelBinding = FragmentChannelBinding.bind(rootView);
}
@Override
@@ -141,6 +137,7 @@ public class ChannelFragment extends BaseListInfoFragment
@Override
public void onDestroyView() {
+ channelBinding = null;
headerBinding = null;
playlistControlBinding = null;
super.onDestroyView();
@@ -529,10 +526,10 @@ public class ChannelFragment extends BaseListInfoFragment
}
private void showContentNotSupported() {
- contentNotSupportedTextView.setVisibility(View.VISIBLE);
- kaomojiTextView.setText("(︶︹︺)");
- kaomojiTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f);
- noVideosTextView.setVisibility(View.GONE);
+ channelBinding.errorContentNotSupported.setVisibility(View.VISIBLE);
+ channelBinding.channelKaomoji.setText("(︶︹︺)");
+ channelBinding.channelKaomoji.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f);
+ channelBinding.channelNoVideos.setVisibility(View.GONE);
}
private PlayQueue getPlayQueue() {
From 97672f06de29fb5f5def106a03fcc5d9c8ce938d Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 13:28:21 +0530
Subject: [PATCH 048/131] Use view binding in SearchFragment.
---
.../fragments/list/search/SearchFragment.java | 39 +++++++++++--------
1 file changed, 23 insertions(+), 16 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 3b48d9c84..0aaf87ea6 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
@@ -37,6 +37,7 @@ import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
+import org.schabi.newpipe.databinding.FragmentSearchBinding;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.MetaInfo;
@@ -155,6 +156,8 @@ public class SearchFragment extends BaseListFragment());
@@ -640,7 +644,8 @@ public class SearchFragment extends BaseListFragment suggestionListAdapter.setItems(suggestions));
+ searchBinding.suggestionsList.smoothScrollToPosition(0);
+ searchBinding.suggestionsList.post(() -> suggestionListAdapter.setItems(suggestions));
if (suggestionsPanelVisible && errorPanelRoot.getVisibility() == View.VISIBLE) {
hideLoading();
@@ -1019,7 +1025,7 @@ public class SearchFragment extends BaseListFragment";
final String text = String.format(helperText, highlightedSearchSuggestion);
- correctSuggestion.setText(HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_LEGACY));
+ searchBinding.correctSuggestion.setText(HtmlCompat.fromHtml(text,
+ HtmlCompat.FROM_HTML_MODE_LEGACY));
- correctSuggestion.setOnClickListener(v -> {
- correctSuggestion.setVisibility(View.GONE);
+ searchBinding.correctSuggestion.setOnClickListener(v -> {
+ searchBinding.correctSuggestion.setVisibility(View.GONE);
search(searchSuggestion, contentFilter, sortFilter);
searchEditText.setText(searchSuggestion);
});
- correctSuggestion.setOnLongClickListener(v -> {
+ searchBinding.correctSuggestion.setOnLongClickListener(v -> {
searchEditText.setText(searchSuggestion);
searchEditText.setSelection(searchSuggestion.length());
showKeyboardSearch();
return true;
});
- correctSuggestion.setVisibility(View.VISIBLE);
+ searchBinding.correctSuggestion.setVisibility(View.VISIBLE);
}
}
From f80e1bd214ca7db29d58229a2428dc8b82533e08 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 13:51:37 +0530
Subject: [PATCH 049/131] Use view binding in FeedFragment.
---
.../schabi/newpipe/local/feed/FeedFragment.kt | 109 ++++++++++--------
1 file changed, 58 insertions(+), 51 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 dbe91ec55..f767f8f22 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
@@ -37,18 +37,10 @@ import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import icepick.State
-import kotlinx.android.synthetic.main.error_retry.error_button_retry
-import kotlinx.android.synthetic.main.error_retry.error_message_view
-import kotlinx.android.synthetic.main.fragment_feed.empty_state_view
-import kotlinx.android.synthetic.main.fragment_feed.error_panel
-import kotlinx.android.synthetic.main.fragment_feed.items_list
-import kotlinx.android.synthetic.main.fragment_feed.loading_progress_bar
-import kotlinx.android.synthetic.main.fragment_feed.loading_progress_text
-import kotlinx.android.synthetic.main.fragment_feed.refresh_root_view
-import kotlinx.android.synthetic.main.fragment_feed.refresh_subtitle_text
-import kotlinx.android.synthetic.main.fragment_feed.refresh_text
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
+import org.schabi.newpipe.databinding.ErrorRetryBinding
+import org.schabi.newpipe.databinding.FragmentFeedBinding
import org.schabi.newpipe.fragments.list.BaseListFragment
import org.schabi.newpipe.local.feed.service.FeedLoadService
import org.schabi.newpipe.report.UserAction
@@ -57,6 +49,12 @@ import org.schabi.newpipe.util.Localization
import java.util.Calendar
class FeedFragment : BaseListFragment() {
+ private var _feedBinding: FragmentFeedBinding? = null
+ private val feedBinding get() = _feedBinding!!
+
+ private var _errorBinding: ErrorRetryBinding? = null
+ private val errorBinding get() = _errorBinding!!
+
private lateinit var viewModel: FeedViewModel
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
@State
@@ -86,15 +84,17 @@ class FeedFragment : BaseListFragment() {
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
super.onViewCreated(rootView, savedInstanceState)
- swipeRefreshLayout = requireView().findViewById(R.id.swiperefresh)
- swipeRefreshLayout.setOnRefreshListener { reloadContent() }
+ _feedBinding = FragmentFeedBinding.bind(rootView)
+ _errorBinding = feedBinding.errorPanel
+
+ feedBinding.swiperefresh.setOnRefreshListener { reloadContent() }
viewModel = ViewModelProvider(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
viewModel.stateLiveData.observe(viewLifecycleOwner, Observer { it?.let(::handleResult) })
}
override fun onPause() {
super.onPause()
- listState = items_list?.layoutManager?.onSaveInstanceState()
+ listState = _feedBinding?.itemsList?.layoutManager?.onSaveInstanceState()
}
override fun onResume() {
@@ -112,7 +112,8 @@ class FeedFragment : BaseListFragment() {
override fun initListeners() {
super.initListeners()
- refresh_root_view.setOnClickListener {
+ // Using the non-null property may result in a NullPointerException
+ _feedBinding?.refreshRootView?.setOnClickListener {
triggerUpdate()
}
}
@@ -169,55 +170,60 @@ class FeedFragment : BaseListFragment() {
activity?.supportActionBar?.subtitle = null
}
+ override fun onDestroyView() {
+ _feedBinding = null
+ super.onDestroyView()
+ }
+
// /////////////////////////////////////////////////////////////////////////
// Handling
// /////////////////////////////////////////////////////////////////////////
override fun showLoading() {
- animateView(refresh_root_view, false, 0)
- animateView(items_list, false, 0)
+ animateView(feedBinding.refreshRootView, false, 0)
+ animateView(feedBinding.itemsList, false, 0)
- animateView(loading_progress_bar, true, 200)
- animateView(loading_progress_text, true, 200)
+ animateView(feedBinding.loadingProgressBar, true, 200)
+ animateView(feedBinding.loadingProgressText, true, 200)
- empty_state_view?.let { animateView(it, false, 0) }
- animateView(error_panel, false, 0)
+ animateView(feedBinding.emptyStateView.root, false, 0)
+ animateView(errorBinding.root, false, 0)
}
override fun hideLoading() {
- animateView(refresh_root_view, true, 200)
- animateView(items_list, true, 300)
+ animateView(feedBinding.refreshRootView, true, 200)
+ animateView(feedBinding.itemsList, true, 300)
- animateView(loading_progress_bar, false, 0)
- animateView(loading_progress_text, false, 0)
+ animateView(feedBinding.loadingProgressBar, false, 0)
+ animateView(feedBinding.loadingProgressText, false, 0)
- empty_state_view?.let { animateView(it, false, 0) }
- animateView(error_panel, false, 0)
- swipeRefreshLayout.isRefreshing = false
+ animateView(feedBinding.emptyStateView.root, false, 0)
+ animateView(errorBinding.root, false, 0)
+ feedBinding.swiperefresh.isRefreshing = false
}
override fun showEmptyState() {
- animateView(refresh_root_view, true, 200)
- animateView(items_list, false, 0)
+ animateView(feedBinding.refreshRootView, true, 200)
+ animateView(feedBinding.itemsList, false, 0)
- animateView(loading_progress_bar, false, 0)
- animateView(loading_progress_text, false, 0)
+ animateView(feedBinding.loadingProgressBar, false, 0)
+ animateView(feedBinding.loadingProgressText, false, 0)
- empty_state_view?.let { animateView(it, true, 800) }
- animateView(error_panel, false, 0)
+ animateView(feedBinding.emptyStateView.root, true, 800)
+ animateView(errorBinding.root, false, 0)
}
override fun showError(message: String, showRetryButton: Boolean) {
infoListAdapter.clearStreamItemList()
- animateView(refresh_root_view, false, 120)
- animateView(items_list, false, 120)
+ animateView(feedBinding.refreshRootView, false, 120)
+ animateView(feedBinding.itemsList, false, 120)
- animateView(loading_progress_bar, false, 120)
- animateView(loading_progress_text, false, 120)
+ animateView(feedBinding.loadingProgressBar, false, 120)
+ animateView(feedBinding.loadingProgressText, false, 120)
- error_message_view.text = message
- animateView(error_button_retry, showRetryButton, if (showRetryButton) 600 else 0)
- animateView(error_panel, true, 300)
+ errorBinding.errorMessageView.text = message
+ animateView(errorBinding.errorButtonRetry, showRetryButton, if (showRetryButton) 600 else 0)
+ animateView(errorBinding.root, true, 300)
}
override fun handleResult(result: FeedState) {
@@ -237,33 +243,34 @@ class FeedFragment : BaseListFragment() {
progressState.maxProgress == -1
if (!isIndeterminate) {
- loading_progress_text.text = "${progressState.currentProgress}/${progressState.maxProgress}"
+ feedBinding.loadingProgressText.text = "${progressState.currentProgress}/${progressState.maxProgress}"
} else if (progressState.progressMessage > 0) {
- loading_progress_text?.setText(progressState.progressMessage)
+ _feedBinding?.loadingProgressText?.setText(progressState.progressMessage)
} else {
- loading_progress_text?.text = "∞/∞"
+ _feedBinding?.loadingProgressText?.text = "∞/∞"
}
- loading_progress_bar.isIndeterminate = isIndeterminate ||
- (progressState.maxProgress > 0 && progressState.currentProgress == 0)
- loading_progress_bar.progress = progressState.currentProgress
+ feedBinding.loadingProgressBar.isIndeterminate = isIndeterminate ||
+ (progressState.maxProgress > 0 && progressState.currentProgress == 0)
+ feedBinding.loadingProgressBar.progress = progressState.currentProgress
- loading_progress_bar.max = progressState.maxProgress
+ feedBinding.loadingProgressBar.max = progressState.maxProgress
}
private fun handleLoadedState(loadedState: FeedState.LoadedState) {
infoListAdapter.setInfoItemList(loadedState.items)
listState?.run {
- items_list.layoutManager?.onRestoreInstanceState(listState)
+ feedBinding.itemsList.layoutManager?.onRestoreInstanceState(listState)
listState = null
}
oldestSubscriptionUpdate = loadedState.oldestUpdate
val loadedCount = loadedState.notLoadedCount > 0
- refresh_subtitle_text.isVisible = loadedCount
+ feedBinding.refreshSubtitleText.isVisible = loadedCount
if (loadedCount) {
- refresh_subtitle_text.text = getString(R.string.feed_subscription_not_loaded_count, loadedState.notLoadedCount)
+ feedBinding.refreshSubtitleText.text = getString(R.string.feed_subscription_not_loaded_count,
+ loadedState.notLoadedCount)
}
if (loadedState.itemsErrors.isNotEmpty()) {
@@ -300,7 +307,7 @@ class FeedFragment : BaseListFragment() {
else -> "—"
}
- refresh_text?.text = getString(R.string.feed_oldest_subscription_update, oldestSubscriptionUpdateText)
+ feedBinding.refreshText.text = getString(R.string.feed_oldest_subscription_update, oldestSubscriptionUpdateText)
}
// /////////////////////////////////////////////////////////////////////////
From 83f33a7d1b45b4378de197f490542628a4413274 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 14:01:36 +0530
Subject: [PATCH 050/131] Use view binding in SubscriptionFragment.
---
.../subscription/SubscriptionFragment.kt | 48 ++++++++++---------
1 file changed, 26 insertions(+), 22 deletions(-)
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 64286dab3..c745ae39e 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
@@ -17,6 +17,7 @@ import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
+import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
@@ -29,11 +30,10 @@ import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.State
import io.reactivex.rxjava3.disposables.CompositeDisposable
-import kotlinx.android.synthetic.main.dialog_title.view.itemAdditionalDetails
-import kotlinx.android.synthetic.main.dialog_title.view.itemTitleView
-import kotlinx.android.synthetic.main.fragment_subscription.items_list
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
+import org.schabi.newpipe.databinding.DialogTitleBinding
+import org.schabi.newpipe.databinding.FragmentSubscriptionBinding
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.fragments.BaseStateFragment
import org.schabi.newpipe.local.subscription.SubscriptionViewModel.SubscriptionState
@@ -70,6 +70,9 @@ import kotlin.math.floor
import kotlin.math.max
class SubscriptionFragment : BaseStateFragment() {
+ private var _binding: FragmentSubscriptionBinding? = null
+ private val binding get() = _binding!!
+
private lateinit var viewModel: SubscriptionViewModel
private lateinit var subscriptionManager: SubscriptionManager
private val disposables: CompositeDisposable = CompositeDisposable()
@@ -129,7 +132,7 @@ class SubscriptionFragment : BaseStateFragment() {
override fun onPause() {
super.onPause()
- itemsListState = items_list.layoutManager?.onSaveInstanceState()
+ itemsListState = binding.itemsList.layoutManager?.onSaveInstanceState()
feedGroupsListState = feedGroupsCarousel?.onSaveInstanceState()
importExportItemExpandedState = importExportItem.isExpanded
@@ -169,7 +172,7 @@ class SubscriptionFragment : BaseStateFragment() {
filters.addAction(IMPORT_COMPLETE_ACTION)
subscriptionBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
- items_list?.post {
+ _binding?.itemsList?.post {
importExportItem.isExpanded = false
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
}
@@ -275,17 +278,18 @@ class SubscriptionFragment : BaseStateFragment() {
override fun initViews(rootView: View, savedInstanceState: Bundle?) {
super.initViews(rootView, savedInstanceState)
+ _binding = FragmentSubscriptionBinding.bind(rootView)
val shouldUseGridLayout = shouldUseGridLayout()
groupAdapter.spanCount = if (shouldUseGridLayout) getGridSpanCount() else 1
- items_list.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply {
+ binding.itemsList.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply {
spanSizeLookup = groupAdapter.spanSizeLookup
}
- items_list.adapter = groupAdapter
+ binding.itemsList.adapter = groupAdapter
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
- viewModel.stateLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { it?.let(this::handleResult) })
- viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { it?.let(this::handleFeedGroups) })
+ viewModel.stateLiveData.observe(viewLifecycleOwner, Observer { it?.let(this::handleResult) })
+ viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, Observer { it?.let(this::handleFeedGroups) })
}
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
@@ -301,16 +305,16 @@ class SubscriptionFragment : BaseStateFragment() {
}
}
- val bannerView = View.inflate(requireContext(), R.layout.dialog_title, null)
- bannerView.isSelected = true
- bannerView.itemTitleView.text = selectedItem.name
- bannerView.itemAdditionalDetails.visibility = View.GONE
+ val dialogTitleBinding = DialogTitleBinding.inflate(LayoutInflater.from(requireContext()))
+ dialogTitleBinding.root.isSelected = true
+ dialogTitleBinding.itemTitleView.text = selectedItem.name
+ dialogTitleBinding.itemAdditionalDetails.visibility = View.GONE
AlertDialog.Builder(requireContext())
- .setCustomTitle(bannerView)
- .setItems(commands, actions)
- .create()
- .show()
+ .setCustomTitle(dialogTitleBinding.root)
+ .setItems(commands, actions)
+ .create()
+ .show()
}
private fun deleteChannel(selectedItem: ChannelInfoItem) {
@@ -368,14 +372,14 @@ class SubscriptionFragment : BaseStateFragment() {
subscriptionsSection.setHideWhenEmpty(false)
if (result.subscriptions.isEmpty() && importExportItemExpandedState == null) {
- items_list.post {
+ binding.itemsList.post {
importExportItem.isExpanded = true
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
}
}
if (itemsListState != null) {
- items_list.layoutManager?.onRestoreInstanceState(itemsListState)
+ binding.itemsList.layoutManager?.onRestoreInstanceState(itemsListState)
itemsListState = null
}
}
@@ -394,7 +398,7 @@ class SubscriptionFragment : BaseStateFragment() {
}
feedGroupsSortMenuItem.showMenuItem = groups.size > 1
- items_list.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) }
+ binding.itemsList.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) }
}
// /////////////////////////////////////////////////////////////////////////
@@ -403,12 +407,12 @@ class SubscriptionFragment : BaseStateFragment() {
override fun showLoading() {
super.showLoading()
- animateView(items_list, false, 100)
+ animateView(binding.itemsList, false, 100)
}
override fun hideLoading() {
super.hideLoading()
- animateView(items_list, true, 200)
+ animateView(binding.itemsList, true, 200)
}
// /////////////////////////////////////////////////////////////////////////
From 5994cd8ea24c1216a771a99258bafc7ffd846675 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 14:15:43 +0530
Subject: [PATCH 051/131] Use view binding in FeedGroupDialog.
---
.../subscription/dialog/FeedGroupDialog.kt | 164 +++++++++---------
1 file changed, 85 insertions(+), 79 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
index 1d5650a99..8260f0fb9 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
@@ -24,10 +24,10 @@ import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
-import kotlinx.android.synthetic.main.dialog_feed_group_create.*
-import kotlinx.android.synthetic.main.toolbar_search_layout.*
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
+import org.schabi.newpipe.databinding.DialogFeedGroupCreateBinding
+import org.schabi.newpipe.databinding.ToolbarSearchLayoutBinding
import org.schabi.newpipe.fragments.BackPressable
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.DeleteScreen
@@ -45,6 +45,12 @@ import java.io.Serializable
import kotlin.collections.contains
class FeedGroupDialog : DialogFragment(), BackPressable {
+ private var _feedGroupCreateBinding: DialogFeedGroupCreateBinding? = null
+ private val feedGroupCreateBinding get() = _feedGroupCreateBinding!!
+
+ private var _searchLayoutBinding: ToolbarSearchLayoutBinding? = null
+ private val searchLayoutBinding get() = _searchLayoutBinding!!
+
private lateinit var viewModel: FeedGroupDialogViewModel
private var groupId: Long = NO_GROUP_SELECTED
private var groupIcon: FeedGroupIcon? = null
@@ -107,14 +113,16 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
- iconsListState = icon_selector.layoutManager?.onSaveInstanceState()
- subscriptionsListState = subscriptions_selector_list.layoutManager?.onSaveInstanceState()
+ iconsListState = feedGroupCreateBinding.iconSelector.layoutManager?.onSaveInstanceState()
+ subscriptionsListState = feedGroupCreateBinding.subscriptionsSelectorList.layoutManager?.onSaveInstanceState()
Icepick.saveInstanceState(this, outState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ _feedGroupCreateBinding = DialogFeedGroupCreateBinding.bind(view)
+ _searchLayoutBinding = feedGroupCreateBinding.subscriptionsHeaderSearchContainer
viewModel = ViewModelProvider(
this,
@@ -146,7 +154,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
add(subscriptionEmptyFooter)
spanCount = 4
}
- subscriptions_selector_list.apply {
+ feedGroupCreateBinding.subscriptionsSelectorList.apply {
// Disable animations, too distracting.
itemAnimator = null
adapter = subscriptionGroupAdapter
@@ -172,8 +180,11 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
override fun onDestroyView() {
super.onDestroyView()
- subscriptions_selector_list?.adapter = null
- icon_selector?.adapter = null
+ feedGroupCreateBinding.subscriptionsSelectorList.adapter = null
+ feedGroupCreateBinding.iconSelector.adapter = null
+
+ _feedGroupCreateBinding = null
+ _searchLayoutBinding = null
}
/*///////////////////////////////////////////////////////////////////////////
@@ -193,30 +204,30 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
}
private fun setupListeners() {
- delete_button.setOnClickListener { showScreen(DeleteScreen) }
+ feedGroupCreateBinding.deleteButton.setOnClickListener { showScreen(DeleteScreen) }
- cancel_button.setOnClickListener {
+ feedGroupCreateBinding.cancelButton.setOnClickListener {
when (currentScreen) {
InitialScreen -> dismiss()
else -> showScreen(InitialScreen)
}
}
- group_name_input_container.error = null
- group_name_input.doOnTextChanged { text, _, _, _ ->
- if (group_name_input_container.isErrorEnabled && !text.isNullOrBlank()) {
- group_name_input_container.error = null
+ feedGroupCreateBinding.groupNameInputContainer.error = null
+ feedGroupCreateBinding.groupNameInput.doOnTextChanged { text, _, _, _ ->
+ if (feedGroupCreateBinding.groupNameInputContainer.isErrorEnabled && !text.isNullOrBlank()) {
+ feedGroupCreateBinding.groupNameInputContainer.error = null
}
}
- confirm_button.setOnClickListener { handlePositiveButton() }
+ feedGroupCreateBinding.confirmButton.setOnClickListener { handlePositiveButton() }
- select_channel_button.setOnClickListener {
- subscriptions_selector_list.scrollToPosition(0)
+ feedGroupCreateBinding.selectChannelButton.setOnClickListener {
+ feedGroupCreateBinding.subscriptionsSelectorList.scrollToPosition(0)
showScreen(SubscriptionsPickerScreen)
}
- val headerMenu = subscriptions_header_toolbar.menu
+ val headerMenu = feedGroupCreateBinding.subscriptionsHeaderToolbar.menu
requireActivity().menuInflater.inflate(R.menu.menu_feed_group_dialog, headerMenu)
headerMenu.findItem(R.id.action_search).setOnMenuItemClickListener {
@@ -234,8 +245,8 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
}
}
- toolbar_search_clear.setOnClickListener {
- if (toolbar_search_edit_text.text.isEmpty()) {
+ searchLayoutBinding.toolbarSearchClear.setOnClickListener {
+ if (searchLayoutBinding.toolbarSearchEditText.text.isNullOrEmpty()) {
hideSearch()
return@setOnClickListener
}
@@ -243,14 +254,14 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
showKeyboardSearch()
}
- toolbar_search_edit_text.setOnClickListener {
+ searchLayoutBinding.toolbarSearchEditText.setOnClickListener {
if (DeviceUtils.isTv(context)) {
showKeyboardSearch()
}
}
- toolbar_search_edit_text.doOnTextChanged { _, _, _, _ ->
- val newQuery: String = toolbar_search_edit_text.text.toString()
+ searchLayoutBinding.toolbarSearchEditText.doOnTextChanged { _, _, _, _ ->
+ val newQuery: String = searchLayoutBinding.toolbarSearchEditText.text.toString()
subscriptionsCurrentSearchQuery = newQuery
viewModel.filterSubscriptionsBy(newQuery)
}
@@ -266,16 +277,16 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
}
private fun handlePositiveButtonInitialScreen() {
- val name = group_name_input.text.toString().trim()
+ val name = feedGroupCreateBinding.groupNameInput.text.toString().trim()
val icon = selectedIcon ?: groupIcon ?: FeedGroupIcon.ALL
if (name.isBlank()) {
- group_name_input_container.error = getString(R.string.feed_group_dialog_empty_name)
- group_name_input.text = null
- group_name_input.requestFocus()
+ feedGroupCreateBinding.groupNameInputContainer.error = getString(R.string.feed_group_dialog_empty_name)
+ feedGroupCreateBinding.groupNameInput.text = null
+ feedGroupCreateBinding.groupNameInput.requestFocus()
return
} else {
- group_name_input_container.error = null
+ feedGroupCreateBinding.groupNameInputContainer.error = null
}
if (selectedSubscriptions.isEmpty()) {
@@ -296,10 +307,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
groupSortOrder = feedGroupEntity?.sortOrder ?: -1
val feedGroupIcon = if (selectedIcon == null) icon else selectedIcon!!
- icon_preview.setImageResource(feedGroupIcon.getDrawableRes(requireContext()))
+ feedGroupCreateBinding.iconPreview.setImageResource(feedGroupIcon.getDrawableRes(requireContext()))
- if (group_name_input.text.isNullOrBlank()) {
- group_name_input.setText(name)
+ if (feedGroupCreateBinding.groupNameInput.text.isNullOrBlank()) {
+ feedGroupCreateBinding.groupNameInput.setText(name)
}
}
@@ -346,10 +357,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
subscriptionMainSection.update(subscriptions, false)
if (subscriptionsListState != null) {
- subscriptions_selector_list.layoutManager?.onRestoreInstanceState(subscriptionsListState)
+ feedGroupCreateBinding.subscriptionsSelectorList.layoutManager?.onRestoreInstanceState(subscriptionsListState)
subscriptionsListState = null
} else {
- subscriptions_selector_list.scrollToPosition(0)
+ feedGroupCreateBinding.subscriptionsSelectorList.scrollToPosition(0)
}
}
@@ -357,17 +368,16 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
val selectedCount = this.selectedSubscriptions.size
val selectedCountText = resources.getQuantityString(
R.plurals.feed_group_dialog_selection_count,
- selectedCount, selectedCount
- )
- selected_subscription_count_view.text = selectedCountText
- subscriptions_header_info.text = selectedCountText
+ selectedCount, selectedCount)
+ feedGroupCreateBinding.selectedSubscriptionCountView.text = selectedCountText
+ feedGroupCreateBinding.subscriptionsHeaderInfo.text = selectedCountText
}
private fun setupIconPicker() {
val groupAdapter = GroupAdapter()
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(requireContext(), it) })
- icon_selector.apply {
+ feedGroupCreateBinding.iconSelector.apply {
layoutManager = GridLayoutManager(requireContext(), 7, RecyclerView.VERTICAL, false)
adapter = groupAdapter
@@ -381,20 +391,20 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
when (item) {
is PickerIconItem -> {
selectedIcon = item.icon
- icon_preview.setImageResource(item.iconRes)
+ feedGroupCreateBinding.iconPreview.setImageResource(item.iconRes)
showScreen(InitialScreen)
}
}
}
- icon_preview.setOnClickListener {
- icon_selector.scrollToPosition(0)
+ feedGroupCreateBinding.iconPreview.setOnClickListener {
+ feedGroupCreateBinding.iconSelector.scrollToPosition(0)
showScreen(IconPickerScreen)
}
if (groupId == NO_GROUP_SELECTED) {
val icon = selectedIcon ?: FeedGroupIcon.ALL
- icon_preview.setImageResource(icon.getDrawableRes(requireContext()))
+ feedGroupCreateBinding.iconPreview.setImageResource(icon.getDrawableRes(requireContext()))
}
}
@@ -405,22 +415,20 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
private fun showScreen(screen: ScreenState) {
currentScreen = screen
- options_root.onlyVisibleIn(InitialScreen)
- icon_selector.onlyVisibleIn(IconPickerScreen)
- subscriptions_selector.onlyVisibleIn(SubscriptionsPickerScreen)
- delete_screen_message.onlyVisibleIn(DeleteScreen)
+ feedGroupCreateBinding.optionsRoot.onlyVisibleIn(InitialScreen)
+ feedGroupCreateBinding.iconSelector.onlyVisibleIn(IconPickerScreen)
+ feedGroupCreateBinding.subscriptionsSelector.onlyVisibleIn(SubscriptionsPickerScreen)
+ feedGroupCreateBinding.deleteScreenMessage.onlyVisibleIn(DeleteScreen)
- separator.onlyVisibleIn(SubscriptionsPickerScreen, IconPickerScreen)
- cancel_button.onlyVisibleIn(InitialScreen, DeleteScreen)
+ feedGroupCreateBinding.separator.onlyVisibleIn(SubscriptionsPickerScreen, IconPickerScreen)
+ feedGroupCreateBinding.cancelButton.onlyVisibleIn(InitialScreen, DeleteScreen)
- confirm_button.setText(
- when {
- currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED -> R.string.create
- else -> android.R.string.ok
- }
- )
+ feedGroupCreateBinding.confirmButton.setText(when {
+ currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED -> R.string.create
+ else -> android.R.string.ok
+ })
- delete_button.isGone = currentScreen != InitialScreen || groupId == NO_GROUP_SELECTED
+ feedGroupCreateBinding.deleteButton.isGone = currentScreen != InitialScreen || groupId == NO_GROUP_SELECTED
hideKeyboard()
hideSearch()
@@ -434,26 +442,26 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
// Utils
////////////////////////////////////////////////////////////////////////// */
- private fun isSearchVisible() = subscriptions_header_search_container?.visibility == View.VISIBLE
+ private fun isSearchVisible() = _searchLayoutBinding?.root?.visibility == View.VISIBLE
private fun resetSearch() {
- toolbar_search_edit_text.setText("")
+ searchLayoutBinding.toolbarSearchEditText.setText("")
subscriptionsCurrentSearchQuery = ""
viewModel.clearSubscriptionsFilter()
}
private fun hideSearch() {
resetSearch()
- subscriptions_header_search_container.visibility = View.GONE
- subscriptions_header_info_container.visibility = View.VISIBLE
- subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = true
+ searchLayoutBinding.root.visibility = View.GONE
+ feedGroupCreateBinding.subscriptionsHeaderInfoContainer.visibility = View.VISIBLE
+ feedGroupCreateBinding.subscriptionsHeaderToolbar.menu.findItem(R.id.action_search).isVisible = true
hideKeyboardSearch()
}
private fun showSearch() {
- subscriptions_header_search_container.visibility = View.VISIBLE
- subscriptions_header_info_container.visibility = View.GONE
- subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = false
+ searchLayoutBinding.root.visibility = View.VISIBLE
+ feedGroupCreateBinding.subscriptionsHeaderInfoContainer.visibility = View.GONE
+ feedGroupCreateBinding.subscriptionsHeaderToolbar.menu.findItem(R.id.action_search).isVisible = false
showKeyboardSearch()
}
@@ -462,37 +470,35 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
}
private fun showKeyboardSearch() {
- if (toolbar_search_edit_text.requestFocus()) {
- inputMethodManager.showSoftInput(toolbar_search_edit_text, InputMethodManager.SHOW_IMPLICIT)
+ if (searchLayoutBinding.toolbarSearchEditText.requestFocus()) {
+ inputMethodManager.showSoftInput(searchLayoutBinding.toolbarSearchEditText,
+ InputMethodManager.SHOW_IMPLICIT)
}
}
private fun hideKeyboardSearch() {
- inputMethodManager.hideSoftInputFromWindow(
- toolbar_search_edit_text.windowToken,
- InputMethodManager.RESULT_UNCHANGED_SHOWN
- )
- toolbar_search_edit_text.clearFocus()
+ inputMethodManager.hideSoftInputFromWindow(searchLayoutBinding.toolbarSearchEditText.windowToken,
+ InputMethodManager.RESULT_UNCHANGED_SHOWN)
+ searchLayoutBinding.toolbarSearchEditText.clearFocus()
}
private fun showKeyboard() {
- if (group_name_input.requestFocus()) {
- inputMethodManager.showSoftInput(group_name_input, InputMethodManager.SHOW_IMPLICIT)
+ if (feedGroupCreateBinding.groupNameInput.requestFocus()) {
+ inputMethodManager.showSoftInput(feedGroupCreateBinding.groupNameInput,
+ InputMethodManager.SHOW_IMPLICIT)
}
}
private fun hideKeyboard() {
- inputMethodManager.hideSoftInputFromWindow(
- group_name_input.windowToken,
- InputMethodManager.RESULT_UNCHANGED_SHOWN
- )
- group_name_input.clearFocus()
+ inputMethodManager.hideSoftInputFromWindow(feedGroupCreateBinding.groupNameInput.windowToken,
+ InputMethodManager.RESULT_UNCHANGED_SHOWN)
+ feedGroupCreateBinding.groupNameInput.clearFocus()
}
private fun disableInput() {
- delete_button?.isEnabled = false
- confirm_button?.isEnabled = false
- cancel_button?.isEnabled = false
+ _feedGroupCreateBinding?.deleteButton?.isEnabled = false
+ _feedGroupCreateBinding?.confirmButton?.isEnabled = false
+ _feedGroupCreateBinding?.cancelButton?.isEnabled = false
isCancelable = false
hideKeyboard()
From f04b5fd42f493df0dc6b35c8c45b3b498f4ef9f5 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Tue, 3 Nov 2020 14:17:24 +0530
Subject: [PATCH 052/131] Use view binding in FeedGroupReorderDialog.
---
.../dialog/FeedGroupReorderDialog.kt | 22 +++++++++++++------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt
index 380bf13f5..3b74ddc74 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt
@@ -16,10 +16,9 @@ import com.xwray.groupie.TouchCallback
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
-import kotlinx.android.synthetic.main.dialog_feed_group_reorder.confirm_button
-import kotlinx.android.synthetic.main.dialog_feed_group_reorder.feed_groups_list
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
+import org.schabi.newpipe.databinding.DialogFeedGroupReorderBinding
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.ProcessingEvent
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
@@ -27,6 +26,9 @@ import org.schabi.newpipe.util.ThemeHelper
import java.util.Collections
class FeedGroupReorderDialog : DialogFragment() {
+ private var _binding: DialogFeedGroupReorderBinding? = null
+ private val binding get() = _binding!!
+
private lateinit var viewModel: FeedGroupReorderDialogViewModel
@State
@@ -48,6 +50,7 @@ class FeedGroupReorderDialog : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ _binding = DialogFeedGroupReorderBinding.bind(view)
viewModel = ViewModelProvider(this).get(FeedGroupReorderDialogViewModel::class.java)
viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups))
@@ -61,15 +64,20 @@ class FeedGroupReorderDialog : DialogFragment() {
}
)
- feed_groups_list.layoutManager = LinearLayoutManager(requireContext())
- feed_groups_list.adapter = groupAdapter
- itemTouchHelper.attachToRecyclerView(feed_groups_list)
+ binding.feedGroupsList.layoutManager = LinearLayoutManager(requireContext())
+ binding.feedGroupsList.adapter = groupAdapter
+ itemTouchHelper.attachToRecyclerView(binding.feedGroupsList)
- confirm_button.setOnClickListener {
+ binding.confirmButton.setOnClickListener {
viewModel.updateOrder(groupOrderedIdList)
}
}
+ override fun onDestroyView() {
+ _binding = null
+ super.onDestroyView()
+ }
+
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Icepick.saveInstanceState(this, outState)
@@ -89,7 +97,7 @@ class FeedGroupReorderDialog : DialogFragment() {
}
private fun disableInput() {
- confirm_button?.isEnabled = false
+ _binding?.confirmButton?.isEnabled = false
isCancelable = false
}
From f6e2dd1480a71fa61d8a285912beb11c9169c069 Mon Sep 17 00:00:00 2001
From: Stypox
Date: Fri, 8 Jan 2021 18:35:33 +0100
Subject: [PATCH 053/131] Merge player classes into a single one
---
.../java/org/schabi/newpipe/MainActivity.java | 4 +-
.../fragments/detail/VideoDetailFragment.java | 28 +-
.../player/BackgroundPlayerActivity.java | 20 +-
.../org/schabi/newpipe/player/BasePlayer.java | 1615 -------
.../org/schabi/newpipe/player/MainPlayer.java | 101 +-
.../newpipe/player/NotificationUtil.java | 92 +-
.../org/schabi/newpipe/player/Player.java | 3973 +++++++++++++++++
.../newpipe/player/PlayerServiceBinder.java | 10 +-
.../newpipe/player/ServicePlayerActivity.java | 43 +-
.../schabi/newpipe/player/VideoPlayer.java | 1036 -----
.../newpipe/player/VideoPlayerImpl.java | 2076 ---------
.../player/event/BasePlayerGestureListener.kt | 116 +-
.../player/event/PlayerGestureListener.java | 135 +-
.../PlayerServiceExtendedEventListener.java | 4 +-
.../helper/PlaybackParameterDialog.java | 2 +-
.../newpipe/player/helper/PlayerHelper.java | 335 +-
.../newpipe/player/helper/PlayerHolder.java | 4 +-
.../mediasession/MediaSessionCallback.java | 10 +-
.../mediasession/PlayQueueNavigator.java | 6 +-
.../PlayQueuePlaybackController.java | 4 +-
...iaSession.java => PlayerMediaSession.java} | 35 +-
.../org/schabi/newpipe/util/Localization.java | 15 +
.../schabi/newpipe/util/NavigationHelper.java | 27 +-
app/src/main/res/values/settings_keys.xml | 6 +-
checkstyle-suppressions.xml | 2 +-
25 files changed, 4540 insertions(+), 5159 deletions(-)
delete mode 100644 app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
create mode 100644 app/src/main/java/org/schabi/newpipe/player/Player.java
delete mode 100644 app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
delete mode 100644 app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
rename app/src/main/java/org/schabi/newpipe/player/playback/{BasePlayerMediaSession.java => PlayerMediaSession.java} (77%)
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 0c784e9d5..b111af1fb 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -68,7 +68,7 @@ import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
-import org.schabi.newpipe.player.VideoPlayer;
+import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
@@ -763,7 +763,7 @@ public class MainActivity extends AppCompatActivity {
switch (linkType) {
case STREAM:
final String intentCacheKey = intent.getStringExtra(
- VideoPlayer.PLAY_QUEUE_KEY);
+ Player.PLAY_QUEUE_KEY);
final PlayQueue playQueue = intentCacheKey != null
? SerializedCache.getInstance()
.take(intentCacheKey, PlayQueue.class)
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 aeb51f63a..b9f4ef6a9 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
@@ -49,7 +49,6 @@ import androidx.viewpager.widget.ViewPager;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.tabs.TabLayout;
@@ -80,9 +79,8 @@ import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager;
-import org.schabi.newpipe.player.BasePlayer;
+import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.MainPlayer;
-import org.schabi.newpipe.player.VideoPlayerImpl;
import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
import org.schabi.newpipe.player.helper.PlayerHelper;
@@ -255,14 +253,14 @@ public final class VideoDetailFragment
private ContentObserver settingsContentObserver;
private MainPlayer playerService;
- private VideoPlayerImpl player;
+ private Player player;
/*//////////////////////////////////////////////////////////////////////////
// Service management
//////////////////////////////////////////////////////////////////////////*/
@Override
- public void onServiceConnected(final VideoPlayerImpl connectedPlayer,
+ public void onServiceConnected(final Player connectedPlayer,
final MainPlayer connectedPlayerService,
final boolean playAfterConnect) {
player = connectedPlayer;
@@ -539,7 +537,7 @@ public final class VideoDetailFragment
break;
case R.id.overlay_play_pause_button:
if (playerIsNotStopped()) {
- player.onPlayPause();
+ player.playPause();
player.hideControls(0, 0);
showSystemUi();
} else {
@@ -805,7 +803,7 @@ public final class VideoDetailFragment
// If we are in fullscreen mode just exit from it via first back press
if (player != null && player.isFullscreen()) {
if (!DeviceUtils.isTablet(activity)) {
- player.onPause();
+ player.pause();
}
restoreDefaultOrientation();
setAutoPlay(false);
@@ -850,7 +848,7 @@ public final class VideoDetailFragment
final PlayQueueItem playQueueItem = item.getPlayQueue().getItem();
// Update title, url, uploader from the last item in the stack (it's current now)
- final boolean isPlayerStopped = player == null || player.isPlayerStopped();
+ final boolean isPlayerStopped = player == null || player.isStopped();
if (playQueueItem != null && isPlayerStopped) {
updateOverlayData(playQueueItem.getTitle(),
playQueueItem.getUploader(), playQueueItem.getThumbnailUrl());
@@ -1569,7 +1567,7 @@ public final class VideoDetailFragment
showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, detailMetaInfoSeparator);
- if (player == null || player.isPlayerStopped()) {
+ if (player == null || player.isStopped()) {
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());
}
@@ -1797,7 +1795,7 @@ public final class VideoDetailFragment
setOverlayPlayPauseImage(player != null && player.isPlaying());
switch (state) {
- case BasePlayer.STATE_PLAYING:
+ case Player.STATE_PLAYING:
if (positionView.getAlpha() != 1.0f
&& player.getPlayQueue() != null
&& player.getPlayQueue().getItem() != null
@@ -1814,7 +1812,7 @@ public final class VideoDetailFragment
final int duration,
final int bufferPercent) {
// Progress updates every second even if media is paused. It's useless until playing
- if (!player.getPlayer().isPlaying() || playQueue == null) {
+ if (!player.isPlaying() || playQueue == null) {
return;
}
@@ -2020,9 +2018,7 @@ public final class VideoDetailFragment
}
private boolean playerIsNotStopped() {
- return player != null
- && player.getPlayer() != null
- && player.getPlayer().getPlaybackState() != Player.STATE_IDLE;
+ return player != null && !player.isStopped();
}
private void restoreDefaultBrightness() {
@@ -2073,7 +2069,7 @@ public final class VideoDetailFragment
player.checkLandscape();
// Let's give a user time to look at video information page if video is not playing
if (globalScreenOrientationLocked(activity) && !player.isPlaying()) {
- player.onPlay();
+ player.play();
}
}
@@ -2287,7 +2283,7 @@ public final class VideoDetailFragment
// Re-enable clicks
setOverlayElementsClickable(true);
if (player != null) {
- player.onQueueClosed();
+ player.closeQueue();
}
setOverlayLook(appBarLayout, behavior, 0);
break;
diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java
index 2fc710fb0..eb1c74dad 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java
@@ -26,15 +26,15 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
@Override
public void startPlayerListener() {
- if (player instanceof VideoPlayerImpl) {
- ((VideoPlayerImpl) player).setActivityListener(this);
+ if (player != null) {
+ player.setActivityListener(this);
}
}
@Override
public void stopPlayerListener() {
- if (player instanceof VideoPlayerImpl) {
- ((VideoPlayerImpl) player).removeActivityListener(this);
+ if (player != null) {
+ player.removeActivityListener(this);
}
}
@@ -45,13 +45,11 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
@Override
public void setupMenu(final Menu menu) {
- if (player == null) {
- return;
+ if (player != null) {
+ menu.findItem(R.id.action_switch_popup)
+ .setVisible(!player.popupPlayerSelected());
+ menu.findItem(R.id.action_switch_background)
+ .setVisible(!player.audioPlayerSelected());
}
-
- menu.findItem(R.id.action_switch_popup)
- .setVisible(!((VideoPlayerImpl) player).popupPlayerSelected());
- menu.findItem(R.id.action_switch_background)
- .setVisible(!((VideoPlayerImpl) player).audioPlayerSelected());
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
deleted file mode 100644
index ea16574e9..000000000
--- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
+++ /dev/null
@@ -1,1615 +0,0 @@
-/*
- * Copyright 2017 Mauricio Colli
- * BasePlayer.java is part of NewPipe
- *
- * License: GPL-3.0+
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.schabi.newpipe.player;
-
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.AudioManager;
-import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.preference.PreferenceManager;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.DefaultRenderersFactory;
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.LoadControl;
-import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.RenderersFactory;
-import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.source.BehindLiveWindowException;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.TrackGroupArray;
-import com.google.android.exoplayer2.trackselection.TrackSelection;
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
-import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
-import com.nostra13.universalimageloader.core.ImageLoader;
-import com.nostra13.universalimageloader.core.assist.FailReason;
-import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.disposables.SerialDisposable;
-import java.io.IOException;
-import org.schabi.newpipe.DownloaderImpl;
-import org.schabi.newpipe.MainActivity;
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.stream.StreamInfo;
-import org.schabi.newpipe.local.history.HistoryRecordManager;
-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.playback.BasePlayerMediaSession;
-import org.schabi.newpipe.player.playback.CustomTrackSelector;
-import org.schabi.newpipe.player.playback.MediaSourceManager;
-import org.schabi.newpipe.player.playback.PlaybackListener;
-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.resolver.MediaSourceTag;
-import org.schabi.newpipe.util.ImageDisplayConstants;
-import org.schabi.newpipe.util.SerializedCache;
-
-/**
- * Base for the players, joining the common properties.
- *
- * @author mauriciocolli
- */
-@SuppressWarnings({"WeakerAccess"})
-public abstract class BasePlayer implements
- Player.EventListener, PlaybackListener, ImageLoadingListener {
- public static final boolean DEBUG = MainActivity.DEBUG;
- @NonNull
- public static final String TAG = "BasePlayer";
-
- public static final int STATE_PREFLIGHT = -1;
- public static final int STATE_BLOCKED = 123;
- public static final int STATE_PLAYING = 124;
- public static final int STATE_BUFFERING = 125;
- public static final int STATE_PAUSED = 126;
- public static final int STATE_PAUSED_SEEK = 127;
- public static final int STATE_COMPLETED = 128;
-
- /*//////////////////////////////////////////////////////////////////////////
- // Intent
- //////////////////////////////////////////////////////////////////////////*/
-
- @NonNull
- public static final String REPEAT_MODE = "repeat_mode";
- @NonNull
- public static final String PLAYBACK_QUALITY = "playback_quality";
- @NonNull
- public static final String PLAY_QUEUE_KEY = "play_queue_key";
- @NonNull
- public static final String APPEND_ONLY = "append_only";
- @NonNull
- public static final String RESUME_PLAYBACK = "resume_playback";
- @NonNull
- public static final String PLAY_WHEN_READY = "play_when_ready";
- @NonNull
- public static final String SELECT_ON_APPEND = "select_on_append";
- @NonNull
- public static final String PLAYER_TYPE = "player_type";
- @NonNull
- public static final String IS_MUTED = "is_muted";
-
- /*//////////////////////////////////////////////////////////////////////////
- // Playback
- //////////////////////////////////////////////////////////////////////////*/
-
- protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
-
- protected PlayQueue playQueue;
- protected PlayQueueAdapter playQueueAdapter;
-
- @Nullable
- protected MediaSourceManager playbackManager;
-
- @Nullable
- private PlayQueueItem currentItem;
- @Nullable
- private MediaSourceTag currentMetadata;
- @Nullable
- private Bitmap currentThumbnail;
-
- @Nullable
- protected Toast errorToast;
-
- /*//////////////////////////////////////////////////////////////////////////
- // Player
- //////////////////////////////////////////////////////////////////////////*/
-
- protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds
- protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
-
- public static final int PLAYER_TYPE_VIDEO = 0;
- public static final int PLAYER_TYPE_AUDIO = 1;
- public static final int PLAYER_TYPE_POPUP = 2;
-
- protected SimpleExoPlayer simpleExoPlayer;
- protected AudioReactor audioReactor;
- protected MediaSessionManager mediaSessionManager;
-
-
- @NonNull
- protected final Context context;
- @NonNull
- protected final BroadcastReceiver broadcastReceiver;
- @NonNull
- protected final IntentFilter intentFilter;
- @NonNull
- protected final HistoryRecordManager recordManager;
- @NonNull
- protected final SharedPreferences sharedPreferences;
- @NonNull
- protected final CustomTrackSelector trackSelector;
- @NonNull
- protected final PlayerDataSource dataSource;
- @NonNull
- private final LoadControl loadControl;
-
- @NonNull
- private final RenderersFactory renderFactory;
- @NonNull
- private final SerialDisposable progressUpdateReactor;
- @NonNull
- private final CompositeDisposable databaseUpdateReactor;
-
- private boolean isPrepared = false;
- private Disposable stateLoader;
-
- protected int currentState = STATE_PREFLIGHT;
-
- public BasePlayer(@NonNull final Context context) {
- this.context = context;
-
- this.broadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(final Context ctx, final Intent intent) {
- onBroadcastReceived(intent);
- }
- };
- this.intentFilter = new IntentFilter();
- setupBroadcastReceiver(intentFilter);
-
- this.recordManager = new HistoryRecordManager(context);
- this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
-
- this.progressUpdateReactor = new SerialDisposable();
- this.databaseUpdateReactor = new CompositeDisposable();
-
- final String userAgent = DownloaderImpl.USER_AGENT;
- final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(context)
- .build();
- this.dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter);
-
- final TrackSelection.Factory trackSelectionFactory = PlayerHelper
- .getQualitySelector();
- this.trackSelector = new CustomTrackSelector(context, trackSelectionFactory);
-
- this.loadControl = new LoadController();
- this.renderFactory = new DefaultRenderersFactory(context);
- }
-
- public void initPlayer(final boolean playOnReady) {
- if (DEBUG) {
- Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]");
- }
-
- simpleExoPlayer = new SimpleExoPlayer.Builder(context, renderFactory)
- .setTrackSelector(trackSelector)
- .setLoadControl(loadControl)
- .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 BasePlayerMediaSession(this));
-
- registerBroadcastReceiver();
- }
-
- public void initListeners() {
- }
-
- public void handleIntent(final Intent intent) {
- if (DEBUG) {
- Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
- }
- if (intent == null) {
- return;
- }
-
- // Resolve play queue
- if (!intent.hasExtra(PLAY_QUEUE_KEY)) {
- return;
- }
- final String intentCacheKey = intent.getStringExtra(PLAY_QUEUE_KEY);
- final PlayQueue queue = SerializedCache.getInstance().take(intentCacheKey, PlayQueue.class);
- if (queue == null) {
- return;
- }
-
- // Resolve append intents
- if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) {
- final int sizeBeforeAppend = playQueue.size();
- playQueue.append(queue.getStreams());
-
- if ((intent.getBooleanExtra(SELECT_ON_APPEND, false)
- || getCurrentState() == STATE_COMPLETED) && queue.getStreams().size() > 0) {
- playQueue.setIndex(sizeBeforeAppend);
- }
-
- return;
- }
-
- final PlaybackParameters savedParameters = retrievePlaybackParametersFromPreferences();
- final float playbackSpeed = savedParameters.speed;
- final float playbackPitch = savedParameters.pitch;
- final boolean playbackSkipSilence = savedParameters.skipSilence;
-
- final boolean samePlayQueue = playQueue != null && playQueue.equals(queue);
-
- final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
- final boolean playWhenReady = intent.getBooleanExtra(PLAY_WHEN_READY, true);
- final boolean isMuted = intent
- .getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted());
-
- /*
- * There are 3 situations when playback shouldn't be started from scratch (zero timestamp):
- * 1. User pressed on a timestamp link and the same video should be rewound to the timestamp
- * 2. User changed a player from, for example. main to popup, or from audio to main, etc
- * 3. User chose to resume a video based on a saved timestamp from history of played videos
- * In those cases time will be saved because re-init of the play queue is a not an instant
- * task and requires network calls
- * */
- // seek to timestamp if stream is already playing
- if (simpleExoPlayer != null
- && queue.size() == 1
- && playQueue != null
- && playQueue.size() == 1
- && playQueue.getItem() != null
- && queue.getItem().getUrl().equals(playQueue.getItem().getUrl())
- && queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
- // Player can have state = IDLE when playback is stopped or failed
- // and we should retry() in this case
- if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
- simpleExoPlayer.retry();
- }
- simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
- simpleExoPlayer.setPlayWhenReady(playWhenReady);
-
- } else if (simpleExoPlayer != null
- && samePlayQueue
- && playQueue != null
- && !playQueue.isDisposed()) {
- // Do not re-init the same PlayQueue. Save time
- // Player can have state = IDLE when playback is stopped or failed
- // and we should retry() in this case
- if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
- simpleExoPlayer.retry();
- }
- simpleExoPlayer.setPlayWhenReady(playWhenReady);
-
- } else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
- && isPlaybackResumeEnabled()
- && !samePlayQueue
- && !queue.isEmpty()
- && queue.getItem().getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
- stateLoader = recordManager.loadStreamState(queue.getItem())
- .observeOn(AndroidSchedulers.mainThread())
- // Do not place initPlayback() in doFinally() because
- // it restarts playback after destroy()
- //.doFinally()
- .subscribe(
- state -> {
- queue.setRecovery(queue.getIndex(), state.getProgressTime());
- initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
- playbackSkipSilence, playWhenReady, isMuted);
- },
- error -> {
- if (DEBUG) {
- error.printStackTrace();
- }
- // In case any error we can start playback without history
- initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
- playbackSkipSilence, playWhenReady, isMuted);
- },
- () -> {
- // Completed but not found in history
- initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
- playbackSkipSilence, playWhenReady, isMuted);
- }
- );
- databaseUpdateReactor.add(stateLoader);
- } else {
- // Good to go...
- // In a case of equal PlayQueues we can re-init old one but only when it is disposed
- initPlayback(samePlayQueue ? playQueue : queue, repeatMode, playbackSpeed,
- playbackPitch, playbackSkipSilence, playWhenReady, isMuted);
- }
- }
-
- private PlaybackParameters retrievePlaybackParametersFromPreferences() {
- final SharedPreferences preferences =
- PreferenceManager.getDefaultSharedPreferences(context);
-
- final float speed = preferences.getFloat(
- context.getString(R.string.playback_speed_key), getPlaybackSpeed());
- final float pitch = preferences.getFloat(
- context.getString(R.string.playback_pitch_key), getPlaybackPitch());
- final boolean skipSilence = preferences.getBoolean(
- context.getString(R.string.playback_skip_silence_key), getPlaybackSkipSilence());
- return new PlaybackParameters(speed, pitch, skipSilence);
- }
-
- protected void initPlayback(@NonNull final PlayQueue queue,
- @Player.RepeatMode final int repeatMode,
- final float playbackSpeed,
- final float playbackPitch,
- final boolean playbackSkipSilence,
- final boolean playOnReady,
- final boolean isMuted) {
- destroyPlayer();
- initPlayer(playOnReady);
- setRepeatMode(repeatMode);
- setPlaybackParameters(playbackSpeed, playbackPitch, playbackSkipSilence);
-
- playQueue = queue;
- playQueue.init();
- if (playbackManager != null) {
- playbackManager.dispose();
- }
- playbackManager = new MediaSourceManager(this, playQueue);
-
- if (playQueueAdapter != null) {
- playQueueAdapter.dispose();
- }
- playQueueAdapter = new PlayQueueAdapter(context, playQueue);
-
- simpleExoPlayer.setVolume(isMuted ? 0 : 1);
- }
-
- public void destroyPlayer() {
- if (DEBUG) {
- Log.d(TAG, "destroyPlayer() called");
- }
- if (simpleExoPlayer != null) {
- simpleExoPlayer.removeListener(this);
- simpleExoPlayer.stop();
- simpleExoPlayer.release();
- }
- if (isProgressLoopRunning()) {
- stopProgressLoop();
- }
- if (playQueue != null) {
- playQueue.dispose();
- }
- if (audioReactor != null) {
- audioReactor.dispose();
- }
- if (playbackManager != null) {
- playbackManager.dispose();
- }
- if (mediaSessionManager != null) {
- mediaSessionManager.dispose();
- }
- if (stateLoader != null) {
- stateLoader.dispose();
- }
-
- if (playQueueAdapter != null) {
- playQueueAdapter.unsetSelectedListener();
- playQueueAdapter.dispose();
- }
- }
-
- public void destroy() {
- if (DEBUG) {
- Log.d(TAG, "destroy() called");
- }
- destroyPlayer();
- unregisterBroadcastReceiver();
-
- databaseUpdateReactor.clear();
- progressUpdateReactor.set(null);
- ImageLoader.getInstance().stop();
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Thumbnail Loading
- //////////////////////////////////////////////////////////////////////////*/
-
- private void initThumbnail(final String url) {
- if (DEBUG) {
- Log.d(TAG, "Thumbnail - initThumbnail() called");
- }
- if (url == null || url.isEmpty()) {
- return;
- }
- ImageLoader.getInstance().resume();
- ImageLoader.getInstance()
- .loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, this);
- }
-
- @Override
- public void onLoadingStarted(final String imageUri, final View view) {
- if (DEBUG) {
- Log.d(TAG, "Thumbnail - onLoadingStarted() called on: "
- + "imageUri = [" + imageUri + "], view = [" + view + "]");
- }
- }
-
- @Override
- public void onLoadingFailed(final String imageUri, final View view,
- final FailReason failReason) {
- Log.e(TAG, "Thumbnail - onLoadingFailed() called on imageUri = [" + imageUri + "]",
- failReason.getCause());
- currentThumbnail = null;
- }
-
- @Override
- public void onLoadingComplete(final String imageUri, final View view,
- final Bitmap loadedImage) {
- final float width = Math.min(
- context.getResources().getDimension(R.dimen.player_notification_thumbnail_width),
- loadedImage.getWidth());
- currentThumbnail = Bitmap.createScaledBitmap(loadedImage,
- (int) width,
- (int) (loadedImage.getHeight() / (loadedImage.getWidth() / width)), true);
- if (DEBUG) {
- Log.d(TAG, "Thumbnail - onLoadingComplete() called with: "
- + "imageUri = [" + imageUri + "], view = [" + view + "], "
- + "loadedImage = [" + loadedImage + "], "
- + loadedImage.getWidth() + "x" + loadedImage.getHeight()
- + ", scaled width = " + width);
- }
- }
-
- @Override
- public void onLoadingCancelled(final String imageUri, final View view) {
- if (DEBUG) {
- Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: "
- + "imageUri = [" + imageUri + "], view = [" + view + "]");
- }
- currentThumbnail = null;
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Broadcast Receiver
- //////////////////////////////////////////////////////////////////////////*/
-
- /**
- * Add your action in the intentFilter.
- *
- * @param intentFltr intent filter that will be used for register the receiver
- */
- protected void setupBroadcastReceiver(final IntentFilter intentFltr) {
- intentFltr.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
- }
-
- public void onBroadcastReceived(final Intent intent) {
- if (intent == null || intent.getAction() == null) {
- return;
- }
- switch (intent.getAction()) {
- case AudioManager.ACTION_AUDIO_BECOMING_NOISY:
- onPause();
- break;
- }
- }
-
- protected void registerBroadcastReceiver() {
- // Try to unregister current first
- unregisterBroadcastReceiver();
- context.registerReceiver(broadcastReceiver, intentFilter);
- }
-
- protected void unregisterBroadcastReceiver() {
- try {
- context.unregisterReceiver(broadcastReceiver);
- } catch (final IllegalArgumentException unregisteredException) {
- Log.w(TAG, "Broadcast receiver already unregistered "
- + "(" + unregisteredException.getMessage() + ")");
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // States Implementation
- //////////////////////////////////////////////////////////////////////////*/
-
- public void changeState(final int state) {
- if (DEBUG) {
- Log.d(TAG, "changeState() called with: state = [" + state + "]");
- }
- currentState = state;
- switch (state) {
- case STATE_BLOCKED:
- onBlocked();
- break;
- case STATE_PLAYING:
- onPlaying();
- break;
- case STATE_BUFFERING:
- onBuffering();
- break;
- case STATE_PAUSED:
- onPaused();
- break;
- case STATE_PAUSED_SEEK:
- onPausedSeek();
- break;
- case STATE_COMPLETED:
- onCompleted();
- break;
- }
- }
-
- public void onBlocked() {
- if (DEBUG) {
- Log.d(TAG, "onBlocked() called");
- }
- if (!isProgressLoopRunning()) {
- startProgressLoop();
- }
- }
-
- public void onPlaying() {
- if (DEBUG) {
- Log.d(TAG, "onPlaying() called");
- }
- if (!isProgressLoopRunning()) {
- startProgressLoop();
- }
- }
-
- public void onBuffering() {
- }
-
- public void onPaused() {
- if (isProgressLoopRunning()) {
- stopProgressLoop();
- }
- }
-
- public void onPausedSeek() {
- }
-
- public void onCompleted() {
- if (DEBUG) {
- Log.d(TAG, "onCompleted() called");
- }
- if (playQueue.getIndex() < playQueue.size() - 1) {
- playQueue.offsetIndex(+1);
- }
- if (isProgressLoopRunning()) {
- stopProgressLoop();
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Repeat and shuffle
- //////////////////////////////////////////////////////////////////////////*/
-
- public void onRepeatClicked() {
- if (DEBUG) {
- Log.d(TAG, "onRepeatClicked() called");
- }
-
- final int mode;
-
- switch (getRepeatMode()) {
- case Player.REPEAT_MODE_OFF:
- mode = Player.REPEAT_MODE_ONE;
- break;
- case Player.REPEAT_MODE_ONE:
- mode = Player.REPEAT_MODE_ALL;
- break;
- case Player.REPEAT_MODE_ALL:
- default:
- mode = Player.REPEAT_MODE_OFF;
- break;
- }
-
- setRepeatMode(mode);
- if (DEBUG) {
- Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + getRepeatMode());
- }
- }
-
- public void onShuffleClicked() {
- if (DEBUG) {
- Log.d(TAG, "onShuffleClicked() called");
- }
-
- if (simpleExoPlayer == null) {
- return;
- }
- simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled());
- }
- /*//////////////////////////////////////////////////////////////////////////
- // Mute / Unmute
- //////////////////////////////////////////////////////////////////////////*/
-
- public void onMuteUnmuteButtonClicked() {
- if (DEBUG) {
- Log.d(TAG, "onMuteUnmuteButtonClicked() called");
- }
- simpleExoPlayer.setVolume(isMuted() ? 1 : 0);
- }
-
- public boolean isMuted() {
- return simpleExoPlayer.getVolume() == 0;
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Progress Updates
- //////////////////////////////////////////////////////////////////////////*/
-
- public abstract void onUpdateProgress(int currentProgress, int duration, int bufferPercent);
-
- protected void startProgressLoop() {
- progressUpdateReactor.set(getProgressReactor());
- }
-
- protected void stopProgressLoop() {
- progressUpdateReactor.set(null);
- }
-
- public void triggerProgressUpdate() {
- if (simpleExoPlayer == null) {
- return;
- }
- onUpdateProgress(
- Math.max((int) simpleExoPlayer.getCurrentPosition(), 0),
- (int) simpleExoPlayer.getDuration(),
- simpleExoPlayer.getBufferedPercentage()
- );
- }
-
- private Disposable getProgressReactor() {
- return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS,
- AndroidSchedulers.mainThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(ignored -> triggerProgressUpdate(),
- error -> Log.e(TAG, "Progress update failure: ", error));
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // ExoPlayer Listener
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onTimelineChanged(final Timeline timeline, final int reason) {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - onTimelineChanged() called with "
- + "timeline size = [" + timeline.getWindowCount() + "], "
- + "reason = [" + reason + "]");
- }
-
- maybeUpdateCurrentMetadata();
- }
-
- @Override
- public void onTracksChanged(final TrackGroupArray trackGroups,
- final TrackSelectionArray trackSelections) {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - onTracksChanged(), "
- + "track group size = " + trackGroups.length);
- }
-
- maybeUpdateCurrentMetadata();
- }
-
- @Override
- public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - playbackParameters(), "
- + "speed: " + playbackParameters.speed + ", "
- + "pitch: " + playbackParameters.pitch);
- }
- }
-
- @Override
- public void onLoadingChanged(final boolean isLoading) {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - onLoadingChanged() called with: "
- + "isLoading = [" + isLoading + "]");
- }
-
- if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning()) {
- stopProgressLoop();
- } else if (isLoading && !isProgressLoopRunning()) {
- startProgressLoop();
- }
-
- maybeUpdateCurrentMetadata();
- }
-
- @Override
- public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - onPlayerStateChanged() called with: "
- + "playWhenReady = [" + playWhenReady + "], "
- + "playbackState = [" + playbackState + "]");
- }
-
- if (getCurrentState() == STATE_PAUSED_SEEK) {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - onPlayerStateChanged() is currently blocked");
- }
- return;
- }
-
- switch (playbackState) {
- case Player.STATE_IDLE: // 1
- isPrepared = false;
- break;
- case Player.STATE_BUFFERING: // 2
- if (isPrepared) {
- changeState(STATE_BUFFERING);
- }
- break;
- case Player.STATE_READY: //3
- maybeUpdateCurrentMetadata();
- maybeCorrectSeekPosition();
- if (!isPrepared) {
- isPrepared = true;
- onPrepared(playWhenReady);
- break;
- }
- changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
- break;
- case Player.STATE_ENDED: // 4
- changeState(STATE_COMPLETED);
- if (currentMetadata != null) {
- resetPlaybackState(currentMetadata.getMetadata());
- }
- isPrepared = false;
- break;
- }
- }
-
- private void maybeCorrectSeekPosition() {
- if (playQueue == null || simpleExoPlayer == null || currentMetadata == null) {
- return;
- }
-
- final PlayQueueItem currentSourceItem = playQueue.getItem();
- if (currentSourceItem == null) {
- return;
- }
-
- final StreamInfo currentInfo = currentMetadata.getMetadata();
- final long presetStartPositionMillis = currentInfo.getStartPosition() * 1000;
- if (presetStartPositionMillis > 0L) {
- // Has another start position?
- if (DEBUG) {
- Log.d(TAG, "Playback - Seeking to preset start "
- + "position=[" + presetStartPositionMillis + "]");
- }
- seekTo(presetStartPositionMillis);
- }
- }
-
- /**
- * Process exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}.
- *
{@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}:
- * If a runtime error occurred, then we can try to recover it by restarting the playback
- * after setting the timestamp recovery.
- *
{@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER}:
- * If the renderer failed, treat the error as unrecoverable.
- *
- *
- * @see #processSourceError(IOException)
- * @see Player.EventListener#onPlayerError(ExoPlaybackException)
- */
- @Override
- public void onPlayerError(final ExoPlaybackException error) {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - onPlayerError() called with: " + "error = [" + error + "]");
- }
- if (errorToast != null) {
- errorToast.cancel();
- errorToast = null;
- }
-
- savePlaybackState();
-
- switch (error.type) {
- case ExoPlaybackException.TYPE_SOURCE:
- processSourceError(error.getSourceException());
- showStreamError(error);
- break;
- case ExoPlaybackException.TYPE_UNEXPECTED:
- showRecoverableError(error);
- setRecovery();
- reload();
- break;
- default:
- showUnrecoverableError(error);
- onPlaybackShutdown();
- break;
- }
- }
-
- private void processSourceError(final IOException error) {
- if (simpleExoPlayer == null || playQueue == null) {
- return;
- }
- setRecovery();
-
- if (error instanceof BehindLiveWindowException) {
- reload();
- } else {
- playQueue.error();
- }
- }
-
- @Override
- public void onPositionDiscontinuity(@Player.DiscontinuityReason final int reason) {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with "
- + "reason = [" + reason + "]");
- }
- if (playQueue == null) {
- return;
- }
-
- // Refresh the playback if there is a transition to the next video
- final int newWindowIndex = simpleExoPlayer.getCurrentWindowIndex();
- switch (reason) {
- case DISCONTINUITY_REASON_PERIOD_TRANSITION:
- // When player is in single repeat mode and a period transition occurs,
- // we need to register a view count here since no metadata has changed
- if (getRepeatMode() == Player.REPEAT_MODE_ONE
- && newWindowIndex == playQueue.getIndex()) {
- registerView();
- break;
- }
- case DISCONTINUITY_REASON_SEEK:
- case DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
- case DISCONTINUITY_REASON_INTERNAL:
- if (playQueue.getIndex() != newWindowIndex) {
- resetPlaybackState(playQueue.getItem());
- playQueue.setIndex(newWindowIndex);
- }
- break;
- }
-
- maybeUpdateCurrentMetadata();
- }
-
- @Override
- public void onRepeatModeChanged(@Player.RepeatMode final int reason) {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: "
- + "mode = [" + reason + "]");
- }
- }
-
- @Override
- public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - onShuffleModeEnabledChanged() called with: "
- + "mode = [" + shuffleModeEnabled + "]");
- }
- if (playQueue == null) {
- return;
- }
- if (shuffleModeEnabled) {
- playQueue.shuffle();
- } else {
- playQueue.unshuffle();
- }
- }
-
- @Override
- public void onSeekProcessed() {
- if (DEBUG) {
- Log.d(TAG, "ExoPlayer - onSeekProcessed() called");
- }
- if (isPrepared) {
- savePlaybackState();
- }
- }
- /*//////////////////////////////////////////////////////////////////////////
- // Playback Listener
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public boolean isApproachingPlaybackEdge(final long timeToEndMillis) {
- // If live, then not near playback edge
- // If not playing, then not approaching playback edge
- if (simpleExoPlayer == null || isLive() || !isPlaying()) {
- return false;
- }
-
- final long currentPositionMillis = simpleExoPlayer.getCurrentPosition();
- final long currentDurationMillis = simpleExoPlayer.getDuration();
- return currentDurationMillis - currentPositionMillis < timeToEndMillis;
- }
-
- @Override
- public void onPlaybackBlock() {
- if (simpleExoPlayer == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Playback - onPlaybackBlock() called");
- }
-
- currentItem = null;
- currentMetadata = null;
- simpleExoPlayer.stop();
- isPrepared = false;
-
- changeState(STATE_BLOCKED);
- }
-
- @Override
- public void onPlaybackUnblock(final MediaSource mediaSource) {
- if (simpleExoPlayer == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Playback - onPlaybackUnblock() called");
- }
-
- if (getCurrentState() == STATE_BLOCKED) {
- changeState(STATE_BUFFERING);
- }
-
- simpleExoPlayer.prepare(mediaSource);
- }
-
- public void onPlaybackSynchronize(@NonNull final PlayQueueItem item) {
- if (DEBUG) {
- Log.d(TAG, "Playback - onPlaybackSynchronize() called with "
- + "item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]");
- }
- if (simpleExoPlayer == null || playQueue == null) {
- return;
- }
-
- final boolean onPlaybackInitial = currentItem == null;
- final boolean hasPlayQueueItemChanged = currentItem != item;
-
- final int currentPlayQueueIndex = playQueue.indexOf(item);
- final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex();
- 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() + "]");
-
- // 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 (currentPlaylistIndex != currentPlayQueueIndex || onPlaybackInitial
- || !isPlaying()) {
- if (DEBUG) {
- Log.d(TAG, "Playback - Rewinding to correct "
- + "index=[" + currentPlayQueueIndex + "], "
- + "from=[" + currentPlaylistIndex + "], "
- + "size=[" + currentPlaylistSize + "].");
- }
-
- if (item.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
- simpleExoPlayer.seekTo(currentPlayQueueIndex, item.getRecoveryPosition());
- playQueue.unsetRecovery(currentPlayQueueIndex);
- } else {
- simpleExoPlayer.seekToDefaultPosition(currentPlayQueueIndex);
- }
- }
- }
-
- protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
- final StreamInfo info = tag.getMetadata();
- if (DEBUG) {
- Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName());
- }
-
- initThumbnail(info.getThumbnailUrl());
- registerView();
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // General Player
- //////////////////////////////////////////////////////////////////////////*/
-
- public void showStreamError(final Exception exception) {
- exception.printStackTrace();
-
- if (errorToast == null) {
- errorToast = Toast
- .makeText(context, R.string.player_stream_failure, Toast.LENGTH_SHORT);
- errorToast.show();
- }
- }
-
- public void showRecoverableError(final Exception exception) {
- exception.printStackTrace();
-
- if (errorToast == null) {
- errorToast = Toast
- .makeText(context, R.string.player_recoverable_failure, Toast.LENGTH_SHORT);
- errorToast.show();
- }
- }
-
- public void showUnrecoverableError(final Exception exception) {
- exception.printStackTrace();
-
- if (errorToast != null) {
- errorToast.cancel();
- }
- errorToast = Toast
- .makeText(context, R.string.player_unrecoverable_failure, Toast.LENGTH_SHORT);
- errorToast.show();
- }
-
- public void onPrepared(final boolean playWhenReady) {
- if (DEBUG) {
- Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
- }
- if (playWhenReady) {
- audioReactor.requestAudioFocus();
- }
- changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
- }
-
- public void onPlay() {
- if (DEBUG) {
- Log.d(TAG, "onPlay() called");
- }
- if (audioReactor == null || playQueue == null || simpleExoPlayer == null) {
- return;
- }
-
- audioReactor.requestAudioFocus();
-
- if (getCurrentState() == STATE_COMPLETED) {
- if (playQueue.getIndex() == 0) {
- seekToDefault();
- } else {
- playQueue.setIndex(0);
- }
- }
-
- simpleExoPlayer.setPlayWhenReady(true);
- savePlaybackState();
- }
-
- public void onPause() {
- if (DEBUG) {
- Log.d(TAG, "onPause() called");
- }
- if (audioReactor == null || simpleExoPlayer == null) {
- return;
- }
-
- audioReactor.abandonAudioFocus();
- simpleExoPlayer.setPlayWhenReady(false);
- savePlaybackState();
- }
-
- public void onPlayPause() {
- if (DEBUG) {
- Log.d(TAG, "onPlayPause() called");
- }
-
- if (isPlaying()) {
- onPause();
- } else {
- onPlay();
- }
- }
-
- public void onFastRewind() {
- if (DEBUG) {
- Log.d(TAG, "onFastRewind() called");
- }
- seekBy(-getSeekDuration());
- triggerProgressUpdate();
- }
-
- public void onFastForward() {
- if (DEBUG) {
- Log.d(TAG, "onFastForward() called");
- }
- seekBy(getSeekDuration());
- triggerProgressUpdate();
- }
-
- private int getSeekDuration() {
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- final String key = context.getString(R.string.seek_duration_key);
- final String value = prefs
- .getString(key, context.getString(R.string.seek_duration_default_value));
- return Integer.parseInt(value);
- }
-
- public void onPlayPrevious() {
- if (simpleExoPlayer == null || playQueue == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "onPlayPrevious() called");
- }
-
- /* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT_MILLIS milliseconds,
- * restart current track. Also restart the track if the current track
- * is the first in a queue.*/
- if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT_MILLIS
- || playQueue.getIndex() == 0) {
- seekToDefault();
- playQueue.offsetIndex(0);
- } else {
- savePlaybackState();
- playQueue.offsetIndex(-1);
- }
- }
-
- public void onPlayNext() {
- if (playQueue == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "onPlayNext() called");
- }
-
- savePlaybackState();
- playQueue.offsetIndex(+1);
- }
-
- public void onSelected(final PlayQueueItem item) {
- if (playQueue == null || simpleExoPlayer == null) {
- return;
- }
-
- final int index = playQueue.indexOf(item);
- if (index == -1) {
- return;
- }
-
- if (playQueue.getIndex() == index && simpleExoPlayer.getCurrentWindowIndex() == index) {
- seekToDefault();
- } else {
- savePlaybackState();
- }
- playQueue.setIndex(index);
- }
-
- public void seekTo(final long positionMillis) {
- if (DEBUG) {
- Log.d(TAG, "seekBy() called with: position = [" + positionMillis + "]");
- }
- if (simpleExoPlayer != null) {
- // 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);
- }
- }
-
- public void seekBy(final long offsetMillis) {
- if (DEBUG) {
- Log.d(TAG, "seekBy() called with: offsetMillis = [" + offsetMillis + "]");
- }
- seekTo(simpleExoPlayer.getCurrentPosition() + offsetMillis);
- }
-
- public boolean isCurrentWindowValid() {
- return simpleExoPlayer != null && simpleExoPlayer.getDuration() >= 0
- && simpleExoPlayer.getCurrentPosition() >= 0;
- }
-
- public void seekToDefault() {
- if (simpleExoPlayer != null) {
- simpleExoPlayer.seekToDefaultPosition();
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Utils
- //////////////////////////////////////////////////////////////////////////*/
-
- private void registerView() {
- if (currentMetadata == null) {
- return;
- }
- final StreamInfo currentInfo = currentMetadata.getMetadata();
- final Disposable viewRegister = recordManager.onViewed(currentInfo).onErrorComplete()
- .subscribe(
- ignored -> { /* successful */ },
- error -> Log.e(TAG, "Player onViewed() failure: ", error)
- );
- databaseUpdateReactor.add(viewRegister);
- }
-
- protected void reload() {
- if (playbackManager != null) {
- playbackManager.dispose();
- }
-
- if (playQueue != null) {
- playbackManager = new MediaSourceManager(this, playQueue);
- }
- }
-
- private void savePlaybackState(final StreamInfo info, final long progress) {
- if (info == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "savePlaybackState() called");
- }
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) {
- final Disposable stateSaver = recordManager.saveStreamState(info, progress)
- .observeOn(AndroidSchedulers.mainThread())
- .doOnError((e) -> {
- if (DEBUG) {
- e.printStackTrace();
- }
- })
- .onErrorComplete()
- .subscribe();
- databaseUpdateReactor.add(stateSaver);
- }
- }
-
- private void resetPlaybackState(final PlayQueueItem queueItem) {
- if (queueItem == null) {
- return;
- }
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) {
- final Disposable stateSaver = queueItem.getStream()
- .flatMapCompletable(info -> recordManager.saveStreamState(info, 0))
- .observeOn(AndroidSchedulers.mainThread())
- .doOnError((e) -> {
- if (DEBUG) {
- e.printStackTrace();
- }
- })
- .onErrorComplete()
- .subscribe();
- databaseUpdateReactor.add(stateSaver);
- }
- }
-
- public void resetPlaybackState(final StreamInfo info) {
- savePlaybackState(info, 0);
- }
-
- public void savePlaybackState() {
- if (simpleExoPlayer == null || currentMetadata == null) {
- return;
- }
- final StreamInfo currentInfo = currentMetadata.getMetadata();
- if (playQueue != null) {
- // Save current position. It will help to restore this position once a user
- // wants to play prev or next stream from the queue
- playQueue.setRecovery(playQueue.getIndex(), simpleExoPlayer.getContentPosition());
- }
- savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
- }
-
- private void maybeUpdateCurrentMetadata() {
- if (simpleExoPlayer == null) {
- return;
- }
-
- final MediaSourceTag metadata;
- try {
- metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
- } catch (IndexOutOfBoundsException | ClassCastException error) {
- if (DEBUG) {
- Log.d(TAG, "Could not update metadata: " + error.getMessage());
- error.printStackTrace();
- }
- return;
- }
-
- if (metadata == null) {
- return;
- }
- maybeAutoQueueNextStream(metadata);
-
- if (currentMetadata == metadata) {
- return;
- }
- currentMetadata = metadata;
- onMetadataChanged(metadata);
- }
-
- private void maybeAutoQueueNextStream(@NonNull final MediaSourceTag metadata) {
- if (playQueue == null || playQueue.getIndex() != playQueue.size() - 1
- || getRepeatMode() != Player.REPEAT_MODE_OFF
- || !PlayerHelper.isAutoQueueEnabled(context)) {
- return;
- }
- // auto queue when starting playback on the last item when not repeating
- final PlayQueue autoQueue = PlayerHelper.autoQueueOf(metadata.getMetadata(),
- playQueue.getStreams());
- if (autoQueue != null) {
- playQueue.append(autoQueue.getStreams());
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Getters and Setters
- //////////////////////////////////////////////////////////////////////////*/
-
- public SimpleExoPlayer getPlayer() {
- return simpleExoPlayer;
- }
-
- public AudioReactor getAudioReactor() {
- return audioReactor;
- }
-
- public int getCurrentState() {
- return currentState;
- }
-
- @Nullable
- public MediaSourceTag getCurrentMetadata() {
- return currentMetadata;
- }
-
- @NonNull
- public LoadController getLoadController() {
- return (LoadController) loadControl;
- }
-
- @NonNull
- public String getVideoUrl() {
- return currentMetadata == null
- ? context.getString(R.string.unknown_content)
- : currentMetadata.getMetadata().getUrl();
- }
-
- @NonNull
- public String getVideoTitle() {
- return currentMetadata == null
- ? context.getString(R.string.unknown_content)
- : currentMetadata.getMetadata().getName();
- }
-
- @NonNull
- public String getUploaderName() {
- return currentMetadata == null
- ? context.getString(R.string.unknown_content)
- : currentMetadata.getMetadata().getUploaderName();
- }
-
- @Nullable
- public Bitmap getThumbnail() {
- return currentThumbnail == null
- ? BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_thumbnail)
- : currentThumbnail;
- }
-
- /**
- * Checks if the current playback is a livestream AND is playing at or beyond the live edge.
- *
- * @return whether the livestream is playing at or beyond the edge
- */
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- public boolean isLiveEdge() {
- if (simpleExoPlayer == null || !isLive()) {
- return false;
- }
-
- final Timeline currentTimeline = simpleExoPlayer.getCurrentTimeline();
- final int currentWindowIndex = simpleExoPlayer.getCurrentWindowIndex();
- if (currentTimeline.isEmpty() || currentWindowIndex < 0
- || currentWindowIndex >= currentTimeline.getWindowCount()) {
- return false;
- }
-
- final Timeline.Window timelineWindow = new Timeline.Window();
- currentTimeline.getWindow(currentWindowIndex, timelineWindow);
- return timelineWindow.getDefaultPositionMs() <= simpleExoPlayer.getCurrentPosition();
- }
-
- public boolean isLive() {
- if (simpleExoPlayer == null) {
- return false;
- }
- try {
- return simpleExoPlayer.isCurrentWindowDynamic();
- } catch (@NonNull final IndexOutOfBoundsException e) {
- // Why would this even happen =(
- // But lets log it anyway. Save is save
- if (DEBUG) {
- Log.d(TAG, "Could not update metadata: " + e.getMessage());
- e.printStackTrace();
- }
- return false;
- }
- }
-
- public boolean isPlaying() {
- return simpleExoPlayer != null && simpleExoPlayer.isPlaying();
- }
-
- public boolean isLoading() {
- return simpleExoPlayer != null && simpleExoPlayer.isLoading();
- }
-
- @Player.RepeatMode
- public int getRepeatMode() {
- return simpleExoPlayer == null
- ? Player.REPEAT_MODE_OFF
- : simpleExoPlayer.getRepeatMode();
- }
-
- public void setRepeatMode(@Player.RepeatMode final int repeatMode) {
- if (simpleExoPlayer != null) {
- simpleExoPlayer.setRepeatMode(repeatMode);
- }
- }
-
- public float getPlaybackSpeed() {
- return getPlaybackParameters().speed;
- }
-
- public void setPlaybackSpeed(final float speed) {
- setPlaybackParameters(speed, getPlaybackPitch(), getPlaybackSkipSilence());
- }
-
- public float getPlaybackPitch() {
- return getPlaybackParameters().pitch;
- }
-
- public boolean getPlaybackSkipSilence() {
- return getPlaybackParameters().skipSilence;
- }
-
- public PlaybackParameters getPlaybackParameters() {
- if (simpleExoPlayer == null) {
- return PlaybackParameters.DEFAULT;
- }
- return simpleExoPlayer.getPlaybackParameters();
- }
-
- /**
- * Sets the playback parameters of the player, and also saves them to shared preferences.
- * Speed and pitch are rounded up to 2 decimal places before being used or saved.
- *
- * @param speed the playback speed, will be rounded to up to 2 decimal places
- * @param pitch the playback pitch, will be rounded to up to 2 decimal places
- * @param skipSilence skip silence during playback
- */
- public void setPlaybackParameters(final float speed, final float pitch,
- final boolean skipSilence) {
- final float roundedSpeed = Math.round(speed * 100.0f) / 100.0f;
- final float roundedPitch = Math.round(pitch * 100.0f) / 100.0f;
-
- savePlaybackParametersToPreferences(roundedSpeed, roundedPitch, skipSilence);
- simpleExoPlayer.setPlaybackParameters(
- new PlaybackParameters(roundedSpeed, roundedPitch, skipSilence));
- }
-
- private void savePlaybackParametersToPreferences(final float speed, final float pitch,
- final boolean skipSilence) {
- PreferenceManager.getDefaultSharedPreferences(context)
- .edit()
- .putFloat(context.getString(R.string.playback_speed_key), speed)
- .putFloat(context.getString(R.string.playback_pitch_key), pitch)
- .putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
- .apply();
- }
-
- public PlayQueue getPlayQueue() {
- return playQueue;
- }
-
- public PlayQueueAdapter getPlayQueueAdapter() {
- return playQueueAdapter;
- }
-
- public boolean isPrepared() {
- return isPrepared;
- }
-
- public boolean isProgressLoopRunning() {
- return progressUpdateReactor.get() != null;
- }
-
- public void setRecovery() {
- if (playQueue == null || simpleExoPlayer == null) {
- return;
- }
-
- final int queuePos = playQueue.getIndex();
- final long windowPos = simpleExoPlayer.getCurrentPosition();
-
- if (windowPos > 0 && windowPos <= simpleExoPlayer.getDuration()) {
- setRecovery(queuePos, windowPos);
- }
- }
-
- public void setRecovery(final int queuePos, final long windowPos) {
- if (playQueue.size() <= queuePos) {
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos);
- }
- playQueue.setRecovery(queuePos, windowPos);
- }
-
- public boolean gotDestroyed() {
- return simpleExoPlayer == null;
- }
-
- private boolean isPlaybackResumeEnabled() {
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- return prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)
- && prefs.getBoolean(context.getString(R.string.enable_playback_resume_key), true);
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
index fc9f110e6..c4099e67e 100644
--- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
@@ -48,9 +48,9 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
*/
public final class MainPlayer extends Service {
private static final String TAG = "MainPlayer";
- private static final boolean DEBUG = BasePlayer.DEBUG;
+ private static final boolean DEBUG = Player.DEBUG;
- private VideoPlayerImpl playerImpl;
+ private Player player;
private WindowManager windowManager;
private final IBinder mBinder = new MainPlayer.LocalBinder();
@@ -69,8 +69,6 @@ public final class MainPlayer extends Service {
= App.PACKAGE_NAME + ".player.MainPlayer.CLOSE";
static final String ACTION_PLAY_PAUSE
= App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE";
- static final String ACTION_OPEN_CONTROLS
- = App.PACKAGE_NAME + ".player.MainPlayer.OPEN_CONTROLS";
static final String ACTION_REPEAT
= App.PACKAGE_NAME + ".player.MainPlayer.REPEAT";
static final String ACTION_PLAY_NEXT
@@ -105,11 +103,10 @@ public final class MainPlayer extends Service {
private void createView() {
final PlayerBinding binding = PlayerBinding.inflate(LayoutInflater.from(this));
- playerImpl = new VideoPlayerImpl(this);
- playerImpl.setup(binding);
- playerImpl.shouldUpdateOnProgress = true;
+ player = new Player(this);
+ player.setupFromView(binding);
- NotificationUtil.getInstance().createNotificationAndStartForeground(playerImpl, this);
+ NotificationUtil.getInstance().createNotificationAndStartForeground(player, this);
}
@Override
@@ -119,19 +116,19 @@ public final class MainPlayer extends Service {
+ "], flags = [" + flags + "], startId = [" + startId + "]");
}
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
- && playerImpl.playQueue == null) {
+ && 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())
- || intent.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY) != null) {
- NotificationUtil.getInstance().createNotificationAndStartForeground(playerImpl, this);
+ || intent.getStringExtra(Player.PLAY_QUEUE_KEY) != null) {
+ NotificationUtil.getInstance().createNotificationAndStartForeground(player, this);
}
- playerImpl.handleIntent(intent);
- if (playerImpl.mediaSessionManager != null) {
- playerImpl.mediaSessionManager.handleMediaButtonIntent(intent);
+ player.handleIntent(intent);
+ if (player.getMediaSessionManager() != null) {
+ player.getMediaSessionManager().handleMediaButtonIntent(intent);
}
return START_NOT_STICKY;
}
@@ -141,20 +138,20 @@ public final class MainPlayer extends Service {
Log.d(TAG, "stop() called");
}
- if (playerImpl.getPlayer() != null) {
- playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady();
+ if (!player.exoPlayerIsNull()) {
+ player.saveWasPlaying();
// Releases wifi & cpu, disables keepScreenOn, etc.
if (!autoplayEnabled) {
- playerImpl.onPause();
+ player.pause();
}
// We can't just pause the player here because it will make transition
// from one stream to a new stream not smooth
- playerImpl.getPlayer().stop(false);
- playerImpl.setRecovery();
+ 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)
- playerImpl.hideControls(0, 0);
- playerImpl.onQueueClosed();
+ player.hideControls(0, 0);
+ player.closeQueue();
// 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.
@@ -168,7 +165,7 @@ public final class MainPlayer extends Service {
@Override
public void onTaskRemoved(final Intent rootIntent) {
super.onTaskRemoved(rootIntent);
- if (!playerImpl.videoPlayerSelected()) {
+ if (!player.videoPlayerSelected()) {
return;
}
onDestroy();
@@ -181,7 +178,23 @@ public final class MainPlayer extends Service {
if (DEBUG) {
Log.d(TAG, "destroy() called");
}
- onClose();
+
+ 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();
+ }
+
+ NotificationUtil.getInstance().cancelNotificationAndStopForeground(this);
+ stopSelf();
}
@Override
@@ -194,32 +207,6 @@ public final class MainPlayer extends Service {
return mBinder;
}
- /*//////////////////////////////////////////////////////////////////////////
- // Actions
- //////////////////////////////////////////////////////////////////////////*/
- private void onClose() {
- if (DEBUG) {
- Log.d(TAG, "onClose() called");
- }
-
- if (playerImpl != null) {
- // Exit from fullscreen when user closes the player via notification
- if (playerImpl.isFullscreen()) {
- playerImpl.toggleFullscreen();
- }
- removeViewFromParent();
-
- playerImpl.setRecovery();
- playerImpl.savePlaybackState();
- playerImpl.stopActivityBinding();
- playerImpl.removePopupFromView();
- playerImpl.destroy();
- }
-
- NotificationUtil.getInstance().cancelNotificationAndStopForeground(this);
- stopSelf();
- }
-
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@@ -227,25 +214,25 @@ public final class MainPlayer extends Service {
boolean isLandscape() {
// DisplayMetrics from activity context knows about MultiWindow feature
// while DisplayMetrics from app context doesn't
- final DisplayMetrics metrics = (playerImpl != null
- && playerImpl.getParentActivity() != null
- ? playerImpl.getParentActivity().getResources()
+ final DisplayMetrics metrics = (player != null
+ && player.getParentActivity() != null
+ ? player.getParentActivity().getResources()
: getResources()).getDisplayMetrics();
return metrics.heightPixels < metrics.widthPixels;
}
@Nullable
public View getView() {
- if (playerImpl == null) {
+ if (player == null) {
return null;
}
- return playerImpl.getRootView();
+ return player.getRootView();
}
public void removeViewFromParent() {
if (getView() != null && getView().getParent() != null) {
- if (playerImpl.getParentActivity() != null) {
+ if (player.getParentActivity() != null) {
// This means view was added to fragment
final ViewGroup parent = (ViewGroup) getView().getParent();
parent.removeView(getView());
@@ -263,8 +250,8 @@ public final class MainPlayer extends Service {
return MainPlayer.this;
}
- public VideoPlayerImpl getPlayer() {
- return MainPlayer.this.playerImpl;
+ public Player getPlayer() {
+ return MainPlayer.this.player;
}
}
}
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 c1c2e4eba..43c1b4405 100644
--- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java
@@ -43,7 +43,7 @@ import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE;
*/
public final class NotificationUtil {
private static final String TAG = NotificationUtil.class.getSimpleName();
- private static final boolean DEBUG = BasePlayer.DEBUG;
+ private static final boolean DEBUG = Player.DEBUG;
private static final int NOTIFICATION_ID = 123789;
@Nullable private static NotificationUtil instance = null;
@@ -76,7 +76,7 @@ public final class NotificationUtil {
* @param forceRecreate whether to force the recreation of the notification even if it already
* exists
*/
- synchronized void createNotificationIfNeededAndUpdate(final VideoPlayerImpl player,
+ synchronized void createNotificationIfNeededAndUpdate(final Player player,
final boolean forceRecreate) {
if (forceRecreate || notificationBuilder == null) {
notificationBuilder = createNotification(player);
@@ -85,14 +85,14 @@ public final class NotificationUtil {
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
- private synchronized NotificationCompat.Builder createNotification(
- final VideoPlayerImpl player) {
+ private synchronized NotificationCompat.Builder createNotification(final Player player) {
if (DEBUG) {
Log.d(TAG, "createNotification()");
}
- notificationManager = NotificationManagerCompat.from(player.context);
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(player.context,
- player.context.getString(R.string.notification_channel_id));
+ notificationManager = NotificationManagerCompat.from(player.getContext());
+ final NotificationCompat.Builder builder =
+ new NotificationCompat.Builder(player.getContext(),
+ player.getContext().getString(R.string.notification_channel_id));
initializeNotificationSlots(player);
@@ -107,25 +107,25 @@ public final class NotificationUtil {
// build the compact slot indices array (need code to convert from Integer... because Java)
final List compactSlotList = NotificationConstants.getCompactSlotsFromPreferences(
- player.context, player.sharedPreferences, nonNothingSlotCount);
+ 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);
}
builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
- .setMediaSession(player.mediaSessionManager.getSessionToken())
+ .setMediaSession(player.getMediaSessionManager().getSessionToken())
.setShowActionsInCompactView(compactSlots))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCategory(NotificationCompat.CATEGORY_TRANSPORT)
.setShowWhen(false)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
- .setColor(ContextCompat.getColor(player.context, R.color.dark_background_color))
- .setColorized(player.sharedPreferences.getBoolean(
- player.context.getString(R.string.notification_colorize_key),
- true))
- .setDeleteIntent(PendingIntent.getBroadcast(player.context, NOTIFICATION_ID,
+ .setColor(ContextCompat.getColor(player.getContext(),
+ R.color.dark_background_color))
+ .setColorized(player.getPrefs().getBoolean(
+ player.getContext().getString(R.string.notification_colorize_key), true))
+ .setDeleteIntent(PendingIntent.getBroadcast(player.getContext(), NOTIFICATION_ID,
new Intent(ACTION_CLOSE), FLAG_UPDATE_CURRENT));
return builder;
@@ -135,20 +135,20 @@ 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 VideoPlayerImpl player) {
+ private synchronized void updateNotification(final Player player) {
if (DEBUG) {
Log.d(TAG, "updateNotification()");
}
// also update content intent, in case the user switched players
- notificationBuilder.setContentIntent(PendingIntent.getActivity(player.context,
+ notificationBuilder.setContentIntent(PendingIntent.getActivity(player.getContext(),
NOTIFICATION_ID, getIntentForNotification(player), FLAG_UPDATE_CURRENT));
notificationBuilder.setContentTitle(player.getVideoTitle());
notificationBuilder.setContentText(player.getUploaderName());
notificationBuilder.setTicker(player.getVideoTitle());
updateActions(notificationBuilder, player);
- final boolean showThumbnail = player.sharedPreferences.getBoolean(
- player.context.getString(R.string.show_thumbnail_key), true);
+ final boolean showThumbnail = player.getPrefs().getBoolean(
+ player.getContext().getString(R.string.show_thumbnail_key), true);
if (showThumbnail) {
setLargeIcon(notificationBuilder, player);
}
@@ -174,7 +174,7 @@ public final class NotificationUtil {
}
- void createNotificationAndStartForeground(final VideoPlayerImpl player, final Service service) {
+ void createNotificationAndStartForeground(final Player player, final Service service) {
if (notificationBuilder == null) {
notificationBuilder = createNotification(player);
}
@@ -203,17 +203,16 @@ public final class NotificationUtil {
// ACTIONS
/////////////////////////////////////////////////////
- private void initializeNotificationSlots(final VideoPlayerImpl player) {
+ private void initializeNotificationSlots(final Player player) {
for (int i = 0; i < 5; ++i) {
- notificationSlots[i] = player.sharedPreferences.getInt(
- player.context.getString(NotificationConstants.SLOT_PREF_KEYS[i]),
+ notificationSlots[i] = player.getPrefs().getInt(
+ player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
NotificationConstants.SLOT_DEFAULTS[i]);
}
}
@SuppressLint("RestrictedApi")
- private void updateActions(final NotificationCompat.Builder builder,
- final VideoPlayerImpl player) {
+ private void updateActions(final NotificationCompat.Builder builder, final Player player) {
builder.mActions.clear();
for (int i = 0; i < 5; ++i) {
addAction(builder, player, notificationSlots[i]);
@@ -221,7 +220,7 @@ public final class NotificationUtil {
}
private void addAction(final NotificationCompat.Builder builder,
- final VideoPlayerImpl player,
+ final Player player,
@NotificationConstants.Action final int slot) {
final NotificationCompat.Action action = getAction(player, slot);
if (action != null) {
@@ -231,7 +230,7 @@ public final class NotificationUtil {
@Nullable
private NotificationCompat.Action getAction(
- final VideoPlayerImpl player,
+ final Player player,
@NotificationConstants.Action final int selectedAction) {
final int baseActionIcon = NotificationConstants.ACTION_ICONS[selectedAction];
switch (selectedAction) {
@@ -252,7 +251,7 @@ public final class NotificationUtil {
R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD);
case NotificationConstants.SMART_REWIND_PREVIOUS:
- if (player.playQueue != null && player.playQueue.size() > 1) {
+ if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) {
return getAction(player, R.drawable.exo_notification_previous,
R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS);
} else {
@@ -261,7 +260,7 @@ public final class NotificationUtil {
}
case NotificationConstants.SMART_FORWARD_NEXT:
- if (player.playQueue != null && player.playQueue.size() > 1) {
+ if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) {
return getAction(player, R.drawable.exo_notification_next,
R.string.exo_controls_next_description, ACTION_PLAY_NEXT);
} else {
@@ -270,23 +269,23 @@ public final class NotificationUtil {
}
case NotificationConstants.PLAY_PAUSE_BUFFERING:
- if (player.getCurrentState() == BasePlayer.STATE_PREFLIGHT
- || player.getCurrentState() == BasePlayer.STATE_BLOCKED
- || player.getCurrentState() == BasePlayer.STATE_BUFFERING) {
+ if (player.getCurrentState() == Player.STATE_PREFLIGHT
+ || player.getCurrentState() == Player.STATE_BLOCKED
+ || player.getCurrentState() == Player.STATE_BUFFERING) {
// null intent -> show hourglass icon that does nothing when clicked
return new NotificationCompat.Action(R.drawable.ic_hourglass_top_white_24dp_png,
- player.context.getString(R.string.notification_action_buffering),
+ player.getContext().getString(R.string.notification_action_buffering),
null);
}
case NotificationConstants.PLAY_PAUSE:
- if (player.getCurrentState() == BasePlayer.STATE_COMPLETED) {
+ if (player.getCurrentState() == Player.STATE_COMPLETED) {
return getAction(player, R.drawable.ic_replay_white_24dp_png,
R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE);
} else if (player.isPlaying()
- || player.getCurrentState() == BasePlayer.STATE_PREFLIGHT
- || player.getCurrentState() == BasePlayer.STATE_BLOCKED
- || player.getCurrentState() == BasePlayer.STATE_BUFFERING) {
+ || player.getCurrentState() == Player.STATE_PREFLIGHT
+ || player.getCurrentState() == Player.STATE_BLOCKED
+ || player.getCurrentState() == Player.STATE_BUFFERING) {
return getAction(player, R.drawable.exo_notification_pause,
R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE);
} else {
@@ -307,7 +306,7 @@ public final class NotificationUtil {
}
case NotificationConstants.SHUFFLE:
- if (player.playQueue != null && player.playQueue.isShuffled()) {
+ if (player.getPlayQueue() != null && player.getPlayQueue().isShuffled()) {
return getAction(player, R.drawable.exo_controls_shuffle_on,
R.string.exo_controls_shuffle_on_description, ACTION_SHUFFLE);
} else {
@@ -326,23 +325,23 @@ public final class NotificationUtil {
}
}
- private NotificationCompat.Action getAction(final VideoPlayerImpl player,
+ private NotificationCompat.Action getAction(final Player player,
@DrawableRes final int drawable,
@StringRes final int title,
final String intentAction) {
- return new NotificationCompat.Action(drawable, player.context.getString(title),
- PendingIntent.getBroadcast(player.context, NOTIFICATION_ID,
+ return new NotificationCompat.Action(drawable, player.getContext().getString(title),
+ PendingIntent.getBroadcast(player.getContext(), NOTIFICATION_ID,
new Intent(intentAction), FLAG_UPDATE_CURRENT));
}
- private Intent getIntentForNotification(final VideoPlayerImpl player) {
+ private Intent getIntentForNotification(final Player player) {
if (player.audioPlayerSelected() || player.popupPlayerSelected()) {
// Means we play in popup or audio only. Let's show the play queue
- return NavigationHelper.getPlayQueueActivityIntent(player.context);
+ return NavigationHelper.getPlayQueueActivityIntent(player.getContext());
} else {
// We are playing in fragment. Don't open another activity just show fragment. That's it
final Intent intent = NavigationHelper.getPlayerIntent(
- player.context, MainActivity.class, null, true);
+ player.getContext(), MainActivity.class, null, true);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
@@ -355,10 +354,9 @@ public final class NotificationUtil {
// BITMAP
/////////////////////////////////////////////////////
- private void setLargeIcon(final NotificationCompat.Builder builder,
- final VideoPlayerImpl player) {
- final boolean scaleImageToSquareAspectRatio = player.sharedPreferences.getBoolean(
- player.context.getString(R.string.scale_to_square_image_in_notifications_key),
+ private void setLargeIcon(final NotificationCompat.Builder builder, final Player player) {
+ 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()));
diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java
new file mode 100644
index 000000000..490a0a693
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/Player.java
@@ -0,0 +1,3973 @@
+package org.schabi.newpipe.player;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+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.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.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+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.PopupMenu;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.content.res.AppCompatResources;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.DisplayCutoutCompat;
+import androidx.core.view.ViewCompat;
+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.ExoPlaybackException;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.RenderersFactory;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.source.BehindLiveWindowException;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.TrackGroup;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.text.CaptionStyleCompat;
+import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
+import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
+import com.google.android.exoplayer2.ui.SubtitleView;
+import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
+import com.google.android.exoplayer2.video.VideoListener;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.assist.FailReason;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
+
+import org.schabi.newpipe.DownloaderImpl;
+import org.schabi.newpipe.MainActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.PlayerBinding;
+import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
+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.OnScrollBelowItemsListener;
+import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
+import org.schabi.newpipe.local.history.HistoryRecordManager;
+import org.schabi.newpipe.player.MainPlayer.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.PlaybackParameterDialog;
+import org.schabi.newpipe.player.helper.PlayerDataSource;
+import org.schabi.newpipe.player.helper.PlayerHelper;
+import org.schabi.newpipe.player.playback.CustomTrackSelector;
+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.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.MediaSourceTag;
+import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
+import org.schabi.newpipe.util.AnimationUtils;
+import org.schabi.newpipe.util.DeviceUtils;
+import org.schabi.newpipe.util.ImageDisplayConstants;
+import org.schabi.newpipe.util.KoreUtil;
+import org.schabi.newpipe.util.ListHelper;
+import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.SerializedCache;
+import org.schabi.newpipe.util.ShareUtils;
+import org.schabi.newpipe.views.ExpandableSurfaceView;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.disposables.SerialDisposable;
+
+import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AD_INSERTION;
+import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
+import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
+import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
+import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
+import static com.google.android.exoplayer2.Player.DiscontinuityReason;
+import static com.google.android.exoplayer2.Player.EventListener;
+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 java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.schabi.newpipe.extractor.ServiceList.YouTube;
+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.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.AnimationUtils.Type.SLIDE_AND_ALPHA;
+import static org.schabi.newpipe.util.AnimationUtils.animateRotation;
+import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
+import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
+import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+import static org.schabi.newpipe.util.Localization.containsCaseInsensitive;
+
+public final class Player implements
+ EventListener,
+ PlaybackListener,
+ ImageLoadingListener,
+ VideoListener,
+ SeekBar.OnSeekBarChangeListener,
+ View.OnClickListener,
+ PopupMenu.OnMenuItemClickListener,
+ PopupMenu.OnDismissListener,
+ View.OnLongClickListener {
+ public static final boolean DEBUG = MainActivity.DEBUG;
+ public static final String TAG = Player.class.getSimpleName();
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // States
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static final int STATE_PREFLIGHT = -1;
+ public static final int STATE_BLOCKED = 123;
+ public static final int STATE_PLAYING = 124;
+ public static final int STATE_BUFFERING = 125;
+ public static final int STATE_PAUSED = 126;
+ public static final int STATE_PAUSED_SEEK = 127;
+ public static final int STATE_COMPLETED = 128;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Intent
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static final String REPEAT_MODE = "repeat_mode";
+ public static final String PLAYBACK_QUALITY = "playback_quality";
+ public static final String PLAY_QUEUE_KEY = "play_queue_key";
+ public static final String APPEND_ONLY = "append_only";
+ public static final String RESUME_PLAYBACK = "resume_playback";
+ public static final String PLAY_WHEN_READY = "play_when_ready";
+ public static final String SELECT_ON_APPEND = "select_on_append";
+ public static final String PLAYER_TYPE = "player_type";
+ public static final String IS_MUTED = "is_muted";
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Time constants
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds
+ public static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500; // 500 millis
+ 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
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // 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;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Playback
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private PlayQueue playQueue;
+ private PlayQueueAdapter playQueueAdapter;
+
+ @Nullable private MediaSourceManager playQueueManager;
+
+ @Nullable private PlayQueueItem currentItem;
+ @Nullable private MediaSourceTag currentMetadata;
+ @Nullable private Bitmap currentThumbnail;
+
+ @Nullable private Toast errorToast;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Player
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private SimpleExoPlayer simpleExoPlayer;
+ private AudioReactor audioReactor;
+ private MediaSessionManager mediaSessionManager;
+
+ @NonNull private final CustomTrackSelector trackSelector;
+ @NonNull private final LoadController loadController;
+ @NonNull private final RenderersFactory renderFactory;
+
+ @NonNull private final VideoPlaybackResolver videoResolver;
+ @NonNull private final AudioPlaybackResolver audioResolver;
+
+ private final MainPlayer service; //TODO try to remove and replace everything with context
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Player states
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private PlayerType playerType = PlayerType.VIDEO;
+ private int currentState = STATE_PREFLIGHT;
+
+ // audio only mode does not mean that player type is background, but that the player was
+ // minimized to background but will resume automatically to the original player type
+ 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
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private PlayerBinding binding;
+
+ private ValueAnimator controlViewAnimator;
+ private final Handler controlsVisibilityHandler = new Handler();
+
+ // fullscreen player
+ private boolean isQueueVisible = 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;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Listeners and disposables
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private BroadcastReceiver broadcastReceiver;
+ private IntentFilter intentFilter;
+ private PlayerServiceEventListener fragmentListener;
+ private PlayerEventListener activityListener;
+ private ContentObserver settingsContentObserver;
+
+ @NonNull private final SerialDisposable progressUpdateDisposable = new SerialDisposable();
+ @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable();
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @NonNull private final Context context;
+ @NonNull private final SharedPreferences prefs;
+ @NonNull private final HistoryRecordManager recordManager;
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Constructor
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ public Player(@NonNull final MainPlayer service) {
+ this.service = service;
+ context = service;
+ prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ recordManager = new HistoryRecordManager(context);
+
+ setupBroadcastReceiver();
+
+ trackSelector = new CustomTrackSelector(context, PlayerHelper.getQualitySelector());
+ final PlayerDataSource dataSource = new PlayerDataSource(context, DownloaderImpl.USER_AGENT,
+ new DefaultBandwidthMeter.Builder(context).build());
+ loadController = new LoadController();
+ renderFactory = new DefaultRenderersFactory(context);
+
+ videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
+ audioResolver = new AudioPlaybackResolver(context, dataSource);
+
+ windowManager = ContextCompat.getSystemService(context, WindowManager.class);
+ }
+
+ private VideoPlaybackResolver.QualityResolver getQualityResolver() {
+ return new VideoPlaybackResolver.QualityResolver() {
+ @Override
+ public int getDefaultResolutionIndex(final List sortedVideos) {
+ return videoPlayerSelected()
+ ? ListHelper.getDefaultResolutionIndex(context, sortedVideos)
+ : ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
+ }
+
+ @Override
+ public int getOverrideResolutionIndex(final List sortedVideos,
+ final String playbackQuality) {
+ return videoPlayerSelected()
+ ? getResolutionIndex(context, sortedVideos, playbackQuality)
+ : getPopupResolutionIndex(context, sortedVideos, playbackQuality);
+ }
+ };
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Setup and initialization
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ public void setupFromView(@NonNull final PlayerBinding playerBinding) {
+ initViews(playerBinding);
+ if (exoPlayerIsNull()) {
+ initPlayer(true);
+ }
+ initListeners();
+ }
+
+ 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));
+
+ qualityPopupMenu = new PopupMenu(context, binding.qualityTextView);
+ playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed);
+ captionPopupMenu = new PopupMenu(context, 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.playQueue.setNestedScrollingEnabled(false);
+ }
+
+ private void initPlayer(final boolean playOnReady) {
+ if (DEBUG) {
+ Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]");
+ }
+
+ simpleExoPlayer = new SimpleExoPlayer.Builder(context, renderFactory)
+ .setTrackSelector(trackSelector)
+ .setLoadControl(loadController)
+ .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
+ simpleExoPlayer.setVideoSurfaceView(binding.surfaceView);
+ simpleExoPlayer.addVideoListener(this);
+
+ // Setup subtitle view
+ simpleExoPlayer.addTextOutput(binding.subtitleView);
+
+ // Setup audio session with onboard equalizer
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ trackSelector.setParameters(trackSelector.buildUponParameters()
+ .setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
+ }
+ }
+
+ private void initListeners() {
+ binding.playbackSeekBar.setOnSeekBarChangeListener(this);
+ binding.playbackSpeed.setOnClickListener(this);
+ binding.qualityTextView.setOnClickListener(this);
+ binding.captionTextView.setOnClickListener(this);
+ binding.resizeTextView.setOnClickListener(this);
+ binding.playbackLiveSync.setOnClickListener(this);
+
+ final PlayerGestureListener listener = new PlayerGestureListener(this, service);
+ gestureDetector = new GestureDetector(context, listener);
+ binding.getRoot().setOnTouchListener(listener);
+
+ binding.queueButton.setOnClickListener(this);
+ binding.repeatButton.setOnClickListener(this);
+ binding.shuffleButton.setOnClickListener(this);
+
+ 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.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.playQueuePanel, (view, windowInsets) -> {
+ final DisplayCutoutCompat cutout = windowInsets.getDisplayCutout();
+ if (cutout != null) {
+ view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
+ cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
+ }
+ return windowInsets;
+ });
+
+ // PlaybackControlRoot already consumed window insets but we should pass them to
+ // player_overlays 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()));
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Playback initialization via intent
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ public void handleIntent(@NonNull final Intent intent) {
+ // fail fast if no play queue was provided
+ final String queueCache = intent.getStringExtra(PLAY_QUEUE_KEY);
+ if (queueCache == null) {
+ return;
+ }
+ final PlayQueue newQueue = SerializedCache.getInstance().take(queueCache, PlayQueue.class);
+ if (newQueue == null) {
+ return;
+ }
+
+ final PlayerType oldPlayerType = playerType;
+ playerType = retrievePlayerTypeFromIntent(intent);
+ // We need to setup audioOnly before super(), see "sourceOf"
+ isAudioOnly = audioPlayerSelected();
+
+ if (intent.hasExtra(PLAYBACK_QUALITY)) {
+ setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
+ }
+
+ // Resolve append intents
+ if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) {
+ final int sizeBeforeAppend = playQueue.size();
+ playQueue.append(newQueue.getStreams());
+
+ if ((intent.getBooleanExtra(SELECT_ON_APPEND, false)
+ || currentState == STATE_COMPLETED) && newQueue.getStreams().size() > 0) {
+ playQueue.setIndex(sizeBeforeAppend);
+ }
+
+ return;
+ }
+
+ final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this);
+ final float playbackSpeed = savedParameters.speed;
+ final float playbackPitch = savedParameters.pitch;
+ final boolean playbackSkipSilence = savedParameters.skipSilence;
+
+ final boolean samePlayQueue = playQueue != null && playQueue.equals(newQueue);
+ final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
+ final boolean playWhenReady = intent.getBooleanExtra(PLAY_WHEN_READY, true);
+ final boolean isMuted = intent.getBooleanExtra(IS_MUTED, isMuted());
+
+ /*
+ * There are 3 situations when playback shouldn't be started from scratch (zero timestamp):
+ * 1. User pressed on a timestamp link and the same video should be rewound to the timestamp
+ * 2. User changed a player from, for example. main to popup, or from audio to main, etc
+ * 3. User chose to resume a video based on a saved timestamp from history of played videos
+ * In those cases time will be saved because re-init of the play queue is a not an instant
+ * task and requires network calls
+ * */
+ // seek to timestamp if stream is already playing
+ if (!exoPlayerIsNull()
+ && newQueue.size() == 1 && newQueue.getItem() != null
+ && playQueue != null && playQueue.size() == 1 && playQueue.getItem() != null
+ && newQueue.getItem().getUrl().equals(playQueue.getItem().getUrl())
+ && newQueue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
+ // Player can have state = IDLE when playback is stopped or failed
+ // and we should retry() in this case
+ if (simpleExoPlayer.getPlaybackState()
+ == com.google.android.exoplayer2.Player.STATE_IDLE) {
+ simpleExoPlayer.retry();
+ }
+ simpleExoPlayer.seekTo(playQueue.getIndex(), newQueue.getItem().getRecoveryPosition());
+ simpleExoPlayer.setPlayWhenReady(playWhenReady);
+
+ } else if (!exoPlayerIsNull()
+ && samePlayQueue
+ && playQueue != null
+ && !playQueue.isDisposed()) {
+ // Do not re-init the same PlayQueue. Save time
+ // Player can have state = IDLE when playback is stopped or failed
+ // and we should retry() in this case
+ if (simpleExoPlayer.getPlaybackState()
+ == com.google.android.exoplayer2.Player.STATE_IDLE) {
+ simpleExoPlayer.retry();
+ }
+ simpleExoPlayer.setPlayWhenReady(playWhenReady);
+
+ } else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
+ && isPlaybackResumeEnabled(this)
+ && !samePlayQueue
+ && !newQueue.isEmpty()
+ && newQueue.getItem().getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
+ databaseUpdateDisposable.add(recordManager.loadStreamState(newQueue.getItem())
+ .observeOn(AndroidSchedulers.mainThread())
+ // Do not place initPlayback() in doFinally() because
+ // it restarts playback after destroy()
+ //.doFinally()
+ .subscribe(
+ state -> {
+ newQueue.setRecovery(newQueue.getIndex(), state.getProgressTime());
+ initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch,
+ playbackSkipSilence, playWhenReady, isMuted);
+ },
+ error -> {
+ if (DEBUG) {
+ error.printStackTrace();
+ }
+ // In case any error we can start playback without history
+ initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch,
+ playbackSkipSilence, playWhenReady, isMuted);
+ },
+ () -> {
+ // Completed but not found in history
+ initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch,
+ playbackSkipSilence, playWhenReady, isMuted);
+ }
+ ));
+ } else {
+ // Good to go...
+ // In a case of equal PlayQueues we can re-init old one but only when it is disposed
+ initPlayback(samePlayQueue ? playQueue : newQueue, repeatMode, playbackSpeed,
+ playbackPitch, playbackSkipSilence, playWhenReady, isMuted);
+ }
+
+ if (oldPlayerType != playerType && playQueue != null) {
+ // If playerType changes from one to another we should reload the player
+ // (to disable/enable video stream or to set quality)
+ setRecovery();
+ 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();
+ closeQueue();
+ // Android TV: without it focus will frame the whole player
+ binding.playPauseButton.requestFocus();
+
+ if (simpleExoPlayer.getPlayWhenReady()) {
+ play();
+ } else {
+ pause();
+ }
+ }
+ NavigationHelper.sendPlayerStartedEvent(context);
+ }
+
+ private void initPlayback(@NonNull final PlayQueue queue,
+ @RepeatMode final int repeatMode,
+ final float playbackSpeed,
+ final float playbackPitch,
+ final boolean playbackSkipSilence,
+ final boolean playOnReady,
+ final boolean isMuted) {
+ destroyPlayer();
+ initPlayer(playOnReady);
+ setRepeatMode(repeatMode);
+ setPlaybackParameters(playbackSpeed, playbackPitch, playbackSkipSilence);
+
+ playQueue = queue;
+ playQueue.init();
+ reloadPlayQueueManager();
+
+ if (playQueueAdapter != null) {
+ playQueueAdapter.dispose();
+ }
+ playQueueAdapter = new PlayQueueAdapter(context, playQueue);
+
+ simpleExoPlayer.setVolume(isMuted ? 0 : 1);
+ notifyQueueUpdateToListeners();
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Destroy and recovery
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ private void destroyPlayer() {
+ if (DEBUG) {
+ Log.d(TAG, "destroyPlayer() called");
+ }
+ if (!exoPlayerIsNull()) {
+ simpleExoPlayer.removeListener(this);
+ simpleExoPlayer.stop();
+ simpleExoPlayer.release();
+ }
+ if (isProgressLoopRunning()) {
+ stopProgressLoop();
+ }
+ if (playQueue != null) {
+ playQueue.dispose();
+ }
+ if (audioReactor != null) {
+ audioReactor.dispose();
+ }
+ if (playQueueManager != null) {
+ playQueueManager.dispose();
+ }
+ if (mediaSessionManager != null) {
+ mediaSessionManager.dispose();
+ }
+
+ if (playQueueAdapter != null) {
+ playQueueAdapter.unsetSelectedListener();
+ playQueueAdapter.dispose();
+ }
+ }
+
+ public void destroy() {
+ if (DEBUG) {
+ Log.d(TAG, "destroy() called");
+ }
+ destroyPlayer();
+ unregisterBroadcastReceiver();
+
+ databaseUpdateDisposable.clear();
+ progressUpdateDisposable.set(null);
+ ImageLoader.getInstance().stop();
+
+ if (binding != null) {
+ binding.endScreen.setImageBitmap(null);
+ }
+
+ context.getContentResolver().unregisterContentObserver(settingsContentObserver);
+ }
+
+ public void setRecovery() {
+ if (playQueue == null || exoPlayerIsNull()) {
+ return;
+ }
+
+ final int queuePos = playQueue.getIndex();
+ final long windowPos = simpleExoPlayer.getCurrentPosition();
+
+ if (windowPos > 0 && windowPos <= simpleExoPlayer.getDuration()) {
+ setRecovery(queuePos, windowPos);
+ }
+ }
+
+ private void setRecovery(final int queuePos, final long windowPos) {
+ if (playQueue.size() <= queuePos) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos);
+ }
+ playQueue.setRecovery(queuePos, windowPos);
+ }
+
+ private void reloadPlayQueueManager() {
+ if (playQueueManager != null) {
+ playQueueManager.dispose();
+ }
+
+ if (playQueue != null) {
+ playQueueManager = new MediaSourceManager(this, playQueue);
+ }
+ }
+
+ @Override // own playback listener
+ public void onPlaybackShutdown() {
+ if (DEBUG) {
+ Log.d(TAG, "onPlaybackShutdown() called");
+ }
+ // destroys the service, which in turn will destroy the player
+ service.onDestroy();
+ }
+
+ public void smoothStopPlayer() {
+ // Pausing would make transition from one stream to a new stream not smooth, so only stop
+ simpleExoPlayer.stop(false);
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Player type specific setup
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ private void initVideoPlayer() {
+ // restore last resize mode
+ setResizeMode(prefs.getInt(context.getString(R.string.last_resize_mode),
+ AspectRatioFrameLayout.RESIZE_MODE_FIT));
+ 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
+
+ /**
+ * 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.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();
+ closeQueue();
+ } 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_white_24dp));
+ 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
+ && KoreUtil.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId())
+ ? View.VISIBLE : View.GONE);
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Broadcast receiver
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ private void setupBroadcastReceiver() {
+ if (DEBUG) {
+ Log.d(TAG, "setupBroadcastReceiver() called");
+ }
+
+ broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context ctx, final Intent intent) {
+ onBroadcastReceived(intent);
+ }
+ };
+ intentFilter = new IntentFilter();
+
+ intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+
+ intentFilter.addAction(ACTION_CLOSE);
+ intentFilter.addAction(ACTION_PLAY_PAUSE);
+ intentFilter.addAction(ACTION_PLAY_PREVIOUS);
+ intentFilter.addAction(ACTION_PLAY_NEXT);
+ intentFilter.addAction(ACTION_FAST_REWIND);
+ intentFilter.addAction(ACTION_FAST_FORWARD);
+ intentFilter.addAction(ACTION_REPEAT);
+ intentFilter.addAction(ACTION_SHUFFLE);
+ intentFilter.addAction(ACTION_RECREATE_NOTIFICATION);
+
+ intentFilter.addAction(VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED);
+ intentFilter.addAction(VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED);
+
+ intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
+ }
+
+ private void onBroadcastReceived(final Intent intent) {
+ if (intent == null || intent.getAction() == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
+ }
+
+ switch (intent.getAction()) {
+ case AudioManager.ACTION_AUDIO_BECOMING_NOISY:
+ pause();
+ break;
+ case ACTION_CLOSE:
+ service.onDestroy();
+ 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();
+ break;
+ case ACTION_PLAY_NEXT:
+ playNext();
+ break;
+ case ACTION_FAST_REWIND:
+ fastRewind();
+ break;
+ case ACTION_FAST_FORWARD:
+ fastForward();
+ break;
+ case ACTION_REPEAT:
+ onRepeatClicked();
+ break;
+ case ACTION_SHUFFLE:
+ onShuffleClicked();
+ 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
+ closeQueue();
+ 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);
+ mediaSessionManager.dispose();
+ mediaSessionManager.enable(getBaseContext(), basePlayerImpl.simpleExoPlayer);*/
+ break;
+ }
+ }
+
+ private void registerBroadcastReceiver() {
+ // Try to unregister current first
+ unregisterBroadcastReceiver();
+ context.registerReceiver(broadcastReceiver, intentFilter);
+ }
+
+ private void unregisterBroadcastReceiver() {
+ try {
+ context.unregisterReceiver(broadcastReceiver);
+ } catch (final IllegalArgumentException unregisteredException) {
+ Log.w(TAG, "Broadcast receiver already unregistered: "
+ + unregisteredException.getMessage());
+ }
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Thumbnail loading
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ private void initThumbnail(final String url) {
+ if (DEBUG) {
+ Log.d(TAG, "Thumbnail - initThumbnail() called");
+ }
+ if (url == null || url.isEmpty()) {
+ return;
+ }
+ ImageLoader.getInstance().resume();
+ ImageLoader.getInstance()
+ .loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, this);
+ }
+
+ @Override
+ public void onLoadingStarted(final String imageUri, final View view) {
+ if (DEBUG) {
+ Log.d(TAG, "Thumbnail - onLoadingStarted() called on: "
+ + "imageUri = [" + imageUri + "], view = [" + view + "]");
+ }
+ }
+
+ @Override
+ public void onLoadingFailed(final String imageUri, final View view,
+ final FailReason failReason) {
+ Log.e(TAG, "Thumbnail - onLoadingFailed() called on imageUri = [" + imageUri + "]",
+ failReason.getCause());
+ currentThumbnail = null;
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ }
+
+ @Override
+ public void onLoadingComplete(final String imageUri, final View view,
+ final Bitmap loadedImage) {
+ final float width = Math.min(
+ context.getResources().getDimension(R.dimen.player_notification_thumbnail_width),
+ loadedImage.getWidth());
+
+ if (DEBUG) {
+ Log.d(TAG, "Thumbnail - onLoadingComplete() called with: "
+ + "imageUri = [" + imageUri + "], view = [" + view + "], "
+ + "loadedImage = [" + loadedImage + "], "
+ + loadedImage.getWidth() + "x" + loadedImage.getHeight()
+ + ", scaled width = " + width);
+ }
+
+ currentThumbnail = Bitmap.createScaledBitmap(loadedImage,
+ (int) width,
+ (int) (loadedImage.getHeight() / (loadedImage.getWidth() / width)), true);
+ binding.endScreen.setImageBitmap(loadedImage);
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ }
+
+ @Override
+ public void onLoadingCancelled(final String imageUri, final View view) {
+ if (DEBUG) {
+ Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: "
+ + "imageUri = [" + imageUri + "], view = [" + view + "]");
+ }
+ currentThumbnail = null;
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Popup player utils
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ /**
+ * 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) {
+ final boolean isCloseOverlayHasParent = closeOverlayBinding != null
+ && closeOverlayBinding.closeButton.getParent() != null;
+ if (popupHasParent()) {
+ windowManager.removeView(binding.getRoot());
+ }
+ if (isCloseOverlayHasParent) {
+ windowManager.removeView(closeOverlayBinding.getRoot());
+ }
+ }
+ }
+
+ 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.onDestroy();
+ }
+ }).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
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Playback parameters
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ public float getPlaybackSpeed() {
+ return getPlaybackParameters().speed;
+ }
+
+ private void setPlaybackSpeed(final float speed) {
+ setPlaybackParameters(speed, getPlaybackPitch(), getPlaybackSkipSilence());
+ }
+
+ public float getPlaybackPitch() {
+ return getPlaybackParameters().pitch;
+ }
+
+ public boolean getPlaybackSkipSilence() {
+ return getPlaybackParameters().skipSilence;
+ }
+
+ public PlaybackParameters getPlaybackParameters() {
+ if (exoPlayerIsNull()) {
+ return PlaybackParameters.DEFAULT;
+ }
+ return simpleExoPlayer.getPlaybackParameters();
+ }
+
+ /**
+ * Sets the playback parameters of the player, and also saves them to shared preferences.
+ * Speed and pitch are rounded up to 2 decimal places before being used or saved.
+ *
+ * @param speed the playback speed, will be rounded to up to 2 decimal places
+ * @param pitch the playback pitch, will be rounded to up to 2 decimal places
+ * @param skipSilence skip silence during playback
+ */
+ public void setPlaybackParameters(final float speed, final float pitch,
+ final boolean skipSilence) {
+ final float roundedSpeed = Math.round(speed * 100.0f) / 100.0f;
+ final float roundedPitch = Math.round(pitch * 100.0f) / 100.0f;
+
+ savePlaybackParametersToPrefs(this, roundedSpeed, roundedPitch, skipSilence);
+ simpleExoPlayer.setPlaybackParameters(
+ new PlaybackParameters(roundedSpeed, roundedPitch, skipSilence));
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Progress loop and updates
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ private void onUpdateProgress(final int currentProgress,
+ final int duration,
+ final int bufferPercent) {
+ if (!isPrepared) {
+ return;
+ }
+
+ if (duration != binding.playbackSeekBar.getMax()) {
+ binding.playbackEndTime.setText(getTimeString(duration));
+ binding.playbackSeekBar.setMax(duration);
+ }
+ if (currentState != STATE_PAUSED) {
+ if (currentState != STATE_PAUSED_SEEK) {
+ binding.playbackSeekBar.setProgress(currentProgress);
+ }
+ binding.playbackCurrentTime.setText(getTimeString(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);
+
+ final boolean showThumbnail = prefs.getBoolean(
+ context.getString(R.string.show_thumbnail_key), true);
+ // setMetadata only updates the metadata when any of the metadata keys are null
+ mediaSessionManager.setMetadata(getVideoTitle(), getUploaderName(),
+ showThumbnail ? getThumbnail() : null, duration);
+ }
+
+ private void startProgressLoop() {
+ progressUpdateDisposable.set(getProgressUpdateDisposable());
+ }
+
+ private void stopProgressLoop() {
+ progressUpdateDisposable.set(null);
+ }
+
+ private boolean isProgressLoopRunning() {
+ return progressUpdateDisposable.get() != null;
+ }
+
+ private void triggerProgressUpdate() {
+ if (exoPlayerIsNull()) {
+ return;
+ }
+ onUpdateProgress(
+ Math.max((int) simpleExoPlayer.getCurrentPosition(), 0),
+ (int) simpleExoPlayer.getDuration(),
+ simpleExoPlayer.getBufferedPercentage()
+ );
+ }
+
+ private Disposable getProgressUpdateDisposable() {
+ return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS,
+ AndroidSchedulers.mainThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(ignored -> triggerProgressUpdate(),
+ error -> Log.e(TAG, "Progress update failure: ", error));
+ }
+
+ @Override // seekbar listener
+ public void onProgressChanged(final SeekBar seekBar, final int progress,
+ final boolean fromUser) {
+ if (DEBUG && fromUser) {
+ Log.d(TAG, "onProgressChanged() called with: "
+ + "seekBar = [" + seekBar + "], progress = [" + progress + "]");
+ }
+ if (fromUser) {
+ binding.currentDisplaySeek.setText(getTimeString(progress));
+ }
+ }
+
+ @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.setPlayWhenReady(false);
+ }
+
+ showControls(0);
+ animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, true,
+ DEFAULT_CONTROLS_DURATION);
+ }
+
+ @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.setPlayWhenReady(true);
+ }
+
+ binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
+ animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
+
+ if (currentState == STATE_PAUSED_SEEK) {
+ changeState(STATE_BUFFERING);
+ }
+ if (!isProgressLoopRunning()) {
+ startProgressLoop();
+ }
+ if (wasPlaying) {
+ showControlsThenHide();
+ }
+ }
+
+ public void saveWasPlaying() {
+ this.wasPlaying = simpleExoPlayer.getPlayWhenReady();
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Controls showing / hiding
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ public boolean isControlsVisible() {
+ return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE;
+ }
+
+ /**
+ * Show a animation, and depending on goneOnEnd, will stay on the screen or be gone.
+ *
+ * @param drawableId the drawable that will be used to animate,
+ * pass -1 to clear any animation that is visible
+ * @param goneOnEnd will set the animation view to GONE on the end of the animation
+ */
+ public void showAndAnimateControl(final int drawableId, final boolean goneOnEnd) {
+ if (DEBUG) {
+ Log.d(TAG, "showAndAnimateControl() called with: "
+ + "drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]");
+ }
+ if (controlViewAnimator != null && controlViewAnimator.isRunning()) {
+ if (DEBUG) {
+ Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning");
+ }
+ controlViewAnimator.end();
+ }
+
+ if (drawableId == -1) {
+ if (binding.controlAnimationView.getVisibility() == View.VISIBLE) {
+ controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(
+ binding.controlAnimationView,
+ PropertyValuesHolder.ofFloat(View.ALPHA, 1.0f, 0.0f),
+ PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1.0f),
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1.0f)
+ ).setDuration(DEFAULT_CONTROLS_DURATION);
+ controlViewAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ binding.controlAnimationView.setVisibility(View.GONE);
+ }
+ });
+ controlViewAnimator.start();
+ }
+ return;
+ }
+
+ final float scaleFrom = goneOnEnd ? 1f : 1f;
+ final float scaleTo = goneOnEnd ? 1.8f : 1.4f;
+ final float alphaFrom = goneOnEnd ? 1f : 0f;
+ final float alphaTo = goneOnEnd ? 0f : 1f;
+
+
+ controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(
+ binding.controlAnimationView,
+ PropertyValuesHolder.ofFloat(View.ALPHA, alphaFrom, alphaTo),
+ PropertyValuesHolder.ofFloat(View.SCALE_X, scaleFrom, scaleTo),
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleFrom, scaleTo)
+ );
+ controlViewAnimator.setDuration(goneOnEnd ? 1000 : 500);
+ controlViewAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ binding.controlAnimationView.setVisibility(goneOnEnd ? View.GONE : View.VISIBLE);
+ }
+ });
+
+
+ binding.controlAnimationView.setVisibility(View.VISIBLE);
+ binding.controlAnimationView.setImageDrawable(
+ AppCompatResources.getDrawable(context, drawableId));
+ controlViewAnimator.start();
+ }
+
+ 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);
+ animateView(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, 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);
+ animateView(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);
+ animateView(binding.playbackControlRoot, false, duration, 0,
+ this::hideSystemUIIfNeeded);
+ }, delay);
+ }
+
+ private void showHideShadow(final boolean show, final long duration) {
+ animateView(binding.playerTopShadow, show, duration, 0, null);
+ animateView(binding.playerBottomShadow, show, duration, 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();
+
+ 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);
+ }
+
+ 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);
+ }
+ 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();
+ }
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Playback states
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ @Override // exoplayer listener
+ public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - onPlayerStateChanged() called with: "
+ + "playWhenReady = [" + playWhenReady + "], "
+ + "playbackState = [" + playbackState + "]");
+ }
+
+ if (currentState == STATE_PAUSED_SEEK) {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - onPlayerStateChanged() is currently blocked");
+ }
+ return;
+ }
+
+ switch (playbackState) {
+ case com.google.android.exoplayer2.Player.STATE_IDLE: // 1
+ isPrepared = false;
+ break;
+ case com.google.android.exoplayer2.Player.STATE_BUFFERING: // 2
+ if (isPrepared) {
+ changeState(STATE_BUFFERING);
+ }
+ break;
+ case com.google.android.exoplayer2.Player.STATE_READY: //3
+ maybeUpdateCurrentMetadata();
+ maybeCorrectSeekPosition();
+ if (!isPrepared) {
+ isPrepared = true;
+ onPrepared(playWhenReady);
+ }
+ changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
+ break;
+ case com.google.android.exoplayer2.Player.STATE_ENDED: // 4
+ changeState(STATE_COMPLETED);
+ if (currentMetadata != null) {
+ resetStreamProgressState(currentMetadata.getMetadata());
+ }
+ isPrepared = false;
+ break;
+ }
+ }
+
+ @Override // exoplayer listener
+ public void onLoadingChanged(final boolean isLoading) {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - onLoadingChanged() called with: "
+ + "isLoading = [" + isLoading + "]");
+ }
+
+ if (!isLoading && currentState == STATE_PAUSED && isProgressLoopRunning()) {
+ stopProgressLoop();
+ } else if (isLoading && !isProgressLoopRunning()) {
+ startProgressLoop();
+ }
+
+ maybeUpdateCurrentMetadata();
+ }
+
+ @Override // own playback listener
+ public void onPlaybackBlock() {
+ if (exoPlayerIsNull()) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Playback - onPlaybackBlock() called");
+ }
+
+ currentItem = null;
+ currentMetadata = null;
+ simpleExoPlayer.stop();
+ isPrepared = false;
+
+ changeState(STATE_BLOCKED);
+ }
+
+ @Override // own playback listener
+ public void onPlaybackUnblock(final MediaSource mediaSource) {
+ if (DEBUG) {
+ Log.d(TAG, "Playback - onPlaybackUnblock() called");
+ }
+
+ if (exoPlayerIsNull()) {
+ return;
+ }
+ if (currentState == STATE_BLOCKED) {
+ changeState(STATE_BUFFERING);
+ }
+ simpleExoPlayer.prepare(mediaSource);
+ }
+
+ public void changeState(final int state) {
+ if (DEBUG) {
+ Log.d(TAG, "changeState() called with: state = [" + state + "]");
+ }
+ currentState = state;
+ switch (state) {
+ case STATE_BLOCKED:
+ onBlocked();
+ break;
+ case STATE_PLAYING:
+ onPlaying();
+ break;
+ case STATE_BUFFERING:
+ onBuffering();
+ break;
+ case STATE_PAUSED:
+ onPaused();
+ break;
+ case STATE_PAUSED_SEEK:
+ onPausedSeek();
+ break;
+ case STATE_COMPLETED:
+ onCompleted();
+ break;
+ }
+ notifyPlaybackUpdateToListeners();
+ }
+
+ private void onPrepared(final boolean playWhenReady) {
+ if (DEBUG) {
+ Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
+ }
+
+ binding.playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
+ binding.playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
+ binding.playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
+
+ if (playWhenReady) {
+ audioReactor.requestAudioFocus();
+ }
+ }
+
+ private void onBlocked() {
+ if (DEBUG) {
+ Log.d(TAG, "onBlocked() called");
+ }
+ if (!isProgressLoopRunning()) {
+ startProgressLoop();
+ }
+
+ controlsVisibilityHandler.removeCallbacksAndMessages(null);
+ animateView(binding.playbackControlRoot, false, DEFAULT_CONTROLS_DURATION);
+
+ binding.playbackSeekBar.setEnabled(false);
+ binding.playbackSeekBar.getThumb()
+ .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
+
+ binding.loadingPanel.setBackgroundColor(Color.BLACK);
+ animateView(binding.loadingPanel, true, 0);
+ animateView(binding.surfaceForeground, true, 100);
+
+ binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
+ animatePlayButtons(false, 100);
+ binding.getRoot().setKeepScreenOn(false);
+
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ }
+
+ private void onPlaying() {
+ if (DEBUG) {
+ Log.d(TAG, "onPlaying() called");
+ }
+ if (!isProgressLoopRunning()) {
+ startProgressLoop();
+ }
+
+ updateStreamRelatedViews();
+
+ showAndAnimateControl(-1, true);
+
+ binding.playbackSeekBar.setEnabled(true);
+ binding.playbackSeekBar.getThumb()
+ .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
+
+ binding.loadingPanel.setVisibility(View.GONE);
+
+ animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
+
+ animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0,
+ () -> {
+ binding.playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
+ animatePlayButtons(true, 200);
+ if (!isQueueVisible) {
+ binding.playPauseButton.requestFocus();
+ }
+ });
+
+ changePopupWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
+ checkLandscape();
+ binding.getRoot().setKeepScreenOn(true);
+
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ }
+
+ private void onBuffering() {
+ if (DEBUG) {
+ Log.d(TAG, "onBuffering() called");
+ }
+ binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT);
+
+ binding.getRoot().setKeepScreenOn(true);
+
+ if (NotificationUtil.getInstance().shouldUpdateBufferingSlot()) {
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ }
+ }
+
+ private void onPaused() {
+ if (DEBUG) {
+ Log.d(TAG, "onPaused() called");
+ }
+
+ if (isProgressLoopRunning()) {
+ stopProgressLoop();
+ }
+
+ showControls(400);
+ binding.loadingPanel.setVisibility(View.GONE);
+
+ animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0,
+ () -> {
+ binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
+ animatePlayButtons(true, 200);
+ if (!isQueueVisible) {
+ binding.playPauseButton.requestFocus();
+ }
+ });
+
+ changePopupWindowFlags(IDLE_WINDOW_FLAGS);
+
+ // Remove running notification when user does not want minimization to background or popup
+ if (PlayerHelper.isMinimizeOnExitDisabled(context) && videoPlayerSelected()) {
+ NotificationUtil.getInstance().cancelNotificationAndStopForeground(service);
+ } else {
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ }
+
+ binding.getRoot().setKeepScreenOn(false);
+ }
+
+ private void onPausedSeek() {
+ if (DEBUG) {
+ Log.d(TAG, "onPausedSeek() called");
+ }
+ showAndAnimateControl(-1, true);
+
+ animatePlayButtons(false, 100);
+ binding.getRoot().setKeepScreenOn(true);
+
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ }
+
+ private void onCompleted() {
+ if (DEBUG) {
+ Log.d(TAG, "onCompleted() called");
+ }
+
+ animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0,
+ () -> {
+ binding.playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
+ animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
+ });
+
+ binding.getRoot().setKeepScreenOn(false);
+ changePopupWindowFlags(IDLE_WINDOW_FLAGS);
+
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ if (isFullscreen) {
+ toggleFullscreen();
+ }
+
+ if (playQueue.getIndex() < playQueue.size() - 1) {
+ playQueue.offsetIndex(+1);
+ }
+ if (isProgressLoopRunning()) {
+ stopProgressLoop();
+ }
+
+ showControls(500);
+ animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
+ binding.loadingPanel.setVisibility(View.GONE);
+ animateView(binding.surfaceForeground, true, 100);
+ }
+
+ private void animatePlayButtons(final boolean show, final int duration) {
+ animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
+
+ boolean showQueueButtons = show;
+ if (playQueue == null) {
+ showQueueButtons = false;
+ }
+
+ if (!showQueueButtons || playQueue.getIndex() > 0) {
+ animateView(
+ binding.playPreviousButton,
+ AnimationUtils.Type.SCALE_AND_ALPHA,
+ showQueueButtons,
+ duration);
+ }
+ if (!showQueueButtons || playQueue.getIndex() + 1 < playQueue.getStreams().size()) {
+ animateView(
+ binding.playNextButton,
+ AnimationUtils.Type.SCALE_AND_ALPHA,
+ showQueueButtons,
+ duration);
+ }
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Repeat and shuffle
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ 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) {
+ if (!exoPlayerIsNull()) {
+ simpleExoPlayer.setRepeatMode(repeatMode);
+ }
+ }
+
+ @Override
+ public void onRepeatModeChanged(@RepeatMode final int repeatMode) {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: "
+ + "repeatMode = [" + repeatMode + "]");
+ }
+ setRepeatModeButton(binding.repeatButton, repeatMode);
+ onShuffleOrRepeatModeChanged();
+ }
+
+ @Override
+ public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - onShuffleModeEnabledChanged() called with: "
+ + "mode = [" + shuffleModeEnabled + "]");
+ }
+
+ if (playQueue != null) {
+ if (shuffleModeEnabled) {
+ playQueue.shuffle();
+ } else {
+ playQueue.unshuffle();
+ }
+ }
+
+ setShuffleButton(binding.shuffleButton, shuffleModeEnabled);
+ onShuffleOrRepeatModeChanged();
+ }
+
+ private void onShuffleOrRepeatModeChanged() {
+ notifyPlaybackUpdateToListeners();
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ }
+
+ private void setRepeatModeButton(final ImageButton imageButton, 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(final ImageButton button, final boolean shuffled) {
+ button.setImageAlpha(shuffled ? 255 : 77);
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Mute / Unmute
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ public void onMuteUnmuteButtonClicked() {
+ if (DEBUG) {
+ Log.d(TAG, "onMuteUnmuteButtonClicked() called");
+ }
+ simpleExoPlayer.setVolume(isMuted() ? 1 : 0);
+ notifyPlaybackUpdateToListeners();
+ setMuteButton(binding.switchMute, isMuted());
+ }
+
+ boolean isMuted() {
+ return !exoPlayerIsNull() && simpleExoPlayer.getVolume() == 0;
+ }
+
+ private void setMuteButton(final ImageButton button, final boolean isMuted) {
+ button.setImageDrawable(AppCompatResources.getDrawable(context, isMuted
+ ? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp));
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // ExoPlayer listeners (that didn't fit in other categories)
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ @Override
+ public void onTimelineChanged(@NonNull final Timeline timeline, final int reason) {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - onTimelineChanged() called with "
+ + "timeline size = [" + timeline.getWindowCount() + "], "
+ + "reason = [" + reason + "]");
+ }
+
+ maybeUpdateCurrentMetadata();
+ // force recreate notification to ensure seek bar is shown when preparation finishes
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true);
+ }
+
+ @Override
+ public void onTracksChanged(@NonNull final TrackGroupArray trackGroups,
+ @NonNull final TrackSelectionArray trackSelections) {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - onTracksChanged(), "
+ + "track group size = " + trackGroups.length);
+ }
+ maybeUpdateCurrentMetadata();
+ onTextTracksChanged();
+ }
+
+ @Override
+ public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - playbackParameters(), speed = [" + playbackParameters.speed
+ + "], pitch = [" + playbackParameters.pitch + "]");
+ }
+ binding.playbackSpeed.setText(formatSpeed(playbackParameters.speed));
+ }
+
+ @Override
+ public void onPositionDiscontinuity(@DiscontinuityReason final int discontinuityReason) {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with "
+ + "discontinuityReason = [" + discontinuityReason + "]");
+ }
+ if (playQueue == null) {
+ return;
+ }
+
+ // Refresh the playback if there is a transition to the next video
+ final int newWindowIndex = simpleExoPlayer.getCurrentWindowIndex();
+ switch (discontinuityReason) {
+ case DISCONTINUITY_REASON_PERIOD_TRANSITION:
+ // When player is in single repeat mode and a period transition occurs,
+ // we need to register a view count here since no metadata has changed
+ if (getRepeatMode() == REPEAT_MODE_ONE && newWindowIndex == playQueue.getIndex()) {
+ registerStreamViewed();
+ break;
+ }
+ case DISCONTINUITY_REASON_SEEK:
+ case DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
+ case DISCONTINUITY_REASON_INTERNAL:
+ if (playQueue.getIndex() != newWindowIndex) {
+ resetStreamProgressState(playQueue.getItem());
+ playQueue.setIndex(newWindowIndex);
+ }
+ break;
+ case DISCONTINUITY_REASON_AD_INSERTION:
+ break; // only makes Android Studio linter happy, as there are no ads
+ }
+
+ maybeUpdateCurrentMetadata();
+ }
+
+ @Override
+ public void onRenderedFirstFrame() {
+ //TODO check if this causes black screen when switching to fullscreen
+ animateView(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION);
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Errors
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+ /**
+ * Process exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}.
+ *
{@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}:
+ * If a runtime error occurred, then we can try to recover it by restarting the playback
+ * after setting the timestamp recovery.
+ *
{@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER}:
+ * If the renderer failed, treat the error as unrecoverable.
+ *
+ *
+ * @see #processSourceError(IOException)
+ * @see com.google.android.exoplayer2.Player.EventListener#onPlayerError(ExoPlaybackException)
+ */
+ @Override
+ public void onPlayerError(@NonNull final ExoPlaybackException error) {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - onPlayerError() called with: " + "error = [" + error + "]");
+ }
+ if (errorToast != null) {
+ errorToast.cancel();
+ errorToast = null;
+ }
+
+ saveStreamProgressState();
+
+ switch (error.type) {
+ case ExoPlaybackException.TYPE_SOURCE:
+ processSourceError(error.getSourceException());
+ showStreamError(error);
+ break;
+ case ExoPlaybackException.TYPE_UNEXPECTED:
+ showRecoverableError(error);
+ setRecovery();
+ reloadPlayQueueManager();
+ break;
+ case ExoPlaybackException.TYPE_OUT_OF_MEMORY:
+ case ExoPlaybackException.TYPE_REMOTE:
+ case ExoPlaybackException.TYPE_RENDERER:
+ default:
+ showUnrecoverableError(error);
+ onPlaybackShutdown();
+ break;
+ }
+
+ if (fragmentListener != null) {
+ fragmentListener.onPlayerError(error);
+ }
+ }
+
+ private void processSourceError(final IOException error) {
+ if (exoPlayerIsNull() || playQueue == null) {
+ return;
+ }
+ setRecovery();
+
+ if (error instanceof BehindLiveWindowException) {
+ reloadPlayQueueManager();
+ } else {
+ playQueue.error();
+ }
+ }
+
+ private void showStreamError(final Exception exception) {
+ exception.printStackTrace();
+
+ if (errorToast == null) {
+ errorToast = Toast
+ .makeText(context, R.string.player_stream_failure, Toast.LENGTH_SHORT);
+ errorToast.show();
+ }
+ }
+
+ private void showRecoverableError(final Exception exception) {
+ exception.printStackTrace();
+
+ if (errorToast == null) {
+ errorToast = Toast
+ .makeText(context, R.string.player_recoverable_failure, Toast.LENGTH_SHORT);
+ errorToast.show();
+ }
+ }
+
+ private void showUnrecoverableError(final Exception exception) {
+ exception.printStackTrace();
+
+ if (errorToast != null) {
+ errorToast.cancel();
+ }
+ errorToast = Toast
+ .makeText(context, R.string.player_unrecoverable_failure, Toast.LENGTH_SHORT);
+ errorToast.show();
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Playback position and seek
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ @Override // own playback listener (this is a getter)
+ public boolean isApproachingPlaybackEdge(final long timeToEndMillis) {
+ // If live, then not near playback edge
+ // If not playing, then not approaching playback edge
+ if (exoPlayerIsNull() || isLive() || !isPlaying()) {
+ return false;
+ }
+
+ final long currentPositionMillis = simpleExoPlayer.getCurrentPosition();
+ final long currentDurationMillis = simpleExoPlayer.getDuration();
+ return currentDurationMillis - currentPositionMillis < timeToEndMillis;
+ }
+
+ /**
+ * Checks if the current playback is a livestream AND is playing at or beyond the live edge.
+ *
+ * @return whether the livestream is playing at or beyond the edge
+ */
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ public boolean isLiveEdge() {
+ if (exoPlayerIsNull() || !isLive()) {
+ return false;
+ }
+
+ final Timeline currentTimeline = simpleExoPlayer.getCurrentTimeline();
+ final int currentWindowIndex = simpleExoPlayer.getCurrentWindowIndex();
+ if (currentTimeline.isEmpty() || currentWindowIndex < 0
+ || currentWindowIndex >= currentTimeline.getWindowCount()) {
+ return false;
+ }
+
+ final Timeline.Window timelineWindow = new Timeline.Window();
+ currentTimeline.getWindow(currentWindowIndex, timelineWindow);
+ return timelineWindow.getDefaultPositionMs() <= simpleExoPlayer.getCurrentPosition();
+ }
+
+ @Override // own playback listener
+ public void onPlaybackSynchronize(@NonNull final PlayQueueItem item) {
+ if (DEBUG) {
+ Log.d(TAG, "Playback - onPlaybackSynchronize() called with "
+ + "item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]");
+ }
+ if (exoPlayerIsNull() || playQueue == null) {
+ return;
+ }
+
+ final boolean onPlaybackInitial = currentItem == null;
+ final boolean hasPlayQueueItemChanged = currentItem != item;
+
+ final int currentPlayQueueIndex = playQueue.indexOf(item);
+ final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex();
+ 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() + "]");
+
+ // 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 (currentPlaylistIndex != currentPlayQueueIndex || onPlaybackInitial
+ || !isPlaying()) {
+ if (DEBUG) {
+ Log.d(TAG, "Playback - Rewinding to correct "
+ + "index=[" + currentPlayQueueIndex + "], "
+ + "from=[" + currentPlaylistIndex + "], "
+ + "size=[" + currentPlaylistSize + "].");
+ }
+
+ if (item.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
+ simpleExoPlayer.seekTo(currentPlayQueueIndex, item.getRecoveryPosition());
+ playQueue.unsetRecovery(currentPlayQueueIndex);
+ } else {
+ simpleExoPlayer.seekToDefaultPosition(currentPlayQueueIndex);
+ }
+ }
+ }
+
+ private void maybeCorrectSeekPosition() {
+ if (playQueue == null || exoPlayerIsNull() || currentMetadata == null) {
+ return;
+ }
+
+ final PlayQueueItem currentSourceItem = playQueue.getItem();
+ if (currentSourceItem == null) {
+ return;
+ }
+
+ final StreamInfo currentInfo = currentMetadata.getMetadata();
+ final long presetStartPositionMillis = currentInfo.getStartPosition() * 1000;
+ if (presetStartPositionMillis > 0L) {
+ // Has another start position?
+ if (DEBUG) {
+ Log.d(TAG, "Playback - Seeking to preset start "
+ + "position=[" + presetStartPositionMillis + "]");
+ }
+ seekTo(presetStartPositionMillis);
+ }
+ }
+
+ public void seekTo(final long positionMillis) {
+ if (DEBUG) {
+ Log.d(TAG, "seekBy() called with: position = [" + positionMillis + "]");
+ }
+ 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);
+ }
+ }
+
+ private void seekBy(final long offsetMillis) {
+ if (DEBUG) {
+ Log.d(TAG, "seekBy() called with: offsetMillis = [" + offsetMillis + "]");
+ }
+ seekTo(simpleExoPlayer.getCurrentPosition() + offsetMillis);
+ }
+
+ public void seekToDefault() {
+ if (!exoPlayerIsNull()) {
+ simpleExoPlayer.seekToDefaultPosition();
+ }
+ }
+
+ @Override // exoplayer override
+ public void onSeekProcessed() {
+ if (DEBUG) {
+ Log.d(TAG, "ExoPlayer - onSeekProcessed() called");
+ }
+ if (isPrepared) {
+ saveStreamProgressState();
+ }
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Player actions (play, pause, previous, fast-forward, ...)
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ public void play() {
+ if (DEBUG) {
+ Log.d(TAG, "play() called");
+ }
+ if (audioReactor == null || playQueue == null || exoPlayerIsNull()) {
+ return;
+ }
+
+ audioReactor.requestAudioFocus();
+
+ if (currentState == STATE_COMPLETED) {
+ if (playQueue.getIndex() == 0) {
+ seekToDefault();
+ } else {
+ playQueue.setIndex(0);
+ }
+ }
+
+ simpleExoPlayer.setPlayWhenReady(true);
+ saveStreamProgressState();
+ }
+
+ public void pause() {
+ if (DEBUG) {
+ Log.d(TAG, "pause() called");
+ }
+ if (audioReactor == null || exoPlayerIsNull()) {
+ return;
+ }
+
+ audioReactor.abandonAudioFocus();
+ simpleExoPlayer.setPlayWhenReady(false);
+ saveStreamProgressState();
+ }
+
+ public void playPause() {
+ if (DEBUG) {
+ Log.d(TAG, "onPlayPause() called");
+ }
+
+ if (isPlaying()) {
+ pause();
+ } else {
+ play();
+ }
+ }
+
+ public void playPrevious() {
+ if (DEBUG) {
+ Log.d(TAG, "onPlayPrevious() called");
+ }
+ if (exoPlayerIsNull() || playQueue == null) {
+ return;
+ }
+
+ /* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT_MILLIS milliseconds,
+ * restart current track. Also restart the track if the current track
+ * is the first in a queue.*/
+ if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT_MILLIS
+ || playQueue.getIndex() == 0) {
+ seekToDefault();
+ playQueue.offsetIndex(0);
+ } else {
+ saveStreamProgressState();
+ playQueue.offsetIndex(-1);
+ }
+ triggerProgressUpdate();
+ }
+
+ public void playNext() {
+ if (DEBUG) {
+ Log.d(TAG, "onPlayNext() called");
+ }
+ if (playQueue == null) {
+ return;
+ }
+
+ saveStreamProgressState();
+ playQueue.offsetIndex(+1);
+ triggerProgressUpdate();
+ }
+
+ public void fastForward() {
+ if (DEBUG) {
+ Log.d(TAG, "fastRewind() called");
+ }
+ seekBy(retrieveSeekDurationFromPreferences(this));
+ triggerProgressUpdate();
+ showAndAnimateControl(R.drawable.ic_fast_forward_white_24dp, true);
+ }
+
+ public void fastRewind() {
+ if (DEBUG) {
+ Log.d(TAG, "fastRewind() called");
+ }
+ seekBy(-retrieveSeekDurationFromPreferences(this));
+ triggerProgressUpdate();
+ showAndAnimateControl(R.drawable.ic_fast_rewind_white_24dp, true);
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // StreamInfo history: views and progress
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ private void registerStreamViewed() {
+ if (currentMetadata != null) {
+ databaseUpdateDisposable.add(recordManager.onViewed(currentMetadata.getMetadata())
+ .onErrorComplete().subscribe());
+ }
+ }
+
+ private void saveStreamProgressState(final StreamInfo info, final long progress) {
+ if (info == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "saveStreamProgressState() called");
+ }
+ if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) {
+ final Disposable stateSaver = recordManager.saveStreamState(info, progress)
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnError((e) -> {
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ })
+ .onErrorComplete()
+ .subscribe();
+ databaseUpdateDisposable.add(stateSaver);
+ }
+ }
+
+ private void resetStreamProgressState(final PlayQueueItem queueItem) {
+ if (queueItem == null) {
+ return;
+ }
+ if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) {
+ final Disposable stateSaver = queueItem.getStream()
+ .flatMapCompletable(info -> recordManager.saveStreamState(info, 0))
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnError((e) -> {
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ })
+ .onErrorComplete()
+ .subscribe();
+ databaseUpdateDisposable.add(stateSaver);
+ }
+ }
+
+ private void resetStreamProgressState(final StreamInfo info) {
+ saveStreamProgressState(info, 0);
+ }
+
+ public void saveStreamProgressState() {
+ if (exoPlayerIsNull() || currentMetadata == null) {
+ return;
+ }
+ final StreamInfo currentInfo = currentMetadata.getMetadata();
+ if (playQueue != null) {
+ // Save current position. It will help to restore this position once a user
+ // wants to play prev or next stream from the queue
+ playQueue.setRecovery(playQueue.getIndex(), simpleExoPlayer.getContentPosition());
+ }
+ saveStreamProgressState(currentInfo, simpleExoPlayer.getCurrentPosition());
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Metadata
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ private void onMetadataChanged(@NonNull final MediaSourceTag tag) {
+ final StreamInfo info = tag.getMetadata();
+ if (DEBUG) {
+ Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName());
+ }
+
+ initThumbnail(info.getThumbnailUrl());
+ registerStreamViewed();
+ updateStreamRelatedViews();
+ showHideKodiButton();
+
+ binding.titleTextView.setText(tag.getMetadata().getName());
+ binding.channelTextView.setText(tag.getMetadata().getUploaderName());
+
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ notifyMetadataUpdateToListeners();
+ }
+
+ private void maybeUpdateCurrentMetadata() {
+ if (exoPlayerIsNull()) {
+ return;
+ }
+
+ final MediaSourceTag metadata;
+ try {
+ metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
+ } catch (IndexOutOfBoundsException | ClassCastException error) {
+ if (DEBUG) {
+ Log.d(TAG, "Could not update metadata: " + error.getMessage());
+ error.printStackTrace();
+ }
+ return;
+ }
+
+ if (metadata == null) {
+ return;
+ }
+ maybeAutoQueueNextStream(metadata);
+
+ if (currentMetadata == metadata) {
+ return;
+ }
+ currentMetadata = metadata;
+ onMetadataChanged(metadata);
+ }
+
+ @NonNull
+ private String getVideoUrl() {
+ return currentMetadata == null
+ ? context.getString(R.string.unknown_content)
+ : currentMetadata.getMetadata().getUrl();
+ }
+
+ @NonNull
+ public String getVideoTitle() {
+ return currentMetadata == null
+ ? context.getString(R.string.unknown_content)
+ : currentMetadata.getMetadata().getName();
+ }
+
+ @NonNull
+ public String getUploaderName() {
+ return currentMetadata == null
+ ? context.getString(R.string.unknown_content)
+ : currentMetadata.getMetadata().getUploaderName();
+ }
+
+ @Nullable
+ public Bitmap getThumbnail() {
+ return currentThumbnail == null
+ ? BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_thumbnail)
+ : currentThumbnail;
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Play queue and streams
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ private void maybeAutoQueueNextStream(@NonNull final MediaSourceTag metadata) {
+ if (playQueue == null || playQueue.getIndex() != playQueue.size() - 1
+ || getRepeatMode() != REPEAT_MODE_OFF
+ || !PlayerHelper.isAutoQueueEnabled(context)) {
+ return;
+ }
+ // auto queue when starting playback on the last item when not repeating
+ final PlayQueue autoQueue = PlayerHelper.autoQueueOf(metadata.getMetadata(),
+ playQueue.getStreams());
+ if (autoQueue != null) {
+ playQueue.append(autoQueue.getStreams());
+ }
+ }
+
+ public void selectQueueItem(final PlayQueueItem item) {
+ if (playQueue == null || exoPlayerIsNull()) {
+ return;
+ }
+
+ final int index = playQueue.indexOf(item);
+ if (index == -1) {
+ return;
+ }
+
+ if (playQueue.getIndex() == index && simpleExoPlayer.getCurrentWindowIndex() == index) {
+ seekToDefault();
+ } else {
+ saveStreamProgressState();
+ }
+ playQueue.setIndex(index);
+ }
+
+ @Override
+ public void onPlayQueueEdited() {
+ notifyPlaybackUpdateToListeners();
+ showOrHideButtons();
+ NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
+ }
+
+ private void onQueueClicked() {
+ isQueueVisible = true;
+
+ hideSystemUIIfNeeded();
+ buildQueue();
+ //updatePlaybackButtons();//TODO verify this can be removed
+
+ hideControls(0, 0);
+ binding.playQueuePanel.requestFocus();
+ animateView(binding.playQueuePanel, SLIDE_AND_ALPHA, true,
+ DEFAULT_CONTROLS_DURATION);
+
+ binding.playQueue.scrollToPosition(playQueue.getIndex());
+ }
+
+ private void buildQueue() {
+ binding.playQueue.setAdapter(playQueueAdapter);
+ binding.playQueue.setClickable(true);
+ binding.playQueue.setLongClickable(true);
+
+ binding.playQueue.clearOnScrollListeners();
+ binding.playQueue.addOnScrollListener(getQueueScrollListener());
+
+ itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
+ itemTouchHelper.attachToRecyclerView(binding.playQueue);
+
+ playQueueAdapter.setSelectedListener(getOnSelectedListener());
+
+ binding.playQueueClose.setOnClickListener(view -> closeQueue());
+ }
+
+ public void closeQueue() {
+ if (isQueueVisible) {
+ isQueueVisible = false;
+ animateView(binding.playQueuePanel, SLIDE_AND_ALPHA, false,
+ DEFAULT_CONTROLS_DURATION, 0, () -> {
+ // Even when queueLayout is GONE it receives touch events
+ // and ruins normal behavior of the app. This line fixes it
+ binding.playQueuePanel.setTranslationY(
+ -binding.playQueuePanel.getHeight() * 5);
+ });
+ 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.playQueue.clearOnScrollListeners();
+ }
+ }
+ };
+ }
+
+ 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) {
+ final int index = playQueue.indexOf(item);
+ if (index != -1) {
+ playQueue.remove(index);
+ }
+ }
+
+ @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) {
+ return (isAudioOnly ? audioResolver : videoResolver).resolve(info);
+ }
+
+ public void disablePreloadingOfCurrentTrack() {
+ loadController.disablePreloadingOfCurrentTrack();
+ }
+
+ @Nullable
+ public VideoStream getSelectedVideoStream() {
+ return (selectedStreamIndex >= 0 && availableStreams != null
+ && availableStreams.size() > selectedStreamIndex)
+ ? availableStreams.get(selectedStreamIndex) : null;
+ }
+
+ private void updateStreamRelatedViews() {
+ if (currentMetadata == null) {
+ return;
+ }
+ final StreamInfo info = currentMetadata.getMetadata();
+
+ 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:
+ 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:
+ if (info.getVideoStreams().size() + info.getVideoOnlyStreams().size() == 0) {
+ break;
+ }
+
+ availableStreams = currentMetadata.getSortedAvailableVideoStreams();
+ selectedStreamIndex = currentMetadata.getSelectedVideoStreamIndex();
+ 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
+
+ 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.resolution);
+ }
+ if (getSelectedVideoStream() != null) {
+ binding.qualityTextView.setText(getSelectedVideoStream().resolution);
+ }
+ 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(final List availableLanguages) {
+ if (captionPopupMenu == null) {
+ return;
+ }
+ captionPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_CAPTION);
+
+ final String userPreferredLanguage =
+ prefs.getString(context.getString(R.string.caption_user_set_key), null);
+ /*
+ * only search for autogenerated cc as fallback
+ * if "(auto-generated)" was not already selected
+ * we are only looking for "(" instead of "(auto-generated)" to hopefully get all
+ * internationalized variants such as "(automatisch-erzeugt)" and so on
+ */
+ boolean searchForAutogenerated = userPreferredLanguage != null
+ && !userPreferredLanguage.contains("(");
+
+ // 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) {
+ trackSelector.setPreferredTextLanguage(captionLanguage);
+ trackSelector.setParameters(trackSelector.buildUponParameters()
+ .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
+ if (userPreferredLanguage != null
+ && (captionLanguage.equals(userPreferredLanguage)
+ || (searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage))
+ || (userPreferredLanguage.contains("(") && captionLanguage.startsWith(
+ userPreferredLanguage.substring(0, userPreferredLanguage.indexOf('(')))))) {
+ final int textRendererIndex = getCaptionRendererIndex();
+ if (textRendererIndex != RENDERER_UNAVAILABLE) {
+ trackSelector.setPreferredTextLanguage(captionLanguage);
+ trackSelector.setParameters(trackSelector.buildUponParameters()
+ .setRendererDisabled(textRendererIndex, false));
+ }
+ searchForAutogenerated = false;
+ }
+ }
+ captionPopupMenu.setOnDismissListener(this);
+ }
+
+ /**
+ * Called when an item of the quality selector or the playback speed selector is selected.
+ */
+ @Override
+ public boolean onMenuItemClick(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).resolution;
+ 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(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().resolution);
+ }
+ if (isPlaying()) {
+ hideControls(DEFAULT_CONTROLS_DURATION, 0);
+ hideSystemUIIfNeeded();
+ }
+ }
+
+ private void onQualitySelectorClicked() {
+ if (DEBUG) {
+ Log.d(TAG, "onQualitySelectorClicked() called");
+ }
+ qualityPopupMenu.show();
+ isSomePopupMenuVisible = true;
+
+ final VideoStream videoStream = getSelectedVideoStream();
+ if (videoStream != null) {
+ final String qualityText = MediaFormat.getNameById(videoStream.getFormatId()) + " "
+ + videoStream.resolution;
+ binding.qualityTextView.setText(qualityText);
+ }
+
+ saveWasPlaying();
+ }
+
+ private void onPlaybackSpeedClicked() {
+ if (DEBUG) {
+ Log.d(TAG, "onPlaybackSpeedClicked() called");
+ }
+ if (videoPlayerSelected()) {
+ PlaybackParameterDialog.newInstance(getPlaybackSpeed(), getPlaybackPitch(),
+ getPlaybackSkipSilence(), this::setPlaybackParameters)
+ .show(getParentActivity().getSupportFragmentManager(), null);
+ } else {
+ playbackSpeedPopupMenu.show();
+ isSomePopupMenuVisible = true;
+ }
+ }
+
+ private void onCaptionClicked() {
+ if (DEBUG) {
+ Log.d(TAG, "onCaptionClicked() called");
+ }
+ captionPopupMenu.show();
+ isSomePopupMenuVisible = true;
+ }
+
+ private void setPlaybackQuality(final String quality) {
+ videoResolver.setPlaybackQuality(quality);
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Captions (text tracks)
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ 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, (float) minimumLength / captionRatioInverse);
+ }
+ binding.subtitleView.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT);
+ binding.subtitleView.setStyle(captionStyle);
+ }
+
+ private void onTextTracksChanged() {
+ final int textRenderer = getCaptionRendererIndex();
+
+ if (binding == null) {
+ return;
+ }
+ if (trackSelector.getCurrentMappedTrackInfo() == null
+ || textRenderer == RENDERER_UNAVAILABLE) {
+ binding.captionTextView.setVisibility(View.GONE);
+ return;
+ }
+
+ final TrackGroupArray textTracks = trackSelector.getCurrentMappedTrackInfo()
+ .getTrackGroups(textRenderer);
+
+ // Extract all loaded languages
+ final List availableLanguages = new ArrayList<>(textTracks.length);
+ for (int i = 0; i < textTracks.length; i++) {
+ final TrackGroup textTrack = textTracks.get(i);
+ if (textTrack.length > 0 && textTrack.getFormat(0) != null) {
+ availableLanguages.add(textTrack.getFormat(0).language);
+ }
+ }
+
+ // Normalize mismatching language strings
+ final String preferredLanguage = trackSelector.getPreferredTextLanguage();
+ // Build UI
+ buildCaptionMenu(availableLanguages);
+ if (trackSelector.getParameters().getRendererDisabled(textRenderer)
+ || preferredLanguage == null || (!availableLanguages.contains(preferredLanguage)
+ && !containsCaseInsensitive(availableLanguages, preferredLanguage))) {
+ binding.captionTextView.setText(R.string.caption_none);
+ } else {
+ binding.captionTextView.setText(preferredLanguage);
+ }
+ binding.captionTextView.setVisibility(
+ availableLanguages.isEmpty() ? View.GONE : View.VISIBLE);
+ }
+
+ private int getCaptionRendererIndex() {
+ if (exoPlayerIsNull()) {
+ return RENDERER_UNAVAILABLE;
+ }
+
+ for (int t = 0; t < simpleExoPlayer.getRendererCount(); t++) {
+ if (simpleExoPlayer.getRendererType(t) == C.TRACK_TYPE_TEXT) {
+ return t;
+ }
+ }
+
+ return RENDERER_UNAVAILABLE;
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Click listeners
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ @Override
+ public void onClick(final View v) {
+ if (DEBUG) {
+ Log.d(TAG, "onClick() called with: v = [" + v + "]");
+ }
+ if (v.getId() == binding.qualityTextView.getId()) {
+ onQualitySelectorClicked();
+ } else if (v.getId() == binding.playbackSpeed.getId()) {
+ onPlaybackSpeedClicked();
+ } else 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.queueButton.getId()) {
+ onQueueClicked();
+ return;
+ } else if (v.getId() == binding.repeatButton.getId()) {
+ onRepeatClicked();
+ return;
+ } else if (v.getId() == binding.shuffleButton.getId()) {
+ onShuffleClicked();
+ return;
+ } else if (v.getId() == binding.moreOptionsButton.getId()) {
+ onMoreOptionsClicked();
+ } else if (v.getId() == binding.share.getId()) {
+ onShareClicked();
+ } 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));
+ }
+
+ if (currentState != STATE_COMPLETED) {
+ controlsVisibilityHandler.removeCallbacksAndMessages(null);
+ showHideShadow(true, DEFAULT_CONTROLS_DURATION);
+ animateView(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, 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();
+ }
+ return true;
+ }
+
+ public boolean onKeyDown(final int keyCode) {
+ switch (keyCode) {
+ default:
+ break;
+ case KeyEvent.KEYCODE_SPACE:
+ if (isFullscreen) {
+ playPause();
+ }
+ 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()) {
+ // do not interfere with focus in playlist etc.
+ return false;
+ }
+
+ if (currentState == Player.STATE_BLOCKED) {
+ return true;
+ }
+
+ if (!isControlsVisible()) {
+ if (!isQueueVisible) {
+ binding.playPauseButton.requestFocus();
+ }
+ showControlsThenHide();
+ showSystemUIPartially();
+ return true;
+ } else {
+ hideControls(DEFAULT_CONTROLS_DURATION, DPAD_CONTROLS_HIDE_TIME);
+ }
+ 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);
+ animateView(binding.secondaryControls, SLIDE_AND_ALPHA, !isMoreControlsVisible,
+ DEFAULT_CONTROLS_DURATION, 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 onShareClicked() {
+ // share video at the current time (youtube.com/watch?v=ID&t=SECONDS)
+ // Timestamp doesn't make sense in a live stream so drop it
+
+ final int ts = binding.playbackSeekBar.getProgress() / 1000;
+ String videoUrl = getVideoUrl();
+ if (!isLive() && ts >= 0 && currentMetadata != null
+ && currentMetadata.getMetadata().getServiceId() == YouTube.getServiceId()) {
+ videoUrl += ("&t=" + ts);
+ }
+ ShareUtils.shareUrl(context, getVideoTitle(), videoUrl);
+ }
+
+ 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);
+ }
+ KoreUtil.showInstallKoreDialog(getParentActivity());
+ }
+ }
+ }
+
+ private void onOpenInBrowserClicked() {
+ if (currentMetadata != null) {
+ ShareUtils.openUrlInBrowser(getParentActivity(),
+ currentMetadata.getMetadata().getOriginalUrl());
+ }
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Video size, resize, orientation, fullscreen
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ 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_white_24dp
+ : R.drawable.ic_fullscreen_white_24dp));
+ }
+
+ 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(final int width, final int height,
+ final int unappliedRotationDegrees,
+ final float pixelWidthHeightRatio) {
+ if (DEBUG) {
+ Log.d(TAG, "onVideoSizeChanged() called with: "
+ + "width / height = [" + width + " / " + height
+ + " = " + (((float) width) / height) + "], "
+ + "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
+ + "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
+ }
+
+ binding.surfaceView.setAspectRatio(((float) width) / height);
+ isVerticalVideo = width < 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() || currentMetadata == null
+ || fragmentListener == null) {
+ return;
+ }
+ //changeState(STATE_BLOCKED); TODO check what this does
+
+ 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();
+ }
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Gestures
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ @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.playQueuePanel.getLayoutParams().height
+ = height - binding.playQueuePanel.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(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(final MotionEvent popupMotionEvent) {
+ return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius();
+ }
+ //endregion
+
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Activity / fragment binding
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ 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.playQueuePanel.setPadding(0, 0, 0, 0);
+ notifyQueueUpdateToListeners();
+ notifyMetadataUpdateToListeners();
+ notifyPlaybackUpdateToListeners();
+ triggerProgressUpdate();
+ }
+
+ public void removeFragmentListener(final PlayerServiceEventListener listener) {
+ if (fragmentListener == listener) {
+ fragmentListener = null;
+ }
+ }
+
+ void setActivityListener(final PlayerEventListener listener) {
+ activityListener = listener;
+ // TODO why not queue update?
+ notifyMetadataUpdateToListeners();
+ notifyPlaybackUpdateToListeners();
+ triggerProgressUpdate();
+ }
+
+ void removeActivityListener(final PlayerEventListener listener) {
+ if (activityListener == listener) {
+ activityListener = null;
+ }
+ }
+
+ void stopActivityBinding() {
+ if (fragmentListener != null) {
+ fragmentListener.onServiceStopped();
+ fragmentListener = null;
+ }
+ if (activityListener != null) {
+ activityListener.onServiceStopped();
+ activityListener = null;
+ }
+ }
+
+ /**
+ * 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);
+ case MINIMIZE_ON_EXIT_MODE_POPUP:
+ setRecovery();
+ NavigationHelper.playOnPopupPlayer(getParentActivity(), playQueue, true);
+ case MINIMIZE_ON_EXIT_MODE_NONE: default:
+ pause();
+ }
+ }
+ }
+
+ private void notifyQueueUpdateToListeners() {
+ if (fragmentListener != null && playQueue != null) {
+ fragmentListener.onQueueUpdate(playQueue);
+ }
+ if (activityListener != null && playQueue != null) {
+ activityListener.onQueueUpdate(playQueue);
+ }
+ }
+
+ private void notifyMetadataUpdateToListeners() {
+ if (fragmentListener != null && currentMetadata != null) {
+ fragmentListener.onMetadataUpdate(currentMetadata.getMetadata(), playQueue);
+ }
+ if (activityListener != null && currentMetadata != null) {
+ activityListener.onMetadataUpdate(currentMetadata.getMetadata(), playQueue);
+ }
+ }
+
+ private void notifyPlaybackUpdateToListeners() {
+ if (fragmentListener != null && !exoPlayerIsNull() && playQueue != null) {
+ fragmentListener.onPlaybackUpdate(currentState, getRepeatMode(),
+ playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters());
+ }
+ if (activityListener != null && !exoPlayerIsNull() && playQueue != null) {
+ activityListener.onPlaybackUpdate(currentState, getRepeatMode(),
+ playQueue.isShuffled(), getPlaybackParameters());
+ }
+ }
+
+ private void notifyProgressUpdateToListeners(final int currentProgress,
+ final int duration,
+ final int bufferPercent) {
+ if (fragmentListener != null) {
+ fragmentListener.onProgressUpdate(currentProgress, duration, bufferPercent);
+ }
+ if (activityListener != null) {
+ activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
+ }
+ }
+
+ 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 video) {
+ if (playQueue == null || isAudioOnly == !video || audioPlayerSelected()) {
+ return;
+ }
+
+ isAudioOnly = !video;
+ // When a user returns from background controls could be hidden
+ // but systemUI will be shown 100%. Hide it
+ if (!isAudioOnly && !isControlsVisible()) {
+ hideSystemUIIfNeeded();
+ }
+ setRecovery();
+ reloadPlayQueueManager();
+ }
+ //endregion
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Getters
+ //////////////////////////////////////////////////////////////////////////*/
+ //region
+
+ public int getCurrentState() {
+ return currentState;
+ }
+
+ public boolean exoPlayerIsNull() {
+ return simpleExoPlayer == null;
+ }
+
+ public boolean isStopped() {
+ return exoPlayerIsNull()
+ || simpleExoPlayer.getPlaybackState() == SimpleExoPlayer.STATE_IDLE;
+ }
+
+ public boolean isPlaying() {
+ return !exoPlayerIsNull() && simpleExoPlayer.isPlaying();
+ }
+
+ private boolean isLoading() {
+ return !exoPlayerIsNull() && simpleExoPlayer.isLoading();
+ }
+
+ private boolean isLive() {
+ try {
+ return !exoPlayerIsNull() && simpleExoPlayer.isCurrentWindowDynamic();
+ } catch (@NonNull final IndexOutOfBoundsException e) {
+ // Why would this even happen =(... but lets log it anyway, better safe than sorry
+ if (DEBUG) {
+ Log.d(TAG, "player.isCurrentWindowDynamic() failed: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return false;
+ }
+ }
+
+
+ @NonNull
+ public Context getContext() {
+ return context;
+ }
+
+ @NonNull
+ public SharedPreferences getPrefs() {
+ return prefs;
+ }
+
+ public MediaSessionManager getMediaSessionManager() {
+ return mediaSessionManager;
+ }
+
+
+ public PlayerType getPlayerType() {
+ return playerType;
+ }
+
+ public boolean audioPlayerSelected() {
+ return playerType == PlayerType.AUDIO;
+ }
+
+ public boolean videoPlayerSelected() {
+ return playerType == PlayerType.VIDEO;
+ }
+
+ public boolean popupPlayerSelected() {
+ return playerType == PlayerType.POPUP;
+ }
+
+
+ public PlayQueue getPlayQueue() {
+ return playQueue;
+ }
+
+ public AudioReactor getAudioReactor() {
+ return audioReactor;
+ }
+
+ public GestureDetector getGestureDetector() {
+ return gestureDetector;
+ }
+
+ public boolean isFullscreen() {
+ return isFullscreen;
+ }
+
+ public boolean isVerticalVideo() {
+ return isVerticalVideo;
+ }
+
+ public boolean isPopupClosing() {
+ return isPopupClosing;
+ }
+
+
+ public boolean isSomePopupMenuVisible() {
+ return isSomePopupMenuVisible;
+ }
+
+ public ImageButton getPlayPauseButton() {
+ return binding.playPauseButton;
+ }
+
+ public View getClosingOverlayView() {
+ return closeOverlayBinding.getRoot();
+ }
+
+ 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 TextView getResizingIndicator() {
+ return binding.resizingIndicator;
+ }
+
+ @Nullable
+ public WindowManager.LayoutParams getPopupLayoutParams() {
+ return popupLayoutParams;
+ }
+
+ @Nullable
+ public WindowManager getWindowManager() {
+ return windowManager;
+ }
+
+ public float getScreenWidth() {
+ return screenWidth;
+ }
+
+ public float getScreenHeight() {
+ return screenHeight;
+ }
+
+ public View getRootView() {
+ return binding.getRoot();
+ }
+
+ public ExpandableSurfaceView getSurfaceView() {
+ return binding.surfaceView;
+ }
+
+ public PlayQueueAdapter getPlayQueueAdapter() {
+ return playQueueAdapter;
+ }
+
+ //endregion
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java b/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java
index e8bd7dc85..5c28c6c7b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java
@@ -5,13 +5,13 @@ import android.os.Binder;
import androidx.annotation.NonNull;
class PlayerServiceBinder extends Binder {
- private final BasePlayer basePlayer;
+ private final Player player;
- PlayerServiceBinder(@NonNull final BasePlayer basePlayer) {
- this.basePlayer = basePlayer;
+ PlayerServiceBinder(@NonNull final Player player) {
+ this.player = player;
}
- BasePlayer getPlayerInstance() {
- return basePlayer;
+ Player getPlayerInstance() {
+ return player;
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
index fd20fd175..283c25e4f 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
@@ -22,7 +22,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding;
@@ -55,7 +54,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47;
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
- protected BasePlayer player;
+ protected Player player;
private boolean serviceBound;
private ServiceConnection serviceConnection;
@@ -167,14 +166,14 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
case R.id.action_switch_popup:
if (PermissionHelper.isPopupEnabled(this)) {
this.player.setRecovery();
- NavigationHelper.playOnPopupPlayer(this, player.playQueue, true);
+ NavigationHelper.playOnPopupPlayer(this, player.getPlayQueue(), true);
} else {
PermissionHelper.showPopupEnablementToast(this);
}
return true;
case R.id.action_switch_background:
this.player.setRecovery();
- NavigationHelper.playOnBackgroundPlayer(this, player.playQueue, true);
+ NavigationHelper.playOnBackgroundPlayer(this, player.getPlayQueue(), true);
return true;
}
return super.onOptionsItemSelected(item);
@@ -235,7 +234,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
}
if (player == null || player.getPlayQueue() == null
- || player.getPlayQueueAdapter() == null || player.getPlayer() == null) {
+ || player.getPlayQueueAdapter() == null || player.exoPlayerIsNull()) {
unbind();
finish();
} else {
@@ -375,7 +374,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
@Override
public void selected(final PlayQueueItem item, final View view) {
if (player != null) {
- player.onSelected(item);
+ player.selectQueueItem(item);
}
}
@@ -436,15 +435,15 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
if (view.getId() == queueControlBinding.controlRepeat.getId()) {
player.onRepeatClicked();
} else if (view.getId() == queueControlBinding.controlBackward.getId()) {
- player.onPlayPrevious();
+ player.playPrevious();
} else if (view.getId() == queueControlBinding.controlFastRewind.getId()) {
- player.onFastRewind();
+ player.fastRewind();
} else if (view.getId() == queueControlBinding.controlPlayPause.getId()) {
- player.onPlayPause();
+ player.playPause();
} else if (view.getId() == queueControlBinding.controlFastForward.getId()) {
- player.onFastForward();
+ player.fastForward();
} else if (view.getId() == queueControlBinding.controlForward.getId()) {
- player.onPlayNext();
+ player.playNext();
} else if (view.getId() == queueControlBinding.controlShuffle.getId()) {
player.onShuffleClicked();
} else if (view.getId() == queueControlBinding.metadata.getId()) {
@@ -616,15 +615,15 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void onStateChanged(final int state) {
switch (state) {
- case BasePlayer.STATE_PAUSED:
+ case Player.STATE_PAUSED:
queueControlBinding.controlPlayPause
.setImageResource(R.drawable.ic_play_arrow_white_24dp);
break;
- case BasePlayer.STATE_PLAYING:
+ case Player.STATE_PLAYING:
queueControlBinding.controlPlayPause
.setImageResource(R.drawable.ic_pause_white_24dp);
break;
- case BasePlayer.STATE_COMPLETED:
+ case Player.STATE_COMPLETED:
queueControlBinding.controlPlayPause
.setImageResource(R.drawable.ic_replay_white_24dp);
break;
@@ -633,9 +632,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
}
switch (state) {
- case BasePlayer.STATE_PAUSED:
- case BasePlayer.STATE_PLAYING:
- case BasePlayer.STATE_COMPLETED:
+ case Player.STATE_PAUSED:
+ case Player.STATE_PLAYING:
+ case Player.STATE_COMPLETED:
queueControlBinding.controlPlayPause.setClickable(true);
queueControlBinding.controlPlayPause.setVisibility(View.VISIBLE);
queueControlBinding.controlProgressBar.setVisibility(View.GONE);
@@ -650,15 +649,15 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void onPlayModeChanged(final int repeatMode, final boolean shuffled) {
switch (repeatMode) {
- case Player.REPEAT_MODE_OFF:
+ case com.google.android.exoplayer2.Player.REPEAT_MODE_OFF:
queueControlBinding.controlRepeat
.setImageResource(R.drawable.exo_controls_repeat_off);
break;
- case Player.REPEAT_MODE_ONE:
+ case com.google.android.exoplayer2.Player.REPEAT_MODE_ONE:
queueControlBinding.controlRepeat
.setImageResource(R.drawable.exo_controls_repeat_one);
break;
- case Player.REPEAT_MODE_ALL:
+ case com.google.android.exoplayer2.Player.REPEAT_MODE_ALL:
queueControlBinding.controlRepeat
.setImageResource(R.drawable.exo_controls_repeat_all);
break;
@@ -700,9 +699,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
// using rootView.getContext() because getApplicationContext() didn't work
final Context context = queueControlBinding.getRoot().getContext();
item.setIcon(ThemeHelper.resolveResourceIdFromAttr(context,
- player.isMuted()
- ? R.attr.ic_volume_off
- : R.attr.ic_volume_up));
+ player.isMuted() ? R.attr.ic_volume_off : R.attr.ic_volume_up));
}
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
deleted file mode 100644
index 8894646c0..000000000
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
+++ /dev/null
@@ -1,1036 +0,0 @@
-/*
- * Copyright 2017 Mauricio Colli
- * VideoPlayer.java is part of NewPipe
- *
- * License: GPL-3.0+
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.schabi.newpipe.player;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.os.Build;
-import android.os.Handler;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.PopupMenu;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.content.res.AppCompatResources;
-import androidx.preference.PreferenceManager;
-
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.TrackGroup;
-import com.google.android.exoplayer2.source.TrackGroupArray;
-import com.google.android.exoplayer2.text.CaptionStyleCompat;
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
-import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
-import com.google.android.exoplayer2.ui.SubtitleView;
-import com.google.android.exoplayer2.video.VideoListener;
-
-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.player.helper.PlayerHelper;
-import org.schabi.newpipe.player.playqueue.PlayQueueItem;
-import org.schabi.newpipe.player.resolver.MediaSourceTag;
-import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
-import org.schabi.newpipe.util.AnimationUtils;
-import org.schabi.newpipe.views.ExpandableSurfaceView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
-import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
-
-/**
- * Base for video players.
- *
- * @author mauriciocolli
- */
-@SuppressWarnings({"WeakerAccess"})
-public abstract class VideoPlayer extends BasePlayer
- implements VideoListener,
- SeekBar.OnSeekBarChangeListener,
- View.OnClickListener,
- Player.EventListener,
- PopupMenu.OnMenuItemClickListener,
- PopupMenu.OnDismissListener {
- public final String TAG;
- public static final boolean DEBUG = BasePlayer.DEBUG;
-
- /*//////////////////////////////////////////////////////////////////////////
- // Player
- //////////////////////////////////////////////////////////////////////////*/
-
- 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
-
- protected static final int RENDERER_UNAVAILABLE = -1;
-
- @NonNull
- private final VideoPlaybackResolver resolver;
-
- private List availableStreams;
- private int selectedStreamIndex;
-
- protected boolean wasPlaying = false;
-
- /*//////////////////////////////////////////////////////////////////////////
- // Views
- //////////////////////////////////////////////////////////////////////////*/
-
- protected PlayerBinding binding;
-
- protected SeekBar playbackSeekBar;
- protected TextView qualityTextView;
- protected TextView playbackSpeed;
-
- private ValueAnimator controlViewAnimator;
- private final Handler controlsVisibilityHandler = new Handler();
-
- boolean isSomePopupMenuVisible = false;
-
- private final int qualityPopupMenuGroupId = 69;
- private PopupMenu qualityPopupMenu;
-
- private final int playbackSpeedPopupMenuGroupId = 79;
- private PopupMenu playbackSpeedPopupMenu;
-
- private final int captionPopupMenuGroupId = 89;
- private PopupMenu captionPopupMenu;
-
- ///////////////////////////////////////////////////////////////////////////
-
- protected VideoPlayer(final String debugTag, final Context context) {
- super(context);
- this.TAG = debugTag;
- this.resolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
- }
-
- // workaround to match normalized captions like english to English or deutsch to Deutsch
- private static boolean containsCaseInsensitive(final List list, final String toFind) {
- for (final String i : list) {
- if (i.equalsIgnoreCase(toFind)) {
- return true;
- }
- }
- return false;
- }
-
- public void setup(@NonNull final PlayerBinding playerBinding) {
- initViews(playerBinding);
- if (simpleExoPlayer == null) {
- initPlayer(true);
- }
- initListeners();
- }
-
- public void initViews(@NonNull final PlayerBinding playerBinding) {
- binding = playerBinding;
- playbackSeekBar = (SeekBar) binding.playbackSeekBar;
- qualityTextView = (TextView) binding.qualityTextView;
- playbackSpeed = (TextView) binding.playbackSpeed;
-
- final float captionScale = PlayerHelper.getCaptionScale(context);
- final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context);
- setupSubtitleView(binding.subtitleView, captionScale, captionStyle);
-
- ((TextView) binding.resizeTextView).setText(PlayerHelper.resizeTypeOf(context,
- binding.surfaceView.getResizeMode()));
-
- playbackSeekBar.getThumb().setColorFilter(new PorterDuffColorFilter(Color.RED,
- PorterDuff.Mode.SRC_IN));
- playbackSeekBar.getProgressDrawable().setColorFilter(new PorterDuffColorFilter(Color.RED,
- PorterDuff.Mode.MULTIPLY));
-
- qualityPopupMenu = new PopupMenu(context, qualityTextView);
- playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeed);
- captionPopupMenu = new PopupMenu(context, binding.captionTextView);
-
- binding.progressBarLoadingPanel.getIndeterminateDrawable()
- .setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY));
- }
-
- protected abstract void setupSubtitleView(@NonNull SubtitleView view, float captionScale,
- @NonNull CaptionStyleCompat captionStyle);
-
- @Override
- public void initListeners() {
- playbackSeekBar.setOnSeekBarChangeListener(this);
- binding.playbackSpeed.setOnClickListener(this);
- binding.qualityTextView.setOnClickListener(this);
- binding.captionTextView.setOnClickListener(this);
- binding.resizeTextView.setOnClickListener(this);
- binding.playbackLiveSync.setOnClickListener(this);
- }
-
- @Override
- public void initPlayer(final boolean playOnReady) {
- super.initPlayer(playOnReady);
-
- // Setup video view
- simpleExoPlayer.setVideoSurfaceView(binding.surfaceView);
- simpleExoPlayer.addVideoListener(this);
-
- // Setup subtitle view
- simpleExoPlayer.addTextOutput(binding.subtitleView);
-
- // Setup audio session with onboard equalizer
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- trackSelector.setParameters(trackSelector.buildUponParameters()
- .setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
- }
- }
-
- @Override
- public void handleIntent(final Intent intent) {
- if (intent == null) {
- return;
- }
-
- if (intent.hasExtra(PLAYBACK_QUALITY)) {
- setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
- }
-
- super.handleIntent(intent);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // UI Builders
- //////////////////////////////////////////////////////////////////////////*/
-
- public void buildQualityMenu() {
- if (qualityPopupMenu == null) {
- return;
- }
-
- qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
- for (int i = 0; i < availableStreams.size(); i++) {
- final VideoStream videoStream = availableStreams.get(i);
- qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat
- .getNameById(videoStream.getFormatId()) + " " + videoStream.resolution);
- }
- if (getSelectedVideoStream() != null) {
- qualityTextView.setText(getSelectedVideoStream().resolution);
- }
- qualityPopupMenu.setOnMenuItemClickListener(this);
- qualityPopupMenu.setOnDismissListener(this);
- }
-
- private void buildPlaybackSpeedMenu() {
- if (playbackSpeedPopupMenu == null) {
- return;
- }
-
- playbackSpeedPopupMenu.getMenu().removeGroup(playbackSpeedPopupMenuGroupId);
- for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) {
- playbackSpeedPopupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE,
- formatSpeed(PLAYBACK_SPEEDS[i]));
- }
- playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
- playbackSpeedPopupMenu.setOnMenuItemClickListener(this);
- playbackSpeedPopupMenu.setOnDismissListener(this);
- }
-
- private void buildCaptionMenu(final List availableLanguages) {
- if (captionPopupMenu == null) {
- return;
- }
- captionPopupMenu.getMenu().removeGroup(captionPopupMenuGroupId);
-
- final String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context)
- .getString(context.getString(R.string.caption_user_set_key), null);
- /*
- * only search for autogenerated cc as fallback
- * if "(auto-generated)" was not already selected
- * we are only looking for "(" instead of "(auto-generated)" to hopefully get all
- * internationalized variants such as "(automatisch-erzeugt)" and so on
- */
- boolean searchForAutogenerated = userPreferredLanguage != null
- && !userPreferredLanguage.contains("(");
-
- // Add option for turning off caption
- final MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
- 0, Menu.NONE, R.string.caption_none);
- captionOffItem.setOnMenuItemClickListener(menuItem -> {
- final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
- if (textRendererIndex != RENDERER_UNAVAILABLE) {
- trackSelector.setParameters(trackSelector.buildUponParameters()
- .setRendererDisabled(textRendererIndex, true));
- }
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- prefs.edit().remove(context.getString(R.string.caption_user_set_key)).commit();
- 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(captionPopupMenuGroupId,
- i + 1, Menu.NONE, captionLanguage);
- captionItem.setOnMenuItemClickListener(menuItem -> {
- final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
- if (textRendererIndex != RENDERER_UNAVAILABLE) {
- trackSelector.setPreferredTextLanguage(captionLanguage);
- trackSelector.setParameters(trackSelector.buildUponParameters()
- .setRendererDisabled(textRendererIndex, false));
- final SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(context);
- prefs.edit().putString(context.getString(R.string.caption_user_set_key),
- captionLanguage).commit();
- }
- return true;
- });
- // apply caption language from previous user preference
- if (userPreferredLanguage != null
- && (captionLanguage.equals(userPreferredLanguage)
- || (searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage))
- || (userPreferredLanguage.contains("(") && captionLanguage.startsWith(
- userPreferredLanguage.substring(0, userPreferredLanguage.indexOf('(')))))) {
- final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
- if (textRendererIndex != RENDERER_UNAVAILABLE) {
- trackSelector.setPreferredTextLanguage(captionLanguage);
- trackSelector.setParameters(trackSelector.buildUponParameters()
- .setRendererDisabled(textRendererIndex, false));
- }
- searchForAutogenerated = false;
- }
- }
- captionPopupMenu.setOnDismissListener(this);
- }
-
- private void updateStreamRelatedViews() {
- if (getCurrentMetadata() == null) {
- return;
- }
-
- final MediaSourceTag tag = getCurrentMetadata();
- final StreamInfo metadata = tag.getMetadata();
-
- binding.qualityTextView.setVisibility(View.GONE);
- binding.playbackSpeed.setVisibility(View.GONE);
-
- binding.playbackEndTime.setVisibility(View.GONE);
- binding.playbackLiveSync.setVisibility(View.GONE);
-
- switch (metadata.getStreamType()) {
- case 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:
- if (metadata.getVideoStreams().size() + metadata.getVideoOnlyStreams().size()
- == 0) {
- break;
- }
-
- availableStreams = tag.getSortedAvailableVideoStreams();
- selectedStreamIndex = tag.getSelectedVideoStreamIndex();
- 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);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Playback Listener
- //////////////////////////////////////////////////////////////////////////*/
-
- protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver();
-
- @Override
- protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
- super.onMetadataChanged(tag);
- updateStreamRelatedViews();
- }
-
- @Override
- @Nullable
- public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
- return resolver.resolve(info);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // States Implementation
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onBlocked() {
- super.onBlocked();
-
- controlsVisibilityHandler.removeCallbacksAndMessages(null);
- animateView(binding.playbackControlRoot, false, DEFAULT_CONTROLS_DURATION);
-
- playbackSeekBar.setEnabled(false);
- playbackSeekBar.getThumb().setColorFilter(new PorterDuffColorFilter(Color.RED,
- PorterDuff.Mode.SRC_IN));
-
- binding.loadingPanel.setBackgroundColor(Color.BLACK);
- animateView(binding.loadingPanel, true, 0);
- animateView(binding.surfaceForeground, true, 100);
- }
-
- @Override
- public void onPlaying() {
- super.onPlaying();
-
- updateStreamRelatedViews();
-
- showAndAnimateControl(-1, true);
-
- playbackSeekBar.setEnabled(true);
- playbackSeekBar.getThumb().setColorFilter(new PorterDuffColorFilter(Color.RED,
- PorterDuff.Mode.SRC_IN));
-
- binding.loadingPanel.setVisibility(View.GONE);
-
- animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false,
- 200);
- }
-
- @Override
- public void onBuffering() {
- if (DEBUG) {
- Log.d(TAG, "onBuffering() called");
- }
- binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT);
- }
-
- @Override
- public void onPaused() {
- if (DEBUG) {
- Log.d(TAG, "onPaused() called");
- }
- showControls(400);
- binding.loadingPanel.setVisibility(View.GONE);
- }
-
- @Override
- public void onPausedSeek() {
- if (DEBUG) {
- Log.d(TAG, "onPausedSeek() called");
- }
- showAndAnimateControl(-1, true);
- }
-
- @Override
- public void onCompleted() {
- super.onCompleted();
-
- showControls(500);
- animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false,
- 200);
- binding.loadingPanel.setVisibility(View.GONE);
-
- animateView(binding.surfaceForeground, true, 100);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // ExoPlayer Video Listener
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onTracksChanged(@NonNull final TrackGroupArray trackGroups,
- @NonNull final TrackSelectionArray trackSelections) {
- super.onTracksChanged(trackGroups, trackSelections);
- onTextTrackUpdate();
- }
-
- @Override
- public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) {
- super.onPlaybackParametersChanged(playbackParameters);
- playbackSpeed.setText(formatSpeed(playbackParameters.speed));
- }
-
- @Override
- public void onVideoSizeChanged(final int width, final int height,
- final int unappliedRotationDegrees,
- final float pixelWidthHeightRatio) {
- if (DEBUG) {
- Log.d(TAG, "onVideoSizeChanged() called with: "
- + "width / height = [" + width + " / " + height
- + " = " + (((float) width) / height) + "], "
- + "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
- + "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
- }
- binding.surfaceView.setAspectRatio(((float) width) / height);
- }
-
- @Override
- public void onRenderedFirstFrame() {
- animateView(binding.surfaceForeground, false, 100);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // ExoPlayer Track Updates
- //////////////////////////////////////////////////////////////////////////*/
-
- private void onTextTrackUpdate() {
- final int textRenderer = getRendererIndex(C.TRACK_TYPE_TEXT);
-
- if (binding == null) {
- return;
- }
- if (trackSelector.getCurrentMappedTrackInfo() == null
- || textRenderer == RENDERER_UNAVAILABLE) {
- binding.captionTextView.setVisibility(View.GONE);
- return;
- }
-
- final TrackGroupArray textTracks = trackSelector.getCurrentMappedTrackInfo()
- .getTrackGroups(textRenderer);
-
- // Extract all loaded languages
- final List availableLanguages = new ArrayList<>(textTracks.length);
- for (int i = 0; i < textTracks.length; i++) {
- final TrackGroup textTrack = textTracks.get(i);
- if (textTrack.length > 0 && textTrack.getFormat(0) != null) {
- availableLanguages.add(textTrack.getFormat(0).language);
- }
- }
-
- // Normalize mismatching language strings
- final String preferredLanguage = trackSelector.getPreferredTextLanguage();
- // Build UI
- buildCaptionMenu(availableLanguages);
- if (trackSelector.getParameters().getRendererDisabled(textRenderer)
- || preferredLanguage == null || (!availableLanguages.contains(preferredLanguage)
- && !containsCaseInsensitive(availableLanguages, preferredLanguage))) {
- binding.captionTextView.setText(R.string.caption_none);
- } else {
- binding.captionTextView.setText(preferredLanguage);
- }
- binding.captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE
- : View.VISIBLE);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // General Player
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onPrepared(final boolean playWhenReady) {
- if (DEBUG) {
- Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
- }
-
- playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
- binding.playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
- playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
-
- super.onPrepared(playWhenReady);
- }
-
- @Override
- public void destroy() {
- super.destroy();
- if (binding != null) {
- binding.endScreen.setImageBitmap(null);
- }
- }
-
- @Override
- public void onUpdateProgress(final int currentProgress, final int duration,
- final int bufferPercent) {
- if (!isPrepared()) {
- return;
- }
-
- if (duration != playbackSeekBar.getMax()) {
- binding.playbackEndTime.setText(getTimeString(duration));
- playbackSeekBar.setMax(duration);
- }
- if (currentState != STATE_PAUSED) {
- if (currentState != STATE_PAUSED_SEEK) {
- playbackSeekBar.setProgress(currentProgress);
- }
- binding.playbackCurrentTime.setText(getTimeString(currentProgress));
- }
- if (simpleExoPlayer.isLoading() || bufferPercent > 90) {
- playbackSeekBar.setSecondaryProgress(
- (int) (playbackSeekBar.getMax() * ((float) bufferPercent / 100)));
- }
- if (DEBUG && bufferPercent % 20 == 0) { //Limit log
- Log.d(TAG, "updateProgress() called with: "
- + "isVisible = " + isControlsVisible() + ", "
- + "currentProgress = [" + currentProgress + "], "
- + "duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]");
- }
- binding.playbackLiveSync.setClickable(!isLiveEdge());
- }
-
- @Override
- public void onLoadingComplete(final String imageUri, final View view,
- final Bitmap loadedImage) {
- super.onLoadingComplete(imageUri, view, loadedImage);
- if (loadedImage != null) {
- binding.endScreen.setImageBitmap(loadedImage);
- }
- }
-
- protected void toggleFullscreen() {
- changeState(STATE_BLOCKED);
- }
-
- @Override
- public void onFastRewind() {
- super.onFastRewind();
- showAndAnimateControl(R.drawable.ic_fast_rewind_white_24dp, true);
- }
-
- @Override
- public void onFastForward() {
- super.onFastForward();
- showAndAnimateControl(R.drawable.ic_fast_forward_white_24dp, true);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // OnClick related
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onClick(final View v) {
- if (DEBUG) {
- Log.d(TAG, "onClick() called with: v = [" + v + "]");
- }
- if (v.getId() == binding.qualityTextView.getId()) {
- onQualitySelectorClicked();
- } else if (v.getId() == binding.playbackSpeed.getId()) {
- onPlaybackSpeedClicked();
- } else if (v.getId() == binding.resizeTextView.getId()) {
- onResizeClicked();
- } else if (v.getId() == binding.captionTextView.getId()) {
- onCaptionClicked();
- } else if (v.getId() == binding.playbackLiveSync.getId()) {
- seekToDefault();
- }
- }
-
- /**
- * Called when an item of the quality selector or the playback speed selector is selected.
- */
- @Override
- public boolean onMenuItemClick(final MenuItem menuItem) {
- if (DEBUG) {
- Log.d(TAG, "onMenuItemClick() called with: "
- + "menuItem = [" + menuItem + "], "
- + "menuItem.getItemId = [" + menuItem.getItemId() + "]");
- }
-
- if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
- final int menuItemIndex = menuItem.getItemId();
- if (selectedStreamIndex == menuItemIndex || availableStreams == null
- || availableStreams.size() <= menuItemIndex) {
- return true;
- }
-
- final String newResolution = availableStreams.get(menuItemIndex).resolution;
- setRecovery();
- setPlaybackQuality(newResolution);
- reload();
-
- qualityTextView.setText(menuItem.getTitle());
- return true;
- } else if (playbackSpeedPopupMenuGroupId == menuItem.getGroupId()) {
- final int speedIndex = menuItem.getItemId();
- final float speed = PLAYBACK_SPEEDS[speedIndex];
-
- setPlaybackSpeed(speed);
- playbackSpeed.setText(formatSpeed(speed));
- }
-
- return false;
- }
-
- /**
- * Called when some popup menu is dismissed.
- */
- @Override
- public void onDismiss(final PopupMenu menu) {
- if (DEBUG) {
- Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]");
- }
- isSomePopupMenuVisible = false;
- if (getSelectedVideoStream() != null) {
- qualityTextView.setText(getSelectedVideoStream().resolution);
- }
- }
-
- public void onQualitySelectorClicked() {
- if (DEBUG) {
- Log.d(TAG, "onQualitySelectorClicked() called");
- }
- qualityPopupMenu.show();
- isSomePopupMenuVisible = true;
-
- final VideoStream videoStream = getSelectedVideoStream();
- if (videoStream != null) {
- final String qualityText = MediaFormat.getNameById(videoStream.getFormatId()) + " "
- + videoStream.resolution;
- qualityTextView.setText(qualityText);
- }
-
- wasPlaying = simpleExoPlayer.getPlayWhenReady();
- }
-
- public void onPlaybackSpeedClicked() {
- if (DEBUG) {
- Log.d(TAG, "onPlaybackSpeedClicked() called");
- }
- playbackSpeedPopupMenu.show();
- isSomePopupMenuVisible = true;
- }
-
- private void onCaptionClicked() {
- if (DEBUG) {
- Log.d(TAG, "onCaptionClicked() called");
- }
- captionPopupMenu.show();
- isSomePopupMenuVisible = true;
- }
-
- void onResizeClicked() {
- if (binding != null) {
- final int currentResizeMode = binding.surfaceView.getResizeMode();
- final int newResizeMode = nextResizeMode(currentResizeMode);
- setResizeMode(newResizeMode);
- }
- }
-
- protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
- binding.surfaceView.setResizeMode(resizeMode);
- ((TextView) binding.resizeTextView).setText(PlayerHelper.resizeTypeOf(context,
- resizeMode));
- }
-
- protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode);
-
- /*//////////////////////////////////////////////////////////////////////////
- // SeekBar Listener
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onProgressChanged(final SeekBar seekBar, final int progress,
- final boolean fromUser) {
- if (DEBUG && fromUser) {
- Log.d(TAG, "onProgressChanged() called with: "
- + "seekBar = [" + seekBar + "], progress = [" + progress + "]");
- }
- if (fromUser) {
- binding.currentDisplaySeek.setText(getTimeString(progress));
- }
- }
-
- @Override
- public void onStartTrackingTouch(final SeekBar seekBar) {
- if (DEBUG) {
- Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]");
- }
- if (getCurrentState() != STATE_PAUSED_SEEK) {
- changeState(STATE_PAUSED_SEEK);
- }
-
- wasPlaying = simpleExoPlayer.getPlayWhenReady();
- if (isPlaying()) {
- simpleExoPlayer.setPlayWhenReady(false);
- }
-
- showControls(0);
- animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, true,
- DEFAULT_CONTROLS_DURATION);
- }
-
- @Override
- 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.setPlayWhenReady(true);
- }
-
- binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
- animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false,
- 200);
-
- if (getCurrentState() == STATE_PAUSED_SEEK) {
- changeState(STATE_BUFFERING);
- }
- if (!isProgressLoopRunning()) {
- startProgressLoop();
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Utils
- //////////////////////////////////////////////////////////////////////////*/
-
- public int getRendererIndex(final int trackIndex) {
- if (simpleExoPlayer == null) {
- return RENDERER_UNAVAILABLE;
- }
-
- for (int t = 0; t < simpleExoPlayer.getRendererCount(); t++) {
- if (simpleExoPlayer.getRendererType(t) == trackIndex) {
- return t;
- }
- }
-
- return RENDERER_UNAVAILABLE;
- }
-
- public boolean isControlsVisible() {
- return binding != null
- && binding.playbackControlRoot.getVisibility() == View.VISIBLE;
- }
-
- /**
- * Show a animation, and depending on goneOnEnd, will stay on the screen or be gone.
- *
- * @param drawableId the drawable that will be used to animate,
- * pass -1 to clear any animation that is visible
- * @param goneOnEnd will set the animation view to GONE on the end of the animation
- */
- public void showAndAnimateControl(final int drawableId, final boolean goneOnEnd) {
- if (DEBUG) {
- Log.d(TAG, "showAndAnimateControl() called with: "
- + "drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]");
- }
- if (controlViewAnimator != null && controlViewAnimator.isRunning()) {
- if (DEBUG) {
- Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning");
- }
- controlViewAnimator.end();
- }
-
- if (drawableId == -1) {
- if (binding.controlAnimationView.getVisibility() == View.VISIBLE) {
- controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(
- binding.controlAnimationView,
- PropertyValuesHolder.ofFloat(View.ALPHA, 1.0f, 0.0f),
- PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1.0f),
- PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1.0f)
- ).setDuration(DEFAULT_CONTROLS_DURATION);
- controlViewAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- binding.controlAnimationView.setVisibility(View.GONE);
- }
- });
- controlViewAnimator.start();
- }
- return;
- }
-
- final float scaleFrom = goneOnEnd ? 1f : 1f;
- final float scaleTo = goneOnEnd ? 1.8f : 1.4f;
- final float alphaFrom = goneOnEnd ? 1f : 0f;
- final float alphaTo = goneOnEnd ? 0f : 1f;
-
-
- controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(
- binding.controlAnimationView,
- PropertyValuesHolder.ofFloat(View.ALPHA, alphaFrom, alphaTo),
- PropertyValuesHolder.ofFloat(View.SCALE_X, scaleFrom, scaleTo),
- PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleFrom, scaleTo)
- );
- controlViewAnimator.setDuration(goneOnEnd ? 1000 : 500);
- controlViewAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- binding.controlAnimationView.setVisibility(goneOnEnd ? View.GONE
- : View.VISIBLE);
- }
- });
-
- binding.controlAnimationView.setVisibility(View.VISIBLE);
- binding.controlAnimationView.setImageDrawable(AppCompatResources.getDrawable(context,
- drawableId));
- controlViewAnimator.start();
- }
-
- public boolean isSomePopupMenuVisible() {
- return isSomePopupMenuVisible;
- }
-
- public void showControlsThenHide() {
- if (DEBUG) {
- Log.d(TAG, "showControlsThenHide() called");
- }
-
- final int hideTime = binding.playbackControlRoot.isInTouchMode()
- ? DEFAULT_CONTROLS_HIDE_TIME
- : DPAD_CONTROLS_HIDE_TIME;
-
- showHideShadow(true, DEFAULT_CONTROLS_DURATION, 0);
- animateView(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, 0,
- () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime));
- }
-
- public void showControls(final long duration) {
- if (DEBUG) {
- Log.d(TAG, "showControls() called");
- }
- controlsVisibilityHandler.removeCallbacksAndMessages(null);
- showHideShadow(true, duration, 0);
- animateView(binding.playbackControlRoot, true, duration);
- }
-
- public void safeHideControls(final long duration, final long delay) {
- if (DEBUG) {
- Log.d(TAG, "safeHideControls() called with: delay = [" + delay + "]");
- }
- if (binding.getRoot().isInTouchMode()) {
- controlsVisibilityHandler.removeCallbacksAndMessages(null);
- controlsVisibilityHandler.postDelayed(
- () -> animateView(binding.playbackControlRoot, false, duration),
- delay);
- }
- }
-
- public void hideControls(final long duration, final long delay) {
- if (DEBUG) {
- Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
- }
- controlsVisibilityHandler.removeCallbacksAndMessages(null);
- controlsVisibilityHandler.postDelayed(() -> {
- showHideShadow(false, duration, 0);
- animateView(binding.playbackControlRoot, false, duration);
- }, delay);
- }
-
- public void hideControlsAndButton(final long duration, final long delay, final View button) {
- if (DEBUG) {
- Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
- }
- controlsVisibilityHandler.removeCallbacksAndMessages(null);
- controlsVisibilityHandler
- .postDelayed(hideControlsAndButtonHandler(duration, button), delay);
- }
-
- private Runnable hideControlsAndButtonHandler(final long duration, final View videoPlayPause) {
- return () -> {
- videoPlayPause.setVisibility(View.INVISIBLE);
- animateView(binding.playbackControlRoot, false, duration);
- };
- }
-
- void showHideShadow(final boolean show, final long duration, final long delay) {
- animateView(binding.playerTopShadow, show, duration, delay, null);
- animateView(binding.playerBottomShadow, show, duration, delay, null);
- }
-
- public abstract void hideSystemUIIfNeeded();
-
- /*//////////////////////////////////////////////////////////////////////////
- // Getters and Setters
- //////////////////////////////////////////////////////////////////////////*/
-
- @Nullable
- public String getPlaybackQuality() {
- return resolver.getPlaybackQuality();
- }
-
- public void setPlaybackQuality(final String quality) {
- this.resolver.setPlaybackQuality(quality);
- }
-
- public ExpandableSurfaceView getSurfaceView() {
- return binding.surfaceView;
- }
-
- public boolean wasPlaying() {
- return wasPlaying;
- }
-
- @Nullable
- public VideoStream getSelectedVideoStream() {
- return (selectedStreamIndex >= 0 && availableStreams != null
- && availableStreams.size() > selectedStreamIndex)
- ? availableStreams.get(selectedStreamIndex) : null;
- }
-
- public Handler getControlsVisibilityHandler() {
- return controlsVisibilityHandler;
- }
-
- @NonNull
- public View getRootView() {
- return binding.getRoot();
- }
-
- @NonNull
- public View getLoadingPanel() {
- return binding.loadingPanel;
- }
-
- @NonNull
- public View getPlaybackControlRoot() {
- return binding.playbackControlRoot;
- }
-
- @NonNull
- public TextView getCurrentDisplaySeek() {
- return binding.currentDisplaySeek;
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
deleted file mode 100644
index 949b11374..000000000
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
+++ /dev/null
@@ -1,2076 +0,0 @@
-/*
- * Copyright 2017 Mauricio Colli
- * Part of NewPipe
- *
- * License: GPL-3.0+
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.schabi.newpipe.player;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.SuppressLint;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-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.MotionEvent;
-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.PopupMenu;
-import android.widget.ProgressBar;
-import android.widget.RelativeLayout;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.content.res.AppCompatResources;
-import androidx.core.content.ContextCompat;
-import androidx.core.view.DisplayCutoutCompat;
-import androidx.core.view.ViewCompat;
-import androidx.preference.PreferenceManager;
-import androidx.recyclerview.widget.ItemTouchHelper;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.text.CaptionStyleCompat;
-import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
-import com.google.android.exoplayer2.ui.SubtitleView;
-import com.nostra13.universalimageloader.core.assist.FailReason;
-
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.databinding.PlayerBinding;
-import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
-import org.schabi.newpipe.extractor.stream.StreamInfo;
-import org.schabi.newpipe.extractor.stream.VideoStream;
-import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
-import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
-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.PlaybackParameterDialog;
-import org.schabi.newpipe.player.helper.PlayerHelper;
-import org.schabi.newpipe.player.playqueue.PlayQueue;
-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.MediaSourceTag;
-import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
-import org.schabi.newpipe.util.AnimationUtils;
-import org.schabi.newpipe.util.DeviceUtils;
-import org.schabi.newpipe.util.KoreUtil;
-import org.schabi.newpipe.util.ListHelper;
-import org.schabi.newpipe.util.NavigationHelper;
-import org.schabi.newpipe.util.ShareUtils;
-
-import java.util.List;
-
-import static org.schabi.newpipe.extractor.ServiceList.YouTube;
-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_OPEN_CONTROLS;
-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.helper.PlayerHelper.globalScreenOrientationLocked;
-import static org.schabi.newpipe.util.AnimationUtils.Type.SLIDE_AND_ALPHA;
-import static org.schabi.newpipe.util.AnimationUtils.animateRotation;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
-import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
-import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
-import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
-
-/**
- * Unified UI for all players.
- *
- * @author mauriciocolli
- */
-
-public class VideoPlayerImpl extends VideoPlayer
- implements View.OnLayoutChangeListener,
- PlaybackParameterDialog.Callback,
- View.OnLongClickListener {
- private static final String TAG = ".VideoPlayerImpl";
-
- static final String POPUP_SAVED_WIDTH = "popup_saved_width";
- static final String POPUP_SAVED_X = "popup_saved_x";
- static final String POPUP_SAVED_Y = "popup_saved_y";
- private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
- private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS
- | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
-
- private static final float MAX_GESTURE_LENGTH = 0.75f;
-
- private ItemTouchHelper itemTouchHelper;
-
- private boolean queueVisible;
- private MainPlayer.PlayerType playerType = MainPlayer.PlayerType.VIDEO;
-
- private int maxGestureLength;
-
- private boolean audioOnly = false;
- private boolean isFullscreen = false;
- private boolean isVerticalVideo = false;
- private boolean fragmentIsVisible = false;
- boolean shouldUpdateOnProgress;
-
- private final MainPlayer service;
- private PlayerServiceEventListener fragmentListener;
- private PlayerEventListener activityListener;
- private GestureDetector gestureDetector;
- private final SharedPreferences defaultPreferences;
- private ContentObserver settingsContentObserver;
- @NonNull
- private final AudioPlaybackResolver resolver;
-
- // Popup
- private WindowManager.LayoutParams popupLayoutParams;
- public WindowManager windowManager;
-
- private PlayerPopupCloseOverlayBinding closeOverlayBinding;
-
- public boolean isPopupClosing = false;
-
- private float screenWidth;
- private float screenHeight;
- private float popupWidth;
- private float popupHeight;
- private float minimumWidth;
- private float minimumHeight;
- private float maximumWidth;
- private float maximumHeight;
- // Popup end
-
-
- @Override
- public void handleIntent(final Intent intent) {
- if (intent.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY) == null) {
- return;
- }
-
- final MainPlayer.PlayerType oldPlayerType = playerType;
- choosePlayerTypeFromIntent(intent);
- audioOnly = audioPlayerSelected();
-
- // We need to setup audioOnly before super(), see "sourceOf"
- super.handleIntent(intent);
-
- if (oldPlayerType != playerType && playQueue != null) {
- // If playerType changes from one to another we should reload the player
- // (to disable/enable video stream or to set quality)
- setRecovery();
- reload();
- }
-
- setupElementsVisibility();
- setupElementsSize();
-
- if (audioPlayerSelected()) {
- service.removeViewFromParent();
- } else if (popupPlayerSelected()) {
- getRootView().setVisibility(View.VISIBLE);
- initPopup();
- initPopupCloseOverlay();
- binding.playPauseButton.requestFocus();
- } else {
- getRootView().setVisibility(View.VISIBLE);
- initVideoPlayer();
- onQueueClosed();
- // Android TV: without it focus will frame the whole player
- binding.playPauseButton.requestFocus();
-
- if (simpleExoPlayer.getPlayWhenReady()) {
- onPlay();
- } else {
- onPause();
- }
- }
- NavigationHelper.sendPlayerStartedEvent(service);
- }
-
- VideoPlayerImpl(final MainPlayer service) {
- super("MainPlayer" + TAG, service);
- this.service = service;
- this.shouldUpdateOnProgress = true;
- this.windowManager = ContextCompat.getSystemService(service, WindowManager.class);
- this.defaultPreferences = PreferenceManager.getDefaultSharedPreferences(service);
- this.resolver = new AudioPlaybackResolver(context, dataSource);
- }
-
- @SuppressLint("ClickableViewAccessibility")
- @Override
- public void initViews(@NonNull final PlayerBinding binding) {
- super.initViews(binding);
-
- binding.titleTextView.setSelected(true);
- binding.channelTextView.setSelected(true);
-
- // Prevent hiding of bottom sheet via swipe inside queue
- binding.playQueue.setNestedScrollingEnabled(false);
- }
-
- @Override
- protected void setupSubtitleView(final @NonNull SubtitleView view,
- final float captionScale,
- @NonNull final CaptionStyleCompat captionStyle) {
- if (popupPlayerSelected()) {
- final float captionRatio = (captionScale - 1.0f) / 5.0f + 1.0f;
- view.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);
- view.setFixedTextSize(TypedValue.COMPLEX_UNIT_PX,
- (float) minimumLength / captionRatioInverse);
- }
- view.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT);
- view.setStyle(captionStyle);
- }
-
- /**
- * 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.metadataView.setVisibility(View.GONE);
- binding.queueButton.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();
- onQueueClosed();
- } else {
- binding.fullScreenButton.setVisibility(View.GONE);
- setupScreenRotationButton();
- binding.resizeTextView.setVisibility(View.VISIBLE);
- binding.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(service,
- R.drawable.ic_expand_more_white_24dp));
- binding.share.setVisibility(View.VISIBLE);
- showHideKodiButton();
- 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.GONE);
- binding.channelTextView.setVisibility(View.GONE);
- } else {
- binding.titleTextView.setVisibility(View.VISIBLE);
- binding.channelTextView.setVisibility(View.VISIBLE);
- }
- 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() {
- if (popupPlayerSelected()) {
- final int controlsPadding = service.getResources()
- .getDimensionPixelSize(R.dimen.player_popup_controls_padding);
- final int buttonsPadding = service.getResources()
- .getDimensionPixelSize(R.dimen.player_popup_buttons_padding);
- binding.topControls.setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
- binding.bottomControls.setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
- binding.qualityTextView.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
- buttonsPadding);
- binding.playbackSpeed.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
- buttonsPadding);
- binding.captionTextView.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
- buttonsPadding);
- binding.playbackSpeed.setMinimumWidth(0);
- } else if (videoPlayerSelected()) {
- final int buttonsMinWidth = service.getResources()
- .getDimensionPixelSize(R.dimen.player_main_buttons_min_width);
- final int playerTopPadding = service.getResources()
- .getDimensionPixelSize(R.dimen.player_main_top_padding);
- final int controlsPadding = service.getResources()
- .getDimensionPixelSize(R.dimen.player_main_controls_padding);
- final int buttonsPadding = service.getResources()
- .getDimensionPixelSize(R.dimen.player_main_buttons_padding);
- binding.topControls.setPaddingRelative(controlsPadding, playerTopPadding,
- controlsPadding, 0);
- binding.bottomControls.setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
- binding.qualityTextView.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
- buttonsPadding);
- binding.playbackSpeed.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
- buttonsPadding);
- binding.playbackSpeed.setMinimumWidth(buttonsMinWidth);
- binding.captionTextView.setPadding(buttonsPadding, buttonsPadding, buttonsPadding,
- buttonsPadding);
- }
- }
-
- @Override
- public void initListeners() {
- super.initListeners();
-
- final PlayerGestureListener listener = new PlayerGestureListener(this, service);
- gestureDetector = new GestureDetector(context, listener);
- getRootView().setOnTouchListener(listener);
-
- binding.queueButton.setOnClickListener(this);
- binding.repeatButton.setOnClickListener(this);
- binding.shuffleButton.setOnClickListener(this);
-
- 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.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();
- }
- };
- service.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false,
- settingsContentObserver);
- getRootView().addOnLayoutChangeListener(this);
-
- ViewCompat.setOnApplyWindowInsetsListener(binding.playQueuePanel,
- (view, windowInsets) -> {
- final DisplayCutoutCompat cutout = windowInsets.getDisplayCutout();
- if (cutout != null) {
- view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
- cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
- }
- return windowInsets;
- });
-
- // PlaybackControlRoot already consumed window insets but we should pass them to
- // player_overlays 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()));
- }
-
- public boolean onKeyDown(final int keyCode) {
- switch (keyCode) {
- default:
- break;
- case KeyEvent.KEYCODE_SPACE:
- if (isFullscreen) {
- onPlayPause();
- }
- break;
- case KeyEvent.KEYCODE_BACK:
- if (DeviceUtils.isTv(service) && 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 (getRootView().hasFocus() && !binding.playbackControlRoot.hasFocus()) {
- // do not interfere with focus in playlist etc.
- return false;
- }
-
- if (getCurrentState() == BasePlayer.STATE_BLOCKED) {
- return true;
- }
-
- if (!isControlsVisible()) {
- if (!queueVisible) {
- binding.playPauseButton.requestFocus();
- }
- showControlsThenHide();
- showSystemUIPartially();
- return true;
- } else {
- hideControls(DEFAULT_CONTROLS_DURATION, DPAD_CONTROLS_HIDE_TIME);
- }
- break;
- }
-
- return false;
- }
-
- public AppCompatActivity getParentActivity() {
- // ! instanceof ViewGroup means that view was added via windowManager for Popup
- if (binding == null || binding.getRoot().getParent() == null
- || !(binding.getRoot().getParent() instanceof ViewGroup)) {
- return null;
- }
-
- final ViewGroup parent = (ViewGroup) binding.getRoot().getParent();
- return (AppCompatActivity) parent.getContext();
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // View
- //////////////////////////////////////////////////////////////////////////*/
-
- private void setRepeatModeButton(final ImageButton imageButton, final int repeatMode) {
- switch (repeatMode) {
- case Player.REPEAT_MODE_OFF:
- imageButton.setImageResource(R.drawable.exo_controls_repeat_off);
- break;
- case Player.REPEAT_MODE_ONE:
- imageButton.setImageResource(R.drawable.exo_controls_repeat_one);
- break;
- case Player.REPEAT_MODE_ALL:
- imageButton.setImageResource(R.drawable.exo_controls_repeat_all);
- break;
- }
- }
-
- private void setShuffleButton(final ImageButton button, final boolean shuffled) {
- final int shuffleAlpha = shuffled ? 255 : 77;
- button.setImageAlpha(shuffleAlpha);
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // Playback Parameters Listener
- ////////////////////////////////////////////////////////////////////////////
-
- @Override
- public void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch,
- final boolean playbackSkipSilence) {
- setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
- }
-
- @Override
- public void onVideoSizeChanged(final int width, final int height,
- final int unappliedRotationDegrees,
- final float pixelWidthHeightRatio) {
- super.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
- isVerticalVideo = width < height;
- prepareOrientation();
- setupScreenRotationButton();
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // ExoPlayer Video Listener
- //////////////////////////////////////////////////////////////////////////*/
-
- void onShuffleOrRepeatModeChanged() {
- updatePlaybackButtons();
- updatePlayback();
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- }
-
- @Override
- public void onRepeatModeChanged(final int i) {
- super.onRepeatModeChanged(i);
- onShuffleOrRepeatModeChanged();
- }
-
- @Override
- public void onShuffleClicked() {
- super.onShuffleClicked();
- onShuffleOrRepeatModeChanged();
-
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Playback Listener
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onPlayerError(final ExoPlaybackException error) {
- super.onPlayerError(error);
-
- if (fragmentListener != null) {
- fragmentListener.onPlayerError(error);
- }
- }
-
- @Override
- public void onTimelineChanged(final Timeline timeline, final int reason) {
- super.onTimelineChanged(timeline, reason);
- // force recreate notification to ensure seek bar is shown when preparation finishes
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true);
- }
-
- @Override
- protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
- super.onMetadataChanged(tag);
-
- showHideKodiButton();
-
- binding.titleTextView.setText(tag.getMetadata().getName());
- binding.channelTextView.setText(tag.getMetadata().getUploaderName());
-
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- updateMetadata();
- }
-
- @Override
- public void onPlaybackShutdown() {
- if (DEBUG) {
- Log.d(TAG, "onPlaybackShutdown() called");
- }
- service.onDestroy();
- }
-
- @Override
- public void onMuteUnmuteButtonClicked() {
- super.onMuteUnmuteButtonClicked();
- updatePlayback();
- setMuteButton(binding.switchMute, isMuted());
- }
-
- @Override
- public void onUpdateProgress(final int currentProgress,
- final int duration, final int bufferPercent) {
- super.onUpdateProgress(currentProgress, duration, bufferPercent);
- updateProgress(currentProgress, duration, bufferPercent);
-
- final boolean showThumbnail =
- sharedPreferences.getBoolean(
- context.getString(R.string.show_thumbnail_key),
- true);
- // setMetadata only updates the metadata when any of the metadata keys are null
- mediaSessionManager.setMetadata(getVideoTitle(), getUploaderName(),
- showThumbnail ? getThumbnail() : null, duration);
- }
-
- @Override
- public void onPlayQueueEdited() {
- updatePlayback();
- showOrHideButtons();
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- }
-
- @Override
- @Nullable
- public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
- // For LiveStream or video/popup players we can use super() method
- // but not for audio player
- if (!audioOnly) {
- return super.sourceOf(item, info);
- } else {
- return resolver.resolve(info);
- }
- }
-
- @Override
- public void onPlayPrevious() {
- super.onPlayPrevious();
- triggerProgressUpdate();
- }
-
- @Override
- public void onPlayNext() {
- super.onPlayNext();
- triggerProgressUpdate();
- }
-
- @Override
- protected void initPlayback(@NonNull final PlayQueue queue, final int repeatMode,
- final float playbackSpeed, final float playbackPitch,
- final boolean playbackSkipSilence,
- final boolean playOnReady, final boolean isMuted) {
- super.initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
- playbackSkipSilence, playOnReady, isMuted);
- updateQueue();
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Player Overrides
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void toggleFullscreen() {
- if (DEBUG) {
- Log.d(TAG, "toggleFullscreen() called");
- }
- if (popupPlayerSelected()
- || simpleExoPlayer == null
- || getCurrentMetadata() == null
- || 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)
- getPlaybackControlRoot().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.GONE);
- binding.channelTextView.setVisibility(View.GONE);
- binding.playerCloseButton.setVisibility(videoPlayerSelected()
- ? View.VISIBLE : View.GONE);
- } else {
- binding.titleTextView.setVisibility(View.VISIBLE);
- binding.channelTextView.setVisibility(View.VISIBLE);
- binding.playerCloseButton.setVisibility(View.GONE);
- }
- setupScreenRotationButton();
- }
-
- @Override
- public void onClick(final View v) {
- super.onClick(v);
- if (v.getId() == binding.playPauseButton.getId()) {
- onPlayPause();
- } else if (v.getId() == binding.playPreviousButton.getId()) {
- onPlayPrevious();
- } else if (v.getId() == binding.playNextButton.getId()) {
- onPlayNext();
- } else if (v.getId() == binding.queueButton.getId()) {
- onQueueClicked();
- return;
- } else if (v.getId() == binding.repeatButton.getId()) {
- onRepeatClicked();
- return;
- } else if (v.getId() == binding.shuffleButton.getId()) {
- onShuffleClicked();
- return;
- } else if (v.getId() == binding.moreOptionsButton.getId()) {
- onMoreOptionsClicked();
- } else if (v.getId() == binding.share.getId()) {
- onShareClicked();
- } 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, getPlayQueue(), 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(service))) {
- fragmentListener.onScreenRotationButtonClicked();
- } else {
- toggleFullscreen();
- }
- } else if (v.getId() == binding.switchMute.getId()) {
- onMuteUnmuteButtonClicked();
- } else if (v.getId() == binding.playerCloseButton.getId()) {
- service.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER));
- }
-
- if (getCurrentState() != STATE_COMPLETED) {
- getControlsVisibilityHandler().removeCallbacksAndMessages(null);
- showHideShadow(true, DEFAULT_CONTROLS_DURATION, 0);
- animateView(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, 0,
- () -> {
- if (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.moreOptionsButton.getId() && isFullscreen()) {
- fragmentListener.onMoreOptionsLongClicked();
- hideControls(0, 0);
- hideSystemUIIfNeeded();
- }
- return true;
- }
-
- private void onQueueClicked() {
- queueVisible = true;
-
- hideSystemUIIfNeeded();
- buildQueue();
- updatePlaybackButtons();
-
- hideControls(0, 0);
- binding.playQueuePanel.requestFocus();
- animateView(binding.playQueuePanel, SLIDE_AND_ALPHA, true,
- DEFAULT_CONTROLS_DURATION);
-
- binding.playQueue.scrollToPosition(playQueue.getIndex());
- }
-
- public void onQueueClosed() {
- if (!queueVisible) {
- return;
- }
-
- animateView(binding.playQueuePanel, SLIDE_AND_ALPHA, false,
- DEFAULT_CONTROLS_DURATION, 0, () -> {
- // Even when queueLayout is GONE it receives touch events
- // and ruins normal behavior of the app. This line fixes it
- binding.playQueuePanel
- .setTranslationY(-binding.playQueuePanel.getHeight() * 5);
- });
- queueVisible = false;
- binding.playPauseButton.requestFocus();
- }
-
- 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);
- animateView(binding.secondaryControls, SLIDE_AND_ALPHA, !isMoreControlsVisible,
- DEFAULT_CONTROLS_DURATION, 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 onShareClicked() {
- // share video at the current time (youtube.com/watch?v=ID&t=SECONDS)
- // Timestamp doesn't make sense in a live stream so drop it
-
- final int ts = playbackSeekBar.getProgress() / 1000;
- final MediaSourceTag metadata = getCurrentMetadata();
- String videoUrl = getVideoUrl();
- if (!isLive() && ts >= 0 && metadata != null
- && metadata.getMetadata().getServiceId() == YouTube.getServiceId()) {
- videoUrl += ("&t=" + ts);
- }
- ShareUtils.shareUrl(service,
- getVideoTitle(),
- videoUrl);
- }
-
- private void onPlayWithKodiClicked() {
- if (getCurrentMetadata() == null) {
- return;
- }
- onPause();
- try {
- NavigationHelper.playWithKore(getParentActivity(), Uri.parse(getVideoUrl()));
- } catch (final Exception e) {
- if (DEBUG) {
- Log.i(TAG, "Failed to start kore", e);
- }
- KoreUtil.showInstallKoreDialog(getParentActivity());
- }
- }
-
- private void onOpenInBrowserClicked() {
- if (getCurrentMetadata() == null) {
- return;
- }
-
- ShareUtils.openUrlInBrowser(getParentActivity(),
- getCurrentMetadata().getMetadata().getOriginalUrl());
- }
-
- private void showHideKodiButton() {
- final boolean kodiEnabled = defaultPreferences.getBoolean(
- service.getString(R.string.show_play_with_kodi_key), false);
- // show kodi button if it supports the current service and it is enabled in settings
- final boolean showKodiButton = playQueue != null && playQueue.getItem() != null
- && KoreUtil.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId());
- binding.playWithKodi.setVisibility(videoPlayerSelected() && kodiEnabled
- && showKodiButton ? View.VISIBLE : View.GONE);
- }
-
- private void setupScreenRotationButton() {
- final boolean orientationLocked = PlayerHelper.globalScreenOrientationLocked(service);
- final boolean showButton = videoPlayerSelected()
- && (orientationLocked || isVerticalVideo || DeviceUtils.isTablet(service));
- binding.screenRotationButton.setVisibility(showButton ? View.VISIBLE : View.GONE);
- binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(service,
- isFullscreen() ? R.drawable.ic_fullscreen_exit_white_24dp
- : R.drawable.ic_fullscreen_white_24dp));
- }
-
- private void prepareOrientation() {
- final boolean orientationLocked = PlayerHelper.globalScreenOrientationLocked(service);
- if (orientationLocked
- && isFullscreen()
- && service.isLandscape() == isVerticalVideo
- && !DeviceUtils.isTv(service)
- && !DeviceUtils.isTablet(service)
- && fragmentListener != null) {
- fragmentListener.onScreenRotationButtonClicked();
- }
- }
-
- @Override
- public void onPlaybackSpeedClicked() {
- if (videoPlayerSelected()) {
- PlaybackParameterDialog
- .newInstance(
- getPlaybackSpeed(), getPlaybackPitch(), getPlaybackSkipSilence(), this)
- .show(getParentActivity().getSupportFragmentManager(), null);
- } else {
- super.onPlaybackSpeedClicked();
- }
- }
-
- @Override
- public void onStopTrackingTouch(final SeekBar seekBar) {
- super.onStopTrackingTouch(seekBar);
- if (wasPlaying()) {
- showControlsThenHide();
- }
- }
-
- @Override
- public void onDismiss(final PopupMenu menu) {
- super.onDismiss(menu);
- if (isPlaying()) {
- hideControls(DEFAULT_CONTROLS_DURATION, 0);
- hideSystemUIIfNeeded();
- }
- }
-
- @Override
- @SuppressWarnings("checkstyle:ParameterNumber")
- 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)
- 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.playQueuePanel.getLayoutParams().height = height
- - binding.playQueuePanel.getTop();
- }
- }
-
- @Override
- protected int nextResizeMode(final int currentResizeMode) {
- final int newResizeMode;
- switch (currentResizeMode) {
- case AspectRatioFrameLayout.RESIZE_MODE_FIT:
- newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL;
- break;
- case AspectRatioFrameLayout.RESIZE_MODE_FILL:
- newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
- break;
- default:
- newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
- break;
- }
-
- storeResizeMode(newResizeMode);
- return newResizeMode;
- }
-
- private void storeResizeMode(final @AspectRatioFrameLayout.ResizeMode int resizeMode) {
- defaultPreferences.edit()
- .putInt(service.getString(R.string.last_resize_mode), resizeMode)
- .apply();
- }
-
- private void restoreResizeMode() {
- setResizeMode(defaultPreferences.getInt(
- service.getString(R.string.last_resize_mode),
- AspectRatioFrameLayout.RESIZE_MODE_FIT));
- }
-
- @Override
- protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
- return new VideoPlaybackResolver.QualityResolver() {
- @Override
- public int getDefaultResolutionIndex(final List sortedVideos) {
- return videoPlayerSelected()
- ? ListHelper.getDefaultResolutionIndex(context, sortedVideos)
- : ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
- }
-
- @Override
- public int getOverrideResolutionIndex(final List sortedVideos,
- final String playbackQuality) {
- return videoPlayerSelected()
- ? getResolutionIndex(context, sortedVideos, playbackQuality)
- : getPopupResolutionIndex(context, sortedVideos, playbackQuality);
- }
- };
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // States
- //////////////////////////////////////////////////////////////////////////*/
-
- private void animatePlayButtons(final boolean show, final int duration) {
- animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, show,
- duration);
-
- boolean showQueueButtons = show;
- if (playQueue == null) {
- showQueueButtons = false;
- }
-
- if (!showQueueButtons || playQueue.getIndex() > 0) {
- animateView(
- binding.playPreviousButton,
- AnimationUtils.Type.SCALE_AND_ALPHA,
- showQueueButtons,
- duration);
- }
- if (!showQueueButtons || playQueue.getIndex() + 1 < playQueue.getStreams().size()) {
- animateView(
- binding.playNextButton,
- AnimationUtils.Type.SCALE_AND_ALPHA,
- showQueueButtons,
- duration);
- }
- }
-
- @Override
- public void changeState(final int state) {
- super.changeState(state);
- updatePlayback();
- }
-
- @Override
- public void onBlocked() {
- super.onBlocked();
- binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
- animatePlayButtons(false, 100);
- getRootView().setKeepScreenOn(false);
-
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- }
-
- @Override
- public void onBuffering() {
- super.onBuffering();
- getRootView().setKeepScreenOn(true);
-
- if (NotificationUtil.getInstance().shouldUpdateBufferingSlot()) {
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- }
- }
-
- @Override
- public void onPlaying() {
- super.onPlaying();
- animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false,
- 80, 0, () -> {
- binding.playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
- animatePlayButtons(true, 200);
- if (!queueVisible) {
- binding.playPauseButton.requestFocus();
- }
- });
-
- updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
- checkLandscape();
- getRootView().setKeepScreenOn(true);
-
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- }
-
- @Override
- public void onPaused() {
- super.onPaused();
- animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA,
- false, 80, 0, () -> {
- binding.playPauseButton
- .setImageResource(R.drawable.ic_play_arrow_white_24dp);
- animatePlayButtons(true, 200);
- if (!queueVisible) {
- binding.playPauseButton.requestFocus();
- }
- });
-
- updateWindowFlags(IDLE_WINDOW_FLAGS);
-
- // Remove running notification when user don't want music (or video in popup)
- // to be played in background
- if (!minimizeOnPopupEnabled() && !backgroundPlaybackEnabled() && videoPlayerSelected()) {
- NotificationUtil.getInstance().cancelNotificationAndStopForeground(service);
- } else {
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- }
-
- getRootView().setKeepScreenOn(false);
- }
-
- @Override
- public void onPausedSeek() {
- super.onPausedSeek();
- animatePlayButtons(false, 100);
- getRootView().setKeepScreenOn(true);
-
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- }
-
-
- @Override
- public void onCompleted() {
- animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false,
- 0, 0, () -> {
- binding.playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
- animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
- });
-
- getRootView().setKeepScreenOn(false);
- updateWindowFlags(IDLE_WINDOW_FLAGS);
-
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- if (isFullscreen) {
- toggleFullscreen();
- }
- super.onCompleted();
- }
-
- @Override
- public void destroy() {
- super.destroy();
- service.getContentResolver().unregisterContentObserver(settingsContentObserver);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Broadcast Receiver
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- protected void setupBroadcastReceiver(final IntentFilter intentFilter) {
- super.setupBroadcastReceiver(intentFilter);
- if (DEBUG) {
- Log.d(TAG, "setupBroadcastReceiver() called with: "
- + "intentFilter = [" + intentFilter + "]");
- }
-
- intentFilter.addAction(ACTION_CLOSE);
- intentFilter.addAction(ACTION_PLAY_PAUSE);
- intentFilter.addAction(ACTION_OPEN_CONTROLS);
- intentFilter.addAction(ACTION_REPEAT);
- intentFilter.addAction(ACTION_PLAY_PREVIOUS);
- intentFilter.addAction(ACTION_PLAY_NEXT);
- intentFilter.addAction(ACTION_FAST_REWIND);
- intentFilter.addAction(ACTION_FAST_FORWARD);
- intentFilter.addAction(ACTION_SHUFFLE);
- intentFilter.addAction(ACTION_RECREATE_NOTIFICATION);
-
- intentFilter.addAction(VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED);
- intentFilter.addAction(VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED);
-
- intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- intentFilter.addAction(Intent.ACTION_SCREEN_ON);
- intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
-
- intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
- }
-
- @Override
- public void onBroadcastReceived(final Intent intent) {
- super.onBroadcastReceived(intent);
- if (intent == null || intent.getAction() == null) {
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
- }
-
- switch (intent.getAction()) {
- case ACTION_CLOSE:
- service.onDestroy();
- break;
- case ACTION_PLAY_NEXT:
- onPlayNext();
- break;
- case ACTION_PLAY_PREVIOUS:
- onPlayPrevious();
- break;
- case ACTION_FAST_FORWARD:
- onFastForward();
- break;
- case ACTION_FAST_REWIND:
- onFastRewind();
- break;
- case ACTION_PLAY_PAUSE:
- onPlayPause();
- 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_REPEAT:
- onRepeatClicked();
- break;
- case ACTION_SHUFFLE:
- onShuffleClicked();
- break;
- case ACTION_RECREATE_NOTIFICATION:
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true);
- break;
- case Intent.ACTION_HEADSET_PLUG: //FIXME
- /*notificationManager.cancel(NOTIFICATION_ID);
- mediaSessionManager.dispose();
- mediaSessionManager.enable(getBaseContext(), basePlayerImpl.simpleExoPlayer);*/
- 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();
- updatePopupSize(getPopupLayoutParams().width, -1);
- checkPopupPositionBounds();
- }
- // Close it because when changing orientation from portrait
- // (in fullscreen mode) the size of queue layout can be larger than the screen size
- onQueueClosed();
- break;
- case Intent.ACTION_SCREEN_ON:
- shouldUpdateOnProgress = true;
- // 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:
- shouldUpdateOnProgress = false;
- // Interrupt playback only when screen turns off with popup player working
- if (popupPlayerSelected() && (isPlaying() || isLoading())) {
- useVideoSource(false);
- }
- break;
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Thumbnail Loading
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onLoadingComplete(final String imageUri,
- final View view,
- final Bitmap loadedImage) {
- super.onLoadingComplete(imageUri, view, loadedImage);
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- }
-
- @Override
- public void onLoadingFailed(final String imageUri,
- final View view,
- final FailReason failReason) {
- super.onLoadingFailed(imageUri, view, failReason);
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- }
-
- @Override
- public void onLoadingCancelled(final String imageUri, final View view) {
- super.onLoadingCancelled(imageUri, view);
- NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Utils
- //////////////////////////////////////////////////////////////////////////*/
-
- private void setInitialGestureValues() {
- if (getAudioReactor() != null) {
- final float currentVolumeNormalized = (float) getAudioReactor()
- .getVolume() / getAudioReactor().getMaxVolume();
- binding.volumeProgressBar.setProgress(
- (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized));
- }
- }
-
- private void choosePlayerTypeFromIntent(final Intent intent) {
- // If you want to open popup from the app just include Constants.POPUP_ONLY into an extra
- if (intent.getIntExtra(PLAYER_TYPE, PLAYER_TYPE_VIDEO) == PLAYER_TYPE_AUDIO) {
- playerType = MainPlayer.PlayerType.AUDIO;
- } else if (intent.getIntExtra(PLAYER_TYPE, PLAYER_TYPE_VIDEO) == PLAYER_TYPE_POPUP) {
- playerType = MainPlayer.PlayerType.POPUP;
- } else {
- playerType = MainPlayer.PlayerType.VIDEO;
- }
- }
-
- public boolean backgroundPlaybackEnabled() {
- return PlayerHelper.getMinimizeOnExitAction(service) == MINIMIZE_ON_EXIT_MODE_BACKGROUND;
- }
-
- public boolean minimizeOnPopupEnabled() {
- return PlayerHelper.getMinimizeOnExitAction(service)
- == PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
- }
-
- public boolean audioPlayerSelected() {
- return playerType == MainPlayer.PlayerType.AUDIO;
- }
-
- public boolean videoPlayerSelected() {
- return playerType == MainPlayer.PlayerType.VIDEO;
- }
-
- public boolean popupPlayerSelected() {
- return playerType == MainPlayer.PlayerType.POPUP;
- }
-
- public boolean isPlayerStopped() {
- return getPlayer() == null || getPlayer().getPlaybackState() == SimpleExoPlayer.STATE_IDLE;
- }
-
- private int distanceFromCloseButton(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(final MotionEvent popupMotionEvent) {
- return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius();
- }
-
- public boolean isFullscreen() {
- return isFullscreen;
- }
-
- public void showControlsThenHide() {
- if (DEBUG) {
- Log.d(TAG, "showControlsThenHide() called");
- }
- showOrHideButtons();
- showSystemUIPartially();
- super.showControlsThenHide();
- }
-
- @Override
- public void showControls(final long duration) {
- if (DEBUG) {
- Log.d(TAG, "showControls() called with: duration = [" + duration + "]");
- }
- showOrHideButtons();
- showSystemUIPartially();
- super.showControls(duration);
- }
-
- @Override
- public void hideControls(final long duration, final long delay) {
- if (DEBUG) {
- Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
- }
-
- showOrHideButtons();
-
- getControlsVisibilityHandler().removeCallbacksAndMessages(null);
- getControlsVisibilityHandler().postDelayed(() -> {
- showHideShadow(false, duration, 0);
- animateView(binding.playbackControlRoot, false, duration, 0,
- this::hideSystemUIIfNeeded);
- }, delay
- );
- }
-
- @Override
- public void safeHideControls(final long duration, final long delay) {
- if (binding.playbackControlRoot.isInTouchMode()) {
- hideControls(duration, delay);
- }
- }
-
- 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();
-
- 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);
- }
-
- 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);
- }
- 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);
- }
- }
-
- @Override
- public void hideSystemUIIfNeeded() {
- if (fragmentListener != null) {
- fragmentListener.hideSystemUiIfNeeded();
- }
- }
-
- public void disablePreloadingOfCurrentTrack() {
- getLoadController().disablePreloadingOfCurrentTrack();
- }
-
- protected void setMuteButton(final ImageButton button, final boolean isMuted) {
- button.setImageDrawable(AppCompatResources.getDrawable(service, isMuted
- ? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp));
- }
-
- /**
- * @return true if main player is attached to activity and activity inside multiWindow mode
- */
- private boolean isInMultiWindow() {
- final AppCompatActivity parent = getParentActivity();
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
- && parent != null
- && parent.isInMultiWindowMode();
- }
-
- private void updatePlaybackButtons() {
- if (binding == null || simpleExoPlayer == null || playQueue == null) {
- return;
- }
-
- setRepeatModeButton(binding.repeatButton, getRepeatMode());
- setShuffleButton(binding.shuffleButton, playQueue.isShuffled());
- }
-
- public void checkLandscape() {
- final AppCompatActivity parent = getParentActivity();
- final boolean videoInLandscapeButNotInFullscreen = service.isLandscape()
- && !isFullscreen()
- && videoPlayerSelected()
- && !audioOnly;
-
- final boolean playingState = getCurrentState() != STATE_COMPLETED
- && getCurrentState() != STATE_PAUSED;
- if (parent != null
- && videoInLandscapeButNotInFullscreen
- && playingState
- && !DeviceUtils.isTablet(service)) {
- toggleFullscreen();
- }
- }
-
- private void buildQueue() {
- binding.playQueue.setAdapter(playQueueAdapter);
- binding.playQueue.setClickable(true);
- binding.playQueue.setLongClickable(true);
-
- binding.playQueue.clearOnScrollListeners();
- binding.playQueue.addOnScrollListener(getQueueScrollListener());
-
- itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
- itemTouchHelper.attachToRecyclerView(binding.playQueue);
-
- playQueueAdapter.setSelectedListener(getOnSelectedListener());
-
- binding.playQueueClose.setOnClickListener(view -> onQueueClosed());
- }
-
- public void useVideoSource(final boolean video) {
- if (playQueue == null || audioOnly == !video || audioPlayerSelected()) {
- return;
- }
-
- audioOnly = !video;
- // When a user returns from background controls could be hidden
- // but systemUI will be shown 100%. Hide it
- if (!audioOnly && !isControlsVisible()) {
- hideSystemUIIfNeeded();
- }
- setRecovery();
- reload();
- }
-
- 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.playQueue.clearOnScrollListeners();
- }
- }
- };
- }
-
- 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) {
- onSelected(item);
- }
-
- @Override
- public void held(final PlayQueueItem item, final View view) {
- final int index = playQueue.indexOf(item);
- if (index != -1) {
- playQueue.remove(index);
- }
- }
-
- @Override
- public void onStartDrag(final PlayQueueItemHolder viewHolder) {
- if (itemTouchHelper != null) {
- itemTouchHelper.startDrag(viewHolder);
- }
- }
- };
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Init
- //////////////////////////////////////////////////////////////////////////*/
-
- @SuppressLint("RtlHardcoded")
- private void initPopup() {
- if (DEBUG) {
- Log.d(TAG, "initPopup() called");
- }
-
- // Popup is already added to windowManager
- if (popupHasParent()) {
- return;
- }
-
- updateScreenSize();
-
- final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(service);
- final float defaultSize = service.getResources().getDimension(R.dimen.popup_default_width);
- final SharedPreferences sharedPreferences =
- PreferenceManager.getDefaultSharedPreferences(service);
- popupWidth = popupRememberSizeAndPos
- ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize)
- : defaultSize;
- popupHeight = getMinimumVideoHeight(popupWidth);
-
- 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;
- getSurfaceView().setHeights((int) popupHeight, (int) popupHeight);
-
- final int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
- final int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
- popupLayoutParams.x = popupRememberSizeAndPos
- ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
- popupLayoutParams.y = popupRememberSizeAndPos
- ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
-
- checkPopupPositionBounds();
-
- binding.loadingPanel.setMinimumWidth(popupLayoutParams.width);
- binding.loadingPanel.setMinimumHeight(popupLayoutParams.height);
-
- service.removeViewFromParent();
- windowManager.addView(getRootView(), 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(service));
-
- 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);
- closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
- closeOverlayLayoutParams.softInputMode =
- WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-
- closeOverlayBinding.closeButton.setVisibility(View.GONE);
- windowManager.addView(closeOverlayBinding.getRoot(), closeOverlayLayoutParams);
- }
-
- private void initVideoPlayer() {
- restoreResizeMode();
- getRootView().setLayoutParams(new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Popup utils
- //////////////////////////////////////////////////////////////////////////*/
-
- /**
- * @return if the popup was out of bounds and have been moved back to it
- * @see #checkPopupPositionBounds(float, float)
- */
- @SuppressWarnings("UnusedReturnValue")
- public boolean checkPopupPositionBounds() {
- return checkPopupPositionBounds(screenWidth, screenHeight);
- }
-
- /**
- * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary
- * that goes from (0, 0) to (boundaryWidth, boundaryHeight).
- *
- * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed
- * and {@code true} is returned to represent this change.
- *
- *
- * @param boundaryWidth width of the boundary
- * @param boundaryHeight height of the boundary
- * @return if the popup was out of bounds and have been moved back to it
- */
- public boolean checkPopupPositionBounds(final float boundaryWidth, final float boundaryHeight) {
- if (DEBUG) {
- Log.d(TAG, "checkPopupPositionBounds() called with: "
- + "boundaryWidth = [" + boundaryWidth + "], "
- + "boundaryHeight = [" + boundaryHeight + "]");
- }
-
- if (popupLayoutParams.x < 0) {
- popupLayoutParams.x = 0;
- return true;
- } else if (popupLayoutParams.x > boundaryWidth - popupLayoutParams.width) {
- popupLayoutParams.x = (int) (boundaryWidth - popupLayoutParams.width);
- return true;
- }
-
- if (popupLayoutParams.y < 0) {
- popupLayoutParams.y = 0;
- return true;
- } else if (popupLayoutParams.y > boundaryHeight - popupLayoutParams.height) {
- popupLayoutParams.y = (int) (boundaryHeight - popupLayoutParams.height);
- return true;
- }
-
- return false;
- }
-
- public void savePositionAndSize() {
- final SharedPreferences sharedPreferences = PreferenceManager
- .getDefaultSharedPreferences(service);
- sharedPreferences.edit().putInt(POPUP_SAVED_X, popupLayoutParams.x).apply();
- sharedPreferences.edit().putInt(POPUP_SAVED_Y, popupLayoutParams.y).apply();
- sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, popupLayoutParams.width).apply();
- }
-
- private float getMinimumVideoHeight(final float width) {
- final float height = width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
- /*if (DEBUG) {
- Log.d(TAG, "getMinimumVideoHeight() called with: width = ["
- + width + "], returned: " + height);
- }*/
- return 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);
- }
-
- popupWidth = service.getResources().getDimension(R.dimen.popup_default_width);
- popupHeight = getMinimumVideoHeight(popupWidth);
-
- minimumWidth = service.getResources().getDimension(R.dimen.popup_minimum_width);
- minimumHeight = getMinimumVideoHeight(minimumWidth);
-
- maximumWidth = screenWidth;
- maximumHeight = screenHeight;
- }
-
- public void updatePopupSize(final int width, final int height) {
- if (DEBUG) {
- Log.d(TAG, "updatePopupSize() called with: width = ["
- + width + "], height = [" + height + "]");
- }
-
- if (popupLayoutParams == null
- || windowManager == null
- || getParentActivity() != null
- || getRootView().getParent() == null) {
- return;
- }
-
- final int actualWidth = (int) (width > maximumWidth
- ? maximumWidth : width < minimumWidth ? minimumWidth : width);
- final int actualHeight;
- if (height == -1) {
- actualHeight = (int) getMinimumVideoHeight(width);
- } else {
- actualHeight = (int) (height > maximumHeight
- ? maximumHeight : height < minimumHeight
- ? minimumHeight : height);
- }
-
- popupLayoutParams.width = actualWidth;
- popupLayoutParams.height = actualHeight;
- popupWidth = actualWidth;
- popupHeight = actualHeight;
- getSurfaceView().setHeights((int) popupHeight, (int) popupHeight);
-
- if (DEBUG) {
- Log.d(TAG, "updatePopupSize() updated values:"
- + " width = [" + actualWidth + "], height = [" + actualHeight + "]");
- }
- windowManager.updateViewLayout(getRootView(), popupLayoutParams);
- }
-
- private void updateWindowFlags(final int flags) {
- if (popupLayoutParams == null
- || windowManager == null
- || getParentActivity() != null
- || getRootView().getParent() == null) {
- return;
- }
-
- popupLayoutParams.flags = flags;
- windowManager.updateViewLayout(getRootView(), popupLayoutParams);
- }
-
- private int popupLayoutParamType() {
- return Build.VERSION.SDK_INT < Build.VERSION_CODES.O
- ? WindowManager.LayoutParams.TYPE_PHONE
- : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Misc
- //////////////////////////////////////////////////////////////////////////*/
-
- public void closePopup() {
- if (DEBUG) {
- Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing);
- }
- if (isPopupClosing) {
- return;
- }
- isPopupClosing = true;
-
- savePlaybackState();
- windowManager.removeView(getRootView());
-
- animateOverlayAndFinishService();
- }
-
- public void removePopupFromView() {
- final boolean isCloseOverlayHasParent = closeOverlayBinding != null
- && closeOverlayBinding.getRoot().getParent() != null;
- if (popupHasParent()) {
- windowManager.removeView(getRootView());
- }
- if (isCloseOverlayHasParent) {
- windowManager.removeView(closeOverlayBinding.getRoot());
- }
- }
-
- private void animateOverlayAndFinishService() {
- 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;
-
- service.onDestroy();
- }
- }).start();
- }
-
- private boolean popupHasParent() {
- return binding != null
- && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams
- && binding.getRoot().getParent() != null;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Manipulations with listener
- ///////////////////////////////////////////////////////////////////////////
-
- 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.playQueuePanel.setPadding(0, 0, 0, 0);
- updateQueue();
- updateMetadata();
- updatePlayback();
- triggerProgressUpdate();
- }
-
- public void removeFragmentListener(final PlayerServiceEventListener listener) {
- if (fragmentListener == listener) {
- fragmentListener = null;
- }
- }
-
- void setActivityListener(final PlayerEventListener listener) {
- activityListener = listener;
- updateMetadata();
- updatePlayback();
- triggerProgressUpdate();
- }
-
- void removeActivityListener(final PlayerEventListener listener) {
- if (activityListener == listener) {
- activityListener = null;
- }
- }
-
- private void updateQueue() {
- if (fragmentListener != null && playQueue != null) {
- fragmentListener.onQueueUpdate(playQueue);
- }
- if (activityListener != null && playQueue != null) {
- activityListener.onQueueUpdate(playQueue);
- }
- }
-
- private void updateMetadata() {
- if (fragmentListener != null && getCurrentMetadata() != null) {
- fragmentListener.onMetadataUpdate(getCurrentMetadata().getMetadata(), playQueue);
- }
- if (activityListener != null && getCurrentMetadata() != null) {
- activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata(), playQueue);
- }
- }
-
- private void updatePlayback() {
- if (fragmentListener != null && simpleExoPlayer != null && playQueue != null) {
- fragmentListener.onPlaybackUpdate(currentState, getRepeatMode(),
- playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters());
- }
- if (activityListener != null && simpleExoPlayer != null && playQueue != null) {
- activityListener.onPlaybackUpdate(currentState, getRepeatMode(),
- playQueue.isShuffled(), getPlaybackParameters());
- }
- }
-
- private void updateProgress(final int currentProgress, final int duration,
- final int bufferPercent) {
- if (fragmentListener != null) {
- fragmentListener.onProgressUpdate(currentProgress, duration, bufferPercent);
- }
- if (activityListener != null) {
- activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
- }
- }
-
- void stopActivityBinding() {
- if (fragmentListener != null) {
- fragmentListener.onServiceStopped();
- fragmentListener = null;
- }
- if (activityListener != null) {
- activityListener.onServiceStopped();
- activityListener = null;
- }
- }
-
- /**
- * 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())) {
- if (backgroundPlaybackEnabled()) {
- useVideoSource(false);
- } else if (minimizeOnPopupEnabled()) {
- setRecovery();
- NavigationHelper.playOnPopupPlayer(getParentActivity(), playQueue, true);
- } else {
- onPause();
- }
- }
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Getters
- ///////////////////////////////////////////////////////////////////////////
-
- public RelativeLayout getVolumeRelativeLayout() {
- return binding.volumeRelativeLayout;
- }
-
- public ProgressBar getVolumeProgressBar() {
- return binding.volumeProgressBar;
- }
-
- public ImageView getVolumeImageView() {
- return binding.volumeImageView;
- }
-
- public RelativeLayout getBrightnessRelativeLayout() {
- return binding.brightnessRelativeLayout;
- }
-
- public ProgressBar getBrightnessProgressBar() {
- return binding.brightnessProgressBar;
- }
-
- public ImageView getBrightnessImageView() {
- return binding.brightnessImageView;
- }
-
- public ImageButton getPlayPauseButton() {
- return binding.playPauseButton;
- }
-
- public int getMaxGestureLength() {
- return maxGestureLength;
- }
-
- public TextView getResizingIndicator() {
- return binding.resizingIndicator;
- }
-
- public GestureDetector getGestureDetector() {
- return gestureDetector;
- }
-
- public WindowManager.LayoutParams getPopupLayoutParams() {
- return popupLayoutParams;
- }
-
- public MainPlayer.PlayerType getPlayerType() {
- return playerType;
- }
-
- public float getScreenWidth() {
- return screenWidth;
- }
-
- public float getScreenHeight() {
- return screenHeight;
- }
-
- public float getPopupWidth() {
- return popupWidth;
- }
-
- public float getPopupHeight() {
- return popupHeight;
- }
-
- public void setPopupWidth(final float width) {
- popupWidth = width;
- }
-
- public void setPopupHeight(final float height) {
- popupHeight = height;
- }
-
- public View getCloseButton() {
- return closeOverlayBinding.closeButton;
- }
-
- public View getClosingOverlay() {
- return binding.closingOverlay;
- }
-
- public boolean isVerticalVideo() {
- return isVerticalVideo;
- }
-}
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
index d34746ca5..46502a270 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt
+++ b/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt
@@ -7,10 +7,10 @@ import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
-import org.schabi.newpipe.player.BasePlayer
import org.schabi.newpipe.player.MainPlayer
-import org.schabi.newpipe.player.VideoPlayerImpl
+import org.schabi.newpipe.player.Player
import org.schabi.newpipe.player.helper.PlayerHelper
+import org.schabi.newpipe.player.helper.PlayerHelper.savePopupPositionAndSizeToPrefs
import org.schabi.newpipe.util.AnimationUtils
import kotlin.math.abs
import kotlin.math.hypot
@@ -18,14 +18,14 @@ import kotlin.math.max
import kotlin.math.min
/**
- * Base gesture handling for [VideoPlayerImpl]
+ * 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 playerImpl: VideoPlayerImpl,
+ protected val player: Player,
@JvmField
protected val service: MainPlayer
) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener {
@@ -78,7 +78,7 @@ abstract class BasePlayerGestureListener(
// ///////////////////////////////////////////////////////////////////
override fun onTouch(v: View, event: MotionEvent): Boolean {
- return if (playerImpl.popupPlayerSelected()) {
+ return if (player.popupPlayerSelected()) {
onTouchInPopup(v, event)
} else {
onTouchInMain(v, event)
@@ -86,14 +86,14 @@ abstract class BasePlayerGestureListener(
}
private fun onTouchInMain(v: View, event: MotionEvent): Boolean {
- playerImpl.gestureDetector.onTouchEvent(event)
+ 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(playerImpl.isFullscreen)
+ v.parent.requestDisallowInterceptTouchEvent(player.isFullscreen)
true
}
MotionEvent.ACTION_UP -> {
@@ -105,7 +105,7 @@ abstract class BasePlayerGestureListener(
}
private fun onTouchInPopup(v: View, event: MotionEvent): Boolean {
- playerImpl.gestureDetector.onTouchEvent(event)
+ player.gestureDetector.onTouchEvent(event)
if (event.pointerCount == 2 && !isMovingInPopup && !isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.")
@@ -157,10 +157,10 @@ abstract class BasePlayerGestureListener(
initSecPointerY = (-1).toFloat()
onPopupResizingEnd()
- playerImpl.changeState(playerImpl.currentState)
+ player.changeState(player.currentState)
}
- if (!playerImpl.isPopupClosing) {
- playerImpl.savePositionAndSize()
+ if (!player.isPopupClosing) {
+ savePopupPositionAndSizeToPrefs(player)
}
}
@@ -190,19 +190,15 @@ abstract class BasePlayerGestureListener(
event.getY(0) - event.getY(1).toDouble()
)
- val popupWidth = playerImpl.popupWidth.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
- playerImpl.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt()
+ player.popupLayoutParams!!.x += ((popupWidth - newWidth) / 2.0).toInt()
- playerImpl.checkPopupPositionBounds()
- playerImpl.updateScreenSize()
-
- playerImpl.updatePopupSize(
- min(playerImpl.screenWidth.toDouble(), newWidth).toInt(),
- -1
- )
+ player.checkPopupPositionBounds()
+ player.updateScreenSize()
+ player.changePopupSize(min(player.screenWidth.toDouble(), newWidth).toInt())
return true
}
}
@@ -222,7 +218,7 @@ abstract class BasePlayerGestureListener(
return true
}
- return if (playerImpl.popupPlayerSelected())
+ return if (player.popupPlayerSelected())
onDownInPopup(e)
else
true
@@ -231,12 +227,10 @@ abstract class BasePlayerGestureListener(
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).
- playerImpl.updateScreenSize()
- playerImpl.checkPopupPositionBounds()
- initialPopupX = playerImpl.popupLayoutParams.x
- initialPopupY = playerImpl.popupLayoutParams.y
- playerImpl.popupWidth = playerImpl.popupLayoutParams.width.toFloat()
- playerImpl.popupHeight = playerImpl.popupLayoutParams.height.toFloat()
+ player.updateScreenSize()
+ player.checkPopupPositionBounds()
+ initialPopupX = player.popupLayoutParams!!.x
+ initialPopupY = player.popupLayoutParams!!.y
return super.onDown(e)
}
@@ -255,15 +249,15 @@ abstract class BasePlayerGestureListener(
if (isDoubleTapping)
return true
- if (playerImpl.popupPlayerSelected()) {
- if (playerImpl.player == null)
+ if (player.popupPlayerSelected()) {
+ if (player.exoPlayerIsNull())
return false
onSingleTap(MainPlayer.PlayerType.POPUP)
return true
} else {
super.onSingleTapConfirmed(e)
- if (playerImpl.currentState == BasePlayer.STATE_BLOCKED)
+ if (player.currentState == Player.STATE_BLOCKED)
return true
onSingleTap(MainPlayer.PlayerType.VIDEO)
@@ -272,10 +266,10 @@ abstract class BasePlayerGestureListener(
}
override fun onLongPress(e: MotionEvent?) {
- if (playerImpl.popupPlayerSelected()) {
- playerImpl.updateScreenSize()
- playerImpl.checkPopupPositionBounds()
- playerImpl.updatePopupSize(playerImpl.screenWidth.toInt(), -1)
+ if (player.popupPlayerSelected()) {
+ player.updateScreenSize()
+ player.checkPopupPositionBounds()
+ player.changePopupSize(player.screenWidth.toInt())
}
}
@@ -285,7 +279,7 @@ abstract class BasePlayerGestureListener(
distanceX: Float,
distanceY: Float
): Boolean {
- return if (playerImpl.popupPlayerSelected()) {
+ return if (player.popupPlayerSelected()) {
onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY)
} else {
onScrollInMain(initialEvent, movingEvent, distanceX, distanceY)
@@ -298,19 +292,18 @@ abstract class BasePlayerGestureListener(
velocityX: Float,
velocityY: Float
): Boolean {
- return if (playerImpl.popupPlayerSelected()) {
+ return if (player.popupPlayerSelected()) {
val absVelocityX = abs(velocityX)
val absVelocityY = abs(velocityY)
if (absVelocityX.coerceAtLeast(absVelocityY) > tossFlingVelocity) {
if (absVelocityX > tossFlingVelocity) {
- playerImpl.popupLayoutParams.x = velocityX.toInt()
+ player.popupLayoutParams!!.x = velocityX.toInt()
}
if (absVelocityY > tossFlingVelocity) {
- playerImpl.popupLayoutParams.y = velocityY.toInt()
+ player.popupLayoutParams!!.y = velocityY.toInt()
}
- playerImpl.checkPopupPositionBounds()
- playerImpl.windowManager
- .updateViewLayout(playerImpl.rootView, playerImpl.popupLayoutParams)
+ player.checkPopupPositionBounds()
+ player.windowManager!!.updateViewLayout(player.rootView, player.popupLayoutParams)
return true
}
return false
@@ -326,13 +319,13 @@ abstract class BasePlayerGestureListener(
distanceY: Float
): Boolean {
- if (!playerImpl.isFullscreen) {
+ if (!player.isFullscreen) {
return false
}
val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(service)
val isTouchingNavigationBar: Boolean =
- initialEvent.y > (playerImpl.rootView.height - getNavigationBarHeight(service))
+ initialEvent.y > (player.rootView.height - getNavigationBarHeight(service))
if (isTouchingStatusBar || isTouchingNavigationBar) {
return false
}
@@ -340,7 +333,7 @@ abstract class BasePlayerGestureListener(
val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD
if (
!isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) ||
- playerImpl.currentState == BasePlayer.STATE_COMPLETED
+ player.currentState == Player.STATE_COMPLETED
) {
return false
}
@@ -371,7 +364,7 @@ abstract class BasePlayerGestureListener(
}
if (!isMovingInPopup) {
- AnimationUtils.animateView(playerImpl.closeButton, true, 200)
+ AnimationUtils.animateView(player.closeOverlayButton, true, 200)
}
isMovingInPopup = true
@@ -381,20 +374,20 @@ abstract class BasePlayerGestureListener(
val diffY: Float = (movingEvent.rawY - initialEvent.rawY)
var posY: Float = (initialPopupY + diffY)
- if (posX > playerImpl.screenWidth - playerImpl.popupWidth) {
- posX = (playerImpl.screenWidth - playerImpl.popupWidth)
+ if (posX > player.screenWidth - player.popupLayoutParams!!.width) {
+ posX = (player.screenWidth - player.popupLayoutParams!!.width)
} else if (posX < 0) {
posX = 0f
}
- if (posY > playerImpl.screenHeight - playerImpl.popupHeight) {
- posY = (playerImpl.screenHeight - playerImpl.popupHeight)
+ if (posY > player.screenHeight - player.popupLayoutParams!!.height) {
+ posY = (player.screenHeight - player.popupLayoutParams!!.height)
} else if (posY < 0) {
posY = 0f
}
- playerImpl.popupLayoutParams.x = posX.toInt()
- playerImpl.popupLayoutParams.y = posY.toInt()
+ player.popupLayoutParams!!.x = posX.toInt()
+ player.popupLayoutParams!!.y = posY.toInt()
onScroll(
MainPlayer.PlayerType.POPUP,
@@ -405,8 +398,7 @@ abstract class BasePlayerGestureListener(
distanceY
)
- playerImpl.windowManager
- .updateViewLayout(playerImpl.rootView, playerImpl.popupLayoutParams)
+ player.windowManager!!.updateViewLayout(player.rootView, player.popupLayoutParams)
return true
}
@@ -474,16 +466,16 @@ abstract class BasePlayerGestureListener(
// ///////////////////////////////////////////////////////////////////
private fun getDisplayPortion(e: MotionEvent): DisplayPortion {
- return if (playerImpl.playerType == MainPlayer.PlayerType.POPUP) {
+ return if (player.playerType == MainPlayer.PlayerType.POPUP) {
when {
- e.x < playerImpl.popupWidth / 3.0 -> DisplayPortion.LEFT
- e.x > playerImpl.popupWidth * 2.0 / 3.0 -> DisplayPortion.RIGHT
+ 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 < playerImpl.rootView.width / 3.0 -> DisplayPortion.LEFT
- e.x > playerImpl.rootView.width * 2.0 / 3.0 -> DisplayPortion.RIGHT
+ e.x < player.rootView.width / 3.0 -> DisplayPortion.LEFT
+ e.x > player.rootView.width * 2.0 / 3.0 -> DisplayPortion.RIGHT
else -> DisplayPortion.MIDDLE
}
}
@@ -491,14 +483,14 @@ abstract class BasePlayerGestureListener(
// Currently needed for scrolling since there is no action more the middle portion
private fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion {
- return if (playerImpl.playerType == MainPlayer.PlayerType.POPUP) {
+ return if (player.playerType == MainPlayer.PlayerType.POPUP) {
when {
- e.x < playerImpl.popupWidth / 2.0 -> DisplayPortion.LEFT_HALF
+ e.x < player.popupLayoutParams!!.width / 2.0 -> DisplayPortion.LEFT_HALF
else -> DisplayPortion.RIGHT_HALF
}
} else /* MainPlayer.PlayerType.VIDEO */ {
when {
- e.x < playerImpl.rootView.width / 2.0 -> DisplayPortion.LEFT_HALF
+ e.x < player.rootView.width / 2.0 -> DisplayPortion.LEFT_HALF
else -> DisplayPortion.RIGHT_HALF
}
}
@@ -522,7 +514,7 @@ abstract class BasePlayerGestureListener(
companion object {
private const val TAG = "BasePlayerGestListener"
- private val DEBUG = BasePlayer.DEBUG
+ 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/PlayerGestureListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
index 8f9514781..887e32a23 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
+++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
@@ -11,15 +11,15 @@ import android.widget.ProgressBar;
import androidx.appcompat.content.res.AppCompatResources;
import org.jetbrains.annotations.NotNull;
+import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainPlayer;
-import org.schabi.newpipe.player.VideoPlayerImpl;
+import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.helper.PlayerHelper;
-import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
-import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
-import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
+import static org.schabi.newpipe.player.Player.STATE_PLAYING;
+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.util.AnimationUtils.Type.SCALE_AND_ALPHA;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -33,14 +33,14 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class PlayerGestureListener
extends BasePlayerGestureListener
implements View.OnTouchListener {
- private static final String TAG = ".PlayerGestureListener";
- private static final boolean DEBUG = BasePlayer.DEBUG;
+ private static final String TAG = PlayerGestureListener.class.getSimpleName();
+ private static final boolean DEBUG = MainActivity.DEBUG;
private final int maxVolume;
- public PlayerGestureListener(final VideoPlayerImpl playerImpl, final MainPlayer service) {
- super(playerImpl, service);
- maxVolume = playerImpl.getAudioReactor().getMaxVolume();
+ public PlayerGestureListener(final Player player, final MainPlayer service) {
+ super(player, service);
+ maxVolume = player.getAudioReactor().getMaxVolume();
}
@Override
@@ -48,46 +48,44 @@ public class PlayerGestureListener
@NotNull final DisplayPortion portion) {
if (DEBUG) {
Log.d(TAG, "onDoubleTap called with playerType = ["
- + playerImpl.getPlayerType() + "], portion = ["
- + portion + "]");
+ + player.getPlayerType() + "], portion = [" + portion + "]");
}
- if (playerImpl.isSomePopupMenuVisible()) {
- playerImpl.hideControls(0, 0);
+ if (player.isSomePopupMenuVisible()) {
+ player.hideControls(0, 0);
}
if (portion == DisplayPortion.LEFT) {
- playerImpl.onFastRewind();
+ player.fastRewind();
} else if (portion == DisplayPortion.MIDDLE) {
- playerImpl.onPlayPause();
+ player.playPause();
} else if (portion == DisplayPortion.RIGHT) {
- playerImpl.onFastForward();
+ player.fastForward();
}
}
@Override
public void onSingleTap(@NotNull final MainPlayer.PlayerType playerType) {
if (DEBUG) {
- Log.d(TAG, "onSingleTap called with playerType = ["
- + playerImpl.getPlayerType() + "]");
+ Log.d(TAG, "onSingleTap called with playerType = [" + player.getPlayerType() + "]");
}
if (playerType == MainPlayer.PlayerType.POPUP) {
- if (playerImpl.isControlsVisible()) {
- playerImpl.hideControls(100, 100);
+ if (player.isControlsVisible()) {
+ player.hideControls(100, 100);
} else {
- playerImpl.getPlayPauseButton().requestFocus();
- playerImpl.showControlsThenHide();
+ player.getPlayPauseButton().requestFocus();
+ player.showControlsThenHide();
}
} else /* playerType == MainPlayer.PlayerType.VIDEO */ {
- if (playerImpl.isControlsVisible()) {
- playerImpl.hideControls(150, 0);
+ if (player.isControlsVisible()) {
+ player.hideControls(150, 0);
} else {
- if (playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
- playerImpl.showControls(0);
+ if (player.getCurrentState() == Player.STATE_COMPLETED) {
+ player.showControls(0);
} else {
- playerImpl.showControlsThenHide();
+ player.showControlsThenHide();
}
}
}
@@ -101,8 +99,7 @@ public class PlayerGestureListener
final float distanceX, final float distanceY) {
if (DEBUG) {
Log.d(TAG, "onScroll called with playerType = ["
- + playerImpl.getPlayerType() + "], portion = ["
- + portion + "]");
+ + player.getPlayerType() + "], portion = [" + portion + "]");
}
if (playerType == MainPlayer.PlayerType.VIDEO) {
final boolean isBrightnessGestureEnabled =
@@ -123,8 +120,8 @@ public class PlayerGestureListener
}
} else /* MainPlayer.PlayerType.POPUP */ {
- final View closingOverlayView = playerImpl.getClosingOverlay();
- if (playerImpl.isInsideClosingRadius(movingEvent)) {
+ final View closingOverlayView = player.getClosingOverlayView();
+ if (player.isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
animateView(closingOverlayView, true, 250);
}
@@ -137,17 +134,17 @@ public class PlayerGestureListener
}
private void onScrollMainVolume(final float distanceX, final float distanceY) {
- playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
- final float currentProgressPercent = (float) playerImpl
- .getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
+ player.getVolumeProgressBar().incrementProgressBy((int) distanceY);
+ final float currentProgressPercent = (float) player
+ .getVolumeProgressBar().getProgress() / player.getMaxGestureLength();
final int currentVolume = (int) (maxVolume * currentProgressPercent);
- playerImpl.getAudioReactor().setVolume(currentVolume);
+ player.getAudioReactor().setVolume(currentVolume);
if (DEBUG) {
Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
}
- playerImpl.getVolumeImageView().setImageDrawable(
+ player.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(service, currentProgressPercent <= 0
? R.drawable.ic_volume_off_white_24dp
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp
@@ -155,23 +152,23 @@ public class PlayerGestureListener
: R.drawable.ic_volume_up_white_24dp)
);
- if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
- animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
+ if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
+ animateView(player.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
- if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
- playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
+ if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
+ player.getBrightnessRelativeLayout().setVisibility(View.GONE);
}
}
private void onScrollMainBrightness(final float distanceX, final float distanceY) {
- final Activity parent = playerImpl.getParentActivity();
+ final Activity parent = player.getParentActivity();
if (parent == null) {
return;
}
final Window window = parent.getWindow();
final WindowManager.LayoutParams layoutParams = window.getAttributes();
- final ProgressBar bar = playerImpl.getBrightnessProgressBar();
+ 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);
@@ -188,7 +185,7 @@ public class PlayerGestureListener
+ "currentBrightness = " + currentProgressPercent);
}
- playerImpl.getBrightnessImageView().setImageDrawable(
+ player.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(service,
currentProgressPercent < 0.25
? R.drawable.ic_brightness_low_white_24dp
@@ -197,11 +194,11 @@ public class PlayerGestureListener
: R.drawable.ic_brightness_high_white_24dp)
);
- if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
- animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
+ if (player.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
+ animateView(player.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
}
- if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
- playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
+ if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
+ player.getVolumeRelativeLayout().setVisibility(View.GONE);
}
}
@@ -210,40 +207,40 @@ public class PlayerGestureListener
@NotNull final MotionEvent event) {
if (DEBUG) {
Log.d(TAG, "onScrollEnd called with playerType = ["
- + playerImpl.getPlayerType() + "]");
+ + player.getPlayerType() + "]");
}
if (playerType == MainPlayer.PlayerType.VIDEO) {
if (DEBUG) {
Log.d(TAG, "onScrollEnd() called");
}
- if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
- animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA,
+ if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
+ animateView(player.getVolumeRelativeLayout(), SCALE_AND_ALPHA,
false, 200, 200);
}
- if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
- animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA,
+ if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
+ animateView(player.getBrightnessRelativeLayout(), SCALE_AND_ALPHA,
false, 200, 200);
}
- if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
- playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
+ if (player.isControlsVisible() && player.getCurrentState() == STATE_PLAYING) {
+ player.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
} else {
- if (playerImpl == null) {
+ if (player == null) {
return;
}
- if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
- playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
+ if (player.isControlsVisible() && player.getCurrentState() == STATE_PLAYING) {
+ player.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
- if (playerImpl.isInsideClosingRadius(event)) {
- playerImpl.closePopup();
+ if (player.isInsideClosingRadius(event)) {
+ player.closePopup();
} else {
- animateView(playerImpl.getClosingOverlay(), false, 0);
+ animateView(player.getClosingOverlayView(), false, 0);
- if (!playerImpl.isPopupClosing) {
- animateView(playerImpl.getCloseButton(), false, 200);
+ if (!player.isPopupClosing()) {
+ animateView(player.getCloseOverlayButton(), false, 200);
}
}
}
@@ -254,12 +251,12 @@ public class PlayerGestureListener
if (DEBUG) {
Log.d(TAG, "onPopupResizingStart called");
}
- playerImpl.showAndAnimateControl(-1, true);
- playerImpl.getLoadingPanel().setVisibility(View.GONE);
+ player.showAndAnimateControl(-1, true);
+ player.getLoadingPanel().setVisibility(View.GONE);
- playerImpl.hideControls(0, 0);
- animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
- animateView(playerImpl.getResizingIndicator(), true, 200, 0);
+ player.hideControls(0, 0);
+ animateView(player.getCurrentDisplaySeek(), false, 0, 0);
+ animateView(player.getResizingIndicator(), true, 200, 0);
}
@Override
@@ -267,7 +264,7 @@ public class PlayerGestureListener
if (DEBUG) {
Log.d(TAG, "onPopupResizingEnd called");
}
- animateView(playerImpl.getResizingIndicator(), false, 100, 0);
+ animateView(player.getResizingIndicator(), false, 100, 0);
}
}
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 93952a811..f774c90a0 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,10 +1,10 @@
package org.schabi.newpipe.player.event;
import org.schabi.newpipe.player.MainPlayer;
-import org.schabi.newpipe.player.VideoPlayerImpl;
+import org.schabi.newpipe.player.Player;
public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener {
- void onServiceConnected(VideoPlayerImpl player,
+ void onServiceConnected(Player player,
MainPlayer playerService,
boolean playAfterConnect);
void onServiceDisconnected();
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 8b2c0e925..253f0fbba 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
@@ -18,7 +18,7 @@ import androidx.fragment.app.DialogFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.SliderStrategy;
-import static org.schabi.newpipe.player.BasePlayer.DEBUG;
+import static org.schabi.newpipe.player.Player.DEBUG;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class PlaybackParameterDialog extends DialogFragment {
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 d89b5dd19..54021b616 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
@@ -1,8 +1,15 @@
package org.schabi.newpipe.player.helper;
+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;
@@ -11,11 +18,14 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
+import com.google.android.exoplayer2.PlaybackParameters;
+import com.google.android.exoplayer2.Player.RepeatMode;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
+import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode;
import com.google.android.exoplayer2.util.MimeTypes;
import org.schabi.newpipe.R;
@@ -27,6 +37,8 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.extractor.utils.Utils;
+import org.schabi.newpipe.player.MainPlayer;
+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;
@@ -41,13 +53,16 @@ import java.util.Formatter;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FILL;
-import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT;
-import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
+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 java.lang.annotation.RetentionPolicy.SOURCE;
+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;
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI;
@@ -71,6 +86,15 @@ public final class PlayerHelper {
int AUTOPLAY_TYPE_NEVER = 2;
}
+ @Retention(SOURCE)
+ @IntDef({MINIMIZE_ON_EXIT_MODE_NONE, MINIMIZE_ON_EXIT_MODE_BACKGROUND,
+ MINIMIZE_ON_EXIT_MODE_POPUP})
+ public @interface MinimizeMode {
+ int MINIMIZE_ON_EXIT_MODE_NONE = 0;
+ int MINIMIZE_ON_EXIT_MODE_BACKGROUND = 1;
+ int MINIMIZE_ON_EXIT_MODE_POPUP = 2;
+ }
+
private PlayerHelper() { }
////////////////////////////////////////////////////////////////////////////
@@ -121,14 +145,16 @@ public final class PlayerHelper {
@NonNull
public static String resizeTypeOf(@NonNull final Context context,
- @AspectRatioFrameLayout.ResizeMode final int resizeMode) {
+ @ResizeMode final int resizeMode) {
switch (resizeMode) {
- case RESIZE_MODE_FIT:
+ case AspectRatioFrameLayout.RESIZE_MODE_FIT:
return context.getResources().getString(R.string.resize_fit);
- case RESIZE_MODE_FILL:
+ case AspectRatioFrameLayout.RESIZE_MODE_FILL:
return context.getResources().getString(R.string.resize_fill);
- case RESIZE_MODE_ZOOM:
+ case AspectRatioFrameLayout.RESIZE_MODE_ZOOM:
return context.getResources().getString(R.string.resize_zoom);
+ case AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT:
+ case AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH:
default:
throw new IllegalArgumentException("Unrecognized resize mode: " + resizeMode);
}
@@ -199,23 +225,23 @@ public final class PlayerHelper {
////////////////////////////////////////////////////////////////////////////
public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) {
- return isResumeAfterAudioFocusGain(context, false);
+ return getPreferences(context)
+ .getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), false);
}
public static boolean isVolumeGestureEnabled(@NonNull final Context context) {
- return isVolumeGestureEnabled(context, true);
+ return getPreferences(context)
+ .getBoolean(context.getString(R.string.volume_gesture_control_key), true);
}
public static boolean isBrightnessGestureEnabled(@NonNull final Context context) {
- return isBrightnessGestureEnabled(context, true);
- }
-
- public static boolean isRememberingPopupDimensions(@NonNull final Context context) {
- return isRememberingPopupDimensions(context, true);
+ return getPreferences(context)
+ .getBoolean(context.getString(R.string.brightness_gesture_control_key), true);
}
public static boolean isAutoQueueEnabled(@NonNull final Context context) {
- return isAutoQueueEnabled(context, false);
+ return getPreferences(context)
+ .getBoolean(context.getString(R.string.auto_queue_key), false);
}
public static boolean isClearingQueueConfirmationRequired(@NonNull final Context context) {
@@ -229,7 +255,8 @@ public final class PlayerHelper {
final String popupAction = context.getString(R.string.minimize_on_exit_popup_key);
final String backgroundAction = context.getString(R.string.minimize_on_exit_background_key);
- final String action = getMinimizeOnExitAction(context, defaultAction);
+ final String action = getPreferences(context)
+ .getString(context.getString(R.string.minimize_on_exit_key), defaultAction);
if (action.equals(popupAction)) {
return MINIMIZE_ON_EXIT_MODE_POPUP;
} else if (action.equals(backgroundAction)) {
@@ -239,9 +266,23 @@ public final class PlayerHelper {
}
}
+ public static boolean isMinimizeOnExitToPopup(@NonNull final Context context) {
+ return getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_POPUP;
+ }
+
+ public static boolean isMinimizeOnExitToBackground(@NonNull final Context context) {
+ return getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_BACKGROUND;
+ }
+
+ public static boolean isMinimizeOnExitDisabled(@NonNull final Context context) {
+ return getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE;
+ }
+
@AutoplayType
public static int getAutoplayType(@NonNull final Context context) {
- final String type = getAutoplayType(context, context.getString(R.string.autoplay_wifi_key));
+ final String type = getPreferences(context).getString(
+ context.getString(R.string.autoplay_key),
+ context.getString(R.string.autoplay_wifi_key));
if (type.equals(context.getString(R.string.autoplay_always_key))) {
return AUTOPLAY_TYPE_ALWAYS;
} else if (type.equals(context.getString(R.string.autoplay_never_key))) {
@@ -350,14 +391,32 @@ public final class PlayerHelper {
return captioningManager.getFontScale();
}
+ /**
+ * @param context the Android context
+ * @return the screen brightness to use. A value less than 0 (the default) means to use the
+ * preferred screen brightness
+ */
public static float getScreenBrightness(@NonNull final Context context) {
- //a value of less than 0, the default, means to use the preferred screen brightness
- return getScreenBrightness(context, -1);
+ final SharedPreferences sp = getPreferences(context);
+ final long timestamp =
+ sp.getLong(context.getString(R.string.screen_brightness_timestamp_key), 0);
+ // Hypothesis: 4h covers a viewing block, e.g. evening.
+ // External lightning conditions will change in the next
+ // viewing block so we fall back to the default brightness
+ if ((System.currentTimeMillis() - timestamp) > TimeUnit.HOURS.toMillis(4)) {
+ return -1;
+ } else {
+ return sp.getFloat(context.getString(R.string.screen_brightness_key), -1);
+ }
}
public static void setScreenBrightness(@NonNull final Context context,
- final float setScreenBrightness) {
- setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis());
+ final float screenBrightness) {
+ getPreferences(context).edit()
+ .putFloat(context.getString(R.string.screen_brightness_key), screenBrightness)
+ .putLong(context.getString(R.string.screen_brightness_timestamp_key),
+ System.currentTimeMillis())
+ .apply();
}
public static boolean globalScreenOrientationLocked(final Context context) {
@@ -376,75 +435,11 @@ public final class PlayerHelper {
return PreferenceManager.getDefaultSharedPreferences(context);
}
- private static boolean isResumeAfterAudioFocusGain(@NonNull final Context context,
- final boolean b) {
- return getPreferences(context)
- .getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), b);
- }
-
- private static boolean isVolumeGestureEnabled(@NonNull final Context context,
- final boolean b) {
- return getPreferences(context)
- .getBoolean(context.getString(R.string.volume_gesture_control_key), b);
- }
-
- private static boolean isBrightnessGestureEnabled(@NonNull final Context context,
- final boolean b) {
- return getPreferences(context)
- .getBoolean(context.getString(R.string.brightness_gesture_control_key), b);
- }
-
- private static boolean isRememberingPopupDimensions(@NonNull final Context context,
- final boolean b) {
- return getPreferences(context)
- .getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
- }
-
private static boolean isUsingInexactSeek(@NonNull final Context context) {
return getPreferences(context)
.getBoolean(context.getString(R.string.use_inexact_seek_key), false);
}
- private static boolean isAutoQueueEnabled(@NonNull final Context context, final boolean b) {
- return getPreferences(context).getBoolean(context.getString(R.string.auto_queue_key), b);
- }
-
- private static void setScreenBrightness(@NonNull final Context context,
- final float screenBrightness, final long timestamp) {
- final SharedPreferences.Editor editor = getPreferences(context).edit();
- editor.putFloat(context.getString(R.string.screen_brightness_key), screenBrightness);
- editor.putLong(context.getString(R.string.screen_brightness_timestamp_key), timestamp);
- editor.apply();
- }
-
- private static float getScreenBrightness(@NonNull final Context context,
- final float screenBrightness) {
- final SharedPreferences sp = getPreferences(context);
- final long timestamp = sp
- .getLong(context.getString(R.string.screen_brightness_timestamp_key), 0);
- // Hypothesis: 4h covers a viewing block, e.g. evening.
- // External lightning conditions will change in the next
- // viewing block so we fall back to the default brightness
- if ((System.currentTimeMillis() - timestamp) > TimeUnit.HOURS.toMillis(4)) {
- return screenBrightness;
- } else {
- return sp
- .getFloat(context.getString(R.string.screen_brightness_key), screenBrightness);
- }
- }
-
- private static String getMinimizeOnExitAction(@NonNull final Context context,
- final String key) {
- return getPreferences(context)
- .getString(context.getString(R.string.minimize_on_exit_key), key);
- }
-
- private static String getAutoplayType(@NonNull final Context context,
- final String key) {
- return getPreferences(context).getString(context.getString(R.string.autoplay_key),
- key);
- }
-
private static SinglePlayQueue getAutoQueuedSinglePlayQueue(
final StreamInfoItem streamInfoItem) {
final SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
@@ -452,12 +447,168 @@ public final class PlayerHelper {
return singlePlayQueue;
}
- @Retention(SOURCE)
- @IntDef({MINIMIZE_ON_EXIT_MODE_NONE, MINIMIZE_ON_EXIT_MODE_BACKGROUND,
- MINIMIZE_ON_EXIT_MODE_POPUP})
- public @interface MinimizeMode {
- int MINIMIZE_ON_EXIT_MODE_NONE = 0;
- int MINIMIZE_ON_EXIT_MODE_BACKGROUND = 1;
- int MINIMIZE_ON_EXIT_MODE_POPUP = 2;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Utils used by player
+ ////////////////////////////////////////////////////////////////////////////
+
+ public static MainPlayer.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())];
+ }
+
+ public static boolean isPlaybackResumeEnabled(final Player player) {
+ return player.getPrefs().getBoolean(
+ player.getContext().getString(R.string.enable_watch_history_key), true)
+ && player.getPrefs().getBoolean(
+ player.getContext().getString(R.string.enable_playback_resume_key), true);
+ }
+
+ @RepeatMode
+ public static int nextRepeatMode(@RepeatMode final int repeatMode) {
+ switch (repeatMode) {
+ case REPEAT_MODE_OFF:
+ return REPEAT_MODE_ONE;
+ case REPEAT_MODE_ONE:
+ return REPEAT_MODE_ALL;
+ case REPEAT_MODE_ALL: default:
+ return REPEAT_MODE_OFF;
+ }
+ }
+
+ @ResizeMode
+ public static int retrieveResizeModeFromPrefs(final Player player) {
+ return player.getPrefs().getInt(player.getContext().getString(R.string.last_resize_mode),
+ AspectRatioFrameLayout.RESIZE_MODE_FIT);
+ }
+
+ @SuppressLint("SwitchIntDef") // only fit, fill and zoom are supported by NewPipe
+ @ResizeMode
+ public static int nextResizeModeAndSaveToPrefs(final Player player,
+ @ResizeMode final int resizeMode) {
+ final int newResizeMode;
+ switch (resizeMode) {
+ case AspectRatioFrameLayout.RESIZE_MODE_FIT:
+ newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL;
+ break;
+ case AspectRatioFrameLayout.RESIZE_MODE_FILL:
+ newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
+ break;
+ case AspectRatioFrameLayout.RESIZE_MODE_ZOOM:
+ default:
+ newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
+ break;
+ }
+
+ player.getPrefs().edit().putInt(
+ player.getContext().getString(R.string.last_resize_mode), resizeMode).apply();
+ return newResizeMode;
+ }
+
+ public static PlaybackParameters retrievePlaybackParametersFromPrefs(final Player player) {
+ final float speed = player.getPrefs().getFloat(player.getContext().getString(
+ R.string.playback_speed_key), player.getPlaybackSpeed());
+ final float pitch = player.getPrefs().getFloat(player.getContext().getString(
+ R.string.playback_pitch_key), player.getPlaybackPitch());
+ final boolean skipSilence = player.getPrefs().getBoolean(player.getContext().getString(
+ R.string.playback_skip_silence_key), player.getPlaybackSkipSilence());
+ return new PlaybackParameters(speed, pitch, skipSilence);
+ }
+
+ public static void savePlaybackParametersToPrefs(final Player player,
+ final float speed,
+ final float pitch,
+ final boolean skipSilence) {
+ player.getPrefs().edit()
+ .putFloat(player.getContext().getString(R.string.playback_speed_key), speed)
+ .putFloat(player.getContext().getString(R.string.playback_pitch_key), pitch)
+ .putBoolean(player.getContext().getString(R.string.playback_skip_silence_key),
+ skipSilence)
+ .apply();
+ }
+
+ /**
+ * @param player {@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 float popupWidth = popupRememberSizeAndPos
+ ? player.getPrefs().getFloat(player.getContext().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) (player.getScreenWidth() / 2f - popupWidth / 2f);
+ final int centerY = (int) (player.getScreenHeight() / 2f - popupHeight / 2f);
+ popupLayoutParams.x = popupRememberSizeAndPos
+ ? player.getPrefs().getInt(player.getContext().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;
+
+ 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)
+ .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);
+
+ 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),
+ player.getContext().getString(R.string.seek_duration_default_value))));
}
}
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 854e3eb2b..da1238c81 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 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.VideoPlayerImpl;
+import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
import org.schabi.newpipe.player.playqueue.PlayQueue;
@@ -33,7 +33,7 @@ public final class PlayerHolder {
private static ServiceConnection serviceConnection;
public static boolean bound;
private static MainPlayer playerService;
- private static VideoPlayerImpl player;
+ private static Player player;
/**
* Returns the current {@link MainPlayer.PlayerType} of the {@link MainPlayer} service,
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
index 883d9bb4f..c4b02d985 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java
@@ -3,11 +3,11 @@ package org.schabi.newpipe.player.mediasession;
import android.support.v4.media.MediaDescriptionCompat;
public interface MediaSessionCallback {
- void onSkipToPrevious();
+ void playPrevious();
- void onSkipToNext();
+ void playNext();
- void onSkipToIndex(int index);
+ void playItemAtIndex(int index);
int getCurrentPlayingIndex();
@@ -15,7 +15,7 @@ public interface MediaSessionCallback {
MediaDescriptionCompat getQueueMetadata(int index);
- void onPlay();
+ void play();
- void onPause();
+ void pause();
}
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 764c375af..62664c827 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
@@ -65,18 +65,18 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
@Override
public void onSkipToPrevious(final Player player, final ControlDispatcher controlDispatcher) {
- callback.onSkipToPrevious();
+ callback.playPrevious();
}
@Override
public void onSkipToQueueItem(final Player player, final ControlDispatcher controlDispatcher,
final long id) {
- callback.onSkipToIndex((int) id);
+ callback.playItemAtIndex((int) id);
}
@Override
public void onSkipToNext(final Player player, final ControlDispatcher controlDispatcher) {
- callback.onSkipToNext();
+ callback.playNext();
}
private void publishFloatingQueueWindow() {
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java
index 21c99859c..8bfbcde6b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java
@@ -14,9 +14,9 @@ public class PlayQueuePlaybackController extends DefaultControlDispatcher {
@Override
public boolean dispatchSetPlayWhenReady(final Player player, final boolean playWhenReady) {
if (playWhenReady) {
- callback.onPlay();
+ callback.play();
} else {
- callback.onPause();
+ callback.pause();
}
return true;
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java
similarity index 77%
rename from app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java
rename to app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java
index 5b20077c3..9dcb12344 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java
@@ -5,33 +5,33 @@ import android.os.Bundle;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
-import org.schabi.newpipe.player.BasePlayer;
+import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
-public class BasePlayerMediaSession implements MediaSessionCallback {
- private final BasePlayer player;
+public class PlayerMediaSession implements MediaSessionCallback {
+ private final Player player;
- public BasePlayerMediaSession(final BasePlayer player) {
+ public PlayerMediaSession(final Player player) {
this.player = player;
}
@Override
- public void onSkipToPrevious() {
- player.onPlayPrevious();
+ public void playPrevious() {
+ player.playPrevious();
}
@Override
- public void onSkipToNext() {
- player.onPlayNext();
+ public void playNext() {
+ player.playNext();
}
@Override
- public void onSkipToIndex(final int index) {
+ public void playItemAtIndex(final int index) {
if (player.getPlayQueue() == null) {
return;
}
- player.onSelected(player.getPlayQueue().getItem(index));
+ player.selectQueueItem(player.getPlayQueue().getItem(index));
}
@Override
@@ -52,11 +52,14 @@ public class BasePlayerMediaSession implements MediaSessionCallback {
@Override
public MediaDescriptionCompat getQueueMetadata(final int index) {
- if (player.getPlayQueue() == null || player.getPlayQueue().getItem(index) == null) {
+ if (player.getPlayQueue() == null) {
+ return null;
+ }
+ final PlayQueueItem item = player.getPlayQueue().getItem(index);
+ if (item == null) {
return null;
}
- final PlayQueueItem item = player.getPlayQueue().getItem(index);
final MediaDescriptionCompat.Builder descriptionBuilder
= new MediaDescriptionCompat.Builder()
.setMediaId(String.valueOf(index))
@@ -83,12 +86,12 @@ public class BasePlayerMediaSession implements MediaSessionCallback {
}
@Override
- public void onPlay() {
- player.onPlay();
+ public void play() {
+ player.play();
}
@Override
- public void onPause() {
- player.onPause();
+ public void pause() {
+ player.pause();
}
}
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 978f558c4..9493fbc92 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -354,4 +354,19 @@ 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;
+ }
}
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 b45a1e7b9..ea02e0f6b 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -47,9 +47,8 @@ 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.BackgroundPlayerActivity;
-import org.schabi.newpipe.player.BasePlayer;
+import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.MainPlayer;
-import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
@@ -78,11 +77,11 @@ public final class NavigationHelper {
if (playQueue != null) {
final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class);
if (cacheKey != null) {
- intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey);
+ intent.putExtra(Player.PLAY_QUEUE_KEY, cacheKey);
}
}
- intent.putExtra(VideoPlayer.RESUME_PLAYBACK, resumePlayback);
- intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_VIDEO);
+ intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
+ intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.VIDEO.ordinal());
return intent;
}
@@ -94,7 +93,7 @@ public final class NavigationHelper {
final boolean resumePlayback,
final boolean playWhenReady) {
return getPlayerIntent(context, targetClazz, playQueue, resumePlayback)
- .putExtra(BasePlayer.PLAY_WHEN_READY, playWhenReady);
+ .putExtra(Player.PLAY_WHEN_READY, playWhenReady);
}
@NonNull
@@ -104,8 +103,8 @@ public final class NavigationHelper {
final boolean selectOnAppend,
final boolean resumePlayback) {
return getPlayerIntent(context, targetClazz, playQueue, resumePlayback)
- .putExtra(BasePlayer.APPEND_ONLY, true)
- .putExtra(BasePlayer.SELECT_ON_APPEND, selectOnAppend);
+ .putExtra(Player.APPEND_ONLY, true)
+ .putExtra(Player.SELECT_ON_APPEND, selectOnAppend);
}
public static void playOnMainPlayer(final AppCompatActivity activity,
@@ -135,7 +134,7 @@ 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(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_POPUP);
+ intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.POPUP.ordinal());
ContextCompat.startForegroundService(context, intent);
}
@@ -145,7 +144,7 @@ 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(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_AUDIO);
+ intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.AUDIO.ordinal());
ContextCompat.startForegroundService(context, intent);
}
@@ -162,7 +161,7 @@ public final class NavigationHelper {
final Intent intent = getPlayerEnqueueIntent(
context, MainPlayer.class, queue, selectOnAppend, resumePlayback);
- intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_VIDEO);
+ intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.VIDEO.ordinal());
ContextCompat.startForegroundService(context, intent);
}
@@ -182,7 +181,7 @@ public final class NavigationHelper {
Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show();
final Intent intent = getPlayerEnqueueIntent(
context, MainPlayer.class, queue, selectOnAppend, resumePlayback);
- intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_POPUP);
+ intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.POPUP.ordinal());
ContextCompat.startForegroundService(context, intent);
}
@@ -198,7 +197,7 @@ public final class NavigationHelper {
Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show();
final Intent intent = getPlayerEnqueueIntent(
context, MainPlayer.class, queue, selectOnAppend, resumePlayback);
- intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_AUDIO);
+ intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.AUDIO.ordinal());
ContextCompat.startForegroundService(context, intent);
}
@@ -493,7 +492,7 @@ public final class NavigationHelper {
if (playQueue != null) {
final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class);
if (cacheKey != null) {
- intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey);
+ intent.putExtra(Player.PLAY_QUEUE_KEY, cacheKey);
}
}
context.startActivity(intent);
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index e9d951520..1e74f25a6 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -21,7 +21,6 @@
use_external_video_playeruse_external_audio_player
- use_oldplayervolume_gesture_controlbrightness_gesture_control
@@ -33,6 +32,10 @@
screen_brightness_timestamp_keyclear_queue_confirmation_key
+ popup_saved_width
+ popup_saved_x
+ popup_saved_y
+
seek_duration10000
@@ -70,7 +73,6 @@
@string/minimize_on_exit_popup_description
-
autoplay_key@string/autoplay_wifi_keyautoplay_always_key
diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml
index 0a5190b29..400a91a29 100644
--- a/checkstyle-suppressions.xml
+++ b/checkstyle-suppressions.xml
@@ -25,7 +25,7 @@
lines="156,158"/>
+ files="Player.java"/>
From 4a12b0ab2d4069200f5608dc01924dfc030e1a41 Mon Sep 17 00:00:00 2001
From: Stypox
Date: Mon, 11 Jan 2021 15:06:40 +0100
Subject: [PATCH 054/131] Revert hiding detail fragment tabs when in fullscreen
---
.../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 2 --
1 file changed, 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 b9f4ef6a9..33c125072 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
@@ -1889,10 +1889,8 @@ public final class VideoDetailFragment
if (fullscreen) {
hideSystemUiIfNeeded();
- viewPager.setVisibility(View.GONE);
} else {
showSystemUi();
- viewPager.setVisibility(View.VISIBLE);
}
if (relatedStreamsLayout != null) {
From cece83328a882d9522bfbe8eb1c09fe3598181f9 Mon Sep 17 00:00:00 2001
From: Stypox
Date: Mon, 11 Jan 2021 15:12:44 +0100
Subject: [PATCH 055/131] Fix wrong speed indicator in queue activity
---
.../java/org/schabi/newpipe/player/ServicePlayerActivity.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
index 283c25e4f..6e13554d2 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
@@ -128,6 +128,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
getMenuInflater().inflate(R.menu.menu_play_queue, m);
getMenuInflater().inflate(getPlayerOptionMenuResource(), m);
onMaybeMuteChanged();
+ onPlaybackParameterChanged(player.getPlaybackParameters());
return true;
}
From 059bb7622dbb04af314b0745cfd2e8cf7708ecf5 Mon Sep 17 00:00:00 2001
From: Stypox
Date: Tue, 12 Jan 2021 21:15:06 +0100
Subject: [PATCH 056/131] Merge and rename into PlayQueueActivity
---
app/src/main/AndroidManifest.xml | 2 +-
.../player/BackgroundPlayerActivity.java | 55 ---------------
...erActivity.java => PlayQueueActivity.java} | 68 +++++++------------
.../schabi/newpipe/util/NavigationHelper.java | 4 +-
.../activity_player_queue_control.xml | 2 +-
.../layout/activity_player_queue_control.xml | 2 +-
app/src/main/res/menu/menu_play_queue.xml | 2 +-
7 files changed, 31 insertions(+), 104 deletions(-)
delete mode 100644 app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java
rename app/src/main/java/org/schabi/newpipe/player/{ServicePlayerActivity.java => PlayQueueActivity.java} (94%)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3509f2d13..ac33a7f0f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -53,7 +53,7 @@
diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java
deleted file mode 100644
index eb1c74dad..000000000
--- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.schabi.newpipe.player;
-
-import android.content.Intent;
-import android.view.Menu;
-
-import org.schabi.newpipe.R;
-
-public final class BackgroundPlayerActivity extends ServicePlayerActivity {
-
- private static final String TAG = "BackgroundPlayerActivity";
-
- @Override
- public String getTag() {
- return TAG;
- }
-
- @Override
- public String getSupportActionTitle() {
- return getResources().getString(R.string.title_activity_play_queue);
- }
-
- @Override
- public Intent getBindIntent() {
- return new Intent(this, MainPlayer.class);
- }
-
- @Override
- public void startPlayerListener() {
- if (player != null) {
- player.setActivityListener(this);
- }
- }
-
- @Override
- public void stopPlayerListener() {
- if (player != null) {
- player.removeActivityListener(this);
- }
- }
-
- @Override
- public int getPlayerOptionMenuResource() {
- return R.menu.menu_play_queue_bg;
- }
-
- @Override
- public void setupMenu(final Menu menu) {
- if (player != null) {
- menu.findItem(R.id.action_switch_popup)
- .setVisible(!player.popupPlayerSelected());
- menu.findItem(R.id.action_switch_background)
- .setVisible(!player.audioPlayerSelected());
- }
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
similarity index 94%
rename from app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
rename to app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
index 6e13554d2..6ea7ecda3 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
@@ -16,7 +16,6 @@ import android.widget.PopupMenu;
import android.widget.SeekBar;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.ActivityCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -48,9 +47,12 @@ import java.util.List;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
-public abstract class ServicePlayerActivity extends AppCompatActivity
+public final class PlayQueueActivity extends AppCompatActivity
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener,
View.OnClickListener, PlaybackParameterDialog.Callback {
+
+ private static final String TAG = PlayQueueActivity.class.getSimpleName();
+
private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47;
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
@@ -60,7 +62,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private ServiceConnection serviceConnection;
private boolean seeking;
- private boolean redraw;
////////////////////////////////////////////////////////////////////////////
// Views
@@ -72,24 +73,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private Menu menu;
- ////////////////////////////////////////////////////////////////////////////
- // Abstracts
- ////////////////////////////////////////////////////////////////////////////
-
- public abstract String getTag();
-
- public abstract String getSupportActionTitle();
-
- public abstract Intent getBindIntent();
-
- public abstract void startPlayerListener();
-
- public abstract void stopPlayerListener();
-
- public abstract int getPlayerOptionMenuResource();
-
- public abstract void setupMenu(Menu m);
-
////////////////////////////////////////////////////////////////////////////
// Activity Lifecycle
////////////////////////////////////////////////////////////////////////////
@@ -106,27 +89,18 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
setSupportActionBar(queueControlBinding.toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- getSupportActionBar().setTitle(getSupportActionTitle());
+ getSupportActionBar().setTitle(R.string.title_activity_play_queue);
}
serviceConnection = getServiceConnection();
bind();
}
- @Override
- protected void onResume() {
- super.onResume();
- if (redraw) {
- ActivityCompat.recreate(this);
- redraw = false;
- }
- }
-
@Override
public boolean onCreateOptionsMenu(final Menu m) {
this.menu = m;
getMenuInflater().inflate(R.menu.menu_play_queue, m);
- getMenuInflater().inflate(getPlayerOptionMenuResource(), m);
+ getMenuInflater().inflate(R.menu.menu_play_queue_bg, m);
onMaybeMuteChanged();
onPlaybackParameterChanged(player.getPlaybackParameters());
return true;
@@ -135,7 +109,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
// Allow to setup visibility of menuItems
@Override
public boolean onPrepareOptionsMenu(final Menu m) {
- setupMenu(m);
+ if (player != null) {
+ menu.findItem(R.id.action_switch_popup)
+ .setVisible(!player.popupPlayerSelected());
+ menu.findItem(R.id.action_switch_background)
+ .setVisible(!player.audioPlayerSelected());
+ }
return super.onPrepareOptionsMenu(m);
}
@@ -191,7 +170,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
////////////////////////////////////////////////////////////////////////////
private void bind() {
- final boolean success = bindService(getBindIntent(), serviceConnection, BIND_AUTO_CREATE);
+ final Intent bindIntent = new Intent(this, MainPlayer.class);
+ final boolean success = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);
if (!success) {
unbindService(serviceConnection);
}
@@ -202,7 +182,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
if (serviceBound) {
unbindService(serviceConnection);
serviceBound = false;
- stopPlayerListener();
+ if (player != null) {
+ player.removeActivityListener(this);
+ }
if (player != null && player.getPlayQueueAdapter() != null) {
player.getPlayQueueAdapter().unsetSelectedListener();
@@ -221,12 +203,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return new ServiceConnection() {
@Override
public void onServiceDisconnected(final ComponentName name) {
- Log.d(getTag(), "Player service is disconnected");
+ Log.d(TAG, "Player service is disconnected");
}
@Override
public void onServiceConnected(final ComponentName name, final IBinder service) {
- Log.d(getTag(), "Player service is connected");
+ Log.d(TAG, "Player service is connected");
if (service instanceof PlayerServiceBinder) {
player = ((PlayerServiceBinder) service).getPlayerInstance();
@@ -240,7 +222,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
finish();
} else {
buildComponents();
- startPlayerListener();
+ if (player != null) {
+ player.setActivityListener(PlayQueueActivity.this);
+ }
}
}
};
@@ -463,7 +447,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return;
}
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
- player.getPlaybackSkipSilence(), this).show(getSupportFragmentManager(), getTag());
+ player.getPlaybackSkipSilence(), this).show(getSupportFragmentManager(), TAG);
}
@Override
@@ -517,10 +501,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
final PlaylistAppendDialog d = PlaylistAppendDialog.fromPlayQueueItems(playlist);
PlaylistAppendDialog.onPlaylistFound(getApplicationContext(),
- () -> d.show(getSupportFragmentManager(), getTag()),
- () -> PlaylistCreationDialog.newInstance(d)
- .show(getSupportFragmentManager(), getTag()
- ));
+ () -> d.show(getSupportFragmentManager(), TAG),
+ () -> PlaylistCreationDialog.newInstance(d).show(getSupportFragmentManager(), TAG));
}
////////////////////////////////////////////////////////////////////////////
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 ea02e0f6b..c90bb3025 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -46,7 +46,7 @@ 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.BackgroundPlayerActivity;
+import org.schabi.newpipe.player.PlayQueueActivity;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.helper.PlayerHelper;
@@ -530,7 +530,7 @@ public final class NavigationHelper {
}
public static Intent getPlayQueueActivityIntent(final Context context) {
- final Intent intent = new Intent(context, BackgroundPlayerActivity.class);
+ final Intent intent = new Intent(context, PlayQueueActivity.class);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
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 2adea9868..b106e7437 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
@@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
- tools:context="org.schabi.newpipe.player.BackgroundPlayerActivity">
+ tools:context="org.schabi.newpipe.player.PlayQueueActivity">
+ tools:context="org.schabi.newpipe.player.PlayQueueActivity">
+ tools:context=".player.PlayQueueActivity">
Date: Thu, 14 Jan 2021 10:11:44 +0100
Subject: [PATCH 057/131] Fix view binding types
---
app/src/main/res/layout-large-land/player.xml | 12 +++++-------
app/src/main/res/layout/player.xml | 7 +++----
2 files changed, 8 insertions(+), 11 deletions(-)
diff --git a/app/src/main/res/layout-large-land/player.xml b/app/src/main/res/layout-large-land/player.xml
index 145d1e2f8..7213f4020 100644
--- a/app/src/main/res/layout-large-land/player.xml
+++ b/app/src/main/res/layout-large-land/player.xml
@@ -145,7 +145,7 @@
tools:text="The Video Artist LONG very LONG very Long" />
-
+ tools:ignore="HardcodedText,RtlHardcoded"
+ tools:text="720p" />
-
@@ -80,12 +80,14 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
* media.ccc.de \[beta\]
* PeerTube instances \[beta\]
-## Updates
-When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can:
+
+
- 1. Add our custom repo to F-Droid and install it from there as soon as we publish a release. The instructions are here: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
- 2. Download the APK from [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it as soon as we publish a release.
- 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. (**IMPORTANT**: as of the time of writing, an F-Droid bug is preventing updates later than 0.20.1 from being published. Thus, till this bug is solved, if you want to use install updates from F-Droid, we recommend method 1.)
+## Installation
+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.schabi.org/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. (**IMPORTANT**: as of the time of writing, an issue is preventing releases later than 0.20.1 from being published. Thus, till this issue is solved, if you want to use F-Droid, we recommend method 1.)
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.
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.
From 337662bd40064686f516caf6e786e87f5d418293 Mon Sep 17 00:00:00 2001
From: mhmdanas <6daf084a-8eaf-40fb-86c7-8500077c3b69@anonaddy.me>
Date: Fri, 15 Jan 2021 18:16:28 +0300
Subject: [PATCH 064/131] Hide F-Droid badge
---
README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 96835a1fe..12b874f25 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,9 @@
NewPipe
A libre lightweight streaming frontend for Android.
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt.br.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_br.md).*
경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.
diff --git a/README.md b/README.md
index 141bedb10..8ba54410e 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt.br.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_br.md).*
WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.
diff --git a/README.pt.br.md b/README.pt_br.md
similarity index 99%
rename from README.pt.br.md
rename to README.pt_br.md
index 4b2662cef..8115d1a91 100644
--- a/README.pt.br.md
+++ b/README.pt_br.md
@@ -16,7 +16,7 @@
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt.br.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_br.md).*
AVISO: ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.
diff --git a/README.so.md b/README.so.md
index e62acf988..9bd3bd5ca 100644
--- a/README.so.md
+++ b/README.so.md
@@ -16,7 +16,7 @@
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_br.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md).*
경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.
diff --git a/README.md b/README.md
index 8ba54410e..a7b1e24af 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_br.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md).*
WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.
diff --git a/README.pt_br.md b/README.pt_BR.md
similarity index 99%
rename from README.pt_br.md
rename to README.pt_BR.md
index 8115d1a91..2229bca21 100644
--- a/README.pt_br.md
+++ b/README.pt_BR.md
@@ -16,7 +16,7 @@
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_br.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md).*
AVISO: ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.
diff --git a/README.so.md b/README.so.md
index 9bd3bd5ca..21b20a9e4 100644
--- a/README.so.md
+++ b/README.so.md
@@ -16,7 +16,7 @@
-*Ku akhri luuqad kale: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_br.md).*
+*Ku akhri luuqad kale: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md).*
DIGNIIN: MIDKAN [NOOCA APP-KA EE HADDA] WALI TIJAABO AYUU KU JIRAA (BETA), SIDAA DARTEED CILLADO AYAAD LA KULMI KARTAA. HADAAD LA KULANTO , KA FUR ARIN SHARAXAYA QAYBTANADA GITHUB-KA.
From 920e560b4b9b602c52c8661b3359ddfa6e89f695 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 16 Jan 2021 09:02:01 +0530
Subject: [PATCH 069/131] Convert AnimationUtils functions to extension
functions.
---
.../newpipe/fragments/BaseStateFragment.java | 26 +-
.../fragments/detail/VideoDetailFragment.java | 47 +-
.../fragments/list/BaseListFragment.java | 12 +-
.../list/channel/ChannelFragment.java | 16 +-
.../list/comments/CommentsFragment.java | 8 +-
.../fragments/list/kiosk/KioskFragment.java | 4 +-
.../list/playlist/PlaylistFragment.java | 12 +-
.../fragments/list/search/SearchFragment.java | 14 +-
.../list/videos/RelatedVideosFragment.java | 4 +-
.../holder/StreamMiniInfoItemHolder.java | 6 +-
.../java/org/schabi/newpipe/ktx/TextView.kt | 47 ++
.../main/java/org/schabi/newpipe/ktx/View.kt | 324 ++++++++++++
.../newpipe/local/BaseLocalListFragment.java | 14 +-
.../schabi/newpipe/local/feed/FeedFragment.kt | 50 +-
.../holder/LocalPlaylistStreamItemHolder.java | 6 +-
.../LocalStatisticStreamItemHolder.java | 6 +-
.../local/playlist/LocalPlaylistFragment.java | 12 +-
.../subscription/SubscriptionFragment.kt | 6 +-
.../subscription/item/FeedImportExportItem.kt | 5 +-
.../item/PickerSubscriptionItem.kt | 9 +-
.../org/schabi/newpipe/player/Player.java | 103 ++--
.../player/event/BasePlayerGestureListener.kt | 4 +-
.../player/event/PlayerGestureListener.java | 36 +-
.../schabi/newpipe/util/AnimationUtils.java | 478 ------------------
.../schabi/newpipe/views/CollapsibleView.java | 6 +-
25 files changed, 568 insertions(+), 687 deletions(-)
create mode 100644 app/src/main/java/org/schabi/newpipe/ktx/TextView.kt
create mode 100644 app/src/main/java/org/schabi/newpipe/ktx/View.kt
delete mode 100644 app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
index 9f1f57998..88f9b78b8 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
@@ -37,7 +37,7 @@ import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.ktx.ViewUtils.animate;
public abstract class BaseStateFragment extends BaseFragment implements ViewContract {
@State
@@ -131,35 +131,35 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC
@Override
public void showLoading() {
if (emptyStateView != null) {
- animateView(emptyStateView, false, 150);
+ animate(emptyStateView, false, 150);
}
if (loadingProgressBar != null) {
- animateView(loadingProgressBar, true, 400);
+ animate(loadingProgressBar, true, 400);
}
- animateView(errorPanelRoot, false, 150);
+ animate(errorPanelRoot, false, 150);
}
@Override
public void hideLoading() {
if (emptyStateView != null) {
- animateView(emptyStateView, false, 150);
+ animate(emptyStateView, false, 150);
}
if (loadingProgressBar != null) {
- animateView(loadingProgressBar, false, 0);
+ animate(loadingProgressBar, false, 0);
}
- animateView(errorPanelRoot, false, 150);
+ animate(errorPanelRoot, false, 150);
}
@Override
public void showEmptyState() {
isLoading.set(false);
if (emptyStateView != null) {
- animateView(emptyStateView, true, 200);
+ animate(emptyStateView, true, 200);
}
if (loadingProgressBar != null) {
- animateView(loadingProgressBar, false, 0);
+ animate(loadingProgressBar, false, 0);
}
- animateView(errorPanelRoot, false, 150);
+ animate(errorPanelRoot, false, 150);
}
@Override
@@ -174,11 +174,11 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC
errorTextView.setText(message);
if (showRetryButton) {
- animateView(errorButtonRetry, true, 600);
+ animate(errorButtonRetry, true, 600);
} else {
- animateView(errorButtonRetry, false, 0);
+ animate(errorButtonRetry, false, 0);
}
- animateView(errorPanelRoot, true, 300);
+ animate(errorPanelRoot, true, 300);
}
@Override
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 b25d23694..8c9ed2bef 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
@@ -76,11 +76,12 @@ import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.EmptyFragment;
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
+import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager;
-import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.MainPlayer;
+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;
@@ -125,7 +126,7 @@ import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
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.AnimationUtils.animateView;
+import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
public final class VideoDetailFragment
@@ -745,8 +746,10 @@ public final class VideoDetailFragment
}
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
- animateView(appendControlsDetail, true, 250, 0, () ->
- animateView(appendControlsDetail, false, 1500, 1000));
+ animate(appendControlsDetail, true, 250, AnimationType.ALPHA,
+ 0, () ->
+ animate(appendControlsDetail, false, 1500,
+ AnimationType.ALPHA, 1000));
}
return false;
};
@@ -1334,8 +1337,8 @@ public final class VideoDetailFragment
thumbnailImageView.setImageDrawable(
AppCompatResources.getDrawable(requireContext(), imageResource));
- animateView(thumbnailImageView, false, 0, 0,
- () -> animateView(thumbnailImageView, true, 500));
+ animate(thumbnailImageView, false, 0, AnimationType.ALPHA, 0,
+ () -> animate(thumbnailImageView, true, 500));
}
@Override
@@ -1417,14 +1420,14 @@ public final class VideoDetailFragment
contentRootLayoutHiding.setVisibility(View.INVISIBLE);
}
- animateView(thumbnailPlayButton, false, 50);
- animateView(detailDurationView, false, 100);
- animateView(detailPositionView, false, 100);
- animateView(positionView, false, 50);
+ animate(thumbnailPlayButton, false, 50);
+ animate(detailDurationView, false, 100);
+ animate(detailPositionView, false, 100);
+ animate(positionView, false, 50);
videoTitleTextView.setText(title);
videoTitleTextView.setMaxLines(1);
- animateView(videoTitleTextView, true, 0);
+ animate(videoTitleTextView, true, 0);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setVisibility(View.GONE);
@@ -1466,7 +1469,7 @@ public final class VideoDetailFragment
player != null && player.isFullscreen() ? View.GONE : View.VISIBLE);
}
}
- animateView(thumbnailPlayButton, true, 200);
+ animate(thumbnailPlayButton, true, 200);
videoTitleTextView.setText(title);
if (!isEmpty(info.getSubChannelName())) {
@@ -1530,12 +1533,12 @@ public final class VideoDetailFragment
detailDurationView.setText(Localization.getDurationString(info.getDuration()));
detailDurationView.setBackgroundColor(
ContextCompat.getColor(activity, R.color.duration_background_color));
- animateView(detailDurationView, true, 100);
+ animate(detailDurationView, true, 100);
} else if (info.getStreamType() == StreamType.LIVE_STREAM) {
detailDurationView.setText(R.string.duration_live);
detailDurationView.setBackgroundColor(
ContextCompat.getColor(activity, R.color.live_duration_background_color));
- animateView(detailDurationView, true, 100);
+ animate(detailDurationView, true, 100);
} else {
detailDurationView.setVisibility(View.GONE);
}
@@ -1703,8 +1706,8 @@ public final class VideoDetailFragment
// Show saved position from backStack if user allows it
showPlaybackProgress(playQueue.getItem().getRecoveryPosition(),
playQueue.getItem().getDuration() * 1000);
- animateView(positionView, true, 500);
- animateView(detailPositionView, true, 500);
+ animate(positionView, true, 500);
+ animate(detailPositionView, true, 500);
}
return;
}
@@ -1718,8 +1721,8 @@ public final class VideoDetailFragment
.observeOn(AndroidSchedulers.mainThread())
.subscribe(state -> {
showPlaybackProgress(state.getProgressTime(), info.getDuration() * 1000);
- animateView(positionView, true, 500);
- animateView(detailPositionView, true, 500);
+ animate(positionView, true, 500);
+ animate(detailPositionView, true, 500);
}, e -> {
if (DEBUG) {
e.printStackTrace();
@@ -1747,8 +1750,8 @@ public final class VideoDetailFragment
detailPositionView.setText(position);
}
if (positionView.getVisibility() != View.VISIBLE) {
- animateView(positionView, true, 100);
- animateView(detailPositionView, true, 100);
+ animate(positionView, true, 100);
+ animate(detailPositionView, true, 100);
}
}
@@ -1802,8 +1805,8 @@ public final class VideoDetailFragment
&& player.getPlayQueue() != null
&& player.getPlayQueue().getItem() != null
&& player.getPlayQueue().getItem().getUrl().equals(url)) {
- animateView(positionView, true, 100);
- animateView(detailPositionView, true, 100);
+ animate(positionView, true, 100);
+ animate(detailPositionView, true, 100);
}
break;
}
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 02c7a4818..b251d4b93 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
@@ -46,7 +46,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Queue;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.ktx.ViewUtils.animate;
public abstract class BaseListFragment extends BaseStateFragment
implements ListViewContract, StateSaver.WriteRead,
@@ -406,23 +406,17 @@ public abstract class BaseListFragment extends BaseStateFragment
// Contract
//////////////////////////////////////////////////////////////////////////*/
- @Override
- public void showLoading() {
- super.showLoading();
- // animateView(itemsList, false, 400);
- }
-
@Override
public void hideLoading() {
super.hideLoading();
- animateView(itemsList, true, 300);
+ animate(itemsList, true, 300);
}
@Override
public void showError(final String message, final boolean showRetryButton) {
super.showError(message, showRetryButton);
showListFooter(false);
- animateView(itemsList, false, 200);
+ animate(itemsList, false, 200);
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
index 003893517..834d8052f 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
@@ -37,12 +37,12 @@ import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
+import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
-import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
@@ -64,9 +64,9 @@ import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
-import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor;
-import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor;
+import static org.schabi.newpipe.ktx.ViewUtils.animate;
+import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor;
public class ChannelFragment extends BaseListInfoFragment
implements View.OnClickListener {
@@ -224,7 +224,7 @@ public class ChannelFragment extends BaseListInfoFragment
private void monitorSubscription(final ChannelInfo info) {
final Consumer onError = (Throwable throwable) -> {
- animateView(headerBinding.channelSubscribeButton, false, 100);
+ animate(headerBinding.channelSubscribeButton, false, 100);
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
NewPipe.getNameOfService(currentInfo.getServiceId()),
"Get subscription status", 0);
@@ -379,8 +379,8 @@ public class ChannelFragment extends BaseListInfoFragment
subscribedText);
}
- animateView(headerBinding.channelSubscribeButton, AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA,
- true, 100);
+ animate(headerBinding.channelSubscribeButton, true, 100,
+ AnimationType.LIGHT_SCALE_AND_ALPHA);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -436,7 +436,7 @@ public class ChannelFragment extends BaseListInfoFragment
IMAGE_LOADER.cancelDisplayTask(headerBinding.channelBannerImage);
IMAGE_LOADER.cancelDisplayTask(headerBinding.channelAvatarView);
IMAGE_LOADER.cancelDisplayTask(headerBinding.subChannelAvatarView);
- animateView(headerBinding.channelSubscribeButton, false, 100);
+ animate(headerBinding.channelSubscribeButton, false, 100);
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
index 68b0d823a..c0abc469b 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
@@ -16,8 +16,8 @@ import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.comments.CommentsInfo;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
+import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.report.UserAction;
-import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import io.reactivex.rxjava3.core.Single;
@@ -84,16 +84,14 @@ public class CommentsFragment extends BaseListInfoFragment {
public void handleResult(@NonNull final CommentsInfo result) {
super.handleResult(result);
- AnimationUtils.slideUp(requireView(), 120, 150, 0.06f);
+ ViewUtils.slideUp(requireView(), 120, 150, 0.06f);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
- if (disposables != null) {
- disposables.clear();
- }
+ disposables.clear();
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java
index 8770e6936..2e5e64539 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java
@@ -28,7 +28,7 @@ import org.schabi.newpipe.util.Localization;
import icepick.State;
import io.reactivex.rxjava3.core.Single;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.ktx.ViewUtils.animate;
/**
* Created by Christian Schabesberger on 23.09.17.
@@ -160,7 +160,7 @@ public class KioskFragment extends BaseListInfoFragment {
@Override
public void showLoading() {
super.showLoading();
- animateView(itemsList, false, 100);
+ animate(itemsList, false, 100);
}
@Override
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 6e723a686..58985e38a 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
@@ -61,7 +61,7 @@ import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
public class PlaylistFragment extends BaseListInfoFragment {
@@ -261,19 +261,19 @@ public class PlaylistFragment extends BaseListInfoFragment {
@Override
public void showLoading() {
super.showLoading();
- animateView(headerBinding.getRoot(), false, 200);
- animateView(itemsList, false, 100);
+ animate(headerBinding.getRoot(), false, 200);
+ animate(itemsList, false, 100);
IMAGE_LOADER.cancelDisplayTask(headerBinding.uploaderAvatarView);
- animateView(headerBinding.uploaderLayout, false, 200);
+ animate(headerBinding.uploaderLayout, false, 200);
}
@Override
public void handleResult(@NonNull final PlaylistInfo result) {
super.handleResult(result);
- animateView(headerBinding.getRoot(), true, 100);
- animateView(headerBinding.uploaderLayout, true, 300);
+ animate(headerBinding.getRoot(), true, 100);
+ animate(headerBinding.uploaderLayout, true, 300);
headerBinding.uploaderLayout.setOnClickListener(null);
// If we have an uploader put them into the UI
if (!TextUtils.isEmpty(result.getUploaderName())) {
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 511040827..67663a073 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
@@ -51,12 +51,12 @@ import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearch
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
+import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
-import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper;
@@ -82,7 +82,7 @@ import io.reactivex.rxjava3.subjects.PublishSubject;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
public class SearchFragment extends BaseListFragment>
@@ -413,7 +413,7 @@ public class SearchFragment extends BaseListFragment view = [$this]")
+ }
+ animate().setListener(null).cancel()
+ isVisible = true
+ alpha = 1f
+ execOnEnd?.run()
+ return
+ } else if ((isGone || isInvisible) && !enterOrExit) {
+ if (MainActivity.DEBUG) {
+ Log.d(TAG, "animate(): view was already gone > view = [$this]")
+ }
+ animate().setListener(null).cancel()
+ isGone = true
+ alpha = 0f
+ execOnEnd?.run()
+ return
+ }
+ animate().setListener(null).cancel()
+ isVisible = true
+ when (animationType) {
+ AnimationType.ALPHA -> animateAlpha(enterOrExit, duration, delay, execOnEnd)
+ AnimationType.SCALE_AND_ALPHA -> animateScaleAndAlpha(enterOrExit, duration, delay, execOnEnd)
+ AnimationType.LIGHT_SCALE_AND_ALPHA -> animateLightScaleAndAlpha(enterOrExit, duration, delay, execOnEnd)
+ AnimationType.SLIDE_AND_ALPHA -> animateSlideAndAlpha(enterOrExit, duration, delay, execOnEnd)
+ AnimationType.LIGHT_SLIDE_AND_ALPHA -> animateLightSlideAndAlpha(enterOrExit, duration, delay, execOnEnd)
+ }
+}
+
+/**
+ * Animate the background color of a view.
+ *
+ * @param duration the duration of the animation
+ * @param colorStart the background color to start with
+ * @param colorEnd the background color to end with
+ */
+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 + "]"
+ )
+ }
+ val empty = arrayOf(IntArray(0))
+ val viewPropertyAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorStart, colorEnd)
+ viewPropertyAnimator.interpolator = FastOutSlowInInterpolator()
+ viewPropertyAnimator.duration = duration
+ viewPropertyAnimator.addUpdateListener { animation: ValueAnimator ->
+ backgroundTintListCompat = ColorStateList(empty, intArrayOf(animation.animatedValue as Int))
+ }
+ viewPropertyAnimator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd))
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ onAnimationEnd(animation)
+ }
+ })
+ 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
+ )
+ }
+ 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()
+ requestLayout()
+ }
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ layoutParams.height = targetHeight
+ requestLayout()
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ layoutParams.height = targetHeight
+ requestLayout()
+ }
+ })
+ 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
+ )
+ }
+ animate().setListener(null).cancel()
+ animate()
+ .rotation(targetRotation.toFloat()).setDuration(duration)
+ .setInterpolator(FastOutSlowInInterpolator())
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationCancel(animation: Animator) {
+ rotation = targetRotation.toFloat()
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ rotation = targetRotation.toFloat()
+ }
+ }).start()
+}
+
+private fun View.animateAlpha(enterOrExit: Boolean, duration: Long, delay: Long, execOnEnd: Runnable?) {
+ if (enterOrExit) {
+ animate().setInterpolator(FastOutSlowInInterpolator()).alpha(1f)
+ .setDuration(duration).setStartDelay(delay)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ execOnEnd?.run()
+ }
+ }).start()
+ } else {
+ animate().setInterpolator(FastOutSlowInInterpolator()).alpha(0f)
+ .setDuration(duration).setStartDelay(delay)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ isGone = true
+ execOnEnd?.run()
+ }
+ }).start()
+ }
+}
+
+private fun View.animateScaleAndAlpha(enterOrExit: Boolean, duration: Long, delay: Long, execOnEnd: Runnable?) {
+ if (enterOrExit) {
+ scaleX = .8f
+ scaleY = .8f
+ animate()
+ .setInterpolator(FastOutSlowInInterpolator())
+ .alpha(1f).scaleX(1f).scaleY(1f)
+ .setDuration(duration).setStartDelay(delay)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ execOnEnd?.run()
+ }
+ }).start()
+ } else {
+ scaleX = 1f
+ scaleY = 1f
+ animate()
+ .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()
+ }
+}
+
+private fun View.animateLightScaleAndAlpha(enterOrExit: Boolean, duration: Long, delay: Long, execOnEnd: Runnable?) {
+ if (enterOrExit) {
+ alpha = .5f
+ scaleX = .95f
+ scaleY = .95f
+ animate()
+ .setInterpolator(FastOutSlowInInterpolator())
+ .alpha(1f).scaleX(1f).scaleY(1f)
+ .setDuration(duration).setStartDelay(delay)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ execOnEnd?.run()
+ }
+ }).start()
+ } else {
+ alpha = 1f
+ scaleX = 1f
+ scaleY = 1f
+ animate()
+ .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()
+ }
+}
+
+private fun View.animateSlideAndAlpha(enterOrExit: Boolean, duration: Long, delay: Long, execOnEnd: Runnable?) {
+ if (enterOrExit) {
+ translationY = -height.toFloat()
+ alpha = 0f
+ animate()
+ .setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f)
+ .setDuration(duration).setStartDelay(delay)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ execOnEnd?.run()
+ }
+ }).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()
+ }
+}
+
+private fun View.animateLightSlideAndAlpha(enterOrExit: Boolean, duration: Long, delay: Long, execOnEnd: Runnable?) {
+ if (enterOrExit) {
+ translationY = -height / 2.0f
+ alpha = 0f
+ animate()
+ .setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f)
+ .setDuration(duration).setStartDelay(delay)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ execOnEnd?.run()
+ }
+ }).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()
+ }
+}
+
+fun View.slideUp(duration: Long, delay: Long, @FloatRange(from = 0.0, to = 1.0) translationPercent: Float) {
+ val newTranslationY = (resources.displayMetrics.heightPixels * translationPercent).toInt()
+ animate().setListener(null).cancel()
+ alpha = 0f
+ translationY = newTranslationY.toFloat()
+ visibility = View.VISIBLE
+ animate()
+ .alpha(1f)
+ .translationY(0f)
+ .setStartDelay(delay)
+ .setDuration(duration)
+ .setInterpolator(FastOutSlowInInterpolator())
+ .start()
+}
+
+enum class AnimationType {
+ ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
+}
diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
index 38ecc1c63..d454da08b 100644
--- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
@@ -24,7 +24,7 @@ import org.schabi.newpipe.databinding.PignateFooterBinding;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.list.ListViewContract;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.ktx.ViewUtils.animate;
/**
* This fragment is design to be used with persistent data such as
@@ -185,10 +185,10 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
public void showLoading() {
super.showLoading();
if (itemsList != null) {
- animateView(itemsList, false, 200);
+ animate(itemsList, false, 200);
}
if (headerRootBinding != null) {
- animateView(headerRootBinding.getRoot(), false, 200);
+ animate(headerRootBinding.getRoot(), false, 200);
}
}
@@ -196,10 +196,10 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
public void hideLoading() {
super.hideLoading();
if (itemsList != null) {
- animateView(itemsList, true, 200);
+ animate(itemsList, true, 200);
}
if (headerRootBinding != null) {
- animateView(headerRootBinding.getRoot(), true, 200);
+ animate(headerRootBinding.getRoot(), true, 200);
}
}
@@ -209,10 +209,10 @@ public abstract class BaseLocalListFragment extends BaseStateFragment
showListFooter(false);
if (itemsList != null) {
- animateView(itemsList, false, 200);
+ animate(itemsList, false, 200);
}
if (headerRootBinding != null) {
- animateView(headerRootBinding.getRoot(), false, 200);
+ animate(headerRootBinding.getRoot(), false, 200);
}
}
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 6df15c8b2..6e95f009f 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
@@ -42,9 +42,9 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.databinding.ErrorRetryBinding
import org.schabi.newpipe.databinding.FragmentFeedBinding
import org.schabi.newpipe.fragments.list.BaseListFragment
+import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.local.feed.service.FeedLoadService
import org.schabi.newpipe.report.UserAction
-import org.schabi.newpipe.util.AnimationUtils.animateView
import org.schabi.newpipe.util.Localization
import java.util.Calendar
@@ -180,50 +180,50 @@ class FeedFragment : BaseListFragment() {
// /////////////////////////////////////////////////////////////////////////
override fun showLoading() {
- animateView(feedBinding.refreshRootView, false, 0)
- animateView(feedBinding.itemsList, false, 0)
+ feedBinding.refreshRootView.animate(false, 0)
+ feedBinding.itemsList.animate(false, 0)
- animateView(feedBinding.loadingProgressBar, true, 200)
- animateView(feedBinding.loadingProgressText, true, 200)
+ feedBinding.loadingProgressBar.animate(true, 200)
+ feedBinding.loadingProgressText.animate(true, 200)
- animateView(feedBinding.emptyStateView.root, false, 0)
- animateView(errorBinding.root, false, 0)
+ feedBinding.emptyStateView.root.animate(false, 0)
+ errorBinding.root.animate(false, 0)
}
override fun hideLoading() {
- animateView(feedBinding.refreshRootView, true, 200)
- animateView(feedBinding.itemsList, true, 300)
+ feedBinding.refreshRootView.animate(true, 200)
+ feedBinding.itemsList.animate(true, 300)
- animateView(feedBinding.loadingProgressBar, false, 0)
- animateView(feedBinding.loadingProgressText, false, 0)
+ feedBinding.loadingProgressBar.animate(false, 0)
+ feedBinding.loadingProgressText.animate(false, 0)
- animateView(feedBinding.emptyStateView.root, false, 0)
- animateView(errorBinding.root, false, 0)
+ feedBinding.emptyStateView.root.animate(false, 0)
+ errorBinding.root.animate(false, 0)
feedBinding.swiperefresh.isRefreshing = false
}
override fun showEmptyState() {
- animateView(feedBinding.refreshRootView, true, 200)
- animateView(feedBinding.itemsList, false, 0)
+ feedBinding.refreshRootView.animate(true, 200)
+ feedBinding.itemsList.animate(false, 0)
- animateView(feedBinding.loadingProgressBar, false, 0)
- animateView(feedBinding.loadingProgressText, false, 0)
+ feedBinding.loadingProgressBar.animate(false, 0)
+ feedBinding.loadingProgressText.animate(false, 0)
- animateView(feedBinding.emptyStateView.root, true, 800)
- animateView(errorBinding.root, false, 0)
+ feedBinding.emptyStateView.root.animate(true, 800)
+ errorBinding.root.animate(false, 0)
}
override fun showError(message: String, showRetryButton: Boolean) {
infoListAdapter.clearStreamItemList()
- animateView(feedBinding.refreshRootView, false, 120)
- animateView(feedBinding.itemsList, false, 120)
+ feedBinding.refreshRootView.animate(false, 120)
+ feedBinding.itemsList.animate(false, 120)
- animateView(feedBinding.loadingProgressBar, false, 120)
- animateView(feedBinding.loadingProgressText, false, 120)
+ feedBinding.loadingProgressBar.animate(false, 120)
+ feedBinding.loadingProgressText.animate(false, 120)
errorBinding.errorMessageView.text = message
- animateView(errorBinding.errorButtonRetry, showRetryButton, if (showRetryButton) 600 else 0)
- animateView(errorBinding.root, true, 300)
+ errorBinding.errorButtonRetry.animate(showRetryButton, if (showRetryButton) 600 else 0)
+ errorBinding.root.animate(true, 300)
}
override fun handleResult(result: FeedState) {
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 703271c42..fd6c8d1d1 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
@@ -12,9 +12,9 @@ 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.AnimationUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
@@ -117,10 +117,10 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(item.getProgressTime()));
- AnimationUtils.animateView(itemProgressView, true, 500);
+ ViewUtils.animate(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
- AnimationUtils.animateView(itemProgressView, false, 500);
+ ViewUtils.animate(itemProgressView, false, 500);
}
}
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 95768b5c2..7c4e47c36 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
@@ -12,9 +12,9 @@ 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.AnimationUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
@@ -148,10 +148,10 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(item.getProgressTime()));
- AnimationUtils.animateView(itemProgressView, true, 500);
+ ViewUtils.animate(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
- AnimationUtils.animateView(itemProgressView, false, 500);
+ ViewUtils.animate(itemProgressView, false, 500);
}
}
}
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 08b7101e6..3137de9e6 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
@@ -58,14 +58,14 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State;
-import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.ktx.ViewUtils.animate;
public class LocalPlaylistFragment extends BaseLocalListFragment, Void> {
// Save the list 10 seconds after the last change occurred
@@ -201,8 +201,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment() {
override fun showLoading() {
super.showLoading()
- animateView(binding.itemsList, false, 100)
+ binding.itemsList.animate(false, 100)
}
override fun hideLoading() {
super.hideLoading()
- animateView(binding.itemsList, true, 200)
+ binding.itemsList.animate(true, 200)
}
// /////////////////////////////////////////////////////////////////////////
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt
index 5fd70a684..00de83538 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt
@@ -17,7 +17,7 @@ import kotlinx.android.synthetic.main.feed_import_export_group.import_from_optio
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.exceptions.ExtractionException
-import org.schabi.newpipe.util.AnimationUtils
+import org.schabi.newpipe.ktx.animateRotation
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.ThemeHelper
import org.schabi.newpipe.views.CollapsibleView
@@ -49,8 +49,7 @@ class FeedImportExportItem(
expandIconListener?.let { viewHolder.import_export_options.removeListener(it) }
expandIconListener = CollapsibleView.StateListener { newState ->
- AnimationUtils.animateRotation(
- viewHolder.import_export_expand_icon,
+ viewHolder.import_export_expand_icon.animateRotation(
250, if (newState == CollapsibleView.COLLAPSED) 0 else 180
)
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
index fd98d0447..0138b1ffe 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
@@ -9,8 +9,8 @@ import kotlinx.android.synthetic.main.picker_subscription_item.*
import kotlinx.android.synthetic.main.picker_subscription_item.view.*
import org.schabi.newpipe.R
import org.schabi.newpipe.database.subscription.SubscriptionEntity
-import org.schabi.newpipe.util.AnimationUtils
-import org.schabi.newpipe.util.AnimationUtils.animateView
+import org.schabi.newpipe.ktx.AnimationType
+import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.util.ImageDisplayConstants
data class PickerSubscriptionItem(
@@ -41,9 +41,6 @@ data class PickerSubscriptionItem(
fun updateSelected(containerView: View, isSelected: Boolean) {
this.isSelected = isSelected
- animateView(
- containerView.selected_highlight,
- AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150
- )
+ containerView.selected_highlight.animate(isSelected, 150, AnimationType.LIGHT_SCALE_AND_ALPHA)
}
}
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 4e2edaa10..2453117f3 100644
--- a/app/src/main/java/org/schabi/newpipe/player/Player.java
+++ b/app/src/main/java/org/schabi/newpipe/player/Player.java
@@ -92,6 +92,7 @@ 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.history.HistoryRecordManager;
import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.event.PlayerEventListener;
@@ -116,7 +117,6 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
-import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.KoreUtil;
@@ -150,6 +150,8 @@ import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
import static com.google.android.exoplayer2.Player.RepeatMode;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
+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;
@@ -176,9 +178,6 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFr
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.AnimationUtils.Type.SLIDE_AND_ALPHA;
-import static org.schabi.newpipe.util.AnimationUtils.animateRotation;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@@ -1560,8 +1559,8 @@ public final class Player implements
}
showControls(0);
- animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, true,
- DEFAULT_CONTROLS_DURATION);
+ animate(binding.currentDisplaySeek, true, DEFAULT_CONTROLS_DURATION,
+ AnimationType.SCALE_AND_ALPHA);
}
@Override // seekbar listener
@@ -1576,7 +1575,7 @@ public final class Player implements
}
binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
- animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
+ animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA);
if (currentState == STATE_PAUSED_SEEK) {
changeState(STATE_BUFFERING);
@@ -1682,8 +1681,8 @@ public final class Player implements
: DPAD_CONTROLS_HIDE_TIME;
showHideShadow(true, DEFAULT_CONTROLS_DURATION);
- animateView(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, 0,
- () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime));
+ animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION,
+ AnimationType.ALPHA, 0, () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime));
}
public void showControls(final long duration) {
@@ -1694,7 +1693,7 @@ public final class Player implements
showSystemUIPartially();
controlsVisibilityHandler.removeCallbacksAndMessages(null);
showHideShadow(true, duration);
- animateView(binding.playbackControlRoot, true, duration);
+ animate(binding.playbackControlRoot, true, duration);
}
public void hideControls(final long duration, final long delay) {
@@ -1708,14 +1707,14 @@ public final class Player implements
controlsVisibilityHandler.removeCallbacksAndMessages(null);
controlsVisibilityHandler.postDelayed(() -> {
showHideShadow(false, duration);
- animateView(binding.playbackControlRoot, false, duration, 0,
- this::hideSystemUIIfNeeded);
+ animate(binding.playbackControlRoot, false, duration, AnimationType.ALPHA,
+ 0, this::hideSystemUIIfNeeded);
}, delay);
}
private void showHideShadow(final boolean show, final long duration) {
- animateView(binding.playerTopShadow, show, duration, 0, null);
- animateView(binding.playerBottomShadow, show, duration, 0, null);
+ animate(binding.playerTopShadow, show, duration, AnimationType.ALPHA, 0, null);
+ animate(binding.playerBottomShadow, show, duration, AnimationType.ALPHA, 0, null);
}
private void showOrHideButtons() {
@@ -1913,15 +1912,15 @@ public final class Player implements
}
controlsVisibilityHandler.removeCallbacksAndMessages(null);
- animateView(binding.playbackControlRoot, false, DEFAULT_CONTROLS_DURATION);
+ animate(binding.playbackControlRoot, false, DEFAULT_CONTROLS_DURATION);
binding.playbackSeekBar.setEnabled(false);
binding.playbackSeekBar.getThumb()
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
binding.loadingPanel.setBackgroundColor(Color.BLACK);
- animateView(binding.loadingPanel, true, 0);
- animateView(binding.surfaceForeground, true, 100);
+ animate(binding.loadingPanel, true, 0);
+ animate(binding.surfaceForeground, true, 100);
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
animatePlayButtons(false, 100);
@@ -1948,9 +1947,9 @@ public final class Player implements
binding.loadingPanel.setVisibility(View.GONE);
- animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
+ animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA);
- animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0,
+ animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0,
() -> {
binding.playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
animatePlayButtons(true, 200);
@@ -1991,7 +1990,7 @@ public final class Player implements
showControls(400);
binding.loadingPanel.setVisibility(View.GONE);
- animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0,
+ animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0,
() -> {
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
animatePlayButtons(true, 200);
@@ -2029,7 +2028,7 @@ public final class Player implements
Log.d(TAG, "onCompleted() called");
}
- animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0,
+ animate(binding.playPauseButton, false, 0, AnimationType.SCALE_AND_ALPHA, 0,
() -> {
binding.playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
@@ -2051,13 +2050,13 @@ public final class Player implements
}
showControls(500);
- animateView(binding.currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
+ animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA);
binding.loadingPanel.setVisibility(View.GONE);
- animateView(binding.surfaceForeground, true, 100);
+ animate(binding.surfaceForeground, true, 100);
}
private void animatePlayButtons(final boolean show, final int duration) {
- animateView(binding.playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
+ animate(binding.playPauseButton, show, duration, AnimationType.SCALE_AND_ALPHA);
boolean showQueueButtons = show;
if (playQueue == null) {
@@ -2065,18 +2064,18 @@ public final class Player implements
}
if (!showQueueButtons || playQueue.getIndex() > 0) {
- animateView(
+ animate(
binding.playPreviousButton,
- AnimationUtils.Type.SCALE_AND_ALPHA,
showQueueButtons,
- duration);
+ duration,
+ AnimationType.SCALE_AND_ALPHA);
}
if (!showQueueButtons || playQueue.getIndex() + 1 < playQueue.getStreams().size()) {
- animateView(
+ animate(
binding.playNextButton,
- AnimationUtils.Type.SCALE_AND_ALPHA,
showQueueButtons,
- duration);
+ duration,
+ AnimationType.SCALE_AND_ALPHA);
}
}
//endregion
@@ -2274,7 +2273,7 @@ public final class Player implements
@Override
public void onRenderedFirstFrame() {
//TODO check if this causes black screen when switching to fullscreen
- animateView(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION);
+ animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION);
}
//endregion
@@ -2871,8 +2870,8 @@ public final class Player implements
hideControls(0, 0);
binding.itemsListPanel.requestFocus();
- animateView(binding.itemsListPanel, SLIDE_AND_ALPHA, true,
- DEFAULT_CONTROLS_DURATION);
+ animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION,
+ AnimationType.SLIDE_AND_ALPHA);
binding.itemsList.scrollToPosition(playQueue.getIndex());
}
@@ -2905,8 +2904,8 @@ public final class Player implements
hideControls(0, 0);
binding.itemsListPanel.requestFocus();
- animateView(binding.itemsListPanel, SLIDE_AND_ALPHA, true,
- DEFAULT_CONTROLS_DURATION);
+ animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION,
+ AnimationType.SLIDE_AND_ALPHA);
final int adapterPosition = getNearestStreamSegmentPosition(simpleExoPlayer
.getCurrentPosition());
@@ -2942,8 +2941,8 @@ public final class Player implements
itemTouchHelper.attachToRecyclerView(null);
}
- animateView(binding.itemsListPanel, SLIDE_AND_ALPHA, false,
- DEFAULT_CONTROLS_DURATION, 0, () -> {
+ 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(
@@ -3451,18 +3450,19 @@ public final class Player implements
if (currentState != STATE_COMPLETED) {
controlsVisibilityHandler.removeCallbacksAndMessages(null);
showHideShadow(true, DEFAULT_CONTROLS_DURATION);
- animateView(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, 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);
- }
- }
- });
+ 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);
+ }
+ }
+ });
}
}
@@ -3531,9 +3531,8 @@ public final class Player implements
animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION,
isMoreControlsVisible ? 0 : 180);
- animateView(binding.secondaryControls, SLIDE_AND_ALPHA, !isMoreControlsVisible,
- DEFAULT_CONTROLS_DURATION, 0,
- () -> {
+ 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
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
index 46502a270..989c78c57 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt
+++ b/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt
@@ -7,11 +7,11 @@ 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 org.schabi.newpipe.util.AnimationUtils
import kotlin.math.abs
import kotlin.math.hypot
import kotlin.math.max
@@ -364,7 +364,7 @@ abstract class BasePlayerGestureListener(
}
if (!isMovingInPopup) {
- AnimationUtils.animateView(player.closeOverlayButton, true, 200)
+ player.closeOverlayButton.animate(true, 200)
}
isMovingInPopup = true
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
index 887e32a23..ecc57ff2f 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
+++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
@@ -17,11 +17,12 @@ import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.helper.PlayerHelper;
-import static org.schabi.newpipe.player.Player.STATE_PLAYING;
+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.util.AnimationUtils.Type.SCALE_AND_ALPHA;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.player.Player.STATE_PLAYING;
/**
* GestureListener for the player
@@ -123,11 +124,11 @@ public class PlayerGestureListener
final View closingOverlayView = player.getClosingOverlayView();
if (player.isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
- animateView(closingOverlayView, true, 250);
+ animate(closingOverlayView, true, 250);
}
} else {
if (closingOverlayView.getVisibility() == View.VISIBLE) {
- animateView(closingOverlayView, false, 0);
+ animate(closingOverlayView, false, 0);
}
}
}
@@ -153,7 +154,7 @@ public class PlayerGestureListener
);
if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
- animateView(player.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
+ animate(player.getVolumeRelativeLayout(), true, 200, SCALE_AND_ALPHA);
}
if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
player.getBrightnessRelativeLayout().setVisibility(View.GONE);
@@ -195,7 +196,7 @@ public class PlayerGestureListener
);
if (player.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
- animateView(player.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
+ animate(player.getBrightnessRelativeLayout(), true, 200, SCALE_AND_ALPHA);
}
if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
player.getVolumeRelativeLayout().setVisibility(View.GONE);
@@ -215,21 +216,18 @@ public class PlayerGestureListener
}
if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
- animateView(player.getVolumeRelativeLayout(), SCALE_AND_ALPHA,
- false, 200, 200);
+ animate(player.getVolumeRelativeLayout(), false, 200, SCALE_AND_ALPHA,
+ 200);
}
if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
- animateView(player.getBrightnessRelativeLayout(), SCALE_AND_ALPHA,
- false, 200, 200);
+ animate(player.getBrightnessRelativeLayout(), false, 200, SCALE_AND_ALPHA,
+ 200);
}
if (player.isControlsVisible() && player.getCurrentState() == STATE_PLAYING) {
player.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
} else {
- if (player == null) {
- return;
- }
if (player.isControlsVisible() && player.getCurrentState() == STATE_PLAYING) {
player.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
@@ -237,10 +235,10 @@ public class PlayerGestureListener
if (player.isInsideClosingRadius(event)) {
player.closePopup();
} else {
- animateView(player.getClosingOverlayView(), false, 0);
+ animate(player.getClosingOverlayView(), false, 0);
if (!player.isPopupClosing()) {
- animateView(player.getCloseOverlayButton(), false, 200);
+ animate(player.getCloseOverlayButton(), false, 200);
}
}
}
@@ -255,8 +253,8 @@ public class PlayerGestureListener
player.getLoadingPanel().setVisibility(View.GONE);
player.hideControls(0, 0);
- animateView(player.getCurrentDisplaySeek(), false, 0, 0);
- animateView(player.getResizingIndicator(), true, 200, 0);
+ animate(player.getCurrentDisplaySeek(), false, 0, ALPHA, 0);
+ animate(player.getResizingIndicator(), true, 200, ALPHA, 0);
}
@Override
@@ -264,7 +262,7 @@ public class PlayerGestureListener
if (DEBUG) {
Log.d(TAG, "onPopupResizingEnd called");
}
- animateView(player.getResizingIndicator(), false, 100, 0);
+ animate(player.getResizingIndicator(), false, 100, ALPHA, 0);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java
deleted file mode 100644
index 2675c2240..000000000
--- a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java
+++ /dev/null
@@ -1,478 +0,0 @@
-/*
- * Copyright 2018 Mauricio Colli
- * AnimationUtils.java is part of NewPipe
- *
- * License: GPL-3.0+
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.schabi.newpipe.util;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ArgbEvaluator;
-import android.animation.ValueAnimator;
-import android.content.res.ColorStateList;
-import android.util.Log;
-import android.view.View;
-import android.widget.TextView;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.FloatRange;
-import androidx.core.view.ViewCompat;
-import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
-
-import org.schabi.newpipe.MainActivity;
-
-public final class AnimationUtils {
- private static final String TAG = "AnimationUtils";
- private static final boolean DEBUG = MainActivity.DEBUG;
-
- private AnimationUtils() { }
-
- public static void animateView(final View view, final boolean enterOrExit,
- final long duration) {
- animateView(view, Type.ALPHA, enterOrExit, duration, 0, null);
- }
-
- public static void animateView(final View view, final boolean enterOrExit,
- final long duration, final long delay) {
- animateView(view, Type.ALPHA, enterOrExit, duration, delay, null);
- }
-
- public static void animateView(final View view, final boolean enterOrExit, final long duration,
- final long delay, final Runnable execOnEnd) {
- animateView(view, Type.ALPHA, enterOrExit, duration, delay, execOnEnd);
- }
-
- public static void animateView(final View view, final Type animationType,
- final boolean enterOrExit, final long duration) {
- animateView(view, animationType, enterOrExit, duration, 0, null);
- }
-
- public static void animateView(final View view, final Type animationType,
- final boolean enterOrExit, final long duration,
- final long delay) {
- animateView(view, animationType, enterOrExit, duration, delay, null);
- }
-
- /**
- * Animate the view.
- *
- * @param view view that will be animated
- * @param animationType {@link Type} of the animation
- * @param enterOrExit true to enter, false to exit
- * @param duration how long the animation will take, in milliseconds
- * @param delay how long the animation will wait to start, in milliseconds
- * @param execOnEnd runnable that will be executed when the animation ends
- */
- public static void animateView(final View view, final Type animationType,
- final boolean enterOrExit, final long duration,
- final long delay, final Runnable execOnEnd) {
- if (DEBUG) {
- String id;
- try {
- id = view.getResources().getResourceEntryName(view.getId());
- } catch (final Exception e) {
- id = view.getId() + "";
- }
-
- final String msg = String.format("%8s → [%s:%s] [%s %s:%s] execOnEnd=%s", enterOrExit,
- view.getClass().getSimpleName(), id, animationType, duration, delay, execOnEnd);
- Log.d(TAG, "animateView()" + msg);
- }
-
- if (view.getVisibility() == View.VISIBLE && enterOrExit) {
- if (DEBUG) {
- Log.d(TAG, "animateView() view was already visible > view = [" + view + "]");
- }
- view.animate().setListener(null).cancel();
- view.setVisibility(View.VISIBLE);
- view.setAlpha(1f);
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- return;
- } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE)
- && !enterOrExit) {
- if (DEBUG) {
- Log.d(TAG, "animateView() view was already gone > view = [" + view + "]");
- }
- view.animate().setListener(null).cancel();
- view.setVisibility(View.GONE);
- view.setAlpha(0f);
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- return;
- }
-
- view.animate().setListener(null).cancel();
- view.setVisibility(View.VISIBLE);
-
- switch (animationType) {
- case ALPHA:
- animateAlpha(view, enterOrExit, duration, delay, execOnEnd);
- break;
- case SCALE_AND_ALPHA:
- animateScaleAndAlpha(view, enterOrExit, duration, delay, execOnEnd);
- break;
- case LIGHT_SCALE_AND_ALPHA:
- animateLightScaleAndAlpha(view, enterOrExit, duration, delay, execOnEnd);
- break;
- case SLIDE_AND_ALPHA:
- animateSlideAndAlpha(view, enterOrExit, duration, delay, execOnEnd);
- break;
- case LIGHT_SLIDE_AND_ALPHA:
- animateLightSlideAndAlpha(view, enterOrExit, duration, delay, execOnEnd);
- break;
- }
- }
-
- /**
- * Animate the background color of a view.
- *
- * @param view the view to animate
- * @param duration the duration of the animation
- * @param colorStart the background color to start with
- * @param colorEnd the background color to end with
- */
- public static void animateBackgroundColor(final View view, final long duration,
- @ColorInt final int colorStart,
- @ColorInt final int colorEnd) {
- if (DEBUG) {
- Log.d(TAG, "animateBackgroundColor() called with: "
- + "view = [" + view + "], duration = [" + duration + "], "
- + "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]");
- }
-
- final int[][] empty = {new int[0]};
- final ValueAnimator viewPropertyAnimator = ValueAnimator
- .ofObject(new ArgbEvaluator(), colorStart, colorEnd);
- viewPropertyAnimator.setInterpolator(new FastOutSlowInInterpolator());
- viewPropertyAnimator.setDuration(duration);
- viewPropertyAnimator.addUpdateListener(animation ->
- ViewCompat.setBackgroundTintList(view,
- new ColorStateList(empty, new int[]{(int) animation.getAnimatedValue()})));
- viewPropertyAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- ViewCompat.setBackgroundTintList(view,
- new ColorStateList(empty, new int[]{colorEnd}));
- }
-
- @Override
- public void onAnimationCancel(final Animator animation) {
- onAnimationEnd(animation);
- }
- });
- viewPropertyAnimator.start();
- }
-
- /**
- * Animate the text color of any view that extends {@link TextView} (Buttons, EditText...).
- *
- * @param view the text view to animate
- * @param duration the duration of the animation
- * @param colorStart the text color to start with
- * @param colorEnd the text color to end with
- */
- public static void animateTextColor(final TextView view, final long duration,
- @ColorInt final int colorStart,
- @ColorInt final int colorEnd) {
- if (DEBUG) {
- Log.d(TAG, "animateTextColor() called with: "
- + "view = [" + view + "], duration = [" + duration + "], "
- + "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]");
- }
-
- final ValueAnimator viewPropertyAnimator = ValueAnimator
- .ofObject(new ArgbEvaluator(), colorStart, colorEnd);
- viewPropertyAnimator.setInterpolator(new FastOutSlowInInterpolator());
- viewPropertyAnimator.setDuration(duration);
- viewPropertyAnimator.addUpdateListener(animation ->
- view.setTextColor((int) animation.getAnimatedValue()));
- viewPropertyAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- view.setTextColor(colorEnd);
- }
-
- @Override
- public void onAnimationCancel(final Animator animation) {
- view.setTextColor(colorEnd);
- }
- });
- viewPropertyAnimator.start();
- }
-
- public static ValueAnimator animateHeight(final View view, final long duration,
- final int targetHeight) {
- final int height = view.getHeight();
- if (DEBUG) {
- Log.d(TAG, "animateHeight: duration = [" + duration + "], "
- + "from " + height + " to → " + targetHeight + " in: " + view);
- }
-
- final ValueAnimator animator = ValueAnimator.ofFloat(height, targetHeight);
- animator.setInterpolator(new FastOutSlowInInterpolator());
- animator.setDuration(duration);
- animator.addUpdateListener(animation -> {
- final float value = (float) animation.getAnimatedValue();
- view.getLayoutParams().height = (int) value;
- view.requestLayout();
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- view.getLayoutParams().height = targetHeight;
- view.requestLayout();
- }
-
- @Override
- public void onAnimationCancel(final Animator animation) {
- view.getLayoutParams().height = targetHeight;
- view.requestLayout();
- }
- });
- animator.start();
-
- return animator;
- }
-
- public static void animateRotation(final View view, final long duration,
- final int targetRotation) {
- if (DEBUG) {
- Log.d(TAG, "animateRotation: duration = [" + duration + "], "
- + "from " + view.getRotation() + " to → " + targetRotation + " in: " + view);
- }
- view.animate().setListener(null).cancel();
-
- view.animate()
- .rotation(targetRotation).setDuration(duration)
- .setInterpolator(new FastOutSlowInInterpolator())
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(final Animator animation) {
- view.setRotation(targetRotation);
- }
-
- @Override
- public void onAnimationEnd(final Animator animation) {
- view.setRotation(targetRotation);
- }
- }).start();
- }
-
- private static void animateAlpha(final View view, final boolean enterOrExit,
- final long duration, final long delay,
- final Runnable execOnEnd) {
- if (enterOrExit) {
- view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- }
- }).start();
- } else {
- view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- view.setVisibility(View.GONE);
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- }
- }).start();
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Internals
- //////////////////////////////////////////////////////////////////////////*/
-
- private static void animateScaleAndAlpha(final View view, final boolean enterOrExit,
- final long duration, final long delay,
- final Runnable execOnEnd) {
- if (enterOrExit) {
- view.setScaleX(.8f);
- view.setScaleY(.8f);
- view.animate()
- .setInterpolator(new FastOutSlowInInterpolator())
- .alpha(1f).scaleX(1f).scaleY(1f)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- }
- }).start();
- } else {
- view.setScaleX(1f);
- view.setScaleY(1f);
- view.animate()
- .setInterpolator(new FastOutSlowInInterpolator())
- .alpha(0f).scaleX(.8f).scaleY(.8f)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- view.setVisibility(View.GONE);
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- }
- }).start();
- }
- }
-
- private static void animateLightScaleAndAlpha(final View view, final boolean enterOrExit,
- final long duration, final long delay,
- final Runnable execOnEnd) {
- if (enterOrExit) {
- view.setAlpha(.5f);
- view.setScaleX(.95f);
- view.setScaleY(.95f);
- view.animate()
- .setInterpolator(new FastOutSlowInInterpolator())
- .alpha(1f).scaleX(1f).scaleY(1f)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- }
- }).start();
- } else {
- view.setAlpha(1f);
- view.setScaleX(1f);
- view.setScaleY(1f);
- view.animate()
- .setInterpolator(new FastOutSlowInInterpolator())
- .alpha(0f).scaleX(.95f).scaleY(.95f)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- view.setVisibility(View.GONE);
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- }
- }).start();
- }
- }
-
- private static void animateSlideAndAlpha(final View view, final boolean enterOrExit,
- final long duration, final long delay,
- final Runnable execOnEnd) {
- if (enterOrExit) {
- view.setTranslationY(-view.getHeight());
- view.setAlpha(0f);
- view.animate()
- .setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- }
- }).start();
- } else {
- view.animate()
- .setInterpolator(new FastOutSlowInInterpolator())
- .alpha(0f).translationY(-view.getHeight())
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- view.setVisibility(View.GONE);
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- }
- }).start();
- }
- }
-
- private static void animateLightSlideAndAlpha(final View view, final boolean enterOrExit,
- final long duration, final long delay,
- final Runnable execOnEnd) {
- if (enterOrExit) {
- view.setTranslationY(-view.getHeight() / 2.0f);
- view.setAlpha(0f);
- view.animate()
- .setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- }
- }).start();
- } else {
- view.animate().setInterpolator(new FastOutSlowInInterpolator())
- .alpha(0f).translationY(-view.getHeight() / 2.0f)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- view.setVisibility(View.GONE);
- if (execOnEnd != null) {
- execOnEnd.run();
- }
- }
- }).start();
- }
- }
-
- public static void slideUp(final View view, final long duration, final long delay,
- @FloatRange(from = 0.0f, to = 1.0f)
- final float translationPercent) {
- final int translationY = (int) (view.getResources().getDisplayMetrics().heightPixels
- * (translationPercent));
-
- view.animate().setListener(null).cancel();
- view.setAlpha(0f);
- view.setTranslationY(translationY);
- view.setVisibility(View.VISIBLE);
- view.animate()
- .alpha(1f)
- .translationY(0)
- .setStartDelay(delay)
- .setDuration(duration)
- .setInterpolator(new FastOutSlowInInterpolator())
- .start();
- }
-
- public enum Type {
- ALPHA,
- SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA,
- SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
- }
-}
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 b34a6be63..e1ada4f9b 100644
--- a/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java
+++ b/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java
@@ -31,7 +31,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import org.schabi.newpipe.util.AnimationUtils;
+import org.schabi.newpipe.ktx.ViewUtils;
import java.lang.annotation.Retention;
import java.util.ArrayList;
@@ -128,7 +128,7 @@ public class CollapsibleView extends LinearLayout {
if (currentAnimator != null && currentAnimator.isRunning()) {
currentAnimator.cancel();
}
- currentAnimator = AnimationUtils.animateHeight(this, ANIMATION_DURATION, 0);
+ currentAnimator = ViewUtils.animateHeight(this, ANIMATION_DURATION, 0);
setCurrentState(COLLAPSED);
}
@@ -151,7 +151,7 @@ public class CollapsibleView extends LinearLayout {
if (currentAnimator != null && currentAnimator.isRunning()) {
currentAnimator.cancel();
}
- currentAnimator = AnimationUtils.animateHeight(this, ANIMATION_DURATION, this.targetHeight);
+ currentAnimator = ViewUtils.animateHeight(this, ANIMATION_DURATION, this.targetHeight);
setCurrentState(EXPANDED);
}
From a57fd69fb4787d08982c6cfb0d867f365588d6dc Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Tue, 15 Dec 2020 20:11:55 +0100
Subject: [PATCH 070/131] External sharing improvements
Improve NewPipe's share on some devices + fix crash when no browser is set on some devices
Catching ActivityNotFoundException when trying to open the default browser
Use an ACTION_CHOOSER intent and put as an extra intent the intent to
open an URI / share an URI when no default app is set.
Add a LinkHelper class which set a custom action when clicking web links
in the description of a content. This class also helps to implement a confirmation dialog when trying to open web links in an external app.
---
.../schabi/newpipe/about/AboutActivity.java | 12 +-
.../fragments/detail/VideoDetailFragment.java | 28 +---
.../list/channel/ChannelFragment.java | 5 +-
.../schabi/newpipe/report/ErrorActivity.java | 14 +-
.../util/CommentTextOnTouchListener.java | 3 +-
.../schabi/newpipe/util/ExtractorHelper.java | 4 +-
.../org/schabi/newpipe/util/LinkHelper.java | 116 +++++++++++++++
.../schabi/newpipe/util/NavigationHelper.java | 27 +---
.../org/schabi/newpipe/util/ShareUtils.java | 138 ++++++++++++++----
.../giga/ui/adapter/MissionAdapter.java | 30 ++--
app/src/main/res/values-fr/strings.xml | 1 +
app/src/main/res/values/strings.xml | 1 +
checkstyle-suppressions.xml | 17 +--
13 files changed, 285 insertions(+), 111 deletions(-)
create mode 100644 app/src/main/java/org/schabi/newpipe/util/LinkHelper.java
diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
index 2569f4a94..d11978c6b 100644
--- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
@@ -146,16 +146,20 @@ public class AboutActivity extends AppCompatActivity {
aboutBinding.appVersion.setText(BuildConfig.VERSION_NAME);
aboutBinding.githubLink.setOnClickListener(nv ->
- openUrlInBrowser(context, context.getString(R.string.github_url)));
+ openUrlInBrowser(context, context.getString(R.string.github_url),
+ false));
aboutBinding.donationLink.setOnClickListener(v ->
- openUrlInBrowser(context, context.getString(R.string.donation_url)));
+ openUrlInBrowser(context, context.getString(R.string.donation_url),
+ false));
aboutBinding.websiteLink.setOnClickListener(nv ->
- openUrlInBrowser(context, context.getString(R.string.website_url)));
+ openUrlInBrowser(context, context.getString(R.string.website_url),
+ false));
aboutBinding.privacyPolicyLink.setOnClickListener(v ->
- openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
+ openUrlInBrowser(context, context.getString(R.string.privacy_policy_url),
+ false));
return aboutBinding.getRoot();
}
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 8c9ed2bef..c9913a808 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
@@ -16,7 +16,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
-import android.text.util.Linkify;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
@@ -96,6 +95,7 @@ import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
+import org.schabi.newpipe.util.LinkHelper;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
@@ -112,10 +112,7 @@ import java.util.Objects;
import java.util.concurrent.TimeUnit;
import icepick.State;
-import io.noties.markwon.Markwon;
-import io.noties.markwon.linkify.LinkifyPlugin;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
@@ -1233,26 +1230,17 @@ public final class VideoDetailFragment
}
if (description.getType() == Description.HTML) {
- disposables.add(Single.just(description.getContent())
- .map(descriptionText ->
- HtmlCompat.fromHtml(descriptionText,
- HtmlCompat.FROM_HTML_MODE_LEGACY))
- .subscribeOn(Schedulers.computation())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(spanned -> {
- videoDescriptionView.setText(spanned);
- videoDescriptionView.setVisibility(View.VISIBLE);
- }));
+ LinkHelper.createLinksFromHtmlBlock(requireContext(), description.getContent(),
+ videoDescriptionView, HtmlCompat.FROM_HTML_MODE_LEGACY);
+ videoDescriptionView.setVisibility(View.VISIBLE);
} else if (description.getType() == Description.MARKDOWN) {
- final Markwon markwon = Markwon.builder(requireContext())
- .usePlugin(LinkifyPlugin.create())
- .build();
- markwon.setMarkdown(videoDescriptionView, description.getContent());
+ LinkHelper.createLinksFromMarkdownText(requireContext(), description.getContent(),
+ videoDescriptionView);
videoDescriptionView.setVisibility(View.VISIBLE);
} else {
//== Description.PLAIN_TEXT
- videoDescriptionView.setAutoLinkMask(Linkify.WEB_URLS);
- videoDescriptionView.setText(description.getContent(), TextView.BufferType.SPANNABLE);
+ LinkHelper.createLinksFromPlainText(requireContext(), description.getContent(),
+ videoDescriptionView);
videoDescriptionView.setVisibility(View.VISIBLE);
}
}
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 834d8052f..6bcaac630 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
@@ -1,8 +1,6 @@
package org.schabi.newpipe.fragments.list.channel;
import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
@@ -188,8 +186,7 @@ public class ChannelFragment extends BaseListInfoFragment
private void openRssFeed() {
final ChannelInfo info = currentInfo;
if (info != null) {
- final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl()));
- startActivity(intent);
+ ShareUtils.openUrlInBrowser(requireContext(), info.getFeedUrl(), false);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
index a4b6af2ab..0321d197a 100644
--- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
@@ -14,7 +14,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
@@ -207,8 +206,7 @@ public class ErrorActivity extends AppCompatActivity {
openPrivacyPolicyDialog(this, "EMAIL"));
activityErrorBinding.errorReportCopyButton.setOnClickListener(v -> {
- ShareUtils.copyToClipboard(this, buildMarkdown());
- Toast.makeText(this, R.string.msg_copied, Toast.LENGTH_SHORT).show();
+ ShareUtils.copyToClipboard(this, buildMarkdown());
});
activityErrorBinding.errorReportGitHubButton.setOnClickListener(v ->
@@ -246,11 +244,7 @@ public class ErrorActivity extends AppCompatActivity {
goToReturnActivity();
break;
case R.id.menu_item_share_error:
- final Intent intent = new Intent();
- intent.setAction(Intent.ACTION_SEND);
- intent.putExtra(Intent.EXTRA_TEXT, buildJson());
- intent.setType("text/plain");
- startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
+ ShareUtils.shareUrl(this, getString(R.string.error_report_title), buildJson());
break;
}
return false;
@@ -273,10 +267,10 @@ public class ErrorActivity extends AppCompatActivity {
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
.putExtra(Intent.EXTRA_TEXT, buildJson());
if (i.resolveActivity(getPackageManager()) != null) {
- startActivity(i);
+ ShareUtils.openContentInApp(context, i);
}
} else if (action.equals("GITHUB")) { // open the NewPipe issue page on GitHub
- ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL);
+ ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL, false);
}
})
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
index 36f39a6e5..5c7eb4c3f 100644
--- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
+++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
@@ -69,7 +69,8 @@ public class CommentTextOnTouchListener implements View.OnTouchListener {
handled = handleUrl(v.getContext(), (URLSpan) link[0]);
}
if (!handled) {
- link[0].onClick(widget);
+ ShareUtils.openUrlInBrowser(v.getContext(),
+ ((URLSpan) link[0]).getURL(), false);
}
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 103d9a72b..7046b8e49 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -365,8 +365,8 @@ public final class ExtractorHelper {
}
}
- metaInfoTextView.setText(HtmlCompat.fromHtml(stringBuilder.toString(),
- HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING));
+ LinkHelper.createLinksFromHtmlBlock(context, stringBuilder.toString(),
+ metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
metaInfoTextView.setMovementMethod(LinkMovementMethod.getInstance());
metaInfoTextView.setVisibility(View.VISIBLE);
metaInfoSeparator.setVisibility(View.VISIBLE);
diff --git a/app/src/main/java/org/schabi/newpipe/util/LinkHelper.java b/app/src/main/java/org/schabi/newpipe/util/LinkHelper.java
new file mode 100644
index 000000000..fba30758f
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/LinkHelper.java
@@ -0,0 +1,116 @@
+package org.schabi.newpipe.util;
+
+import android.content.Context;
+import android.text.SpannableStringBuilder;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.text.style.URLSpan;
+import android.text.util.Linkify;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.core.text.HtmlCompat;
+
+import io.noties.markwon.Markwon;
+import io.noties.markwon.linkify.LinkifyPlugin;
+
+public final class LinkHelper {
+ private LinkHelper() {
+ }
+
+ /**
+ * Create web links for contents with an HTML description.
+ *
+ * This will call
+ * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
+ * after linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
+ *
+ * @param context the context to use
+ * @param htmlBlock the htmlBlock to be linked
+ * @param textView the TextView to set the htmlBlock linked
+ * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
+ * will be called
+ */
+ public static void createLinksFromHtmlBlock(final Context context,
+ final String htmlBlock,
+ final TextView textView,
+ final int htmlCompatFlag) {
+ changeIntentsOfDescriptionLinks(context, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag),
+ textView);
+ }
+
+ /**
+ * Create web links for contents with a plain text description.
+ *
+ * This will call
+ * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
+ * after linked the URLs with {@link TextView#setAutoLinkMask(int)} and
+ * {@link TextView#setText(CharSequence, TextView.BufferType)}.
+ *
+ * @param context the context to use
+ * @param plainTextBlock the block of plain text to be linked
+ * @param textView the TextView to set the plain text block linked
+ */
+ public static void createLinksFromPlainText(final Context context,
+ final String plainTextBlock,
+ final TextView textView) {
+ textView.setAutoLinkMask(Linkify.WEB_URLS);
+ textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
+ changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
+ }
+
+ /**
+ * Create web links for contents with a markdown description.
+ *
+ * This will call
+ * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
+ * after creating an {@link Markwon} object and using
+ * {@link Markwon#setMarkdown(TextView, String)}.
+ *
+ * @param context the context to use
+ * @param markdownBlock the block of markdown text to be linked
+ * @param textView the TextView to set the plain text block linked
+ */
+ public static void createLinksFromMarkdownText(final Context context,
+ final String markdownBlock,
+ final TextView textView) {
+ final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build();
+ markwon.setMarkdown(textView, markdownBlock);
+ changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
+ }
+
+ /**
+ * Change links generated by libraries in the description of a content to a custom link action.
+ *
+ * Instead of using an ACTION_VIEW intent in the description of a content, this method will
+ * parse the CharSequence and replace all current web links with
+ * {@link ShareUtils#openUrlInBrowser(Context, String, Boolean)}.
+ *
+ * This method is required in order to intercept links and maybe, show a confirmation dialog
+ * before opening a web link.
+ *
+ * @param context the context to use
+ * @param chars the CharSequence to be parsed
+ * @param textView the TextView in which the converted CharSequence will be applied
+ */
+ private static void changeIntentsOfDescriptionLinks(final Context context,
+ final CharSequence chars,
+ final TextView textView) {
+ final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars);
+ final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
+
+ for (final URLSpan span : urls) {
+ final ClickableSpan clickableSpan = new ClickableSpan() {
+ public void onClick(final View view) {
+ ShareUtils.openUrlInBrowser(context, span.getURL(), false);
+ }
+ };
+ textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span),
+ textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span));
+ textBlockLinked.removeSpan(span);
+ }
+
+ textView.setText(textBlockLinked);
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+}
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 c90bb3025..936b4595c 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -246,16 +246,14 @@ public final class NavigationHelper {
public static void resolveActivityOrAskToInstall(final Context context, final Intent intent) {
if (intent.resolveActivity(context.getPackageManager()) != null) {
- context.startActivity(intent);
+ ShareUtils.openContentInApp(context, intent);
} else {
if (context instanceof Activity) {
new AlertDialog.Builder(context)
.setMessage(R.string.no_player_found)
.setPositiveButton(R.string.install, (dialog, which) -> {
- final Intent i = new Intent();
- i.setAction(Intent.ACTION_VIEW);
- i.setData(Uri.parse(context.getString(R.string.fdroid_vlc_url)));
- context.startActivity(i);
+ ShareUtils.openUrlInBrowser(context,
+ context.getString(R.string.fdroid_vlc_url), false);
})
.setNegativeButton(R.string.cancel, (dialog, which)
-> Log.i("NavigationHelper", "You unlocked a secret unicorn."))
@@ -568,27 +566,14 @@ public final class NavigationHelper {
return getOpenIntent(context, url, service.getServiceId(), linkType);
}
- private static Uri openMarketUrl(final String packageName) {
- return Uri.parse("market://details")
- .buildUpon()
- .appendQueryParameter("id", packageName)
- .build();
- }
-
- private static Uri getGooglePlayUrl(final String packageName) {
- return Uri.parse("https://play.google.com/store/apps/details")
- .buildUpon()
- .appendQueryParameter("id", packageName)
- .build();
- }
-
private static void installApp(final Context context, final String packageName) {
try {
// Try market:// scheme
- context.startActivity(new Intent(Intent.ACTION_VIEW, openMarketUrl(packageName)));
+ ShareUtils.openUrlInBrowser(context, "market://details?id=" + packageName, false);
} catch (final ActivityNotFoundException e) {
// Fall back to google play URL (don't worry F-Droid can handle it :)
- context.startActivity(new Intent(Intent.ACTION_VIEW, getGooglePlayUrl(packageName)));
+ ShareUtils.openUrlInBrowser(context,
+ "https://play.google.com/store/apps/details?id=" + packageName, false);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
index b631f19da..142b2b20c 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
@@ -1,5 +1,6 @@
package org.schabi.newpipe.util;
+import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
@@ -21,22 +22,80 @@ public final class ShareUtils {
* Open the url with the system default browser.
*
* If no browser is set as default, fallbacks to
- * {@link ShareUtils#openInDefaultApp(Context, String)}
+ * {@link ShareUtils#openInDefaultApp(Context, Intent)}
+ *
+ * @param context the context to use
+ * @param url the url to browse
+ * @param httpDefaultBrowserTest the boolean to set if the
+ * test for the default browser will be for HTTP protocol
+ * or for the created intent
+ */
+ public static void openUrlInBrowser(final Context context, final String url,
+ final Boolean httpDefaultBrowserTest) {
+ final String defaultPackageName;
+ final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (httpDefaultBrowserTest) {
+ defaultPackageName = getDefaultBrowserPackageName(context);
+ } else {
+ defaultPackageName = getDefaultAppPackageName(context, intent);
+ }
+
+ if (defaultPackageName.equals("android")) {
+ // no browser set as default (doesn't work on some devices)
+ openInDefaultApp(context, intent);
+ } else {
+ try {
+ intent.setPackage(defaultPackageName);
+ context.startActivity(intent);
+ } catch (final ActivityNotFoundException e) {
+ // not a browser but an app chooser because of OEMs changes
+ intent.setPackage(null);
+ openInDefaultApp(context, intent);
+ }
+ }
+ }
+
+ /**
+ * Open the url with the system default browser.
+ *
+ * If no browser is set as default, fallbacks to
+ * {@link ShareUtils#openInDefaultApp(Context, Intent)}
+ *
+ * This call {@link ShareUtils#openUrlInBrowser(Context, String, Boolean)} with true
+ * for the boolean parameter
*
* @param context the context to use
* @param url the url to browse
- */
+ **/
public static void openUrlInBrowser(final Context context, final String url) {
- final String defaultBrowserPackageName = getDefaultBrowserPackageName(context);
+ openUrlInBrowser(context, url, true);
+ }
- if (defaultBrowserPackageName.equals("android")) {
- // no browser set as default
- openInDefaultApp(context, url);
+ /**
+ * Open a content with the system default browser.
+ *
+ * If no app is set as default, fallbacks to
+ * {@link ShareUtils#openInDefaultApp(Context, Intent)}
+ *
+ * @param context the context to use
+ * @param intent the intent of the file to open
+ */
+ public static void openContentInApp(final Context context, final Intent intent) {
+ final String defaultAppPackageName = getDefaultAppPackageName(context, intent);
+
+ if (defaultAppPackageName.equals("android")) {
+ // no app set as default (doesn't work on some devices)
+ openInDefaultApp(context, intent);
} else {
- final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url))
- .setPackage(defaultBrowserPackageName)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
+ try {
+ intent.setPackage(defaultAppPackageName);
+ context.startActivity(intent);
+ } catch (final ActivityNotFoundException e) {
+ // not an app to open a file but an app chooser because of OEMs changes
+ intent.setPackage(null);
+ openInDefaultApp(context, intent);
+ }
}
}
@@ -45,20 +104,38 @@ public final class ShareUtils {
*
* If no app is set as default, it will open a chooser
*
- * @param context the context to use
- * @param url the url to browse
+ * @param context the context to use
+ * @param viewIntent the intent to open
*/
- private static void openInDefaultApp(final Context context, final String url) {
- final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
- context.startActivity(Intent.createChooser(
- intent, context.getString(R.string.share_dialog_title))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ private static void openInDefaultApp(final Context context, final Intent viewIntent) {
+ final Intent intent = new Intent(Intent.ACTION_CHOOSER);
+ intent.putExtra(Intent.EXTRA_INTENT, viewIntent);
+ intent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.open_with));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Get the default app package name.
+ *
+ * If no app is set as default, it will return "android".
+ *
+ * Note: it doesn't return "android" on some devices because some OEMs changed the app chooser.
+ *
+ * @param context the context to use
+ * @param intent the intent to get default app
+ * @return the package name of the default app, or the app chooser if there's no default
+ */
+ private static String getDefaultAppPackageName(final Context context, final Intent intent) {
+ return context.getPackageManager().resolveActivity(intent,
+ PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
}
/**
* Get the default browser package name.
*
* If no browser is set as default, it will return "android"
+ * Note: it doesn't return "android" on some devices because some OEMs changed the app chooser.
*
* @param context the context to use
* @return the package name of the default browser, or "android" if there's no default
@@ -66,8 +143,8 @@ public final class ShareUtils {
private static String getDefaultBrowserPackageName(final Context context) {
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(
- intent, PackageManager.MATCH_DEFAULT_ONLY);
+ final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
return resolveInfo.activityInfo.packageName;
}
@@ -79,13 +156,15 @@ public final class ShareUtils {
* @param url the url to share
*/
public static void shareUrl(final Context context, final String subject, final String url) {
- final Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_SUBJECT, subject);
- intent.putExtra(Intent.EXTRA_TEXT, url);
- context.startActivity(Intent.createChooser(
- intent, context.getString(R.string.share_dialog_title))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ final Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ shareIntent.putExtra(Intent.EXTRA_TEXT, url);
+ final Intent intent = new Intent(Intent.ACTION_CHOOSER);
+ intent.putExtra(Intent.EXTRA_INTENT, shareIntent);
+ intent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.share_dialog_title));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
}
/**
@@ -100,14 +179,11 @@ public final class ShareUtils {
ContextCompat.getSystemService(context, ClipboardManager.class);
if (clipboardManager == null) {
- Toast.makeText(context,
- R.string.permission_denied,
- Toast.LENGTH_LONG).show();
+ Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_LONG).show();
return;
}
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
- Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT)
- .show();
+ Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
}
}
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 f102206c1..79b91cb57 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
@@ -2,6 +2,7 @@ package us.shandian.giga.ui.adapter;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
@@ -44,6 +45,7 @@ import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.ShareUtils;
import java.io.File;
import java.net.URI;
@@ -346,10 +348,9 @@ public class MissionAdapter extends Adapter implements Handler.Callb
if (BuildConfig.DEBUG)
Log.v(TAG, "Mime: " + mimeType + " package: " + BuildConfig.APPLICATION_ID + ".provider");
- Uri uri = resolveShareableUri(mission);
+ final Uri uri = resolveShareableUri(mission);
- Intent intent = new Intent();
- intent.setAction(Intent.ACTION_VIEW);
+ Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mimeType);
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
@@ -363,7 +364,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (intent.resolveActivity(mContext.getPackageManager()) != null) {
- mContext.startActivity(intent);
+ ShareUtils.openContentInApp(mContext, intent);
} else {
Toast.makeText(mContext, R.string.toast_no_player, Toast.LENGTH_LONG).show();
}
@@ -372,12 +373,23 @@ public class MissionAdapter extends Adapter implements Handler.Callb
private void shareFile(Mission mission) {
if (checkInvalidFile(mission)) return;
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType(resolveMimeType(mission));
- intent.putExtra(Intent.EXTRA_STREAM, resolveShareableUri(mission));
- intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+ final Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType(resolveMimeType(mission));
+ shareIntent.putExtra(Intent.EXTRA_STREAM, resolveShareableUri(mission));
+ shareIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+ final Intent intent = new Intent(Intent.ACTION_CHOOSER);
+ intent.putExtra(Intent.EXTRA_INTENT, shareIntent);
+ intent.putExtra(Intent.EXTRA_TITLE, mContext.getString(R.string.share_dialog_title));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(Intent.createChooser(intent, null));
+ try {
+ intent.setPackage("android");
+ mContext.startActivity(intent);
+ } catch (final ActivityNotFoundException e) {
+ // falling back to OEM chooser if Android's system chooser was removed by the OEM
+ intent.setPackage(null);
+ mContext.startActivity(intent);
+ }
}
/**
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index c128c4214..a6b64da71 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -651,4 +651,5 @@
Demander à Android de personnaliser la couleur de la notification en fonction de la couleur principale de la miniature (noter que cela n’est pas disponible sur tous les appareils)Afficher la miniatureUtiliser la miniature pour l\'arrière-plan de l’écran de verrouillage et les notifications
+ Ouvrir avec
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8ac951b69..82a5bda6c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -11,6 +11,7 @@
https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlcOpen in browserOpen in popup mode
+ Open withShareDownloadDownload stream file
diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml
index 400a91a29..0aca3c4ab 100644
--- a/checkstyle-suppressions.xml
+++ b/checkstyle-suppressions.xml
@@ -12,21 +12,20 @@
lines="253,325"/>
+ files="ListHelper.java"
+ lines="281,313"/>
+ files="ContentSettingsFragment.java"
+ lines="227,245"/>
-
+ files="WebMWriter.java"
+ lines="156,158"/>
+ files="Player.java"/>
+ files="VideoDetailFragment.java"/>
From 79e98db3bd58adfe2bc298ce672e963587facf33 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Fri, 15 Jan 2021 17:11:04 +0100
Subject: [PATCH 071/131] Apply the requested changes and little improvements
Apply the requested changes, use ShareUtils.shareText to share an stream in
the play queue and optimize imports for Java files, using Android Studio
functionality.
Apply the requested changes and do little improvements
Apply the requested changes, use ShareUtils.shareText to share an stream in the play queue and optimize imports for Java files, using Android Studio functionality.
---
.../material/appbar/FlingBehavior.java | 1 +
app/src/main/java/org/schabi/newpipe/App.java | 31 +++++-----
.../org/schabi/newpipe/DownloaderImpl.java | 2 +-
.../org/schabi/newpipe/ImageDownloader.java | 1 +
.../java/org/schabi/newpipe/MainActivity.java | 14 +++--
.../org/schabi/newpipe/RouterActivity.java | 2 +-
.../schabi/newpipe/about/AboutActivity.java | 9 +--
.../fragments/detail/VideoDetailFragment.java | 8 +--
.../list/channel/ChannelFragment.java | 2 +-
.../list/playlist/PlaylistFragment.java | 2 +-
.../holder/CommentsMiniInfoItemHolder.java | 3 +-
.../holder/StreamInfoItemHolder.java | 3 +-
.../newpipe/local/BaseLocalListFragment.java | 5 +-
.../local/bookmark/BookmarkFragment.java | 2 +-
.../subscription/SubscriptionFragment.kt | 2 +-
.../org/schabi/newpipe/player/MainPlayer.java | 2 +-
.../newpipe/player/PlayQueueActivity.java | 15 +----
.../org/schabi/newpipe/player/Player.java | 2 +-
.../event/CustomBottomSheetBehavior.java | 3 +
.../helper/PlaybackParameterDialog.java | 2 +-
.../newpipe/player/playqueue/PlayQueue.java | 2 +-
.../schabi/newpipe/report/ErrorActivity.java | 4 +-
.../settings/BasePreferenceFragment.java | 2 +-
.../settings/SelectChannelFragment.java | 2 +-
.../custom/NotificationActionsPreference.java | 5 +-
.../newpipe/settings/tabs/TabsManager.java | 3 +-
.../util/CommentTextOnTouchListener.java | 2 +-
.../schabi/newpipe/util/ExtractorHelper.java | 2 +-
.../schabi/newpipe/util/FilenameUtils.java | 1 +
.../org/schabi/newpipe/util/ListHelper.java | 5 +-
.../org/schabi/newpipe/util/Localization.java | 15 +++--
.../schabi/newpipe/util/NavigationHelper.java | 18 ++----
.../schabi/newpipe/util/PeertubeHelper.java | 1 +
.../schabi/newpipe/util/ServiceHelper.java | 2 +-
.../org/schabi/newpipe/util/ShareUtils.java | 56 +++++++++++++------
.../newpipe/util/StreamDialogEntry.java | 2 +-
.../{LinkHelper.java => TextLinkifier.java} | 13 +++--
.../org/schabi/newpipe/util/ThemeHelper.java | 2 +-
.../views/CustomCollapsingToolbarLayout.java | 2 +
.../newpipe/views/ExpandableSurfaceView.java | 1 +
.../newpipe/views/FocusAwareCoordinator.java | 3 +-
.../giga/service/DownloadManagerService.java | 7 +--
.../giga/ui/adapter/MissionAdapter.java | 2 +-
.../giga/ui/fragment/MissionsFragment.java | 2 +-
checkstyle-suppressions.xml | 2 +-
45 files changed, 145 insertions(+), 122 deletions(-)
rename app/src/main/java/org/schabi/newpipe/util/{LinkHelper.java => TextLinkifier.java} (92%)
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 6106d0437..743ff1ff2 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
@@ -10,6 +10,7 @@ import android.widget.OverScroller;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+
import org.schabi.newpipe.R;
import java.lang.reflect.Field;
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index 342ce3498..cd4a04d9a 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -6,26 +6,16 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.multidex.MultiDexApplication;
import androidx.preference.PreferenceManager;
+
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
-import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.exceptions.CompositeException;
-import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
-import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
-import io.reactivex.rxjava3.exceptions.UndeliverableException;
-import io.reactivex.rxjava3.functions.Consumer;
-import io.reactivex.rxjava3.plugins.RxJavaPlugins;
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.net.SocketException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
+
import org.acra.ACRA;
import org.acra.config.ACRAConfigurationException;
import org.acra.config.CoreConfiguration;
@@ -41,6 +31,21 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.SocketException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.CompositeException;
+import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
+import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
+import io.reactivex.rxjava3.exceptions.UndeliverableException;
+import io.reactivex.rxjava3.functions.Consumer;
+import io.reactivex.rxjava3.plugins.RxJavaPlugins;
+
/*
* Copyright (C) Hans-Christoph Steiner 2016
* App.java is part of NewPipe.
diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
index 431b8034a..50972fb2f 100644
--- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
+++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
@@ -2,10 +2,10 @@ package org.schabi.newpipe;
import android.content.Context;
import android.os.Build;
-import androidx.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.downloader.Request;
diff --git a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java
index c2897cff1..ceae11777 100644
--- a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java
+++ b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
+
import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 5cecbc6d2..277209211 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -20,8 +20,6 @@
package org.schabi.newpipe;
-import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -43,6 +41,7 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.Spinner;
+
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
@@ -53,10 +52,9 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;
+
import com.google.android.material.bottomsheet.BottomSheetBehavior;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
+
import org.schabi.newpipe.databinding.ActivityMainBinding;
import org.schabi.newpipe.databinding.DrawerHeaderBinding;
import org.schabi.newpipe.databinding.DrawerLayoutBinding;
@@ -89,6 +87,12 @@ import org.schabi.newpipe.util.TLSSocketFactoryCompat;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
index 98a0921e4..7f935d007 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
@@ -188,7 +188,7 @@ public class RouterActivity extends AppCompatActivity {
.setPositiveButton(R.string.open_in_browser,
(dialog, which) -> ShareUtils.openUrlInBrowser(this, url))
.setNegativeButton(R.string.share,
- (dialog, which) -> ShareUtils.shareUrl(this, "", url)) // no subject
+ (dialog, which) -> ShareUtils.shareText(this, "", url)) // no subject
.setNeutralButton(R.string.cancel, null)
.setOnDismissListener(dialog -> finish())
.show();
diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
index d11978c6b..ec28be237 100644
--- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
@@ -146,16 +146,13 @@ public class AboutActivity extends AppCompatActivity {
aboutBinding.appVersion.setText(BuildConfig.VERSION_NAME);
aboutBinding.githubLink.setOnClickListener(nv ->
- openUrlInBrowser(context, context.getString(R.string.github_url),
- false));
+ openUrlInBrowser(context, context.getString(R.string.github_url), false));
aboutBinding.donationLink.setOnClickListener(v ->
- openUrlInBrowser(context, context.getString(R.string.donation_url),
- false));
+ openUrlInBrowser(context, context.getString(R.string.donation_url), false));
aboutBinding.websiteLink.setOnClickListener(nv ->
- openUrlInBrowser(context, context.getString(R.string.website_url),
- false));
+ openUrlInBrowser(context, context.getString(R.string.website_url), false));
aboutBinding.privacyPolicyLink.setOnClickListener(v ->
openUrlInBrowser(context, context.getString(R.string.privacy_policy_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 c9913a808..7ebf66390 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -95,12 +95,12 @@ import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
-import org.schabi.newpipe.util.LinkHelper;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ShareUtils;
+import org.schabi.newpipe.util.TextLinkifier;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.AnimatedProgressBar;
import org.schabi.newpipe.views.LargeTextMovementMethod;
@@ -1230,16 +1230,16 @@ public final class VideoDetailFragment
}
if (description.getType() == Description.HTML) {
- LinkHelper.createLinksFromHtmlBlock(requireContext(), description.getContent(),
+ TextLinkifier.createLinksFromHtmlBlock(requireContext(), description.getContent(),
videoDescriptionView, HtmlCompat.FROM_HTML_MODE_LEGACY);
videoDescriptionView.setVisibility(View.VISIBLE);
} else if (description.getType() == Description.MARKDOWN) {
- LinkHelper.createLinksFromMarkdownText(requireContext(), description.getContent(),
+ TextLinkifier.createLinksFromMarkdownText(requireContext(), description.getContent(),
videoDescriptionView);
videoDescriptionView.setVisibility(View.VISIBLE);
} else {
//== Description.PLAIN_TEXT
- LinkHelper.createLinksFromPlainText(requireContext(), description.getContent(),
+ TextLinkifier.createLinksFromPlainText(requireContext(), description.getContent(),
videoDescriptionView);
videoDescriptionView.setVisibility(View.VISIBLE);
}
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 6bcaac630..b4dd45e93 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
@@ -206,7 +206,7 @@ public class ChannelFragment extends BaseListInfoFragment
break;
case R.id.menu_item_share:
if (currentInfo != null) {
- ShareUtils.shareUrl(requireContext(), name, currentInfo.getOriginalUrl());
+ ShareUtils.shareText(requireContext(), name, currentInfo.getOriginalUrl());
}
break;
default:
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 58985e38a..85dea83dc 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
@@ -242,7 +242,7 @@ public class PlaylistFragment extends BaseListInfoFragment {
ShareUtils.openUrlInBrowser(requireContext(), url);
break;
case R.id.menu_item_share:
- ShareUtils.shareUrl(requireContext(), name, url);
+ ShareUtils.shareText(requireContext(), name, url);
break;
case R.id.menu_item_bookmark:
onBookmarkClicked();
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 e6cae0c7e..fe35a18c4 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
@@ -13,15 +13,14 @@ import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
-
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
-import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.CommentTextOnTouchListener;
+import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
index 3440fbe3c..1915ff283 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
@@ -1,10 +1,11 @@
package org.schabi.newpipe.info_list.holder;
-import androidx.preference.PreferenceManager;
import android.text.TextUtils;
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.preference.PreferenceManager;
+
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
index d454da08b..84908e41a 100644
--- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
@@ -4,16 +4,15 @@ import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
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 d5241ae02..ee77db89f 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
@@ -33,9 +33,9 @@ import org.schabi.newpipe.util.OnClickGesture;
import java.util.List;
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.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
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 8be281dc3..a84745dbf 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
@@ -300,7 +300,7 @@ class SubscriptionFragment : BaseStateFragment() {
val actions = DialogInterface.OnClickListener { _, i ->
when (i) {
- 0 -> ShareUtils.shareUrl(requireContext(), selectedItem.name, selectedItem.url)
+ 0 -> ShareUtils.shareText(requireContext(), selectedItem.name, selectedItem.url)
1 -> deleteChannel(selectedItem)
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
index e9ae1a1db..945bc9a04 100644
--- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
@@ -34,8 +34,8 @@ import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
-import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.App;
+import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
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 6ea7ecda3..d757a9268 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
@@ -46,6 +46,7 @@ import java.util.List;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+import static org.schabi.newpipe.util.ShareUtils.shareText;
public final class PlayQueueActivity extends AppCompatActivity
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener,
@@ -311,7 +312,7 @@ public final class PlayQueueActivity extends AppCompatActivity
final MenuItem share = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 3,
Menu.NONE, R.string.share);
share.setOnMenuItemClickListener(menuItem -> {
- shareUrl(item.getTitle(), item.getUrl());
+ shareText(getApplicationContext(), item.getTitle(), item.getUrl());
return true;
});
@@ -505,18 +506,6 @@ public final class PlayQueueActivity extends AppCompatActivity
() -> PlaylistCreationDialog.newInstance(d).show(getSupportFragmentManager(), TAG));
}
- ////////////////////////////////////////////////////////////////////////////
- // Share
- ////////////////////////////////////////////////////////////////////////////
-
- private void shareUrl(final String subject, final String url) {
- final Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_SUBJECT, subject);
- intent.putExtra(Intent.EXTRA_TEXT, url);
- startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
- }
-
////////////////////////////////////////////////////////////////////////////
// Binding Service Listener
////////////////////////////////////////////////////////////////////////////
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 2453117f3..b2b65477a 100644
--- a/app/src/main/java/org/schabi/newpipe/player/Player.java
+++ b/app/src/main/java/org/schabi/newpipe/player/Player.java
@@ -3553,7 +3553,7 @@ public final class Player implements
&& currentMetadata.getMetadata().getServiceId() == YouTube.getServiceId()) {
videoUrl += ("&t=" + ts);
}
- ShareUtils.shareUrl(context, getVideoTitle(), videoUrl);
+ ShareUtils.shareText(context, getVideoTitle(), videoUrl);
}
private void onPlayWithKodiClicked() {
diff --git a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java
index 347118de5..00ce3e616 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java
+++ b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java
@@ -6,9 +6,12 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
+
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+
import com.google.android.material.bottomsheet.BottomSheetBehavior;
+
import org.schabi.newpipe.R;
import java.util.Arrays;
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 253f0fbba..a9a36e2f5 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
@@ -3,7 +3,6 @@ package org.schabi.newpipe.player.helper;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
-import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
@@ -14,6 +13,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.SliderStrategy;
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 90a979872..6131d8565 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
@@ -21,8 +21,8 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
-import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
index 0321d197a..e76af3944 100644
--- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
@@ -244,7 +244,7 @@ public class ErrorActivity extends AppCompatActivity {
goToReturnActivity();
break;
case R.id.menu_item_share_error:
- ShareUtils.shareUrl(this, getString(R.string.error_report_title), buildJson());
+ ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson());
break;
}
return false;
@@ -267,7 +267,7 @@ public class ErrorActivity extends AppCompatActivity {
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
.putExtra(Intent.EXTRA_TEXT, buildJson());
if (i.resolveActivity(getPackageManager()) != null) {
- ShareUtils.openContentInApp(context, i);
+ ShareUtils.openIntentInApp(context, i);
}
} else if (action.equals("GITHUB")) { // open the NewPipe issue page on GitHub
ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL, false);
diff --git a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
index 7a42c9bea..b64543d27 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
@@ -2,12 +2,12 @@ package org.schabi.newpipe.settings;
import android.content.SharedPreferences;
import android.os.Bundle;
-import androidx.preference.PreferenceManager;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.util.ThemeHelper;
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
index 90537564a..afe42d5d8 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
@@ -30,8 +30,8 @@ import java.util.List;
import java.util.Vector;
import de.hdodenhof.circleimageview.CircleImageView;
-import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
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 1fe405552..e50c858ca 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
@@ -15,6 +15,7 @@ import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
+
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
@@ -22,7 +23,7 @@ import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.widget.TextViewCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
-import java.util.List;
+
import org.schabi.newpipe.R;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.NotificationConstants;
@@ -30,6 +31,8 @@ import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
+import java.util.List;
+
public class NotificationActionsPreference extends Preference {
public NotificationActionsPreference(final Context context, final AttributeSet attrs) {
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
index 02b0c3df5..2836fe52b 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
@@ -2,9 +2,10 @@ package org.schabi.newpipe.settings.tabs;
import android.content.Context;
import android.content.SharedPreferences;
-import androidx.preference.PreferenceManager;
import android.widget.Toast;
+import androidx.preference.PreferenceManager;
+
import org.schabi.newpipe.R;
import java.util.List;
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
index 5c7eb4c3f..d26116139 100644
--- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
+++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
@@ -23,8 +23,8 @@ import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class CommentTextOnTouchListener implements View.OnTouchListener {
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 7046b8e49..6938cf4cc 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -365,7 +365,7 @@ public final class ExtractorHelper {
}
}
- LinkHelper.createLinksFromHtmlBlock(context, stringBuilder.toString(),
+ TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(),
metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
metaInfoTextView.setMovementMethod(LinkMovementMethod.getInstance());
metaInfoTextView.setVisibility(View.VISIBLE);
diff --git a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java
index 27e1f3048..edcb565a0 100644
--- a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java
@@ -2,6 +2,7 @@ package org.schabi.newpipe.util;
import android.content.Context;
import android.content.SharedPreferences;
+
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
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 5f8fb5898..eb3c21827 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
@@ -4,11 +4,10 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
-import androidx.core.content.ContextCompat;
-import androidx.preference.PreferenceManager;
-
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
+import androidx.core.content.ContextCompat;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
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 c235d7d67..f62f959c4 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -9,10 +9,19 @@ import android.icu.text.CompactDecimalFormat;
import android.os.Build;
import android.text.TextUtils;
import android.util.DisplayMetrics;
+
import androidx.annotation.NonNull;
import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
+
+import org.ocpsoft.prettytime.PrettyTime;
+import org.ocpsoft.prettytime.units.Decade;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.ListExtractor;
+import org.schabi.newpipe.extractor.localization.ContentCountry;
+import org.schabi.newpipe.ktx.OffsetDateTimeKt;
+
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
@@ -24,12 +33,6 @@ import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
-import org.ocpsoft.prettytime.PrettyTime;
-import org.ocpsoft.prettytime.units.Decade;
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.ListExtractor;
-import org.schabi.newpipe.extractor.localization.ContentCountry;
-import org.schabi.newpipe.ktx.OffsetDateTimeKt;
/*
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 936b4595c..4f4fd5283 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -2,7 +2,6 @@ package org.schabi.newpipe.util;
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -46,9 +45,9 @@ 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.PlayQueueActivity;
import org.schabi.newpipe.player.Player;
-import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
@@ -57,6 +56,8 @@ import org.schabi.newpipe.settings.SettingsActivity;
import java.util.ArrayList;
+import static org.schabi.newpipe.util.ShareUtils.installApp;
+
public final class NavigationHelper {
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag";
@@ -246,7 +247,7 @@ public final class NavigationHelper {
public static void resolveActivityOrAskToInstall(final Context context, final Intent intent) {
if (intent.resolveActivity(context.getPackageManager()) != null) {
- ShareUtils.openContentInApp(context, intent);
+ ShareUtils.openIntentInApp(context, intent);
} else {
if (context instanceof Activity) {
new AlertDialog.Builder(context)
@@ -566,17 +567,6 @@ public final class NavigationHelper {
return getOpenIntent(context, url, service.getServiceId(), linkType);
}
- private static void installApp(final Context context, final String packageName) {
- try {
- // Try market:// scheme
- ShareUtils.openUrlInBrowser(context, "market://details?id=" + packageName, false);
- } catch (final ActivityNotFoundException e) {
- // Fall back to google play URL (don't worry F-Droid can handle it :)
- ShareUtils.openUrlInBrowser(context,
- "https://play.google.com/store/apps/details?id=" + packageName, false);
- }
- }
-
/**
* Start an activity to install Kore.
*
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 e28095798..dcc39eccf 100644
--- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java
@@ -2,6 +2,7 @@ package org.schabi.newpipe.util;
import android.content.Context;
import android.content.SharedPreferences;
+
import androidx.preference.PreferenceManager;
import com.grack.nanojson.JsonArray;
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 a6756991c..b38edfeb4 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
@@ -2,10 +2,10 @@ package org.schabi.newpipe.util;
import android.content.Context;
import android.content.SharedPreferences;
-import androidx.preference.PreferenceManager;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
+import androidx.preference.PreferenceManager;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
diff --git a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
index 142b2b20c..ffb965ae7 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
@@ -6,7 +6,6 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.widget.Toast;
@@ -18,6 +17,27 @@ public final class ShareUtils {
private ShareUtils() {
}
+ /**
+ * Open an Intent to install an app.
+ *
+ * This method will first try open to Google Play Store with the market scheme and falls back to
+ * Google Play Store web url if this first cannot be found.
+ *
+ * @param context the context to use
+ * @param packageName the package to be installed
+ */
+ public static void installApp(final Context context, final String packageName) {
+ try {
+ // Try market:// scheme
+ openIntentInApp(context, new Intent(Intent.ACTION_VIEW,
+ Uri.parse("market://details?id=" + packageName)));
+ } catch (final ActivityNotFoundException e) {
+ // Fall back to Google Play Store Web URL (don't worry, F-Droid can handle it :))
+ openUrlInBrowser(context,
+ "https://play.google.com/store/apps/details?id=" + packageName, false);
+ }
+ }
+
/**
* Open the url with the system default browser.
*
@@ -26,12 +46,11 @@ public final class ShareUtils {
*
* @param context the context to use
* @param url the url to browse
- * @param httpDefaultBrowserTest the boolean to set if the
- * test for the default browser will be for HTTP protocol
- * or for the created intent
+ * @param httpDefaultBrowserTest the boolean to set if the test for the default browser will be
+ * for HTTP protocol or for the created intent
*/
public static void openUrlInBrowser(final Context context, final String url,
- final Boolean httpDefaultBrowserTest) {
+ final boolean httpDefaultBrowserTest) {
final String defaultPackageName;
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -42,14 +61,14 @@ public final class ShareUtils {
}
if (defaultPackageName.equals("android")) {
- // no browser set as default (doesn't work on some devices)
+ // No browser set as default (doesn't work on some devices)
openInDefaultApp(context, intent);
} else {
try {
intent.setPackage(defaultPackageName);
context.startActivity(intent);
} catch (final ActivityNotFoundException e) {
- // not a browser but an app chooser because of OEMs changes
+ // Not a browser but an app chooser because of OEMs changes
intent.setPackage(null);
openInDefaultApp(context, intent);
}
@@ -62,7 +81,7 @@ public final class ShareUtils {
* If no browser is set as default, fallbacks to
* {@link ShareUtils#openInDefaultApp(Context, Intent)}
*
- * This call {@link ShareUtils#openUrlInBrowser(Context, String, Boolean)} with true
+ * This calls {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} with true
* for the boolean parameter
*
* @param context the context to use
@@ -73,26 +92,29 @@ public final class ShareUtils {
}
/**
- * Open a content with the system default browser.
+ * Open an intent with the system default app.
+ *
+ * The intent can be of every type, excepted a web intent for which
+ * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} should be used.
*
* If no app is set as default, fallbacks to
* {@link ShareUtils#openInDefaultApp(Context, Intent)}
*
* @param context the context to use
- * @param intent the intent of the file to open
+ * @param intent the intent to open
*/
- public static void openContentInApp(final Context context, final Intent intent) {
+ public static void openIntentInApp(final Context context, final Intent intent) {
final String defaultAppPackageName = getDefaultAppPackageName(context, intent);
if (defaultAppPackageName.equals("android")) {
- // no app set as default (doesn't work on some devices)
+ // No app set as default (doesn't work on some devices)
openInDefaultApp(context, intent);
} else {
try {
intent.setPackage(defaultAppPackageName);
context.startActivity(intent);
} catch (final ActivityNotFoundException e) {
- // not an app to open a file but an app chooser because of OEMs changes
+ // Not an app to open the intent but an app chooser because of OEMs changes
intent.setPackage(null);
openInDefaultApp(context, intent);
}
@@ -143,9 +165,8 @@ public final class ShareUtils {
private static String getDefaultBrowserPackageName(final Context context) {
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
- PackageManager.MATCH_DEFAULT_ONLY);
- return resolveInfo.activityInfo.packageName;
+ return context.getPackageManager().resolveActivity(intent,
+ PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
}
/**
@@ -155,11 +176,12 @@ public final class ShareUtils {
* @param subject the url subject, typically the title
* @param url the url to share
*/
- public static void shareUrl(final Context context, final String subject, final String url) {
+ public static void shareText(final Context context, final String subject, final String url) {
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
+
final Intent intent = new Intent(Intent.ACTION_CHOOSER);
intent.putExtra(Intent.EXTRA_INTENT, shareIntent);
intent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.share_dialog_title));
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
index 153ba7cf4..73fee32f7 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
@@ -81,7 +81,7 @@ public enum StreamDialogEntry {
}),
share(R.string.share, (fragment, item) ->
- ShareUtils.shareUrl(fragment.getContext(), item.getName(), item.getUrl()));
+ ShareUtils.shareText(fragment.getContext(), item.getName(), item.getUrl()));
///////////////
diff --git a/app/src/main/java/org/schabi/newpipe/util/LinkHelper.java b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
similarity index 92%
rename from app/src/main/java/org/schabi/newpipe/util/LinkHelper.java
rename to app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
index fba30758f..eb7296611 100644
--- a/app/src/main/java/org/schabi/newpipe/util/LinkHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
@@ -14,15 +14,15 @@ import androidx.core.text.HtmlCompat;
import io.noties.markwon.Markwon;
import io.noties.markwon.linkify.LinkifyPlugin;
-public final class LinkHelper {
- private LinkHelper() {
+public final class TextLinkifier {
+ private TextLinkifier() {
}
/**
* Create web links for contents with an HTML description.
*
* This will call
- * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
+ * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
* after linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
*
* @param context the context to use
@@ -43,7 +43,7 @@ public final class LinkHelper {
* Create web links for contents with a plain text description.
*
* This will call
- * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
+ * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
* after linked the URLs with {@link TextView#setAutoLinkMask(int)} and
* {@link TextView#setText(CharSequence, TextView.BufferType)}.
*
@@ -63,7 +63,7 @@ public final class LinkHelper {
* Create web links for contents with a markdown description.
*
* This will call
- * {@link LinkHelper#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
+ * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
* after creating an {@link Markwon} object and using
* {@link Markwon#setMarkdown(TextView, String)}.
*
@@ -84,7 +84,7 @@ public final class LinkHelper {
*
* Instead of using an ACTION_VIEW intent in the description of a content, this method will
* parse the CharSequence and replace all current web links with
- * {@link ShareUtils#openUrlInBrowser(Context, String, Boolean)}.
+ * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
*
* This method is required in order to intercept links and maybe, show a confirmation dialog
* before opening a web link.
@@ -105,6 +105,7 @@ public final class LinkHelper {
ShareUtils.openUrlInBrowser(context, span.getURL(), false);
}
};
+
textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span),
textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span));
textBlockLinked.removeSpan(span);
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 a1af0387a..5ac4de84c 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
@@ -22,7 +22,6 @@ package org.schabi.newpipe.util;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
-import androidx.preference.PreferenceManager;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -32,6 +31,7 @@ import androidx.annotation.StyleRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
diff --git a/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java b/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java
index 23e16ff58..dc667b22a 100644
--- a/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java
+++ b/app/src/main/java/org/schabi/newpipe/views/CustomCollapsingToolbarLayout.java
@@ -2,10 +2,12 @@ package org.schabi.newpipe.views;
import android.content.Context;
import android.util.AttributeSet;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
+
import com.google.android.material.appbar.CollapsingToolbarLayout;
public class CustomCollapsingToolbarLayout extends CollapsingToolbarLayout {
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 e7a028d50..cfa17e20c 100644
--- a/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java
+++ b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.SurfaceView;
+
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT;
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 f400b62b1..798d08c72 100644
--- a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java
+++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java
@@ -24,11 +24,12 @@ import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-
import android.view.WindowInsets;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+
import org.schabi.newpipe.R;
public final class FocusAwareCoordinator extends CoordinatorLayout {
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 e77196445..568c3497a 100755
--- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
+++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
@@ -24,10 +24,6 @@ import android.os.Handler.Callback;
import android.os.IBinder;
import android.os.Message;
import android.os.Parcelable;
-
-import androidx.core.app.ServiceCompat;
-import androidx.core.content.ContextCompat;
-import androidx.preference.PreferenceManager;
import android.util.Log;
import android.util.SparseArray;
import android.widget.Toast;
@@ -37,6 +33,9 @@ import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.Builder;
+import androidx.core.app.ServiceCompat;
+import androidx.core.content.ContextCompat;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.download.DownloadActivity;
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 79b91cb57..bea4b6f94 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
@@ -364,7 +364,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (intent.resolveActivity(mContext.getPackageManager()) != null) {
- ShareUtils.openContentInApp(mContext, intent);
+ ShareUtils.openIntentInApp(mContext, intent);
} else {
Toast.makeText(mContext, R.string.toast_no_player, Toast.LENGTH_LONG).show();
}
diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java
index 9632c4ae0..3270b2b6f 100644
--- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java
+++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java
@@ -11,7 +11,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
-import androidx.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -21,6 +20,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml
index 0aca3c4ab..2ff7ae68d 100644
--- a/checkstyle-suppressions.xml
+++ b/checkstyle-suppressions.xml
@@ -13,7 +13,7 @@
+ lines="280,312"/>
Date: Fri, 15 Jan 2021 20:57:19 +0100
Subject: [PATCH 072/131] Move TextLinkifier computation out of main thread
---
.../fragments/detail/VideoDetailFragment.java | 31 +++---
.../fragments/list/search/SearchFragment.java | 8 +-
.../schabi/newpipe/util/ExtractorHelper.java | 16 +--
.../schabi/newpipe/util/TextLinkifier.java | 100 +++++++++++-------
4 files changed, 92 insertions(+), 63 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 7ebf66390..9079c47ca 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
@@ -1229,19 +1229,20 @@ public final class VideoDetailFragment
return;
}
- if (description.getType() == Description.HTML) {
- TextLinkifier.createLinksFromHtmlBlock(requireContext(), description.getContent(),
- videoDescriptionView, HtmlCompat.FROM_HTML_MODE_LEGACY);
- videoDescriptionView.setVisibility(View.VISIBLE);
- } else if (description.getType() == Description.MARKDOWN) {
- TextLinkifier.createLinksFromMarkdownText(requireContext(), description.getContent(),
- videoDescriptionView);
- videoDescriptionView.setVisibility(View.VISIBLE);
- } else {
- //== Description.PLAIN_TEXT
- TextLinkifier.createLinksFromPlainText(requireContext(), description.getContent(),
- videoDescriptionView);
- videoDescriptionView.setVisibility(View.VISIBLE);
+ switch (description.getType()) {
+ case Description.HTML:
+ disposables.add(TextLinkifier.createLinksFromHtmlBlock(requireContext(),
+ description.getContent(), videoDescriptionView,
+ HtmlCompat.FROM_HTML_MODE_LEGACY));
+ break;
+ case Description.MARKDOWN:
+ disposables.add(TextLinkifier.createLinksFromMarkdownText(requireContext(),
+ description.getContent(), videoDescriptionView));
+ break;
+ case Description.PLAIN_TEXT: default:
+ disposables.add(TextLinkifier.createLinksFromPlainText(requireContext(),
+ description.getContent(), videoDescriptionView));
+ break;
}
}
@@ -1557,8 +1558,8 @@ public final class VideoDetailFragment
prepareDescription(info.getDescription());
updateProgressInfo(info);
initThumbnailViews(info);
- showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, detailMetaInfoSeparator);
-
+ disposables.add(showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView,
+ detailMetaInfoSeparator));
if (player == null || player.isStopped()) {
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());
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 67663a073..b15bb97f2 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
@@ -280,8 +280,8 @@ public class SearchFragment extends BaseListFragment cannot be bundled without creating some containers
metaInfo = new MetaInfo[result.getMetaInfo().size()];
metaInfo = result.getMetaInfo().toArray(metaInfo);
+ disposables.add(showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView,
+ metaInfoSeparator));
handleSearchSuggestion();
- showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, metaInfoSeparator);
-
lastSearchedString = searchString;
nextPage = result.getNextPage();
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 6938cf4cc..6ee69dcd9 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -22,7 +22,6 @@ package org.schabi.newpipe.util;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
-import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
@@ -68,6 +67,7 @@ import java.util.List;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.disposables.Disposable;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
@@ -325,10 +325,11 @@ public final class ExtractorHelper {
* @param metaInfos a list of meta information, can be null or empty
* @param metaInfoTextView the text view in which to show the formatted HTML
* @param metaInfoSeparator another view to be shown or hidden accordingly to the text view
+ * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
- public static void showMetaInfoInTextView(@Nullable final List metaInfos,
- final TextView metaInfoTextView,
- final View metaInfoSeparator) {
+ public static Disposable showMetaInfoInTextView(@Nullable final List metaInfos,
+ final TextView metaInfoTextView,
+ final View metaInfoSeparator) {
final Context context = metaInfoTextView.getContext();
final boolean showMetaInfo = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(context.getString(R.string.show_meta_info_key), true);
@@ -336,6 +337,7 @@ public final class ExtractorHelper {
if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) {
metaInfoTextView.setVisibility(View.GONE);
metaInfoSeparator.setVisibility(View.GONE);
+ return Disposable.empty();
} else {
final StringBuilder stringBuilder = new StringBuilder();
@@ -365,11 +367,9 @@ public final class ExtractorHelper {
}
}
- TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(),
- metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
- metaInfoTextView.setMovementMethod(LinkMovementMethod.getInstance());
- metaInfoTextView.setVisibility(View.VISIBLE);
metaInfoSeparator.setVisibility(View.VISIBLE);
+ return TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(),
+ metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
index eb7296611..087677333 100644
--- a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
+++ b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
@@ -6,15 +6,23 @@ import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.text.util.Linkify;
+import android.util.Log;
import android.view.View;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.core.text.HtmlCompat;
import io.noties.markwon.Markwon;
import io.noties.markwon.linkify.LinkifyPlugin;
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
public final class TextLinkifier {
+ public static final String TAG = TextLinkifier.class.getSimpleName();
+
private TextLinkifier() {
}
@@ -23,20 +31,21 @@ public final class TextLinkifier {
*
* This will call
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
- * after linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
+ * after having linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
*
* @param context the context to use
* @param htmlBlock the htmlBlock to be linked
* @param textView the TextView to set the htmlBlock linked
* @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
* will be called
+ * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
- public static void createLinksFromHtmlBlock(final Context context,
- final String htmlBlock,
- final TextView textView,
- final int htmlCompatFlag) {
- changeIntentsOfDescriptionLinks(context, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag),
- textView);
+ public static Disposable createLinksFromHtmlBlock(final Context context,
+ final String htmlBlock,
+ final TextView textView,
+ final int htmlCompatFlag) {
+ return changeIntentsOfDescriptionLinks(context,
+ HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), textView);
}
/**
@@ -44,19 +53,20 @@ public final class TextLinkifier {
*
* This will call
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
- * after linked the URLs with {@link TextView#setAutoLinkMask(int)} and
+ * after having linked the URLs with {@link TextView#setAutoLinkMask(int)} and
* {@link TextView#setText(CharSequence, TextView.BufferType)}.
*
* @param context the context to use
* @param plainTextBlock the block of plain text to be linked
* @param textView the TextView to set the plain text block linked
+ * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
- public static void createLinksFromPlainText(final Context context,
- final String plainTextBlock,
- final TextView textView) {
+ public static Disposable createLinksFromPlainText(final Context context,
+ final String plainTextBlock,
+ final TextView textView) {
textView.setAutoLinkMask(Linkify.WEB_URLS);
textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
- changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
+ return changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
}
/**
@@ -70,48 +80,66 @@ public final class TextLinkifier {
* @param context the context to use
* @param markdownBlock the block of markdown text to be linked
* @param textView the TextView to set the plain text block linked
+ * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
- public static void createLinksFromMarkdownText(final Context context,
- final String markdownBlock,
- final TextView textView) {
+ public static Disposable createLinksFromMarkdownText(final Context context,
+ final String markdownBlock,
+ final TextView textView) {
final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build();
markwon.setMarkdown(textView, markdownBlock);
- changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
+ return changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
}
/**
* Change links generated by libraries in the description of a content to a custom link action.
*
- * Instead of using an ACTION_VIEW intent in the description of a content, this method will
- * parse the CharSequence and replace all current web links with
- * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
+ * Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of a
+ * content, this method will parse the {@link CharSequence} and replace all current web links
+ * with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
*
- * This method is required in order to intercept links and maybe, show a confirmation dialog
+ * This method is required in order to intercept links and e.g. show a confirmation dialog
* before opening a web link.
*
* @param context the context to use
* @param chars the CharSequence to be parsed
* @param textView the TextView in which the converted CharSequence will be applied
+ * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
- private static void changeIntentsOfDescriptionLinks(final Context context,
- final CharSequence chars,
- final TextView textView) {
- final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars);
- final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
+ private static Disposable changeIntentsOfDescriptionLinks(final Context context,
+ final CharSequence chars,
+ final TextView textView) {
+ return Single.fromCallable(() -> {
+ final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars);
+ final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
- for (final URLSpan span : urls) {
- final ClickableSpan clickableSpan = new ClickableSpan() {
- public void onClick(final View view) {
- ShareUtils.openUrlInBrowser(context, span.getURL(), false);
- }
- };
+ for (final URLSpan span : urls) {
+ final ClickableSpan clickableSpan = new ClickableSpan() {
+ public void onClick(@NonNull final View view) {
+ ShareUtils.openUrlInBrowser(context, span.getURL(), false);
+ }
+ };
- textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span),
- textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span));
- textBlockLinked.removeSpan(span);
- }
+ textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span),
+ textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span));
+ textBlockLinked.removeSpan(span);
+ }
- textView.setText(textBlockLinked);
+ return textBlockLinked;
+ }).subscribeOn(Schedulers.computation())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ textBlockLinked -> setTextViewCharSequence(textView, textBlockLinked),
+ throwable -> {
+ Log.e(TAG, "Unable to linkify text", throwable);
+ // this should never happen, but if it does, just fallback to it
+ setTextViewCharSequence(textView, chars);
+ });
+ }
+
+ private static void setTextViewCharSequence(final TextView textView,
+ final CharSequence charSequence) {
+ textView.setText(charSequence);
textView.setMovementMethod(LinkMovementMethod.getInstance());
+ textView.setVisibility(View.VISIBLE);
}
}
From 68be87724ae4470aae7a6058f98082f4e0b7316f Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 31 Oct 2020 10:07:18 +0530
Subject: [PATCH 073/131] Switch to Groupie view binding module.
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index ab3449784..6647b413d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -217,7 +217,7 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "com.xwray:groupie:${groupieVersion}"
- implementation "com.xwray:groupie-kotlin-android-extensions:${groupieVersion}"
+ implementation "com.xwray:groupie-viewbinding:${groupieVersion}"
implementation "de.hdodenhof:circleimageview:3.1.0"
implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5"
From 46afe5153f6d7e774fd63e0a07007eb485d42e84 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Fri, 27 Nov 2020 20:18:38 +0530
Subject: [PATCH 074/131] Use BindableItem in EmptyPlaceholderItem.
---
.../local/subscription/item/EmptyPlaceholderItem.kt | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/EmptyPlaceholderItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/EmptyPlaceholderItem.kt
index ef7eb93cd..59bef55cf 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/EmptyPlaceholderItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/EmptyPlaceholderItem.kt
@@ -1,11 +1,13 @@
package org.schabi.newpipe.local.subscription.item
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
+import android.view.View
+import com.xwray.groupie.viewbinding.BindableItem
import org.schabi.newpipe.R
+import org.schabi.newpipe.databinding.ListEmptyViewBinding
-class EmptyPlaceholderItem : Item() {
+class EmptyPlaceholderItem : BindableItem() {
override fun getLayout(): Int = R.layout.list_empty_view
- override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
+ override fun bind(viewBinding: ListEmptyViewBinding, position: Int) {}
override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount
+ override fun initializeViewBinding(view: View) = ListEmptyViewBinding.bind(view)
}
From b387946d34e388a7c9a54a745b5c693a9d445c0a Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Fri, 27 Nov 2020 20:19:48 +0530
Subject: [PATCH 075/131] Use BindableItem in FeedGroupAddItem.
---
.../local/subscription/item/FeedGroupAddItem.kt | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt
index 1bc4d1673..434b4f29a 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt
@@ -1,10 +1,12 @@
package org.schabi.newpipe.local.subscription.item
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
+import android.view.View
+import com.xwray.groupie.viewbinding.BindableItem
import org.schabi.newpipe.R
+import org.schabi.newpipe.databinding.FeedGroupAddNewItemBinding
-class FeedGroupAddItem : Item() {
+class FeedGroupAddItem : BindableItem() {
override fun getLayout(): Int = R.layout.feed_group_add_new_item
- override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
+ override fun bind(viewBinding: FeedGroupAddNewItemBinding, position: Int) {}
+ override fun initializeViewBinding(view: View) = FeedGroupAddNewItemBinding.bind(view)
}
From ee94b296ae77054e8097c3ba3817761a0648737f Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Fri, 27 Nov 2020 20:22:06 +0530
Subject: [PATCH 076/131] Use BindableItem in FeedGroupCardItem.
---
.../subscription/item/FeedGroupCardItem.kt | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt
index 12ff47b3f..a9731df8a 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt
@@ -1,18 +1,17 @@
package org.schabi.newpipe.local.subscription.item
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
-import kotlinx.android.synthetic.main.feed_group_card_item.icon
-import kotlinx.android.synthetic.main.feed_group_card_item.title
+import android.view.View
+import com.xwray.groupie.viewbinding.BindableItem
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
+import org.schabi.newpipe.databinding.FeedGroupCardItemBinding
import org.schabi.newpipe.local.subscription.FeedGroupIcon
data class FeedGroupCardItem(
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
val name: String,
val icon: FeedGroupIcon
-) : Item() {
+) : BindableItem() {
constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon)
override fun getId(): Long {
@@ -24,8 +23,10 @@ data class FeedGroupCardItem(
override fun getLayout(): Int = R.layout.feed_group_card_item
- override fun bind(viewHolder: GroupieViewHolder, position: Int) {
- viewHolder.title.text = name
- viewHolder.icon.setImageResource(icon.getDrawableRes(viewHolder.containerView.context))
+ override fun bind(viewBinding: FeedGroupCardItemBinding, position: Int) {
+ viewBinding.title.text = name
+ viewBinding.icon.setImageResource(icon.getDrawableRes(viewBinding.root.context))
}
+
+ override fun initializeViewBinding(view: View) = FeedGroupCardItemBinding.bind(view)
}
From 761f6568fa1e4a3da0964552e60376224158e5e0 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 28 Nov 2020 13:32:39 +0530
Subject: [PATCH 077/131] Use BindableItem in FeedGroupCarouselItem.
---
.../subscription/SubscriptionFragment.kt | 24 +++++++-----------
.../item/FeedGroupCarouselItem.kt | 25 +++++++++++--------
2 files changed, 23 insertions(+), 26 deletions(-)
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 a84745dbf..61a6601fa 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
@@ -17,7 +17,6 @@ import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
-import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
@@ -27,12 +26,13 @@ import com.xwray.groupie.Group
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.Item
import com.xwray.groupie.Section
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
+import com.xwray.groupie.viewbinding.GroupieViewHolder
import icepick.State
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.databinding.DialogTitleBinding
+import org.schabi.newpipe.databinding.FeedItemCarouselBinding
import org.schabi.newpipe.databinding.FragmentSubscriptionBinding
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.fragments.BaseStateFragment
@@ -79,7 +79,7 @@ class SubscriptionFragment : BaseStateFragment() {
private var subscriptionBroadcastReceiver: BroadcastReceiver? = null
- private val groupAdapter = GroupAdapter()
+ private val groupAdapter = GroupAdapter>()
private val feedGroupsSection = Section()
private var feedGroupsCarousel: FeedGroupCarouselItem? = null
private lateinit var importExportItem: FeedImportExportItem
@@ -234,7 +234,7 @@ class SubscriptionFragment : BaseStateFragment() {
private fun setupInitialLayout() {
Section().apply {
- val carouselAdapter = GroupAdapter()
+ val carouselAdapter = GroupAdapter>()
carouselAdapter.add(FeedGroupCardItem(-1, getString(R.string.all), FeedGroupIcon.RSS))
carouselAdapter.add(feedGroupsSection)
@@ -288,15 +288,12 @@ class SubscriptionFragment : BaseStateFragment() {
binding.itemsList.adapter = groupAdapter
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
- viewModel.stateLiveData.observe(viewLifecycleOwner, Observer { it?.let(this::handleResult) })
- viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, Observer { it?.let(this::handleFeedGroups) })
+ viewModel.stateLiveData.observe(viewLifecycleOwner, { it?.let(this::handleResult) })
+ viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, { it?.let(this::handleFeedGroups) })
}
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
- val commands = arrayOf(
- getString(R.string.share),
- getString(R.string.unsubscribe)
- )
+ val commands = arrayOf(getString(R.string.share), getString(R.string.unsubscribe))
val actions = DialogInterface.OnClickListener { _, i ->
when (i) {
@@ -439,11 +436,8 @@ class SubscriptionFragment : BaseStateFragment() {
return when (listMode) {
getString(R.string.list_view_mode_auto_key) -> {
val configuration = resources.configuration
-
- (
- configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
- configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE)
- )
+ configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+ && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE)
}
getString(R.string.list_view_mode_grid_key) -> true
else -> false
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt
index dfffce59c..44af16280 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt
@@ -6,13 +6,16 @@ import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.xwray.groupie.GroupAdapter
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
-import kotlinx.android.synthetic.main.feed_item_carousel.recycler_view
+import com.xwray.groupie.viewbinding.BindableItem
+import com.xwray.groupie.viewbinding.GroupieViewHolder
import org.schabi.newpipe.R
+import org.schabi.newpipe.databinding.FeedItemCarouselBinding
import org.schabi.newpipe.local.subscription.decoration.FeedGroupCarouselDecoration
-class FeedGroupCarouselItem(context: Context, private val carouselAdapter: GroupAdapter) : Item() {
+class FeedGroupCarouselItem(
+ context: Context,
+ private val carouselAdapter: GroupAdapter>
+) : BindableItem() {
private val feedGroupCarouselDecoration = FeedGroupCarouselDecoration(context)
private var linearLayoutManager: LinearLayoutManager? = null
@@ -30,12 +33,12 @@ class FeedGroupCarouselItem(context: Context, private val carouselAdapter: Group
listState = state
}
- override fun createViewHolder(itemView: View): GroupieViewHolder {
- val viewHolder = super.createViewHolder(itemView)
+ override fun initializeViewBinding(view: View): FeedItemCarouselBinding {
+ val viewHolder = FeedItemCarouselBinding.bind(view)
- linearLayoutManager = LinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)
+ linearLayoutManager = LinearLayoutManager(view.context, RecyclerView.HORIZONTAL, false)
- viewHolder.recycler_view.apply {
+ viewHolder.recyclerView.apply {
layoutManager = linearLayoutManager
adapter = carouselAdapter
addItemDecoration(feedGroupCarouselDecoration)
@@ -44,12 +47,12 @@ class FeedGroupCarouselItem(context: Context, private val carouselAdapter: Group
return viewHolder
}
- override fun bind(viewHolder: GroupieViewHolder, position: Int) {
- viewHolder.recycler_view.apply { adapter = carouselAdapter }
+ override fun bind(viewBinding: FeedItemCarouselBinding, position: Int) {
+ viewBinding.recyclerView.apply { adapter = carouselAdapter }
linearLayoutManager?.onRestoreInstanceState(listState)
}
- override fun unbind(viewHolder: GroupieViewHolder) {
+ override fun unbind(viewHolder: GroupieViewHolder) {
super.unbind(viewHolder)
listState = linearLayoutManager?.onSaveInstanceState()
From 9d27d49c1f849b3a6170bfb6efa2680761406ee2 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 28 Nov 2020 13:43:15 +0530
Subject: [PATCH 078/131] Use BindableItem in FeedGroupReorderItem.
---
.../subscription/item/FeedGroupReorderItem.kt | 23 ++++++++++---------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt
index e56bb408c..48b06b08a 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt
@@ -1,16 +1,15 @@
package org.schabi.newpipe.local.subscription.item
import android.view.MotionEvent
+import android.view.View
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
import androidx.recyclerview.widget.ItemTouchHelper.UP
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
-import kotlinx.android.synthetic.main.feed_group_reorder_item.group_icon
-import kotlinx.android.synthetic.main.feed_group_reorder_item.group_name
-import kotlinx.android.synthetic.main.feed_group_reorder_item.handle
+import com.xwray.groupie.viewbinding.BindableItem
+import com.xwray.groupie.viewbinding.GroupieViewHolder
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
+import org.schabi.newpipe.databinding.FeedGroupReorderItemBinding
import org.schabi.newpipe.local.subscription.FeedGroupIcon
data class FeedGroupReorderItem(
@@ -18,7 +17,7 @@ data class FeedGroupReorderItem(
val name: String,
val icon: FeedGroupIcon,
val dragCallback: ItemTouchHelper
-) : Item() {
+) : BindableItem() {
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper) :
this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
@@ -31,12 +30,12 @@ data class FeedGroupReorderItem(
override fun getLayout(): Int = R.layout.feed_group_reorder_item
- override fun bind(viewHolder: GroupieViewHolder, position: Int) {
- viewHolder.group_name.text = name
- viewHolder.group_icon.setImageResource(icon.getDrawableRes(viewHolder.containerView.context))
- viewHolder.handle.setOnTouchListener { _, event ->
+ override fun bind(viewBinding: FeedGroupReorderItemBinding, position: Int) {
+ viewBinding.groupName.text = name
+ viewBinding.groupIcon.setImageResource(icon.getDrawableRes(viewBinding.root.context))
+ viewBinding.handle.setOnTouchListener { _, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
- dragCallback.startDrag(viewHolder)
+ dragCallback.startDrag(GroupieViewHolder(viewBinding))
return@setOnTouchListener true
}
@@ -47,4 +46,6 @@ data class FeedGroupReorderItem(
override fun getDragDirs(): Int {
return UP or DOWN
}
+
+ override fun initializeViewBinding(view: View) = FeedGroupReorderItemBinding.bind(view)
}
From 51a948bfcf2b733e5497c67432e0d9145f1b7376 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 28 Nov 2020 13:51:39 +0530
Subject: [PATCH 079/131] Use BindableItem in FeedImportExportItem.
---
.../subscription/item/FeedImportExportItem.kt | 51 +++++++++----------
1 file changed, 25 insertions(+), 26 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt
index 00de83538..afca7064f 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt
@@ -7,14 +7,10 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
-import kotlinx.android.synthetic.main.feed_import_export_group.export_to_options
-import kotlinx.android.synthetic.main.feed_import_export_group.import_export
-import kotlinx.android.synthetic.main.feed_import_export_group.import_export_expand_icon
-import kotlinx.android.synthetic.main.feed_import_export_group.import_export_options
-import kotlinx.android.synthetic.main.feed_import_export_group.import_from_options
+import com.xwray.groupie.viewbinding.BindableItem
+import com.xwray.groupie.viewbinding.GroupieViewHolder
import org.schabi.newpipe.R
+import org.schabi.newpipe.databinding.FeedImportExportGroupBinding
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.exceptions.ExtractionException
import org.schabi.newpipe.ktx.animateRotation
@@ -27,50 +23,52 @@ class FeedImportExportItem(
val onImportFromServiceSelected: (Int) -> Unit,
val onExportSelected: () -> Unit,
var isExpanded: Boolean = false
-) : Item() {
+) : BindableItem() {
companion object {
const val REFRESH_EXPANDED_STATUS = 123
}
- override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList) {
+ override fun bind(viewBinding: FeedImportExportGroupBinding, position: Int, payloads: MutableList) {
if (payloads.contains(REFRESH_EXPANDED_STATUS)) {
- viewHolder.import_export_options.apply { if (isExpanded) expand() else collapse() }
+ viewBinding.importExportOptions.apply { if (isExpanded) expand() else collapse() }
return
}
- super.bind(viewHolder, position, payloads)
+ super.bind(viewBinding, position, payloads)
}
override fun getLayout(): Int = R.layout.feed_import_export_group
- override fun bind(viewHolder: GroupieViewHolder, position: Int) {
- if (viewHolder.import_from_options.childCount == 0) setupImportFromItems(viewHolder.import_from_options)
- if (viewHolder.export_to_options.childCount == 0) setupExportToItems(viewHolder.export_to_options)
+ override fun bind(viewBinding: FeedImportExportGroupBinding, position: Int) {
+ if (viewBinding.importFromOptions.childCount == 0) setupImportFromItems(viewBinding.importFromOptions)
+ if (viewBinding.exportToOptions.childCount == 0) setupExportToItems(viewBinding.exportToOptions)
- expandIconListener?.let { viewHolder.import_export_options.removeListener(it) }
+ expandIconListener?.let { viewBinding.importExportOptions.removeListener(it) }
expandIconListener = CollapsibleView.StateListener { newState ->
- viewHolder.import_export_expand_icon.animateRotation(
+ viewBinding.importExportExpandIcon.animateRotation(
250, if (newState == CollapsibleView.COLLAPSED) 0 else 180
)
}
- viewHolder.import_export_options.currentState = if (isExpanded) CollapsibleView.EXPANDED else CollapsibleView.COLLAPSED
- viewHolder.import_export_expand_icon.rotation = if (isExpanded) 180F else 0F
- viewHolder.import_export_options.ready()
+ viewBinding.importExportOptions.currentState = if (isExpanded) CollapsibleView.EXPANDED else CollapsibleView.COLLAPSED
+ viewBinding.importExportExpandIcon.rotation = if (isExpanded) 180F else 0F
+ viewBinding.importExportOptions.ready()
- viewHolder.import_export_options.addListener(expandIconListener)
- viewHolder.import_export.setOnClickListener {
- viewHolder.import_export_options.switchState()
- isExpanded = viewHolder.import_export_options.currentState == CollapsibleView.EXPANDED
+ viewBinding.importExportOptions.addListener(expandIconListener)
+ viewBinding.importExport.setOnClickListener {
+ viewBinding.importExportOptions.switchState()
+ isExpanded = viewBinding.importExportOptions.currentState == CollapsibleView.EXPANDED
}
}
- override fun unbind(viewHolder: GroupieViewHolder) {
+ override fun unbind(viewHolder: GroupieViewHolder) {
super.unbind(viewHolder)
- expandIconListener?.let { viewHolder.import_export_options.removeListener(it) }
+ expandIconListener?.let { viewHolder.binding.importExportOptions.removeListener(it) }
expandIconListener = null
}
+ override fun initializeViewBinding(view: View) = FeedImportExportGroupBinding.bind(view)
+
private var expandIconListener: CollapsibleView.StateListener? = null
private fun addItemView(title: String, @DrawableRes icon: Int, container: ViewGroup): View {
@@ -117,7 +115,8 @@ class FeedImportExportItem(
private fun setupExportToItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView(
listHolder.context.getString(R.string.file),
- ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder
+ ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save),
+ listHolder
)
previousBackupItem.setOnClickListener { onExportSelected() }
}
From 9e5f079cf254d741b9e3357075f0c43cb81730ab Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 28 Nov 2020 13:58:34 +0530
Subject: [PATCH 080/131] Use BindableItem in HeaderItem.
---
.../local/subscription/item/HeaderItem.kt | 20 +++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderItem.kt
index 9798dac1b..e04164573 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderItem.kt
@@ -1,19 +1,23 @@
package org.schabi.newpipe.local.subscription.item
+import android.view.View
import android.view.View.OnClickListener
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
-import kotlinx.android.synthetic.main.header_item.header_title
+import com.xwray.groupie.viewbinding.BindableItem
import org.schabi.newpipe.R
+import org.schabi.newpipe.databinding.HeaderItemBinding
-class HeaderItem(val title: String, private val onClickListener: (() -> Unit)? = null) : Item() {
-
+class HeaderItem(
+ val title: String,
+ private val onClickListener: (() -> Unit)? = null
+) : BindableItem() {
override fun getLayout(): Int = R.layout.header_item
- override fun bind(viewHolder: GroupieViewHolder, position: Int) {
- viewHolder.header_title.text = title
+ override fun bind(viewBinding: HeaderItemBinding, position: Int) {
+ viewBinding.headerTitle.text = title
val listener: OnClickListener? = if (onClickListener != null) OnClickListener { onClickListener.invoke() } else null
- viewHolder.root.setOnClickListener(listener)
+ viewBinding.root.setOnClickListener(listener)
}
+
+ override fun initializeViewBinding(view: View) = HeaderItemBinding.bind(view)
}
From a188125982626a9cdb2d5584fa2bfa82ef530119 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 28 Nov 2020 14:01:30 +0530
Subject: [PATCH 081/131] Use BindableItem in HeaderWithMenuItem.
---
.../subscription/item/HeaderWithMenuItem.kt | 37 ++++++++++---------
1 file changed, 19 insertions(+), 18 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt
index b5aa6b1d0..55f56b3f5 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt
@@ -1,13 +1,12 @@
package org.schabi.newpipe.local.subscription.item
+import android.view.View
import android.view.View.OnClickListener
import androidx.annotation.DrawableRes
import androidx.core.view.isVisible
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
-import kotlinx.android.synthetic.main.header_with_menu_item.header_menu_item
-import kotlinx.android.synthetic.main.header_with_menu_item.header_title
+import com.xwray.groupie.viewbinding.BindableItem
import org.schabi.newpipe.R
+import org.schabi.newpipe.databinding.HeaderWithMenuItemBinding
class HeaderWithMenuItem(
val title: String,
@@ -15,37 +14,39 @@ class HeaderWithMenuItem(
var showMenuItem: Boolean = true,
private val onClickListener: (() -> Unit)? = null,
private val menuItemOnClickListener: (() -> Unit)? = null
-) : Item() {
+) : BindableItem() {
companion object {
const val PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM = 1
}
override fun getLayout(): Int = R.layout.header_with_menu_item
- override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList) {
+ override fun bind(viewBinding: HeaderWithMenuItemBinding, position: Int, payloads: MutableList) {
if (payloads.contains(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM)) {
- updateMenuItemVisibility(viewHolder)
+ updateMenuItemVisibility(viewBinding)
return
}
- super.bind(viewHolder, position, payloads)
+ super.bind(viewBinding, position, payloads)
}
- override fun bind(viewHolder: GroupieViewHolder, position: Int) {
- viewHolder.header_title.text = title
- viewHolder.header_menu_item.setImageResource(itemIcon)
+ override fun bind(viewBinding: HeaderWithMenuItemBinding, position: Int) {
+ viewBinding.headerTitle.text = title
+ viewBinding.headerMenuItem.setImageResource(itemIcon)
val listener: OnClickListener? =
- onClickListener?.let { OnClickListener { onClickListener.invoke() } }
- viewHolder.root.setOnClickListener(listener)
+ onClickListener?.let { OnClickListener { onClickListener.invoke() } }
+ viewBinding.root.setOnClickListener(listener)
val menuItemListener: OnClickListener? =
- menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
- viewHolder.header_menu_item.setOnClickListener(menuItemListener)
- updateMenuItemVisibility(viewHolder)
+ menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
+ viewBinding.headerMenuItem.setOnClickListener(menuItemListener)
+ updateMenuItemVisibility(viewBinding)
}
- private fun updateMenuItemVisibility(viewHolder: GroupieViewHolder) {
- viewHolder.header_menu_item.isVisible = showMenuItem
+ override fun initializeViewBinding(view: View) = HeaderWithMenuItemBinding.bind(view)
+
+ private fun updateMenuItemVisibility(viewBinding: HeaderWithMenuItemBinding) {
+ viewBinding.headerMenuItem.isVisible = showMenuItem
}
}
From e2dd058430ef92c315b0e2fd59c88efa141e8944 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 28 Nov 2020 14:03:26 +0530
Subject: [PATCH 082/131] Use BindableItem in PickerIconItem.
---
.../local/subscription/item/PickerIconItem.kt | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerIconItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerIconItem.kt
index 4f3678e95..11fc4833a 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerIconItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerIconItem.kt
@@ -1,20 +1,25 @@
package org.schabi.newpipe.local.subscription.item
import android.content.Context
+import android.view.View
import androidx.annotation.DrawableRes
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
-import kotlinx.android.synthetic.main.picker_icon_item.icon_view
+import com.xwray.groupie.viewbinding.BindableItem
import org.schabi.newpipe.R
+import org.schabi.newpipe.databinding.PickerIconItemBinding
import org.schabi.newpipe.local.subscription.FeedGroupIcon
-class PickerIconItem(context: Context, val icon: FeedGroupIcon) : Item() {
+class PickerIconItem(
+ context: Context,
+ val icon: FeedGroupIcon
+) : BindableItem() {
@DrawableRes
val iconRes: Int = icon.getDrawableRes(context)
override fun getLayout(): Int = R.layout.picker_icon_item
- override fun bind(viewHolder: GroupieViewHolder, position: Int) {
- viewHolder.icon_view.setImageResource(iconRes)
+ override fun bind(viewBinding: PickerIconItemBinding, position: Int) {
+ viewBinding.iconView.setImageResource(iconRes)
}
+
+ override fun initializeViewBinding(view: View) = PickerIconItemBinding.bind(view)
}
From 77675b361fa5f516b96d03d045d11ccf1250db20 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 28 Nov 2020 14:08:24 +0530
Subject: [PATCH 083/131] Use BindableItem in PickerSubscriptionItem.
---
.../item/PickerSubscriptionItem.kt | 33 +++++++++++--------
1 file changed, 19 insertions(+), 14 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
index 0138b1ffe..3540e6938 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
@@ -1,14 +1,14 @@
package org.schabi.newpipe.local.subscription.item
import android.view.View
+import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.nostra13.universalimageloader.core.ImageLoader
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
-import kotlinx.android.synthetic.main.picker_subscription_item.*
-import kotlinx.android.synthetic.main.picker_subscription_item.view.*
+import com.xwray.groupie.viewbinding.BindableItem
+import com.xwray.groupie.viewbinding.GroupieViewHolder
import org.schabi.newpipe.R
import org.schabi.newpipe.database.subscription.SubscriptionEntity
+import org.schabi.newpipe.databinding.PickerSubscriptionItemBinding
import org.schabi.newpipe.ktx.AnimationType
import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.util.ImageDisplayConstants
@@ -16,31 +16,36 @@ import org.schabi.newpipe.util.ImageDisplayConstants
data class PickerSubscriptionItem(
val subscriptionEntity: SubscriptionEntity,
var isSelected: Boolean = false
-) : Item() {
+) : BindableItem() {
override fun getId(): Long = subscriptionEntity.uid
override fun getLayout(): Int = R.layout.picker_subscription_item
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
- override fun bind(viewHolder: GroupieViewHolder, position: Int) {
+ override fun bind(viewBinding: PickerSubscriptionItemBinding, position: Int) {
ImageLoader.getInstance().displayImage(
subscriptionEntity.avatarUrl,
- viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
+ viewBinding.thumbnailView, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
)
- viewHolder.title_view.text = subscriptionEntity.name
- viewHolder.selected_highlight.isVisible = isSelected
+ viewBinding.titleView.text = subscriptionEntity.name
+ viewBinding.selectedHighlight.isVisible = isSelected
}
- override fun unbind(viewHolder: GroupieViewHolder) {
+ override fun unbind(viewHolder: GroupieViewHolder) {
super.unbind(viewHolder)
- viewHolder.selected_highlight.animate().setListener(null).cancel()
- viewHolder.selected_highlight.visibility = View.GONE
- viewHolder.selected_highlight.alpha = 1F
+ viewHolder.binding.selectedHighlight.apply {
+ animate().setListener(null).cancel()
+ isGone = true
+ alpha = 1F
+ }
}
+ override fun initializeViewBinding(view: View) = PickerSubscriptionItemBinding.bind(view)
+
fun updateSelected(containerView: View, isSelected: Boolean) {
this.isSelected = isSelected
- containerView.selected_highlight.animate(isSelected, 150, AnimationType.LIGHT_SCALE_AND_ALPHA)
+ PickerSubscriptionItemBinding.bind(containerView).selectedHighlight
+ .animate(isSelected, 150, AnimationType.LIGHT_SCALE_AND_ALPHA)
}
}
From e0de66b1bea0ca70859261f1872955734b5db2da Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sat, 28 Nov 2020 14:31:39 +0530
Subject: [PATCH 084/131] Fix some issues.
---
.../newpipe/local/subscription/SubscriptionFragment.kt | 4 ++--
.../newpipe/local/subscription/item/HeaderWithMenuItem.kt | 6 ++----
2 files changed, 4 insertions(+), 6 deletions(-)
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 61a6601fa..b74288a34 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
@@ -436,8 +436,8 @@ class SubscriptionFragment : BaseStateFragment() {
return when (listMode) {
getString(R.string.list_view_mode_auto_key) -> {
val configuration = resources.configuration
- configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
- && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE)
+ configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
+ configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE)
}
getString(R.string.list_view_mode_grid_key) -> true
else -> false
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt
index 55f56b3f5..79a272178 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt
@@ -34,12 +34,10 @@ class HeaderWithMenuItem(
viewBinding.headerTitle.text = title
viewBinding.headerMenuItem.setImageResource(itemIcon)
- val listener: OnClickListener? =
- onClickListener?.let { OnClickListener { onClickListener.invoke() } }
+ val listener = onClickListener?.let { OnClickListener { onClickListener.invoke() } }
viewBinding.root.setOnClickListener(listener)
- val menuItemListener: OnClickListener? =
- menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
+ val menuItemListener = menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
viewBinding.headerMenuItem.setOnClickListener(menuItemListener)
updateMenuItemVisibility(viewBinding)
}
From 01396923f112e965ead4a92f1c92f41ceec2ae24 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sun, 29 Nov 2020 16:08:54 +0530
Subject: [PATCH 085/131] Use the base Groupie library in ChannelItem.
---
.../subscription/dialog/FeedGroupDialog.kt | 2 +-
.../dialog/FeedGroupReorderDialog.kt | 2 +-
.../local/subscription/item/ChannelItem.kt | 32 +++++++++++--------
3 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
index 7c7ebdea9..6f821f432 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
@@ -19,9 +19,9 @@ import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.xwray.groupie.GroupAdapter
+import com.xwray.groupie.GroupieViewHolder
import com.xwray.groupie.OnItemClickListener
import com.xwray.groupie.Section
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
import org.schabi.newpipe.R
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt
index 3b74ddc74..2b09a3b3b 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt
@@ -12,8 +12,8 @@ import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.xwray.groupie.GroupAdapter
+import com.xwray.groupie.GroupieViewHolder
import com.xwray.groupie.TouchCallback
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
import org.schabi.newpipe.R
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 8089f6480..a87ffb695 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
@@ -1,13 +1,11 @@
package org.schabi.newpipe.local.subscription.item
import android.content.Context
+import android.widget.ImageView
+import android.widget.TextView
import com.nostra13.universalimageloader.core.ImageLoader
-import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
-import com.xwray.groupie.kotlinandroidextensions.Item
-import kotlinx.android.synthetic.main.list_channel_item.itemAdditionalDetails
-import kotlinx.android.synthetic.main.list_channel_item.itemChannelDescriptionView
-import kotlinx.android.synthetic.main.list_channel_item.itemThumbnailView
-import kotlinx.android.synthetic.main.list_channel_item.itemTitleView
+import com.xwray.groupie.GroupieViewHolder
+import com.xwray.groupie.Item
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.util.ImageDisplayConstants
@@ -19,8 +17,7 @@ class ChannelItem(
private val subscriptionId: Long = -1L,
var itemVersion: ItemVersion = ItemVersion.NORMAL,
var gesturesListener: OnClickGesture? = null
-) : Item() {
-
+) : Item() {
override fun getId(): Long = if (subscriptionId == -1L) super.getId() else subscriptionId
enum class ItemVersion { NORMAL, MINI, GRID }
@@ -32,18 +29,25 @@ class ChannelItem(
}
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
- viewHolder.itemTitleView.text = infoItem.name
- viewHolder.itemAdditionalDetails.text = getDetailLine(viewHolder.root.context)
- if (itemVersion == ItemVersion.NORMAL) viewHolder.itemChannelDescriptionView.text = infoItem.description
+ val itemTitleView = viewHolder.root.findViewById(R.id.itemTitleView)
+ val itemAdditionalDetails = viewHolder.root.findViewById(R.id.itemAdditionalDetails)
+ val itemChannelDescriptionView = viewHolder.root.findViewById(R.id.itemChannelDescriptionView)
+ val itemThumbnailView = viewHolder.root.findViewById(R.id.itemThumbnailView)
+
+ itemTitleView.text = infoItem.name
+ itemAdditionalDetails.text = getDetailLine(viewHolder.root.context)
+ if (itemVersion == ItemVersion.NORMAL) {
+ itemChannelDescriptionView.text = infoItem.description
+ }
ImageLoader.getInstance().displayImage(
- infoItem.thumbnailUrl, viewHolder.itemThumbnailView,
+ infoItem.thumbnailUrl, itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS
)
gesturesListener?.run {
- viewHolder.containerView.setOnClickListener { selected(infoItem) }
- viewHolder.containerView.setOnLongClickListener { held(infoItem); true }
+ viewHolder.root.setOnClickListener { selected(infoItem) }
+ viewHolder.root.setOnLongClickListener { held(infoItem); true }
}
}
From fe92abde0e3f55711bad82c5d66a3d7f721fa632 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne
Date: Sun, 17 Jan 2021 09:51:11 +0530
Subject: [PATCH 086/131] Use view binding in VideoDetailFragment.
---
.../fragments/detail/VideoDetailFragment.java | 533 +++++++-----------
.../item/PickerSubscriptionItem.kt | 2 +-
.../event/CustomBottomSheetBehavior.java | 2 +-
.../fragment_video_detail.xml | 6 +-
.../main/res/layout/fragment_video_detail.xml | 6 +-
5 files changed, 221 insertions(+), 328 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 9079c47ca..0ac49cd7e 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
@@ -26,11 +26,7 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.RelativeLayout;
-import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
@@ -44,13 +40,11 @@ import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
-import androidx.viewpager.widget.ViewPager;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
-import com.google.android.material.tabs.TabLayout;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
@@ -58,6 +52,7 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene
import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
+import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe;
@@ -102,7 +97,6 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.TextLinkifier;
import org.schabi.newpipe.util.ThemeHelper;
-import org.schabi.newpipe.views.AnimatedProgressBar;
import org.schabi.newpipe.views.LargeTextMovementMethod;
import java.util.Iterator;
@@ -120,10 +114,10 @@ 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.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.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
public final class VideoDetailFragment
@@ -194,66 +188,14 @@ public final class VideoDetailFragment
// Views
//////////////////////////////////////////////////////////////////////////*/
- private LinearLayout contentRootLayoutHiding;
+ private FragmentVideoDetailBinding binding;
- private View thumbnailBackgroundButton;
- private ImageView thumbnailImageView;
- private ImageView thumbnailPlayButton;
- private AnimatedProgressBar positionView;
- private ViewGroup playerPlaceholder;
-
- private View videoTitleRoot;
- private TextView videoTitleTextView;
- private ImageView videoTitleToggleArrow;
- private TextView videoCountView;
-
- private TextView detailControlsBackground;
- private TextView detailControlsPopup;
- private TextView detailControlsAddToPlaylist;
- private TextView detailControlsDownload;
- private TextView appendControlsDetail;
- private TextView detailDurationView;
- private TextView detailPositionView;
-
- private View detailMetaInfoSeparator;
- private TextView detailMetaInfoTextView;
-
- private LinearLayout videoDescriptionRootLayout;
- private TextView videoUploadDateView;
- private TextView videoDescriptionView;
-
- private View uploaderRootLayout;
- private TextView uploaderTextView;
- private ImageView uploaderThumb;
- private TextView subChannelTextView;
- private ImageView subChannelThumb;
-
- private TextView thumbsUpTextView;
- private ImageView thumbsUpImageView;
- private TextView thumbsDownTextView;
- private ImageView thumbsDownImageView;
- private TextView thumbsDisabledTextView;
-
- private RelativeLayout overlay;
- private LinearLayout overlayMetadata;
- private ImageView overlayThumbnailImageView;
- private TextView overlayTitleTextView;
- private TextView overlayChannelTextView;
- private LinearLayout overlayButtons;
- private ImageButton overlayPlayPauseButton;
- private ImageButton overlayCloseButton;
-
- private AppBarLayout appBarLayout;
- private ViewPager viewPager;
private TabAdapter pageAdapter;
- private TabLayout tabLayout;
- private FrameLayout relatedStreamsLayout;
private ContentObserver settingsContentObserver;
private MainPlayer playerService;
private Player player;
-
/*//////////////////////////////////////////////////////////////////////////
// Service management
//////////////////////////////////////////////////////////////////////////*/
@@ -373,7 +315,7 @@ public final class VideoDetailFragment
PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit()
.putString(getString(R.string.stream_info_selected_tab_key),
- pageAdapter.getItemTitle(viewPager.getCurrentItem()))
+ pageAdapter.getItemTitle(binding.viewPager.getCurrentItem()))
.apply();
}
@@ -416,6 +358,7 @@ public final class VideoDetailFragment
@Override
public void onDestroy() {
super.onDestroy();
+ binding = null;
// Stop the service when user leaves the app with double back press
// if video player is selected. Otherwise unbind
@@ -580,9 +523,7 @@ public final class VideoDetailFragment
break;
case R.id.overlay_thumbnail:
case R.id.overlay_metadata_layout:
- if (currentInfo != null) {
- openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
- }
+ openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
break;
case R.id.detail_uploader_root_layout:
if (isEmpty(currentInfo.getSubChannelUrl())) {
@@ -594,7 +535,7 @@ public final class VideoDetailFragment
break;
case R.id.detail_title_root_layout:
ShareUtils.copyToClipboard(requireContext(),
- videoTitleTextView.getText().toString());
+ binding.detailVideoTitleView.getText().toString());
break;
}
@@ -602,18 +543,18 @@ public final class VideoDetailFragment
}
private void toggleTitleAndDescription() {
- if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
- videoTitleTextView.setMaxLines(1);
- videoDescriptionRootLayout.setVisibility(View.GONE);
- videoDescriptionView.setFocusable(false);
- videoTitleToggleArrow.setImageResource(
+ if (binding.detailDescriptionRootLayout.getVisibility() == View.VISIBLE) {
+ binding.detailVideoTitleView.setMaxLines(1);
+ binding.detailDescriptionRootLayout.setVisibility(View.GONE);
+ binding.detailDescriptionView.setFocusable(false);
+ binding.detailToggleDescriptionView.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more));
} else {
- videoTitleTextView.setMaxLines(10);
- videoDescriptionRootLayout.setVisibility(View.VISIBLE);
- videoDescriptionView.setFocusable(true);
- videoDescriptionView.setMovementMethod(new LargeTextMovementMethod());
- videoTitleToggleArrow.setImageResource(
+ binding.detailVideoTitleView.setMaxLines(10);
+ binding.detailDescriptionRootLayout.setVisibility(View.VISIBLE);
+ binding.detailDescriptionView.setFocusable(true);
+ binding.detailDescriptionView.setMovementMethod(new LargeTextMovementMethod());
+ binding.detailToggleDescriptionView.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_less));
}
}
@@ -625,107 +566,55 @@ public final class VideoDetailFragment
@Override
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
- thumbnailBackgroundButton = rootView.findViewById(R.id.detail_thumbnail_root_layout);
- thumbnailImageView = rootView.findViewById(R.id.detail_thumbnail_image_view);
- thumbnailPlayButton = rootView.findViewById(R.id.detail_thumbnail_play_button);
- playerPlaceholder = rootView.findViewById(R.id.player_placeholder);
+ binding = FragmentVideoDetailBinding.bind(rootView);
- contentRootLayoutHiding = rootView.findViewById(R.id.detail_content_root_hiding);
-
- videoTitleRoot = rootView.findViewById(R.id.detail_title_root_layout);
- videoTitleTextView = rootView.findViewById(R.id.detail_video_title_view);
- videoTitleToggleArrow = rootView.findViewById(R.id.detail_toggle_description_view);
- videoCountView = rootView.findViewById(R.id.detail_view_count_view);
- positionView = rootView.findViewById(R.id.position_view);
-
- detailControlsBackground = rootView.findViewById(R.id.detail_controls_background);
- detailControlsPopup = rootView.findViewById(R.id.detail_controls_popup);
- detailControlsAddToPlaylist = rootView.findViewById(R.id.detail_controls_playlist_append);
- detailControlsDownload = rootView.findViewById(R.id.detail_controls_download);
- appendControlsDetail = rootView.findViewById(R.id.touch_append_detail);
- detailDurationView = rootView.findViewById(R.id.detail_duration_view);
- detailPositionView = rootView.findViewById(R.id.detail_position_view);
-
- detailMetaInfoSeparator = rootView.findViewById(R.id.detail_meta_info_separator);
- detailMetaInfoTextView = rootView.findViewById(R.id.detail_meta_info_text_view);
-
- videoDescriptionRootLayout = rootView.findViewById(R.id.detail_description_root_layout);
- videoUploadDateView = rootView.findViewById(R.id.detail_upload_date_view);
- videoDescriptionView = rootView.findViewById(R.id.detail_description_view);
-
- thumbsUpTextView = rootView.findViewById(R.id.detail_thumbs_up_count_view);
- thumbsUpImageView = rootView.findViewById(R.id.detail_thumbs_up_img_view);
- thumbsDownTextView = rootView.findViewById(R.id.detail_thumbs_down_count_view);
- thumbsDownImageView = rootView.findViewById(R.id.detail_thumbs_down_img_view);
- thumbsDisabledTextView = rootView.findViewById(R.id.detail_thumbs_disabled_view);
-
- uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout);
- uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view);
- uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view);
- subChannelTextView = rootView.findViewById(R.id.detail_sub_channel_text_view);
- subChannelThumb = rootView.findViewById(R.id.detail_sub_channel_thumbnail_view);
-
- overlay = rootView.findViewById(R.id.overlay_layout);
- overlayMetadata = rootView.findViewById(R.id.overlay_metadata_layout);
- overlayThumbnailImageView = rootView.findViewById(R.id.overlay_thumbnail);
- overlayTitleTextView = rootView.findViewById(R.id.overlay_title_text_view);
- overlayChannelTextView = rootView.findViewById(R.id.overlay_channel_text_view);
- overlayButtons = rootView.findViewById(R.id.overlay_buttons_layout);
- overlayPlayPauseButton = rootView.findViewById(R.id.overlay_play_pause_button);
- overlayCloseButton = rootView.findViewById(R.id.overlay_close_button);
-
- appBarLayout = rootView.findViewById(R.id.appbarlayout);
- viewPager = rootView.findViewById(R.id.viewpager);
pageAdapter = new TabAdapter(getChildFragmentManager());
- viewPager.setAdapter(pageAdapter);
- tabLayout = rootView.findViewById(R.id.tablayout);
- tabLayout.setupWithViewPager(viewPager);
+ binding.viewPager.setAdapter(pageAdapter);
+ binding.tabLayout.setupWithViewPager(binding.viewPager);
- relatedStreamsLayout = rootView.findViewById(R.id.relatedStreamsLayout);
-
- thumbnailBackgroundButton.requestFocus();
+ binding.detailThumbnailRootLayout.requestFocus();
if (DeviceUtils.isTv(getContext())) {
// remove ripple effects from detail controls
- final int transparent = getResources().getColor(R.color.transparent_background_color);
- detailControlsAddToPlaylist.setBackgroundColor(transparent);
- detailControlsBackground.setBackgroundColor(transparent);
- detailControlsPopup.setBackgroundColor(transparent);
- detailControlsDownload.setBackgroundColor(transparent);
+ final int transparent = ContextCompat.getColor(requireContext(),
+ R.color.transparent_background_color);
+ binding.detailControlsPlaylistAppend.setBackgroundColor(transparent);
+ binding.detailControlsBackground.setBackgroundColor(transparent);
+ binding.detailControlsPopup.setBackgroundColor(transparent);
+ binding.detailControlsDownload.setBackgroundColor(transparent);
}
-
}
@Override
protected void initListeners() {
super.initListeners();
- videoTitleRoot.setOnLongClickListener(this);
- uploaderRootLayout.setOnClickListener(this);
- uploaderRootLayout.setOnLongClickListener(this);
- videoTitleRoot.setOnClickListener(this);
- thumbnailBackgroundButton.setOnClickListener(this);
- detailControlsBackground.setOnClickListener(this);
- detailControlsPopup.setOnClickListener(this);
- detailControlsAddToPlaylist.setOnClickListener(this);
- detailControlsDownload.setOnClickListener(this);
- detailControlsDownload.setOnLongClickListener(this);
+ binding.detailTitleRootLayout.setOnLongClickListener(this);
+ binding.detailUploaderRootLayout.setOnClickListener(this);
+ binding.detailUploaderRootLayout.setOnLongClickListener(this);
+ binding.detailTitleRootLayout.setOnClickListener(this);
+ binding.detailThumbnailRootLayout.setOnClickListener(this);
+ binding.detailControlsBackground.setOnClickListener(this);
+ binding.detailControlsPopup.setOnClickListener(this);
+ binding.detailControlsPlaylistAppend.setOnClickListener(this);
+ binding.detailControlsDownload.setOnClickListener(this);
+ binding.detailControlsDownload.setOnLongClickListener(this);
- detailControlsBackground.setLongClickable(true);
- detailControlsPopup.setLongClickable(true);
- detailControlsBackground.setOnLongClickListener(this);
- detailControlsPopup.setOnLongClickListener(this);
+ binding.detailControlsBackground.setLongClickable(true);
+ binding.detailControlsPopup.setLongClickable(true);
+ binding.detailControlsBackground.setOnLongClickListener(this);
+ binding.detailControlsPopup.setOnLongClickListener(this);
- overlayThumbnailImageView.setOnClickListener(this);
- overlayThumbnailImageView.setOnLongClickListener(this);
- overlayMetadata.setOnClickListener(this);
- overlayMetadata.setOnLongClickListener(this);
- overlayButtons.setOnClickListener(this);
- overlayCloseButton.setOnClickListener(this);
- overlayPlayPauseButton.setOnClickListener(this);
+ binding.overlayThumbnail.setOnClickListener(this);
+ binding.overlayThumbnail.setOnLongClickListener(this);
+ binding.overlayMetadataLayout.setOnClickListener(this);
+ binding.overlayMetadataLayout.setOnLongClickListener(this);
+ binding.overlayButtonsLayout.setOnClickListener(this);
+ binding.overlayCloseButton.setOnClickListener(this);
+ binding.overlayPlayPauseButton.setOnClickListener(this);
- detailControlsBackground.setOnTouchListener(getOnControlsTouchListener());
- detailControlsPopup.setOnTouchListener(getOnControlsTouchListener());
+ binding.detailControlsBackground.setOnTouchListener(getOnControlsTouchListener());
+ binding.detailControlsPopup.setOnTouchListener(getOnControlsTouchListener());
setupBottomPlayer();
if (!PlayerHolder.bound) {
@@ -743,9 +632,9 @@ public final class VideoDetailFragment
}
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
- animate(appendControlsDetail, true, 250, AnimationType.ALPHA,
+ animate(binding.touchAppendDetail, true, 250, AnimationType.ALPHA,
0, () ->
- animate(appendControlsDetail, false, 1500,
+ animate(binding.touchAppendDetail, false, 1500,
AnimationType.ALPHA, 1000));
}
return false;
@@ -753,7 +642,7 @@ public final class VideoDetailFragment
}
private void initThumbnailViews(@NonNull final StreamInfo info) {
- thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
+ binding.detailThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
if (!isEmpty(info.getThumbnailUrl())) {
final String infoServiceName = NewPipe.getNameOfService(info.getServiceId());
@@ -766,17 +655,19 @@ public final class VideoDetailFragment
}
};
- IMAGE_LOADER.displayImage(info.getThumbnailUrl(), thumbnailImageView,
+ IMAGE_LOADER.displayImage(info.getThumbnailUrl(), binding.detailThumbnailImageView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener);
}
if (!isEmpty(info.getSubChannelAvatarUrl())) {
- IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(), subChannelThumb,
+ IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(),
+ binding.detailSubChannelThumbnailView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
}
if (!isEmpty(info.getUploaderAvatarUrl())) {
- IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), uploaderThumb,
+ IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(),
+ binding.detailUploaderThumbnailView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
}
}
@@ -896,7 +787,7 @@ public final class VideoDetailFragment
return;
}
// Data can already be drawn, don't spend time twice
- if (info.getName().equals(videoTitleTextView.getText().toString())) {
+ if (info.getName().equals(binding.detailVideoTitleView.getText().toString())) {
return;
}
prepareAndHandleInfo(info, scrollToTop);
@@ -984,7 +875,7 @@ public final class VideoDetailFragment
private void initTabs() {
if (pageAdapter.getCount() != 0) {
- selectedTabTag = pageAdapter.getItemTitle(viewPager.getCurrentItem());
+ selectedTabTag = pageAdapter.getItemTitle(binding.viewPager.getCurrentItem());
}
pageAdapter.clearAllItems();
@@ -993,7 +884,7 @@ public final class VideoDetailFragment
CommentsFragment.getInstance(serviceId, url, title), COMMENTS_TAB_TAG);
}
- if (showRelatedStreams && null == relatedStreamsLayout) {
+ if (showRelatedStreams && binding.relatedStreamsLayout == null) {
//temp empty fragment. will be updated in handleResult
pageAdapter.addFragment(new Fragment(), RELATED_TAB_TAG);
}
@@ -1005,13 +896,13 @@ public final class VideoDetailFragment
pageAdapter.notifyDataSetUpdate();
if (pageAdapter.getCount() < 2) {
- tabLayout.setVisibility(View.GONE);
+ binding.tabLayout.setVisibility(View.GONE);
} else {
final int position = pageAdapter.getItemPositionByTitle(selectedTabTag);
if (position != -1) {
- viewPager.setCurrentItem(position);
+ binding.viewPager.setCurrentItem(position);
}
- tabLayout.setVisibility(View.VISIBLE);
+ binding.tabLayout.setVisibility(View.VISIBLE);
}
}
@@ -1027,7 +918,7 @@ public final class VideoDetailFragment
}
public void scrollToTop() {
- appBarLayout.setExpanded(true, true);
+ binding.appBarLayout.setExpanded(true, true);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -1197,14 +1088,14 @@ public final class VideoDetailFragment
}
// Check if viewHolder already contains a child
- if (player.getRootView().getParent() != playerPlaceholder) {
+ if (player.getRootView().getParent() != binding.playerPlaceholder) {
playerService.removeViewFromParent();
}
setHeightThumbnail();
// Prevent from re-adding a view multiple times
if (player.getRootView().getParent() == null) {
- playerPlaceholder.addView(player.getRootView());
+ binding.playerPlaceholder.addView(player.getRootView());
}
}
@@ -1219,8 +1110,8 @@ public final class VideoDetailFragment
return;
}
- playerPlaceholder.getLayoutParams().height = FrameLayout.LayoutParams.MATCH_PARENT;
- playerPlaceholder.requestLayout();
+ binding.playerPlaceholder.getLayoutParams().height = FrameLayout.LayoutParams.MATCH_PARENT;
+ binding.playerPlaceholder.requestLayout();
}
private void prepareDescription(final Description description) {
@@ -1232,16 +1123,16 @@ public final class VideoDetailFragment
switch (description.getType()) {
case Description.HTML:
disposables.add(TextLinkifier.createLinksFromHtmlBlock(requireContext(),
- description.getContent(), videoDescriptionView,
+ description.getContent(), binding.detailDescriptionView,
HtmlCompat.FROM_HTML_MODE_LEGACY));
break;
case Description.MARKDOWN:
disposables.add(TextLinkifier.createLinksFromMarkdownText(requireContext(),
- description.getContent(), videoDescriptionView));
+ description.getContent(), binding.detailDescriptionView));
break;
case Description.PLAIN_TEXT: default:
disposables.add(TextLinkifier.createLinksFromPlainText(requireContext(),
- description.getContent(), videoDescriptionView));
+ description.getContent(), binding.detailDescriptionView));
break;
}
}
@@ -1294,10 +1185,10 @@ public final class VideoDetailFragment
}
private void setHeightThumbnail(final int newHeight, final DisplayMetrics metrics) {
- thumbnailImageView.setLayoutParams(
+ binding.detailThumbnailImageView.setLayoutParams(
new FrameLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, newHeight));
- thumbnailImageView.setMinimumHeight(newHeight);
+ binding.detailThumbnailImageView.setMinimumHeight(newHeight);
if (player != null) {
final int maxHeight = (int) (metrics.heightPixels * MAX_PLAYER_HEIGHT);
player.getSurfaceView()
@@ -1306,7 +1197,7 @@ public final class VideoDetailFragment
}
private void showContent() {
- contentRootLayoutHiding.setVisibility(View.VISIBLE);
+ binding.detailContentRootHiding.setVisibility(View.VISIBLE);
}
protected void setInitialData(final int newServiceId,
@@ -1320,14 +1211,14 @@ public final class VideoDetailFragment
}
private void setErrorImage(final int imageResource) {
- if (thumbnailImageView == null || activity == null) {
+ if (binding == null || activity == null) {
return;
}
- thumbnailImageView.setImageDrawable(
+ binding.detailThumbnailImageView.setImageDrawable(
AppCompatResources.getDrawable(requireContext(), imageResource));
- animate(thumbnailImageView, false, 0, AnimationType.ALPHA, 0,
- () -> animate(thumbnailImageView, true, 500));
+ animate(binding.detailThumbnailImageView, false, 0, AnimationType.ALPHA,
+ 0, () -> animate(binding.detailThumbnailImageView, true, 500));
}
@Override
@@ -1406,35 +1297,35 @@ public final class VideoDetailFragment
//if data is already cached, transition from VISIBLE -> INVISIBLE -> VISIBLE is not required
if (!ExtractorHelper.isCached(serviceId, url, InfoItem.InfoType.STREAM)) {
- contentRootLayoutHiding.setVisibility(View.INVISIBLE);
+ binding.detailContentRootHiding.setVisibility(View.INVISIBLE);
}
- animate(thumbnailPlayButton, false, 50);
- animate(detailDurationView, false, 100);
- animate(detailPositionView, false, 100);
- animate(positionView, false, 50);
+ animate(binding.detailThumbnailPlayButton, false, 50);
+ animate(binding.detailDurationView, false, 100);
+ animate(binding.detailPositionView, false, 100);
+ animate(binding.positionView, false, 50);
- videoTitleTextView.setText(title);
- videoTitleTextView.setMaxLines(1);
- animate(videoTitleTextView, true, 0);
+ binding.detailVideoTitleView.setText(title);
+ binding.detailVideoTitleView.setMaxLines(1);
+ animate(binding.detailVideoTitleView, true, 0);
- videoDescriptionRootLayout.setVisibility(View.GONE);
- videoTitleToggleArrow.setVisibility(View.GONE);
- videoTitleRoot.setClickable(false);
+ binding.detailDescriptionRootLayout.setVisibility(View.GONE);
+ binding.detailToggleDescriptionView.setVisibility(View.GONE);
+ binding.detailTitleRootLayout.setClickable(false);
- if (relatedStreamsLayout != null) {
+ if (binding.relatedStreamsLayout != null) {
if (showRelatedStreams) {
- relatedStreamsLayout.setVisibility(
+ binding.relatedStreamsLayout.setVisibility(
player != null && player.isFullscreen() ? View.GONE : View.INVISIBLE);
} else {
- relatedStreamsLayout.setVisibility(View.GONE);
+ binding.relatedStreamsLayout.setVisibility(View.GONE);
}
}
- IMAGE_LOADER.cancelDisplayTask(thumbnailImageView);
- IMAGE_LOADER.cancelDisplayTask(subChannelThumb);
- thumbnailImageView.setImageBitmap(null);
- subChannelThumb.setImageBitmap(null);
+ IMAGE_LOADER.cancelDisplayTask(binding.detailThumbnailImageView);
+ IMAGE_LOADER.cancelDisplayTask(binding.detailSubChannelThumbnailView);
+ binding.detailThumbnailImageView.setImageBitmap(null);
+ binding.detailSubChannelThumbnailView.setImageBitmap(null);
}
@Override
@@ -1445,7 +1336,7 @@ public final class VideoDetailFragment
setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName(), playQueue);
if (showRelatedStreams) {
- if (null == relatedStreamsLayout) { //phone
+ if (binding.relatedStreamsLayout == null) { //phone
pageAdapter.updateItem(RELATED_TAB_TAG,
RelatedVideosFragment.getInstance(info));
pageAdapter.notifyDataSetUpdate();
@@ -1454,98 +1345,100 @@ public final class VideoDetailFragment
.replace(R.id.relatedStreamsLayout,
RelatedVideosFragment.getInstance(info))
.commitAllowingStateLoss();
- relatedStreamsLayout.setVisibility(
+ binding.relatedStreamsLayout.setVisibility(
player != null && player.isFullscreen() ? View.GONE : View.VISIBLE);
}
}
- animate(thumbnailPlayButton, true, 200);
- videoTitleTextView.setText(title);
+ animate(binding.detailThumbnailPlayButton, true, 200);
+ binding.detailVideoTitleView.setText(title);
if (!isEmpty(info.getSubChannelName())) {
displayBothUploaderAndSubChannel(info);
} else if (!isEmpty(info.getUploaderName())) {
displayUploaderAsSubChannel(info);
} else {
- uploaderTextView.setVisibility(View.GONE);
- uploaderThumb.setVisibility(View.GONE);
+ binding.detailUploadDateView.setVisibility(View.GONE);
+ binding.detailUploaderThumbnailView.setVisibility(View.GONE);
}
final Drawable buddyDrawable = AppCompatResources.getDrawable(activity, R.drawable.buddy);
- subChannelThumb.setImageDrawable(buddyDrawable);
- uploaderThumb.setImageDrawable(buddyDrawable);
+ binding.detailSubChannelThumbnailView.setImageDrawable(buddyDrawable);
+ binding.detailUploaderThumbnailView.setImageDrawable(buddyDrawable);
if (info.getViewCount() >= 0) {
if (info.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
- videoCountView.setText(Localization.listeningCount(activity, info.getViewCount()));
+ binding.detailViewCountView.setText(Localization.listeningCount(activity,
+ info.getViewCount()));
} else if (info.getStreamType().equals(StreamType.LIVE_STREAM)) {
- videoCountView.setText(Localization
+ binding.detailViewCountView.setText(Localization
.localizeWatchingCount(activity, info.getViewCount()));
} else {
- videoCountView.setText(Localization
+ binding.detailViewCountView.setText(Localization
.localizeViewCount(activity, info.getViewCount()));
}
- videoCountView.setVisibility(View.VISIBLE);
+ binding.detailViewCountView.setVisibility(View.VISIBLE);
} else {
- videoCountView.setVisibility(View.GONE);
+ binding.detailViewCountView.setVisibility(View.GONE);
}
if (info.getDislikeCount() == -1 && info.getLikeCount() == -1) {
- thumbsDownImageView.setVisibility(View.VISIBLE);
- thumbsUpImageView.setVisibility(View.VISIBLE);
- thumbsUpTextView.setVisibility(View.GONE);
- thumbsDownTextView.setVisibility(View.GONE);
+ binding.detailThumbsDownImgView.setVisibility(View.VISIBLE);
+ binding.detailThumbsUpImgView.setVisibility(View.VISIBLE);
+ binding.detailThumbsUpCountView.setVisibility(View.GONE);
+ binding.detailThumbsDownCountView.setVisibility(View.GONE);
- thumbsDisabledTextView.setVisibility(View.VISIBLE);
+ binding.detailThumbsDisabledView.setVisibility(View.VISIBLE);
} else {
if (info.getDislikeCount() >= 0) {
- thumbsDownTextView.setText(Localization
+ binding.detailThumbsDownCountView.setText(Localization
.shortCount(activity, info.getDislikeCount()));
- thumbsDownTextView.setVisibility(View.VISIBLE);
- thumbsDownImageView.setVisibility(View.VISIBLE);
+ binding.detailThumbsDownCountView.setVisibility(View.VISIBLE);
+ binding.detailThumbsDownImgView.setVisibility(View.VISIBLE);
} else {
- thumbsDownTextView.setVisibility(View.GONE);
- thumbsDownImageView.setVisibility(View.GONE);
+ binding.detailThumbsDownCountView.setVisibility(View.GONE);
+ binding.detailThumbsDownImgView.setVisibility(View.GONE);
}
if (info.getLikeCount() >= 0) {
- thumbsUpTextView.setText(Localization.shortCount(activity, info.getLikeCount()));
- thumbsUpTextView.setVisibility(View.VISIBLE);
- thumbsUpImageView.setVisibility(View.VISIBLE);
+ binding.detailThumbsUpCountView.setText(Localization.shortCount(activity,
+ info.getLikeCount()));
+ binding.detailThumbsUpCountView.setVisibility(View.VISIBLE);
+ binding.detailThumbsUpImgView.setVisibility(View.VISIBLE);
} else {
- thumbsUpTextView.setVisibility(View.GONE);
- thumbsUpImageView.setVisibility(View.GONE);
+ binding.detailThumbsUpCountView.setVisibility(View.GONE);
+ binding.detailThumbsUpImgView.setVisibility(View.GONE);
}
- thumbsDisabledTextView.setVisibility(View.GONE);
+ binding.detailThumbsDisabledView.setVisibility(View.GONE);
}
if (info.getDuration() > 0) {
- detailDurationView.setText(Localization.getDurationString(info.getDuration()));
- detailDurationView.setBackgroundColor(
+ binding.detailDurationView.setText(Localization.getDurationString(info.getDuration()));
+ binding.detailDurationView.setBackgroundColor(
ContextCompat.getColor(activity, R.color.duration_background_color));
- animate(detailDurationView, true, 100);
+ animate(binding.detailDurationView, true, 100);
} else if (info.getStreamType() == StreamType.LIVE_STREAM) {
- detailDurationView.setText(R.string.duration_live);
- detailDurationView.setBackgroundColor(
+ binding.detailDurationView.setText(R.string.duration_live);
+ binding.detailDurationView.setBackgroundColor(
ContextCompat.getColor(activity, R.color.live_duration_background_color));
- animate(detailDurationView, true, 100);
+ animate(binding.detailDurationView, true, 100);
} else {
- detailDurationView.setVisibility(View.GONE);
+ binding.detailDurationView.setVisibility(View.GONE);
}
- videoDescriptionView.setVisibility(View.GONE);
- videoTitleRoot.setClickable(true);
- videoTitleToggleArrow.setImageResource(
+ binding.detailDescriptionView.setVisibility(View.GONE);
+ binding.detailTitleRootLayout.setClickable(true);
+ binding.detailToggleDescriptionView.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more));
- videoTitleToggleArrow.setVisibility(View.VISIBLE);
- videoDescriptionRootLayout.setVisibility(View.GONE);
+ binding.detailToggleDescriptionView.setVisibility(View.VISIBLE);
+ binding.detailDescriptionRootLayout.setVisibility(View.GONE);
if (info.getUploadDate() != null) {
- videoUploadDateView.setText(Localization
+ binding.detailUploadDateView.setText(Localization
.localizeUploadDate(activity, info.getUploadDate().offsetDateTime()));
- videoUploadDateView.setVisibility(View.VISIBLE);
+ binding.detailUploadDateView.setVisibility(View.VISIBLE);
} else {
- videoUploadDateView.setText(null);
- videoUploadDateView.setVisibility(View.GONE);
+ binding.detailUploadDateView.setText(null);
+ binding.detailUploadDateView.setVisibility(View.GONE);
}
sortedVideoStreams = ListHelper.getSortedStreamVideosList(
@@ -1558,8 +1451,8 @@ public final class VideoDetailFragment
prepareDescription(info.getDescription());
updateProgressInfo(info);
initThumbnailViews(info);
- disposables.add(showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView,
- detailMetaInfoSeparator));
+ disposables.add(showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
+ binding.detailMetaInfoSeparator));
if (player == null || player.isStopped()) {
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());
@@ -1573,15 +1466,15 @@ public final class VideoDetailFragment
0);
}
- detailControlsDownload.setVisibility(info.getStreamType() == StreamType.LIVE_STREAM
+ binding.detailControlsDownload.setVisibility(info.getStreamType() == StreamType.LIVE_STREAM
|| info.getStreamType() == StreamType.AUDIO_LIVE_STREAM ? View.GONE : View.VISIBLE);
- detailControlsBackground.setVisibility(info.getAudioStreams().isEmpty()
+ binding.detailControlsBackground.setVisibility(info.getAudioStreams().isEmpty()
? View.GONE : View.VISIBLE);
final boolean noVideoStreams =
info.getVideoStreams().isEmpty() && info.getVideoOnlyStreams().isEmpty();
- detailControlsPopup.setVisibility(noVideoStreams ? View.GONE : View.VISIBLE);
- thumbnailPlayButton.setImageResource(
+ binding.detailControlsPopup.setVisibility(noVideoStreams ? View.GONE : View.VISIBLE);
+ binding.detailThumbnailPlayButton.setImageResource(
noVideoStreams ? R.drawable.ic_headset_shadow : R.drawable.ic_play_arrow_shadow);
}
@@ -1589,39 +1482,38 @@ public final class VideoDetailFragment
showError(getString(R.string.restricted_video,
getString(R.string.show_age_restricted_content_title)), false);
- if (relatedStreamsLayout != null) { // tablet
- relatedStreamsLayout.setVisibility(View.INVISIBLE);
+ if (binding.relatedStreamsLayout != null) { // tablet
+ binding.relatedStreamsLayout.setVisibility(View.INVISIBLE);
}
- viewPager.setVisibility(View.GONE);
- tabLayout.setVisibility(View.GONE);
+ binding.viewPager.setVisibility(View.GONE);
+ binding.tabLayout.setVisibility(View.GONE);
}
private void displayUploaderAsSubChannel(final StreamInfo info) {
- subChannelTextView.setText(info.getUploaderName());
- subChannelTextView.setVisibility(View.VISIBLE);
- subChannelTextView.setSelected(true);
- uploaderTextView.setVisibility(View.GONE);
+ binding.detailSubChannelTextView.setText(info.getUploaderName());
+ binding.detailSubChannelTextView.setVisibility(View.VISIBLE);
+ binding.detailSubChannelTextView.setSelected(true);
+ binding.detailUploadDateView.setVisibility(View.GONE);
}
private void displayBothUploaderAndSubChannel(final StreamInfo info) {
- subChannelTextView.setText(info.getSubChannelName());
- subChannelTextView.setVisibility(View.VISIBLE);
- subChannelTextView.setSelected(true);
+ binding.detailSubChannelTextView.setText(info.getSubChannelName());
+ binding.detailSubChannelTextView.setVisibility(View.VISIBLE);
+ binding.detailSubChannelTextView.setSelected(true);
- subChannelThumb.setVisibility(View.VISIBLE);
+ binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
if (!isEmpty(info.getUploaderName())) {
- uploaderTextView.setText(
+ binding.detailUploadDateView.setText(
String.format(getString(R.string.video_detail_by), info.getUploaderName()));
- uploaderTextView.setVisibility(View.VISIBLE);
- uploaderTextView.setSelected(true);
+ binding.detailUploadDateView.setVisibility(View.VISIBLE);
+ binding.detailUploadDateView.setSelected(true);
} else {
- uploaderTextView.setVisibility(View.GONE);
+ binding.detailUploadDateView.setVisibility(View.GONE);
}
}
-
public void openDownloadDialog() {
try {
final DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
@@ -1683,8 +1575,8 @@ public final class VideoDetailFragment
if (playQueue == null || playQueue.getStreams().isEmpty()
|| playQueue.getItem().getRecoveryPosition() == RECOVERY_UNSET
|| !showPlaybackPosition) {
- positionView.setVisibility(View.INVISIBLE);
- detailPositionView.setVisibility(View.GONE);
+ binding.positionView.setVisibility(View.INVISIBLE);
+ binding.detailPositionView.setVisibility(View.GONE);
// TODO: Remove this check when separation of concerns is done.
// (live streams weren't getting updated because they are mixed)
if (!info.getStreamType().equals(StreamType.LIVE_STREAM)
@@ -1695,8 +1587,8 @@ public final class VideoDetailFragment
// Show saved position from backStack if user allows it
showPlaybackProgress(playQueue.getItem().getRecoveryPosition(),
playQueue.getItem().getDuration() * 1000);
- animate(positionView, true, 500);
- animate(detailPositionView, true, 500);
+ animate(binding.positionView, true, 500);
+ animate(binding.detailPositionView, true, 500);
}
return;
}
@@ -1710,15 +1602,15 @@ public final class VideoDetailFragment
.observeOn(AndroidSchedulers.mainThread())
.subscribe(state -> {
showPlaybackProgress(state.getProgressTime(), info.getDuration() * 1000);
- animate(positionView, true, 500);
- animate(detailPositionView, true, 500);
+ animate(binding.positionView, true, 500);
+ animate(binding.detailPositionView, true, 500);
}, e -> {
if (DEBUG) {
e.printStackTrace();
}
}, () -> {
- positionView.setVisibility(View.GONE);
- detailPositionView.setVisibility(View.GONE);
+ binding.positionView.setVisibility(View.GONE);
+ binding.detailPositionView.setVisibility(View.GONE);
});
}
@@ -1727,20 +1619,21 @@ public final class VideoDetailFragment
final int durationSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(duration);
// If the old and the new progress values have a big difference then use
// animation. Otherwise don't because it affects CPU
- final boolean shouldAnimate = Math.abs(positionView.getProgress() - progressSeconds) > 2;
- positionView.setMax(durationSeconds);
+ final boolean shouldAnimate = Math.abs(binding.positionView.getProgress()
+ - progressSeconds) > 2;
+ binding.positionView.setMax(durationSeconds);
if (shouldAnimate) {
- positionView.setProgressAnimated(progressSeconds);
+ binding.positionView.setProgressAnimated(progressSeconds);
} else {
- positionView.setProgress(progressSeconds);
+ binding.positionView.setProgress(progressSeconds);
}
final String position = Localization.getDurationString(progressSeconds);
- if (position != detailPositionView.getText()) {
- detailPositionView.setText(position);
+ if (position != binding.detailPositionView.getText()) {
+ binding.detailPositionView.setText(position);
}
- if (positionView.getVisibility() != View.VISIBLE) {
- animate(positionView, true, 100);
- animate(detailPositionView, true, 100);
+ if (binding.positionView.getVisibility() != View.VISIBLE) {
+ animate(binding.positionView, true, 100);
+ animate(binding.detailPositionView, true, 100);
}
}
@@ -1790,12 +1683,12 @@ public final class VideoDetailFragment
switch (state) {
case Player.STATE_PLAYING:
- if (positionView.getAlpha() != 1.0f
+ if (binding.positionView.getAlpha() != 1.0f
&& player.getPlayQueue() != null
&& player.getPlayQueue().getItem() != null
&& player.getPlayQueue().getItem().getUrl().equals(url)) {
- animate(positionView, true, 100);
- animate(detailPositionView, true, 100);
+ animate(binding.positionView, true, 100);
+ animate(binding.detailPositionView, true, 100);
}
break;
}
@@ -1887,8 +1780,8 @@ public final class VideoDetailFragment
showSystemUi();
}
- if (relatedStreamsLayout != null) {
- relatedStreamsLayout.setVisibility(fullscreen ? View.GONE : View.VISIBLE);
+ if (binding.relatedStreamsLayout != null) {
+ binding.relatedStreamsLayout.setVisibility(fullscreen ? View.GONE : View.VISIBLE);
}
scrollToTop();
@@ -1926,14 +1819,14 @@ public final class VideoDetailFragment
@Override
public void onMoreOptionsLongClicked() {
final CoordinatorLayout.LayoutParams params =
- (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
+ (CoordinatorLayout.LayoutParams) binding.appBarLayout.getLayoutParams();
final AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
final ValueAnimator valueAnimator = ValueAnimator
- .ofInt(0, -playerPlaceholder.getHeight());
+ .ofInt(0, -binding.playerPlaceholder.getHeight());
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(animation -> {
behavior.setTopAndBottomOffset((int) animation.getAnimatedValue());
- appBarLayout.requestLayout();
+ binding.appBarLayout.requestLayout();
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(500);
@@ -2185,7 +2078,7 @@ public final class VideoDetailFragment
mainFragment.setDescendantFocusability(blockDescendants);
toolbar.setDescendantFocusability(blockDescendants);
((ViewGroup) requireView()).setDescendantFocusability(afterDescendants);
- thumbnailBackgroundButton.requestFocus();
+ binding.detailThumbnailRootLayout.requestFocus();
}
}
@@ -2215,7 +2108,7 @@ public final class VideoDetailFragment
private void setupBottomPlayer() {
final CoordinatorLayout.LayoutParams params =
- (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
+ (CoordinatorLayout.LayoutParams) binding.appBarLayout.getLayoutParams();
final AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
final FrameLayout bottomSheetLayout = activity.findViewById(R.id.fragment_player_holder);
@@ -2226,9 +2119,9 @@ public final class VideoDetailFragment
manageSpaceAtTheBottom(false);
bottomSheetBehavior.setPeekHeight(peekHeight);
if (bottomSheetState == BottomSheetBehavior.STATE_COLLAPSED) {
- overlay.setAlpha(MAX_OVERLAY_ALPHA);
+ binding.overlayLayout.setAlpha(MAX_OVERLAY_ALPHA);
} else if (bottomSheetState == BottomSheetBehavior.STATE_EXPANDED) {
- overlay.setAlpha(0);
+ binding.overlayLayout.setAlpha(0);
setOverlayElementsClickable(false);
}
}
@@ -2264,7 +2157,7 @@ public final class VideoDetailFragment
&& player.videoPlayerSelected()) {
player.toggleFullscreen();
}
- setOverlayLook(appBarLayout, behavior, 1);
+ setOverlayLook(binding.appBarLayout, behavior, 1);
break;
case BottomSheetBehavior.STATE_COLLAPSED:
moveFocusToMainFragment(true);
@@ -2277,7 +2170,7 @@ public final class VideoDetailFragment
if (player != null) {
player.closeItemsList();
}
- setOverlayLook(appBarLayout, behavior, 0);
+ setOverlayLook(binding.appBarLayout, behavior, 0);
break;
case BottomSheetBehavior.STATE_DRAGGING:
case BottomSheetBehavior.STATE_SETTLING:
@@ -2293,7 +2186,7 @@ public final class VideoDetailFragment
@Override
public void onSlide(@NonNull final View bottomSheet, final float slideOffset) {
- setOverlayLook(appBarLayout, behavior, slideOffset);
+ setOverlayLook(binding.appBarLayout, behavior, slideOffset);
}
});
@@ -2308,11 +2201,11 @@ public final class VideoDetailFragment
private void updateOverlayData(@Nullable final String overlayTitle,
@Nullable final String uploader,
@Nullable final String thumbnailUrl) {
- overlayTitleTextView.setText(isEmpty(title) ? "" : title);
- overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
- overlayThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
+ binding.overlayTitleTextView.setText(isEmpty(title) ? "" : title);
+ binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
+ binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark);
if (!isEmpty(thumbnailUrl)) {
- IMAGE_LOADER.displayImage(thumbnailUrl, overlayThumbnailImageView,
+ IMAGE_LOADER.displayImage(thumbnailUrl, binding.overlayThumbnail,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, null);
}
}
@@ -2321,7 +2214,7 @@ public final class VideoDetailFragment
final int attr = playerIsPlaying
? R.attr.ic_pause
: R.attr.ic_play_arrow;
- overlayPlayPauseButton.setImageResource(
+ binding.overlayPlayPauseButton.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(activity, attr));
}
@@ -2333,20 +2226,20 @@ public final class VideoDetailFragment
if (behavior == null || slideOffset < 0) {
return;
}
- overlay.setAlpha(Math.min(MAX_OVERLAY_ALPHA, 1 - slideOffset));
+ binding.overlayLayout.setAlpha(Math.min(MAX_OVERLAY_ALPHA, 1 - slideOffset));
// These numbers are not special. They just do a cool transition
behavior.setTopAndBottomOffset(
- (int) (-thumbnailImageView.getHeight() * 2 * (1 - slideOffset) / 3));
+ (int) (-binding.detailThumbnailImageView.getHeight() * 2 * (1 - slideOffset) / 3));
appBar.requestLayout();
}
private void setOverlayElementsClickable(final boolean enable) {
- overlayThumbnailImageView.setClickable(enable);
- overlayThumbnailImageView.setLongClickable(enable);
- overlayMetadata.setClickable(enable);
- overlayMetadata.setLongClickable(enable);
- overlayButtons.setClickable(enable);
- overlayPlayPauseButton.setClickable(enable);
- overlayCloseButton.setClickable(enable);
+ binding.overlayThumbnail.setClickable(enable);
+ binding.overlayThumbnail.setLongClickable(enable);
+ binding.overlayMetadataLayout.setClickable(enable);
+ binding.overlayMetadataLayout.setLongClickable(enable);
+ binding.overlayButtonsLayout.setClickable(enable);
+ binding.overlayPlayPauseButton.setClickable(enable);
+ binding.overlayCloseButton.setClickable(enable);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
index 3540e6938..d4d4e7db1 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
@@ -46,6 +46,6 @@ data class PickerSubscriptionItem(
fun updateSelected(containerView: View, isSelected: Boolean) {
this.isSelected = isSelected
PickerSubscriptionItemBinding.bind(containerView).selectedHighlight
- .animate(isSelected, 150, AnimationType.LIGHT_SCALE_AND_ALPHA)
+ .animate(isSelected, 150, AnimationType.LIGHT_SCALE_AND_ALPHA)
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java
index 00ce3e616..830d1c55c 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java
+++ b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java
@@ -27,7 +27,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior
private boolean skippingInterception = false;
private final List skipInterceptionOfElements = Arrays.asList(
R.id.detail_content_root_layout, R.id.relatedStreamsLayout,
- R.id.itemsListPanel, R.id.viewpager, R.id.bottomControls,
+ R.id.itemsListPanel, R.id.view_pager, R.id.bottomControls,
R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton);
@Override
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 d90c782ef..73af54d63 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
@@ -23,7 +23,7 @@
android:isScrollContainer="true">
Date: Sun, 17 Jan 2021 07:50:05 +0300
Subject: [PATCH 087/131] Update README.so.md
---
README.so.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.so.md b/README.so.md
index 21b20a9e4..dc9ec1547 100644
--- a/README.so.md
+++ b/README.so.md
@@ -12,13 +12,13 @@
- * If no app is set as default, it will return "android".
+ * If no app is set as default, it will return "android" (not on some devices because some
+ * OEMs changed the app chooser).
*
- * Note: it doesn't return "android" on some devices because some OEMs changed the app chooser.
+ * If no app is installed on user's device to handle the intent, it will return an empty string.
*
* @param context the context to use
* @param intent the intent to get default app
- * @return the package name of the default app, or the app chooser if there's no default
+ * @return the package name of the default app to open the intent, an empty string if there's no
+ * app installed to handle it or the app chooser if there's no default
*/
private static String getDefaultAppPackageName(final Context context, final Intent intent) {
- return context.getPackageManager().resolveActivity(intent,
- PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+ final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (resolveInfo == null) {
+ return "";
+ } else {
+ return resolveInfo.activityInfo.packageName;
+ }
}
/**
* Get the default browser package name.
*
- * If no browser is set as default, it will return "android"
- * Note: it doesn't return "android" on some devices because some OEMs changed the app chooser.
- *
+ * If no browser is set as default, it will return "android" (not on some devices because some
+ * OEMs changed the app chooser).
+ *
+ * If no browser is installed on user's device, it will return an empty string.
* @param context the context to use
- * @return the package name of the default browser, or "android" if there's no default
+ * @return the package name of the default browser, an empty string if there's no browser
+ * installed or the app chooser if there's no default
*/
private static String getDefaultBrowserPackageName(final Context context) {
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return context.getPackageManager().resolveActivity(intent,
- PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+ final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (resolveInfo == null) {
+ return "";
+ } else {
+ return resolveInfo.activityInfo.packageName;
+ }
}
/**
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 521228e7c..d8c53ce78 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -658,4 +658,5 @@
Notification de hachage vidéoDésactivez cette option pour masquer les zones de méta-informations contenant des informations supplémentaires sur le créateur du flux, le contenu du flux ou une demande de recherche.Afficher les méta-infos
-
\ No newline at end of file
+ Aucune application sur votre appareil ne peut ouvrir ceci
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 702dcafbf..ba3093618 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -699,4 +699,5 @@
Use thumbnail for both lock screen background and notificationsRecentChapters
-
\ No newline at end of file
+ No app on your device can open this
+
From e327f7ba2c576525bd5f72519021a700f73bd81a Mon Sep 17 00:00:00 2001
From: Stypox
Date: Tue, 19 Jan 2021 09:33:47 +0100
Subject: [PATCH 106/131] Fix popup closing x button animation
---
.../main/java/org/schabi/newpipe/player/Player.java | 2 +-
.../newpipe/player/event/PlayerGestureListener.java | 13 +++++--------
2 files changed, 6 insertions(+), 9 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 13a0a7d02..9d239e781 100644
--- a/app/src/main/java/org/schabi/newpipe/player/Player.java
+++ b/app/src/main/java/org/schabi/newpipe/player/Player.java
@@ -4003,7 +4003,7 @@ public final class Player implements
}
public View getClosingOverlayView() {
- return closeOverlayBinding.getRoot();
+ return binding.closingOverlay;
}
public ProgressBar getVolumeProgressBar() {
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
index ecc57ff2f..c639bde9f 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
+++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
@@ -124,11 +124,11 @@ public class PlayerGestureListener
final View closingOverlayView = player.getClosingOverlayView();
if (player.isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
- animate(closingOverlayView, true, 250);
+ animate(closingOverlayView, true, 200);
}
} else {
if (closingOverlayView.getVisibility() == View.VISIBLE) {
- animate(closingOverlayView, false, 0);
+ animate(closingOverlayView, false, 200);
}
}
}
@@ -234,12 +234,9 @@ public class PlayerGestureListener
if (player.isInsideClosingRadius(event)) {
player.closePopup();
- } else {
- animate(player.getClosingOverlayView(), false, 0);
-
- if (!player.isPopupClosing()) {
- animate(player.getCloseOverlayButton(), false, 200);
- }
+ } else if (!player.isPopupClosing()) {
+ animate(player.getCloseOverlayButton(), false, 200);
+ animate(player.getClosingOverlayView(), false, 200);
}
}
}
From 10ec67854ec33a48ef425b9b9d354c4471f31d11 Mon Sep 17 00:00:00 2001
From: Stypox
Date: Wed, 20 Jan 2021 10:44:44 +0100
Subject: [PATCH 107/131] Fix number being shown instead of corresponding
string resorce in feed
---
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 5a39ce93e..04090abc6 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
@@ -236,7 +236,7 @@ class FeedFragment : BaseListFragment() {
feedBinding.loadingProgressText.text = if (!isIndeterminate) {
"${progressState.currentProgress}/${progressState.maxProgress}"
} else if (progressState.progressMessage > 0) {
- progressState.progressMessage.toString()
+ getString(progressState.progressMessage)
} else {
"∞/∞"
}
From 0c5df29417ec9222f87b7085f372b9b0037cf23e Mon Sep 17 00:00:00 2001
From: * <66680085+ryota-hasegawa@users.noreply.github.com>
Date: Thu, 21 Jan 2021 16:12:09 +0900
Subject: [PATCH 108/131] Add Japanese translation of README
---
README.ja.md | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++
README.ko.md | 2 +-
README.md | 2 +-
README.so.md | 2 +-
4 files changed, 152 insertions(+), 3 deletions(-)
create mode 100644 README.ja.md
diff --git a/README.ja.md b/README.ja.md
new file mode 100644
index 000000000..7041c936e
--- /dev/null
+++ b/README.ja.md
@@ -0,0 +1,149 @@
+
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md).*
경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.
diff --git a/README.md b/README.md
index a7b1e24af..e7b285b2d 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md).*
WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.
diff --git a/README.so.md b/README.so.md
index e01146acd..bb9fc1258 100644
--- a/README.so.md
+++ b/README.so.md
@@ -16,7 +16,7 @@
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.
diff --git a/README.md b/README.md
index e7b285b2d..80c7e2e88 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md),[Română](README.ro.md) .*
WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.
diff --git a/README.pt_BR.md b/README.pt_BR.md
index d1087cb04..dedb64a7c 100644
--- a/README.pt_BR.md
+++ b/README.pt_BR.md
@@ -16,7 +16,7 @@
-*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md).*
+*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
AVISO: ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.
diff --git a/README.ro.md b/README.ro.md
new file mode 100644
index 000000000..75e3bd5b0
--- /dev/null
+++ b/README.ro.md
@@ -0,0 +1,144 @@
+
+
NewPipe
+
Un front-end de streaming „uşor” liber, pentru Android.
+
+
+*Citiţi în alte limbi: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md)*
+
+Atenţionare: ACEASTA ESTE O VERSIUNE BETA, AŞA CĂ S-AR PUTE SĂ ÎNTÂLNIŢI ERORI. DACĂ SE ÎNTÂMPLĂ ACEST LUCRU, DESCHIDEŢI UN ISSUE PRIN REPSITORY-UL NOSTRU GITHUB.
+
+PUNERA NEWPIPE SAU ORICĂRUI FORK AL ACESTUIA ÎN MAGAZINUL GOOGLE PLAY LE ÎNCALCĂ TERMENII ŞI CONDIŢIILE.
+
+## Capturi de ecran
+
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
+[](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
+[](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
+
+## Descriere
+
+NewPipe nu foloseşte nici-o bibliotecă Google framework sau API-ul Youtube. Website-urile sunt doar analizate pentru a prelua informaţia necesară, aşa că această aplicaţie poate fi folosită pe telefoane fără Serviciile Google instalate. De asemenea, nu aveţi nevoie de un cont Youtube pentru a folosi Newpipe, care este sofware liber şi copylefted.
+
+### Funcţii
+
+* Căutarea videoclipurilor
+* Afişarea informaţiilor generale despre videoclipuri
+* Urmărirea videoclipurilor Youtube
+* Ascultarea videoclipurilor Youtube
+* Modul popup (player plutitor)
+* Selectarea playerului de streaming pentru vizionarea videoclipului
+* Descărcarea videoclipurilor
+* Doar descărcarea sunetului
+* Deschiderea videoclipurilor cu Kodi
+* Expunerea videoclipurilor următoare/asociate
+* Căutarea YouTube într-o limbă specifică
+* Vizionarea/Blocarea materialului restricţionat în funcţie de vârstă
+* Afişarea informaţiilor generale despre canale
+* Căutarea canalelor
+* Vizionarea videoclipurilor dintr-un canal
+* Suport Orbot/Tor (încă nu direct)
+* Suport 1080p/2K/4K
+* Vizionarea istoricului
+* Abonarea la canale
+* Căutarea în istoric
+* Căutarea/vizionarea playlisturilor
+* Vizionarea ca playlisturi puse în coadă
+* Punerea în coadă a videoclipurilor
+* Playlisturi locale
+* Subtitrări
+* Suport al transmiterilor live
+* Afişarea comentariilor
+
+### Servicii întreţinute
+
+NewPipe suportă servicii multiple. [Documentele](https://teamnewpipe.github.io/documentation/) noastre furnizează mai multe informaţii în legătură cu modalităţile prin care un nou serviciu poate fi adăugat aplicaţiei şi extractorului. Vă rugăm să ne contactaţi dacă doriţi să adăugaţi unul nou. Serviciile întreţinute acum sunt:
+
+* YouTube
+* SoundCloud \[beta\]
+* media.ccc.de \[beta\]
+* Instanţe PeerTube \[beta\]
+
+
+
+
+## Instalare şi actualizări
+Puteţi instala NewPipe folosind una dintre următoarele metode:
+ 1. Adăugaţi depozitul nostru F-droid personalizat. Instrucţiunile sunt aici: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
+ 2. Descărcaţi APK-ul din [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) şi instalaţi-l.
+ 3. Actualizaţi via F-Droid. Aceasta este cea mai lentă metodă de a obţine actualizări, deoarece F-Droid trebuie să recunoască schimbările, să constriască APK-ul, să îl semneze, iar apoi să îl trimită utilizatorilor. (**IMPORTANT**: în momentul scrierii, o problemă împiedică versiunile mai noi de 0.20.1 să fie publicate. Aşa că, dacă doriţi să folosiţi F-droid, până această problemă este rezolvată, vă recomandăm metoda 1.)
+ 4. Construiţi un APK de depanare. Aceasta este cea mai rapidă metodă de a primi funcţii noi, dar este mult mai complicată, aşa că vă recomandăm să folosiţi una dintre celelalte metode.
+
+Recomandăm metoda 1 pentru majoritatea utilizatorilor. APK-urile din metodele 1 şi 2 suntcompatibile una cu cealaltă, dar nu cu cele din metoda 3. Acest lucru se datorează faptului că aceeași cheie de semnare (a noastră) este utilizată pentru 1 și 2, dar o altă cheie de semnare (F-Droid) este utilizată pentru 3. Construirea unui APK de depanare folosind metoda 4 exclude o cheie în întregime. Cheile de semnare vă asigură că un utilizator nu este păcălit să instaleze o actualizare rău intenționată a unei aplicații.
+
+Între timp, dacă doriți să schimbați sursa dintr-un anumit motiv (de exemplu, funcționalitatea de bază a NewPipe a fost întreruptă și F-Droid nu are încă actualizarea), vă recomandăm să folosiţi următoarea procedură:
+1. Faceți o copie de rezervă a datelor prin Setări> Conținut> Exportați baza de date, astfel încât să vă păstrați istoricul, abonamentele și playlisturile
+2. Dezinstalaţi NewPipe
+3. Descărcaţi APK-ul din noua sursă şi instalaţi-l
+4. Importați datele de la pasul 1 prin Setări> Conținut> Importare bază de date
+
+## Contribuţie
+Dacă aveţi idei, traduceri, schimbări de design, curaţarea codului, sau schimbări majore ale codului, ajutorul este întotdeauna binevenit.
+Cu cât se face mai mult cu atât mai bună devine aplicaţia!
+
+Dacă doriţi să vă implicaţi, accesaţi [notele noastre de contribuţie](.github/CONTRIBUTING.md).
+
+
+
+
+
+## Donaţii
+Dacă vă place NewPipe, am fi bucuroşi să primim o donaţie. Puteţi să ne trimiteţi bitcoin sau să ne donaţi cu Bountysource sau Liberapay. Pentru mai multe informaţii în legătură cu donaţiile către NewPipe, vă rugăm vizitaţi [website-ul nostru](https://newpipe.net/donate).
+
+
+
+
+
+
16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Politica de Confidenţialitate
+
+Proiectul NewPipe îşi propune să furnizeze o experienţă privată şi anonimă pentru utilizarea serviciilor web media.
+Prin urmare, aplicaţia nu colectează niciun fel de informaţii fără acordul dumneavoastră. Politica de confidențialitate a NewPipe explică în detaliu ce date sunt trimise și stocate atunci când trimiteți un raport de blocare sau comentați pe blogul nostru. Puteți găsi documentul [aici](https://newpipe.net/legal/privacy/).
+
+## Licenţă
+[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
+
+NewPipe este Software Gratuit: Îl puteţi folosi şi împărtăşi cum doriţi. Mai exact, îl puteți redistribui și / sau modifica în conformitate cu termenii
+[GNU General Public License](https://www.gnu.org/licenses/gpl.html) aşa cum a fost publicat de Free Software Foundation, fie versiunea 3 a Licenței, fie
+(la alegerea dvs.) orice versiune ulterioară.
diff --git a/README.so.md b/README.so.md
index bb9fc1258..f8bc51e59 100644
--- a/README.so.md
+++ b/README.so.md
@@ -16,7 +16,7 @@
-*Ku akhri luuqad kale: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md).*
+*Ku akhri luuqad kale: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
DIGNIIN: MIDKAN, NOOCA APP-KA EE HADDA WALI TIJAABO AYUU KU JIRAA, SIDAA DARTEED CILLADO AYAAD LA KULMI KARTAA. HADAAD LA KULANTO, KA FUR ARIN SHARAXAYA QAYBTANADA ARRIMAHA EE GITHUB-KA.
From e98838ad7eda4908e79b38087750ba6f11993f70 Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 30 Jan 2021 09:32:17 +0100
Subject: [PATCH 120/131] Invert usage of manager.isExpired()
When it's expired it means, that the app should get the data. Meaning it should not abort prematurely by returning null.
Co-authored-by: Tobias Groza
---
app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
index 630fb01ff..8bea85b19 100644
--- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
+++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
@@ -180,7 +180,7 @@ public final class CheckForNewAppVersion {
}
final long expiry = prefs.getLong(app.getString(R.string.update_expiry_key), 0);
- if (manager.isExpired(expiry)) {
+ if (!manager.isExpired(expiry)) {
return null;
}
From 522d6d8b01aa67ea95a99b2b8a7e86e9558df94d Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 30 Jan 2021 11:18:58 +0100
Subject: [PATCH 121/131] Re-add APK testing section to PR template (#5465)
Because normal users don't know that the CI process produces an APK and where it is.
---
.github/PULL_REQUEST_TEMPLATE.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 5e5cfd08d..2787e2238 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -20,5 +20,10 @@
-
+#### APK testing
+
+
+On the website the APK can be found by going to the "Checks" tab below the title and then on "artifacts" on the right.
+
#### Due diligence
- [ ] I read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md).
From bdc85b435ca3d8937eeb366e5a0b875c29a415da Mon Sep 17 00:00:00 2001
From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com>
Date: Sat, 30 Jan 2021 14:24:25 +0100
Subject: [PATCH 122/131] Add comments explaining the expiry field
Co-authored-by: Tobias Groza
---
.../main/java/org/schabi/newpipe/CheckForNewAppVersion.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
index 8bea85b19..63baef547 100644
--- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
+++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
@@ -179,6 +179,8 @@ public final class CheckForNewAppVersion {
return null;
}
+ // Check if the last request has happened a certain time ago
+ // to reduce the number of API requests.
final long expiry = prefs.getLong(app.getString(R.string.update_expiry_key), 0);
if (!manager.isExpired(expiry)) {
return null;
@@ -198,6 +200,8 @@ public final class CheckForNewAppVersion {
.subscribe(
response -> {
try {
+ // Store a timestamp which needs to be exceeded,
+ // before a new request to the API is made.
final long newExpiry = manager
.coerceExpiry(response.getHeader("expires"));
prefs.edit()
From c55f87c9623a3f71f1bf60703cb9a554e64f16ac Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Sat, 16 Jan 2021 17:49:01 +0100
Subject: [PATCH 123/131] Fix some things in ShareUtils.java and do little
improvements Fix a bug in which NewPipe doesn't fall back to Google Play
Store web url in InstallApp Fusion getDefaultBrowserPackageName and
getDefaultAppPackageName, rename openInDefaultApp to openAppChooser Update
some JavaDocs
---
.../org/schabi/newpipe/util/ShareUtils.java | 121 +++++++++---------
1 file changed, 57 insertions(+), 64 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
index 32157e43e..45ec1d015 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
@@ -21,21 +21,26 @@ public final class ShareUtils {
/**
* Open an Intent to install an app.
*
- * This method will first try open to Google Play Store with the market scheme and falls back to
- * Google Play Store web url if this first cannot be found.
+ * This method tries to open the default app market with the package id passed as the
+ * second param (a system chooser will be opened if there are multiple markets and no default)
+ * and falls back to Google Play Store web URL if no app to handle the market scheme was found.
+ *
+ * It uses {@link ShareUtils#openIntentInApp(Context, Intent)} to open market scheme and
+ * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} to open Google Play Store web
+ * URL with false for the boolean param.
*
- * @param context the context to use
- * @param packageName the package to be installed
+ * @param context the context to use
+ * @param packageId the package id of the app to be installed
*/
- public static void installApp(final Context context, final String packageName) {
- try {
- // Try market:// scheme
- openIntentInApp(context, new Intent(Intent.ACTION_VIEW,
- Uri.parse("market://details?id=" + packageName)));
- } catch (final ActivityNotFoundException e) {
- // Fall back to Google Play Store Web URL (don't worry, F-Droid can handle it :))
+ public static void installApp(final Context context, final String packageId) {
+ // Try market:// scheme
+ final boolean marketSchemeResult = openIntentInApp(context, new Intent(Intent.ACTION_VIEW,
+ Uri.parse("market://details?id=" + packageId))
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ if (!marketSchemeResult) {
+ // Fall back to Google Play Store Web URL (F-Droid can handle it)
openUrlInBrowser(context,
- "https://play.google.com/store/apps/details?id=" + packageName, false);
+ "https://play.google.com/store/apps/details?id=" + packageId, false);
}
}
@@ -43,31 +48,35 @@ public final class ShareUtils {
* Open the url with the system default browser.
*
* If no browser is set as default, fallbacks to
- * {@link ShareUtils#openInDefaultApp(Context, Intent)}
+ * {@link ShareUtils#openAppChooser(Context, Intent, String)}
*
* @param context the context to use
* @param url the url to browse
* @param httpDefaultBrowserTest the boolean to set if the test for the default browser will be
* for HTTP protocol or for the created intent
+ * @return true if the URL can be opened or false if it cannot
*/
- public static void openUrlInBrowser(final Context context, final String url,
- final boolean httpDefaultBrowserTest) {
+ public static boolean openUrlInBrowser(final Context context, final String url,
+ final boolean httpDefaultBrowserTest) {
final String defaultPackageName;
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
if (httpDefaultBrowserTest) {
- defaultPackageName = getDefaultBrowserPackageName(context);
+ defaultPackageName = getDefaultAppPackageName(context, new Intent(Intent.ACTION_VIEW,
+ Uri.parse("http://")).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} else {
defaultPackageName = getDefaultAppPackageName(context, intent);
}
if (defaultPackageName.equals("android")) {
// No browser set as default (doesn't work on some devices)
- openInDefaultApp(context, intent);
+ openAppChooser(context, intent, context.getString(R.string.open_with));
} else {
if (defaultPackageName.isEmpty()) {
// No app installed to open a web url
Toast.makeText(context, R.string.no_app_to_open_intent, Toast.LENGTH_LONG).show();
+ return false;
} else {
try {
intent.setPackage(defaultPackageName);
@@ -75,26 +84,29 @@ public final class ShareUtils {
} catch (final ActivityNotFoundException e) {
// Not a browser but an app chooser because of OEMs changes
intent.setPackage(null);
- openInDefaultApp(context, intent);
+ openAppChooser(context, intent, context.getString(R.string.open_with));
}
}
}
+
+ return true;
}
/**
* Open the url with the system default browser.
*
* If no browser is set as default, fallbacks to
- * {@link ShareUtils#openInDefaultApp(Context, Intent)}
+ * {@link ShareUtils#openAppChooser(Context, Intent, String)}
*
* This calls {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} with true
* for the boolean parameter
*
* @param context the context to use
* @param url the url to browse
+ * @return true if the URL can be opened or false if it cannot be
**/
- public static void openUrlInBrowser(final Context context, final String url) {
- openUrlInBrowser(context, url, true);
+ public static boolean openUrlInBrowser(final Context context, final String url) {
+ return openUrlInBrowser(context, url, true);
}
/**
@@ -104,21 +116,23 @@ public final class ShareUtils {
* {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} should be used.
*
* If no app is set as default, fallbacks to
- * {@link ShareUtils#openInDefaultApp(Context, Intent)}
+ * {@link ShareUtils#openAppChooser(Context, Intent, String)}
*
* @param context the context to use
* @param intent the intent to open
+ * @return true if the intent can be opened or false if it cannot be
*/
- public static void openIntentInApp(final Context context, final Intent intent) {
+ public static boolean openIntentInApp(final Context context, final Intent intent) {
final String defaultPackageName = getDefaultAppPackageName(context, intent);
if (defaultPackageName.equals("android")) {
// No app set as default (doesn't work on some devices)
- openInDefaultApp(context, intent);
+ openAppChooser(context, intent, context.getString(R.string.open_with));
} else {
if (defaultPackageName.isEmpty()) {
// No app installed to open the intent
Toast.makeText(context, R.string.no_app_to_open_intent, Toast.LENGTH_LONG).show();
+ return false;
} else {
try {
intent.setPackage(defaultPackageName);
@@ -126,26 +140,31 @@ public final class ShareUtils {
} catch (final ActivityNotFoundException e) {
// Not an app to open the intent but an app chooser because of OEMs changes
intent.setPackage(null);
- openInDefaultApp(context, intent);
+ openAppChooser(context, intent, context.getString(R.string.open_with));
}
}
}
+
+ return true;
}
/**
- * Open the url in the default app set to open this type of link.
+ * Open the system chooser to launch an intent.
*
- * If no app is set as default, it will open a chooser
+ * This method opens an {@link android.content.Intent#ACTION_CHOOSER} of the intent putted
+ * as the viewIntent param. A string for the chooser's title must be passed as the last param.
*
- * @param context the context to use
- * @param viewIntent the intent to open
+ * @param context the context to use
+ * @param intent the intent to open
+ * @param chooserStringTitle the string of chooser's title
*/
- private static void openInDefaultApp(final Context context, final Intent viewIntent) {
- final Intent intent = new Intent(Intent.ACTION_CHOOSER);
- intent.putExtra(Intent.EXTRA_INTENT, viewIntent);
- intent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.open_with));
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
+ private static void openAppChooser(final Context context, final Intent intent,
+ final String chooserStringTitle) {
+ final Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
+ chooserIntent.putExtra(Intent.EXTRA_INTENT, intent);
+ chooserIntent.putExtra(Intent.EXTRA_TITLE, chooserStringTitle);
+ chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(chooserIntent);
}
/**
@@ -158,35 +177,13 @@ public final class ShareUtils {
*
* @param context the context to use
* @param intent the intent to get default app
- * @return the package name of the default app to open the intent, an empty string if there's no
- * app installed to handle it or the app chooser if there's no default
+ * @return the package name of the default app, an empty string if there's no app installed to
+ * handle the intent or the app chooser if there's no default
*/
private static String getDefaultAppPackageName(final Context context, final Intent intent) {
final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
PackageManager.MATCH_DEFAULT_ONLY);
- if (resolveInfo == null) {
- return "";
- } else {
- return resolveInfo.activityInfo.packageName;
- }
- }
- /**
- * Get the default browser package name.
- *
- * If no browser is set as default, it will return "android" (not on some devices because some
- * OEMs changed the app chooser).
- *