mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-10-02 15:00:51 +02:00
Compare commits
95 Commits
player-cla
...
v0.28.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
95a0e0ca39 | ||
![]() |
4d97a7653d | ||
![]() |
5aefa4aff2 | ||
![]() |
b846746119 | ||
![]() |
b7b836e941 | ||
![]() |
d96c0aebb1 | ||
![]() |
8400a9ae8e | ||
![]() |
7cecd11f72 | ||
![]() |
ed93603815 | ||
![]() |
86efde5996 | ||
![]() |
ca9fc14c2a | ||
![]() |
7130adb4ec | ||
![]() |
e08d2d8726 | ||
![]() |
6516fb96fd | ||
![]() |
e9922fe162 | ||
![]() |
eea2b7417e | ||
![]() |
893a1cb699 | ||
![]() |
ebd5e1a318 | ||
![]() |
70841db92f | ||
![]() |
859555e129 | ||
![]() |
c1cef19b33 | ||
![]() |
9ba30887f9 | ||
![]() |
0ef38e3a4d | ||
![]() |
a9ce2e9605 | ||
![]() |
71fcc5ebce | ||
![]() |
30e33d59e8 | ||
![]() |
a4bd82be8a | ||
![]() |
45589dbf26 | ||
![]() |
99ae3fdd4e | ||
![]() |
f48e73eb2a | ||
![]() |
99003bab07 | ||
![]() |
9e14f93186 | ||
![]() |
abd9aade87 | ||
![]() |
b8f9c125cd | ||
![]() |
893a227ab1 | ||
![]() |
0db859e225 | ||
![]() |
e61f98bd47 | ||
![]() |
991d9ea3df | ||
![]() |
f94892166d | ||
![]() |
9697112db6 | ||
![]() |
f64dba0107 | ||
![]() |
9bf01e1241 | ||
![]() |
474efbebc1 | ||
![]() |
fe58ec85ed | ||
![]() |
941f85781b | ||
![]() |
7e0ee4eb7a | ||
![]() |
4a41214df4 | ||
![]() |
938265d127 | ||
![]() |
ba4e7a3c7f | ||
![]() |
58b5ccb66f | ||
![]() |
4e94b2602d | ||
![]() |
4ddc0648ef | ||
![]() |
4c920a4406 | ||
![]() |
1c0eabf75c | ||
![]() |
f119a368d8 | ||
![]() |
f3c20d43be | ||
![]() |
c9559fa801 | ||
![]() |
8ab79488e9 | ||
![]() |
f0b26e208b | ||
![]() |
79084568f2 | ||
![]() |
705b5e5580 | ||
![]() |
a4d457b2b2 | ||
![]() |
834c93f22a | ||
![]() |
a0adeb0099 | ||
![]() |
2dd11f70a3 | ||
![]() |
d048bca8b4 | ||
![]() |
0c9f5ddcaf | ||
![]() |
aa75a1449f | ||
![]() |
16e32dfc96 | ||
![]() |
8c4a789f78 | ||
![]() |
769e98acd0 | ||
![]() |
8e036b5e69 | ||
![]() |
571b7bc74b | ||
![]() |
033cc08c26 | ||
![]() |
205d18f4c4 | ||
![]() |
712724211c | ||
![]() |
fd09e6147f | ||
![]() |
279caac915 | ||
![]() |
f8ed8e575e | ||
![]() |
436626fa83 | ||
![]() |
7c3989ff93 | ||
![]() |
e6c4690e7d | ||
![]() |
86869f0a14 | ||
![]() |
aa0b45c05f | ||
![]() |
55bf74b4a7 | ||
![]() |
de3d11568d | ||
![]() |
16077dee80 | ||
![]() |
c9155f7834 | ||
![]() |
7dd1abdf9c | ||
![]() |
f3858e70a3 | ||
![]() |
76202e6b4b | ||
![]() |
90e2f234e7 | ||
![]() |
42a52b7118 | ||
![]() |
e554c77f2e | ||
![]() |
d2dc20c551 |
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -111,6 +111,7 @@ jobs:
|
|||||||
path: app/build/reports/androidTests/connected/**
|
path: app/build/reports/androidTests/connected/**
|
||||||
|
|
||||||
sonar:
|
sonar:
|
||||||
|
if: ${{ false }} # the key has expired and needs to be regenerated by the sonar admins
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
16
README.md
16
README.md
@@ -1,20 +1,26 @@
|
|||||||
<h3 align="center">We are planning to <i>rewrite</i> large chunks of the codebase, to bring about <a href="https://github.com/TeamNewPipe/NewPipe/discussions/10118">a new, modern and stable NewPipe</a>!</h3>
|
<h3 align="center">We are <i>rewriting</i> large chunks of the codebase, to bring about <a href="https://newpipe.net/blog/pinned/announcement/newpipe-0.27.6-rewrite-team-states/#the-refactor">a modern and stable NewPipe</a>! You can download nightly builds <a href="https://github.com/TeamNewPipe/NewPipe-refactor-nightly/releases">here</a>.</h3>
|
||||||
<h4 align="center">Please do <b>not</b> open pull requests for <i>new features</i> now, only bugfix PRs will be accepted.</h4>
|
<h4 align="center">Please work on the <code>refactor</code> branch if you want to contribute <i>new features</i>. The current codebase is in maintenance mode and will only receive <i>bugfixes</i>.</h4>
|
||||||
|
|
||||||
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
||||||
<h2 align="center"><b>NewPipe</b></h2>
|
<h2 align="center"><b>NewPipe</b></h2>
|
||||||
<h4 align="center">A libre lightweight streaming front-end for Android.</h4>
|
<h4 align="center">A libre lightweight streaming front-end for Android.</h4>
|
||||||
|
|
||||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-en.svg" alt="Get it on F-Droid" height=80/></a></p>
|
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-en.svg" alt="Get it on F-Droid" width=206/></a></p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub NewPipe releases"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
||||||
|
<a href="https://github.com/TeamNewPipe/NewPipe-nightly/releases" alt="GitHub NewPipe nightly releases"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe-nightly.svg?labelColor=purple&label=dev%20nightly"></a>
|
||||||
|
<a href="https://github.com/TeamNewPipe/NewPipe-refactor-nightly/releases" alt="GitHub NewPipe refactor nightly releases"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe-refactor-nightly.svg?labelColor=purple&label=refactor%20nightly"></a>
|
||||||
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
|
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
|
||||||
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
|
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/actions/workflows/ci.yml/badge.svg?branch=dev&event=push"></a>
|
||||||
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
|
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
<a href="https://web.libera.chat/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
<a href="https://web.libera.chat/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||||
<a href="https://matrix.to/#/#newpipe:matrix.newpipe-ev.de" alt="Matrix channel: #newpipe"><img src="https://img.shields.io/badge/Matrix%20chat-%23newpipe-blue"></a>
|
<a href="https://matrix.to/#/#newpipe:matrix.newpipe-ev.de" alt="Matrix channel: #newpipe"><img src="https://img.shields.io/badge/Matrix%20chat-%23newpipe-blue"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#supported-services">Supported Services</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#installation-and-updates">Installation and updates</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#supported-services">Supported Services</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#installation-and-updates">Installation and updates</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
||||||
<p align="center"><a href="https://newpipe.net">Website</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Press</a></p>
|
<p align="center"><a href="https://newpipe.net">Website</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Press</a></p>
|
||||||
|
@@ -23,9 +23,9 @@ android {
|
|||||||
if (System.properties.containsKey('versionCodeOverride')) {
|
if (System.properties.containsKey('versionCodeOverride')) {
|
||||||
versionCode System.getProperty('versionCodeOverride') as Integer
|
versionCode System.getProperty('versionCodeOverride') as Integer
|
||||||
} else {
|
} else {
|
||||||
versionCode 1004
|
versionCode 1005
|
||||||
}
|
}
|
||||||
versionName "0.27.7"
|
versionName "0.28.0"
|
||||||
if (System.properties.containsKey('versionNameSuffix')) {
|
if (System.properties.containsKey('versionNameSuffix')) {
|
||||||
versionNameSuffix System.getProperty('versionNameSuffix')
|
versionNameSuffix System.getProperty('versionNameSuffix')
|
||||||
}
|
}
|
||||||
@@ -209,12 +209,12 @@ dependencies {
|
|||||||
// Or you can use a commit you pushed to GitHub by just replacing TeamNewPipe with your GitHub
|
// Or you can use a commit you pushed to GitHub by just replacing TeamNewPipe with your GitHub
|
||||||
// name and the commit hash with the commit hash of the (pushed) commit you want to test
|
// name and the commit hash with the commit hash of the (pushed) commit you want to test
|
||||||
// This works thanks to JitPack: https://jitpack.io/
|
// This works thanks to JitPack: https://jitpack.io/
|
||||||
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
implementation 'com.github.TeamNewPipe:nanojson:e9d656ddb49a412a5a0a5d5ef20ca7ef09549996'
|
||||||
// WORKAROUND: if you get errors with the NewPipeExtractor dependency, replace `v0.24.3` with
|
// WORKAROUND: if you get errors with the NewPipeExtractor dependency, replace `v0.24.3` with
|
||||||
// the corresponding commit hash, since JitPack sometimes deletes artifacts.
|
// the corresponding commit hash, since JitPack sometimes deletes artifacts.
|
||||||
// If there’s already a git hash, just add more of it to the end (or remove a letter)
|
// If there’s already a git hash, just add more of it to the end (or remove a letter)
|
||||||
// to cause jitpack to regenerate the artifact.
|
// to cause jitpack to regenerate the artifact.
|
||||||
implementation 'com.github.TeamNewPipe.NewPipeExtractor:NewPipeExtractor:v0.24.6'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.24.8'
|
||||||
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
|
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
|
||||||
|
|
||||||
/** Checkstyle **/
|
/** Checkstyle **/
|
||||||
@@ -225,7 +225,7 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
|
||||||
|
|
||||||
/** AndroidX **/
|
/** AndroidX **/
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.7.1'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.core:core-ktx:1.12.0'
|
implementation 'androidx.core:core-ktx:1.12.0'
|
||||||
|
@@ -57,6 +57,15 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||||
|
android:enabled="false"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data
|
||||||
|
android:name="autoStoreLocales"
|
||||||
|
android:value="true" />
|
||||||
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".player.PlayerService"
|
android:name=".player.PlayerService"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@@ -102,7 +102,7 @@ public class App extends Application {
|
|||||||
NewPipe.init(getDownloader(),
|
NewPipe.init(getDownloader(),
|
||||||
Localization.getPreferredLocalization(this),
|
Localization.getPreferredLocalization(this),
|
||||||
Localization.getPreferredContentCountry(this));
|
Localization.getPreferredContentCountry(this));
|
||||||
Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext()));
|
Localization.initPrettyTime(Localization.resolvePrettyTime());
|
||||||
|
|
||||||
BridgeStateSaverInitializer.init(this);
|
BridgeStateSaverInitializer.init(this);
|
||||||
StateSaver.init(this);
|
StateSaver.init(this);
|
||||||
|
@@ -29,7 +29,7 @@ import okhttp3.ResponseBody;
|
|||||||
|
|
||||||
public final class DownloaderImpl extends Downloader {
|
public final class DownloaderImpl extends Downloader {
|
||||||
public static final String USER_AGENT =
|
public static final String USER_AGENT =
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0";
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0";
|
||||||
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY =
|
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY =
|
||||||
"youtube_restricted_mode_key";
|
"youtube_restricted_mode_key";
|
||||||
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
|
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
|
||||||
|
@@ -20,8 +20,6 @@
|
|||||||
|
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -81,6 +79,7 @@ import org.schabi.newpipe.player.event.OnKeyDownListener;
|
|||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.settings.UpdateSettingsFragment;
|
import org.schabi.newpipe.settings.UpdateSettingsFragment;
|
||||||
|
import org.schabi.newpipe.settings.migration.MigrationManager;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.KioskTranslator;
|
import org.schabi.newpipe.util.KioskTranslator;
|
||||||
@@ -126,7 +125,10 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private static final int ITEM_ID_ABOUT = 2;
|
private static final int ITEM_ID_ABOUT = 2;
|
||||||
|
|
||||||
private static final int ORDER = 0;
|
private static final int ORDER = 0;
|
||||||
|
public static final String KEY_IS_IN_BACKGROUND = "is_in_background";
|
||||||
|
|
||||||
|
private SharedPreferences sharedPreferences;
|
||||||
|
private SharedPreferences.Editor sharedPrefEditor;
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Activity's LifeCycle
|
// Activity's LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@@ -138,6 +140,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
+ "savedInstanceState = [" + savedInstanceState + "]");
|
+ "savedInstanceState = [" + savedInstanceState + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Localization.migrateAppLanguageSettingIfNecessary(getApplicationContext());
|
||||||
ThemeHelper.setDayNightMode(this);
|
ThemeHelper.setDayNightMode(this);
|
||||||
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
||||||
|
|
||||||
@@ -154,8 +157,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assureCorrectAppLanguage(this);
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
sharedPrefEditor = sharedPreferences.edit();
|
||||||
|
|
||||||
mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
|
mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||||
drawerLayoutBinding = mainBinding.drawerLayout;
|
drawerLayoutBinding = mainBinding.drawerLayout;
|
||||||
@@ -191,7 +195,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
UpdateSettingsFragment.askForConsentToUpdateChecks(this);
|
UpdateSettingsFragment.askForConsentToUpdateChecks(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Localization.migrateAppLanguageSettingIfNecessary(getApplicationContext());
|
MigrationManager.showUserInfoIfPresent(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -199,16 +203,29 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
super.onPostCreate(savedInstanceState);
|
super.onPostCreate(savedInstanceState);
|
||||||
|
|
||||||
final App app = App.getApp();
|
final App app = App.getApp();
|
||||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
|
||||||
|
|
||||||
if (prefs.getBoolean(app.getString(R.string.update_app_key), false)
|
if (sharedPreferences.getBoolean(app.getString(R.string.update_app_key), false)
|
||||||
&& prefs.getBoolean(app.getString(R.string.update_check_consent_key), false)) {
|
&& sharedPreferences
|
||||||
|
.getBoolean(app.getString(R.string.update_check_consent_key), false)) {
|
||||||
// Start the worker which is checking all conditions
|
// Start the worker which is checking all conditions
|
||||||
// and eventually searching for a new version.
|
// and eventually searching for a new version.
|
||||||
NewVersionWorker.enqueueNewVersionCheckingWork(app, false);
|
NewVersionWorker.enqueueNewVersionCheckingWork(app, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
sharedPrefEditor.putBoolean(KEY_IS_IN_BACKGROUND, false).apply();
|
||||||
|
Log.d(TAG, "App moved to foreground");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
sharedPrefEditor.putBoolean(KEY_IS_IN_BACKGROUND, true).apply();
|
||||||
|
Log.d(TAG, "App moved to background");
|
||||||
|
}
|
||||||
private void setupDrawer() throws ExtractionException {
|
private void setupDrawer() throws ExtractionException {
|
||||||
addDrawerMenuForCurrentService();
|
addDrawerMenuForCurrentService();
|
||||||
|
|
||||||
@@ -246,19 +263,6 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
*/
|
*/
|
||||||
private void addDrawerMenuForCurrentService() throws ExtractionException {
|
private void addDrawerMenuForCurrentService() throws ExtractionException {
|
||||||
//Tabs
|
//Tabs
|
||||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
|
||||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
|
||||||
|
|
||||||
int kioskMenuItemId = 0;
|
|
||||||
|
|
||||||
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
|
||||||
.add(R.id.menu_tabs_group, kioskMenuItemId, 0, KioskTranslator
|
|
||||||
.getTranslatedKioskName(ks, this))
|
|
||||||
.setIcon(KioskTranslator.getKioskIcon(ks));
|
|
||||||
kioskMenuItemId++;
|
|
||||||
}
|
|
||||||
|
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
drawerLayoutBinding.navigation.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
|
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
|
||||||
R.string.tab_subscriptions)
|
R.string.tab_subscriptions)
|
||||||
@@ -276,6 +280,20 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||||
.setIcon(R.drawable.ic_history);
|
.setIcon(R.drawable.ic_history);
|
||||||
|
|
||||||
|
//Kiosks
|
||||||
|
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||||
|
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||||
|
|
||||||
|
int kioskMenuItemId = 0;
|
||||||
|
|
||||||
|
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
||||||
|
drawerLayoutBinding.navigation.getMenu()
|
||||||
|
.add(R.id.menu_kiosks_group, kioskMenuItemId, 0, KioskTranslator
|
||||||
|
.getTranslatedKioskName(ks, this))
|
||||||
|
.setIcon(KioskTranslator.getKioskIcon(ks));
|
||||||
|
kioskMenuItemId++;
|
||||||
|
}
|
||||||
|
|
||||||
//Settings and About
|
//Settings and About
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
drawerLayoutBinding.navigation.getMenu()
|
||||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||||
@@ -295,10 +313,13 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
changeService(item);
|
changeService(item);
|
||||||
break;
|
break;
|
||||||
case R.id.menu_tabs_group:
|
case R.id.menu_tabs_group:
|
||||||
|
tabSelected(item);
|
||||||
|
break;
|
||||||
|
case R.id.menu_kiosks_group:
|
||||||
try {
|
try {
|
||||||
tabSelected(item);
|
kioskSelected(item);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorUtil.showUiErrorSnackbar(this, "Selecting main page tab", e);
|
ErrorUtil.showUiErrorSnackbar(this, "Selecting drawer kiosk", e);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.menu_options_about_group:
|
case R.id.menu_options_about_group:
|
||||||
@@ -322,7 +343,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
.setChecked(true);
|
.setChecked(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tabSelected(final MenuItem item) throws ExtractionException {
|
private void tabSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case ITEM_ID_SUBSCRIPTIONS:
|
case ITEM_ID_SUBSCRIPTIONS:
|
||||||
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
|
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
|
||||||
@@ -339,18 +360,19 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
case ITEM_ID_HISTORY:
|
case ITEM_ID_HISTORY:
|
||||||
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
|
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
final StreamingService currentService = ServiceHelper.getSelectedService(this);
|
}
|
||||||
int kioskMenuItemId = 0;
|
|
||||||
for (final String kioskId : currentService.getKioskList().getAvailableKiosks()) {
|
private void kioskSelected(final MenuItem item) throws ExtractionException {
|
||||||
if (kioskMenuItemId == item.getItemId()) {
|
final StreamingService currentService = ServiceHelper.getSelectedService(this);
|
||||||
NavigationHelper.openKioskFragment(getSupportFragmentManager(),
|
int kioskMenuItemId = 0;
|
||||||
currentService.getServiceId(), kioskId);
|
for (final String kioskId : currentService.getKioskList().getAvailableKiosks()) {
|
||||||
break;
|
if (kioskMenuItemId == item.getItemId()) {
|
||||||
}
|
NavigationHelper.openKioskFragment(getSupportFragmentManager(),
|
||||||
kioskMenuItemId++;
|
currentService.getServiceId(), kioskId);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
kioskMenuItemId++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,6 +413,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_services_group);
|
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_services_group);
|
||||||
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_tabs_group);
|
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_tabs_group);
|
||||||
|
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_kiosks_group);
|
||||||
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_options_about_group);
|
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_options_about_group);
|
||||||
|
|
||||||
// Show up or down arrow
|
// Show up or down arrow
|
||||||
@@ -484,9 +507,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
assureCorrectAppLanguage(this);
|
|
||||||
// Change the date format to match the selected language on resume
|
// Change the date format to match the selected language on resume
|
||||||
Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext()));
|
Localization.initPrettyTime(Localization.resolvePrettyTime());
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
// Close drawer on return, and don't show animation,
|
// Close drawer on return, and don't show animation,
|
||||||
@@ -508,13 +530,11 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e);
|
ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
final SharedPreferences sharedPreferences =
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
|
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Theme has changed, recreating activity...");
|
Log.d(TAG, "Theme has changed, recreating activity...");
|
||||||
}
|
}
|
||||||
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
|
sharedPrefEditor.putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
|
||||||
ActivityCompat.recreate(this);
|
ActivityCompat.recreate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,7 +542,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "main page has changed, recreating main fragment...");
|
Log.d(TAG, "main page has changed, recreating main fragment...");
|
||||||
}
|
}
|
||||||
sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply();
|
sharedPrefEditor.putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply();
|
||||||
NavigationHelper.openMainActivity(this);
|
NavigationHelper.openMainActivity(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -84,7 +84,6 @@ import org.schabi.newpipe.util.ChannelTabHelper;
|
|||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.Localization;
|
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.PermissionHelper;
|
import org.schabi.newpipe.util.PermissionHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
@@ -132,7 +131,6 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
ThemeHelper.setDayNightMode(this);
|
ThemeHelper.setDayNightMode(this);
|
||||||
setTheme(ThemeHelper.isLightThemeSelected(this)
|
setTheme(ThemeHelper.isLightThemeSelected(this)
|
||||||
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
|
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
|
||||||
Localization.assureCorrectAppLanguage(this);
|
|
||||||
|
|
||||||
// Pass-through touch events to background activities
|
// Pass-through touch events to background activities
|
||||||
// so that our transparent window won't lock UI in the mean time
|
// so that our transparent window won't lock UI in the mean time
|
||||||
|
@@ -16,14 +16,12 @@ import org.schabi.newpipe.BuildConfig
|
|||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.databinding.ActivityAboutBinding
|
import org.schabi.newpipe.databinding.ActivityAboutBinding
|
||||||
import org.schabi.newpipe.databinding.FragmentAboutBinding
|
import org.schabi.newpipe.databinding.FragmentAboutBinding
|
||||||
import org.schabi.newpipe.util.Localization
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
import org.schabi.newpipe.util.ThemeHelper
|
||||||
import org.schabi.newpipe.util.external_communication.ShareUtils
|
import org.schabi.newpipe.util.external_communication.ShareUtils
|
||||||
|
|
||||||
class AboutActivity : AppCompatActivity() {
|
class AboutActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
Localization.assureCorrectAppLanguage(this)
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeHelper.setTheme(this)
|
ThemeHelper.setTheme(this)
|
||||||
title = getString(R.string.title_activity_about)
|
title = getString(R.string.title_activity_about)
|
||||||
|
@@ -19,7 +19,6 @@ import org.schabi.newpipe.R
|
|||||||
import org.schabi.newpipe.databinding.FragmentLicensesBinding
|
import org.schabi.newpipe.databinding.FragmentLicensesBinding
|
||||||
import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding
|
import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding
|
||||||
import org.schabi.newpipe.ktx.parcelableArrayList
|
import org.schabi.newpipe.ktx.parcelableArrayList
|
||||||
import org.schabi.newpipe.util.Localization
|
|
||||||
import org.schabi.newpipe.util.external_communication.ShareUtils
|
import org.schabi.newpipe.util.external_communication.ShareUtils
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,7 +99,6 @@ class LicenseFragment : Fragment() {
|
|||||||
val webView = WebView(context)
|
val webView = WebView(context)
|
||||||
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64")
|
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64")
|
||||||
|
|
||||||
Localization.assureCorrectAppLanguage(context)
|
|
||||||
val builder = AlertDialog.Builder(requireContext())
|
val builder = AlertDialog.Builder(requireContext())
|
||||||
.setTitle(softwareComponent.name)
|
.setTitle(softwareComponent.name)
|
||||||
.setView(webView)
|
.setView(webView)
|
||||||
|
@@ -20,8 +20,6 @@ import org.schabi.newpipe.views.FocusOverlayView;
|
|||||||
import us.shandian.giga.service.DownloadManagerService;
|
import us.shandian.giga.service.DownloadManagerService;
|
||||||
import us.shandian.giga.ui.fragment.MissionsFragment;
|
import us.shandian.giga.ui.fragment.MissionsFragment;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
|
|
||||||
public class DownloadActivity extends AppCompatActivity {
|
public class DownloadActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag";
|
private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag";
|
||||||
@@ -33,7 +31,6 @@ public class DownloadActivity extends AppCompatActivity {
|
|||||||
i.setClass(this, DownloadManagerService.class);
|
i.setClass(this, DownloadManagerService.class);
|
||||||
startService(i);
|
startService(i);
|
||||||
|
|
||||||
assureCorrectAppLanguage(this);
|
|
||||||
ThemeHelper.setTheme(this);
|
ThemeHelper.setTheme(this);
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@@ -2,7 +2,6 @@ package org.schabi.newpipe.download;
|
|||||||
|
|
||||||
import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP;
|
import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP;
|
||||||
import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery;
|
import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery;
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@@ -751,7 +750,6 @@ public class DownloadDialog extends DialogFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showFailedDialog(@StringRes final int msg) {
|
private void showFailedDialog(@StringRes final int msg) {
|
||||||
assureCorrectAppLanguage(requireContext());
|
|
||||||
new AlertDialog.Builder(context)
|
new AlertDialog.Builder(context)
|
||||||
.setTitle(R.string.general_error)
|
.setTitle(R.string.general_error)
|
||||||
.setMessage(msg)
|
.setMessage(msg)
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
package org.schabi.newpipe.error;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -79,7 +77,6 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
assureCorrectAppLanguage(this);
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
ThemeHelper.setDayNightMode(this);
|
ThemeHelper.setDayNightMode(this);
|
||||||
@@ -306,7 +303,7 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getAppLanguage() {
|
private String getAppLanguage() {
|
||||||
return Localization.getAppLocale(getApplicationContext()).toString();
|
return Localization.getAppLocale().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getOsString() {
|
private String getOsString() {
|
||||||
|
@@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit
|
|||||||
class ErrorPanelHelper(
|
class ErrorPanelHelper(
|
||||||
private val fragment: Fragment,
|
private val fragment: Fragment,
|
||||||
rootView: View,
|
rootView: View,
|
||||||
onRetry: Runnable
|
onRetry: Runnable?,
|
||||||
) {
|
) {
|
||||||
private val context: Context = rootView.context!!
|
private val context: Context = rootView.context!!
|
||||||
|
|
||||||
@@ -56,12 +56,15 @@ class ErrorPanelHelper(
|
|||||||
errorPanelRoot.findViewById(R.id.error_open_in_browser)
|
errorPanelRoot.findViewById(R.id.error_open_in_browser)
|
||||||
|
|
||||||
private var errorDisposable: Disposable? = null
|
private var errorDisposable: Disposable? = null
|
||||||
|
private var retryShouldBeShown: Boolean = (onRetry != null)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
errorDisposable = errorRetryButton.clicks()
|
if (onRetry != null) {
|
||||||
.debounce(300, TimeUnit.MILLISECONDS)
|
errorDisposable = errorRetryButton.clicks()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.debounce(300, TimeUnit.MILLISECONDS)
|
||||||
.subscribe { onRetry.run() }
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { onRetry.run() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureDefaultVisibility() {
|
private fun ensureDefaultVisibility() {
|
||||||
@@ -101,7 +104,7 @@ class ErrorPanelHelper(
|
|||||||
errorActionButton.setOnClickListener(null)
|
errorActionButton.setOnClickListener(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
errorRetryButton.isVisible = true
|
errorRetryButton.isVisible = retryShouldBeShown
|
||||||
showAndSetOpenInBrowserButtonAction(errorInfo)
|
showAndSetOpenInBrowserButtonAction(errorInfo)
|
||||||
} else if (errorInfo.throwable is AccountTerminatedException) {
|
} else if (errorInfo.throwable is AccountTerminatedException) {
|
||||||
errorTextView.setText(R.string.account_terminated)
|
errorTextView.setText(R.string.account_terminated)
|
||||||
@@ -130,7 +133,7 @@ class ErrorPanelHelper(
|
|||||||
errorInfo.throwable !is ContentNotSupportedException
|
errorInfo.throwable !is ContentNotSupportedException
|
||||||
) {
|
) {
|
||||||
// show retry button only for content which is not unavailable or unsupported
|
// show retry button only for content which is not unavailable or unsupported
|
||||||
errorRetryButton.isVisible = true
|
errorRetryButton.isVisible = retryShouldBeShown
|
||||||
}
|
}
|
||||||
showAndSetOpenInBrowserButtonAction(errorInfo)
|
showAndSetOpenInBrowserButtonAction(errorInfo)
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,9 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.PendingIntentCompat
|
import androidx.core.app.PendingIntentCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import org.schabi.newpipe.MainActivity
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,12 +37,20 @@ class ErrorUtil {
|
|||||||
* activity (since the workflow would be interrupted anyway in that case). So never use this
|
* activity (since the workflow would be interrupted anyway in that case). So never use this
|
||||||
* for background services.
|
* for background services.
|
||||||
*
|
*
|
||||||
|
* If the crashed occurred while the app was in the background open a notification instead
|
||||||
|
*
|
||||||
* @param context the context to use to start the new activity
|
* @param context the context to use to start the new activity
|
||||||
* @param errorInfo the error info to be reported
|
* @param errorInfo the error info to be reported
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun openActivity(context: Context, errorInfo: ErrorInfo) {
|
fun openActivity(context: Context, errorInfo: ErrorInfo) {
|
||||||
context.startActivity(getErrorActivityIntent(context, errorInfo))
|
if (PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getBoolean(MainActivity.KEY_IS_IN_BACKGROUND, true)
|
||||||
|
) {
|
||||||
|
createNotification(context, errorInfo)
|
||||||
|
} else {
|
||||||
|
context.startActivity(getErrorActivityIntent(context, errorInfo))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -32,7 +32,8 @@ public enum UserAction {
|
|||||||
PREFERENCES_MIGRATION("migration of preferences"),
|
PREFERENCES_MIGRATION("migration of preferences"),
|
||||||
SHARE_TO_NEWPIPE("share to newpipe"),
|
SHARE_TO_NEWPIPE("share to newpipe"),
|
||||||
CHECK_FOR_NEW_APP_VERSION("check for new app version"),
|
CHECK_FOR_NEW_APP_VERSION("check for new app version"),
|
||||||
OPEN_INFO_ITEM_DIALOG("open info item dialog");
|
OPEN_INFO_ITEM_DIALOG("open info item dialog"),
|
||||||
|
GETTING_MAIN_SCREEN_TAB("getting main screen tab");
|
||||||
|
|
||||||
private final String message;
|
private final String message;
|
||||||
|
|
||||||
|
@@ -7,16 +7,57 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.evernote.android.state.State;
|
||||||
|
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.ErrorPanelHelper;
|
||||||
|
|
||||||
public class BlankFragment extends BaseFragment {
|
public class BlankFragment extends BaseFragment {
|
||||||
|
|
||||||
|
@State
|
||||||
|
@Nullable
|
||||||
|
ErrorInfo errorInfo;
|
||||||
|
@Nullable
|
||||||
|
ErrorPanelHelper errorPanel = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a blank fragment that just says the app name and suggests clicking on search.
|
||||||
|
*/
|
||||||
|
public BlankFragment() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param errorInfo if null acts like {@link BlankFragment}, else shows an error panel.
|
||||||
|
*/
|
||||||
|
public BlankFragment(@Nullable final ErrorInfo errorInfo) {
|
||||||
|
this.errorInfo = errorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
final Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
setTitle("NewPipe");
|
setTitle("NewPipe");
|
||||||
return inflater.inflate(R.layout.fragment_blank, container, false);
|
final View view = inflater.inflate(R.layout.fragment_blank, container, false);
|
||||||
|
if (errorInfo != null) {
|
||||||
|
errorPanel = new ErrorPanelHelper(this, view, null);
|
||||||
|
errorPanel.showError(errorInfo);
|
||||||
|
view.findViewById(R.id.blank_page_content).setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
|
||||||
|
if (errorPanel != null) {
|
||||||
|
errorPanel.dispose();
|
||||||
|
errorPanel = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -36,8 +36,9 @@ import com.google.android.material.tabs.TabLayout;
|
|||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.databinding.FragmentMainBinding;
|
import org.schabi.newpipe.databinding.FragmentMainBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.error.ErrorUtil;
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
||||||
import org.schabi.newpipe.settings.tabs.Tab;
|
import org.schabi.newpipe.settings.tabs.Tab;
|
||||||
import org.schabi.newpipe.settings.tabs.TabsManager;
|
import org.schabi.newpipe.settings.tabs.TabsManager;
|
||||||
@@ -303,9 +304,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
final Fragment fragment;
|
final Fragment fragment;
|
||||||
try {
|
try {
|
||||||
fragment = tab.getFragment(context);
|
fragment = tab.getFragment(context);
|
||||||
} catch (final ExtractionException e) {
|
} catch (final Throwable t) {
|
||||||
ErrorUtil.showUiErrorSnackbar(context, "Getting fragment item", e);
|
return new BlankFragment(new ErrorInfo(t, UserAction.GETTING_MAIN_SCREEN_TAB,
|
||||||
return new BlankFragment();
|
"Tab " + tab.getClass().getSimpleName() + ":" + tab.getTabName(context)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fragment instanceof BaseFragment) {
|
if (fragment instanceof BaseFragment) {
|
||||||
|
@@ -93,7 +93,7 @@ public class DescriptionFragment extends BaseDescriptionFragment {
|
|||||||
|
|
||||||
if (streamInfo.getLanguageInfo() != null) {
|
if (streamInfo.getLanguageInfo() != null) {
|
||||||
addMetadataItem(inflater, layout, false, R.string.metadata_language,
|
addMetadataItem(inflater, layout, false, R.string.metadata_language,
|
||||||
streamInfo.getLanguageInfo().getDisplayLanguage(getAppLocale(getContext())));
|
streamInfo.getLanguageInfo().getDisplayLanguage(getAppLocale()));
|
||||||
}
|
}
|
||||||
|
|
||||||
addMetadataItem(inflater, layout, true, R.string.metadata_support,
|
addMetadataItem(inflater, layout, true, R.string.metadata_support,
|
||||||
|
@@ -8,6 +8,7 @@ import android.util.Log;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.evernote.android.state.State;
|
import com.evernote.android.state.State;
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
|
|||||||
|
|
||||||
private final UserAction errorUserAction;
|
private final UserAction errorUserAction;
|
||||||
protected L currentInfo;
|
protected L currentInfo;
|
||||||
|
@Nullable
|
||||||
protected Page currentNextPage;
|
protected Page currentNextPage;
|
||||||
protected Disposable currentWorker;
|
protected Disposable currentWorker;
|
||||||
|
|
||||||
|
@@ -81,9 +81,7 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
|
|||||||
|
|
||||||
if (channelInfo.getSubscriberCount() != UNKNOWN_SUBSCRIBER_COUNT) {
|
if (channelInfo.getSubscriberCount() != UNKNOWN_SUBSCRIBER_COUNT) {
|
||||||
addMetadataItem(inflater, layout, false, R.string.metadata_subscribers,
|
addMetadataItem(inflater, layout, false, R.string.metadata_subscribers,
|
||||||
Localization.localizeNumber(
|
Localization.localizeNumber(channelInfo.getSubscriberCount()));
|
||||||
requireContext(),
|
|
||||||
channelInfo.getSubscriberCount()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addImagesMetadataItem(inflater, layout, R.string.metadata_avatars,
|
addImagesMetadataItem(inflater, layout, R.string.metadata_avatars,
|
||||||
|
@@ -144,6 +144,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
|
|
||||||
private final SparseArrayCompat<String> menuItemToFilterName = new SparseArrayCompat<>();
|
private final SparseArrayCompat<String> menuItemToFilterName = new SparseArrayCompat<>();
|
||||||
private StreamingService service;
|
private StreamingService service;
|
||||||
|
@Nullable
|
||||||
private Page nextPage;
|
private Page nextPage;
|
||||||
private boolean showLocalSuggestions = true;
|
private boolean showLocalSuggestions = true;
|
||||||
private boolean showRemoteSuggestions = true;
|
private boolean showRemoteSuggestions = true;
|
||||||
@@ -219,6 +220,15 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
|
||||||
searchBinding = FragmentSearchBinding.bind(rootView);
|
searchBinding = FragmentSearchBinding.bind(rootView);
|
||||||
super.onViewCreated(rootView, savedInstanceState);
|
super.onViewCreated(rootView, savedInstanceState);
|
||||||
|
|
||||||
|
updateService();
|
||||||
|
// Add the service name to search string hint
|
||||||
|
// to make it more obvious which platform is being searched.
|
||||||
|
if (service != null) {
|
||||||
|
searchEditText.setHint(
|
||||||
|
getString(R.string.search_with_service_name,
|
||||||
|
service.getServiceInfo().getName()));
|
||||||
|
}
|
||||||
showSearchOnStart();
|
showSearchOnStart();
|
||||||
initSearchListeners();
|
initSearchListeners();
|
||||||
}
|
}
|
||||||
@@ -936,6 +946,20 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
filterItemCheckedId = item.getItemId();
|
filterItemCheckedId = item.getItemId();
|
||||||
item.setChecked(true);
|
item.setChecked(true);
|
||||||
|
|
||||||
|
if (service != null) {
|
||||||
|
final boolean isNotFiltered = theContentFilter.isEmpty()
|
||||||
|
|| "all".equals(theContentFilter.get(0));
|
||||||
|
if (isNotFiltered) {
|
||||||
|
searchEditText.setHint(
|
||||||
|
getString(R.string.search_with_service_name,
|
||||||
|
service.getServiceInfo().getName()));
|
||||||
|
} else {
|
||||||
|
searchEditText.setHint(getString(R.string.search_with_service_name_and_filter,
|
||||||
|
service.getServiceInfo().getName(),
|
||||||
|
item.getTitle()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contentFilter = theContentFilter.toArray(new String[0]);
|
contentFilter = theContentFilter.toArray(new String[0]);
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(searchString)) {
|
if (!TextUtils.isEmpty(searchString)) {
|
||||||
@@ -1065,15 +1089,25 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
public void handleNextItems(final ListExtractor.InfoItemsPage<?> result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage<?> result) {
|
||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
infoListAdapter.addInfoItemList(result.getItems());
|
infoListAdapter.addInfoItemList(result.getItems());
|
||||||
nextPage = result.getNextPage();
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
|
// nextPage should be non-null at this point, because it refers to the page
|
||||||
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
// whose results are handled here, but let's check it anyway
|
||||||
+ "pageIds: " + nextPage.getIds() + ", "
|
if (nextPage == null) {
|
||||||
+ "pageCookies: " + nextPage.getCookies(),
|
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
|
||||||
serviceId));
|
"\"" + searchString + "\" → nextPage == null", serviceId));
|
||||||
|
} else {
|
||||||
|
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
|
||||||
|
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
||||||
|
+ "pageIds: " + nextPage.getIds() + ", "
|
||||||
|
+ "pageCookies: " + nextPage.getCookies(),
|
||||||
|
serviceId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keep the reassignment of nextPage after the error handling to ensure that nextPage
|
||||||
|
// still holds the correct value during the error handling
|
||||||
|
nextPage = result.getNextPage();
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -269,7 +269,12 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||||||
|
|
||||||
override fun onDestroyOptionsMenu() {
|
override fun onDestroyOptionsMenu() {
|
||||||
super.onDestroyOptionsMenu()
|
super.onDestroyOptionsMenu()
|
||||||
activity?.supportActionBar?.subtitle = null
|
if (
|
||||||
|
(groupName != "") &&
|
||||||
|
(activity?.supportActionBar?.subtitle == groupName)
|
||||||
|
) {
|
||||||
|
activity?.supportActionBar?.subtitle = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@@ -281,7 +286,13 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
activity?.supportActionBar?.subtitle = null
|
|
||||||
|
if (
|
||||||
|
(groupName != "") &&
|
||||||
|
(activity?.supportActionBar?.subtitle == groupName)
|
||||||
|
) {
|
||||||
|
activity?.supportActionBar?.subtitle = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
package org.schabi.newpipe.local.subscription;
|
package org.schabi.newpipe.local.subscription;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -35,7 +33,6 @@ public class ImportConfirmationDialog extends DialogFragment {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
||||||
assureCorrectAppLanguage(getContext());
|
|
||||||
return new AlertDialog.Builder(requireContext())
|
return new AlertDialog.Builder(requireContext())
|
||||||
.setMessage(R.string.import_network_expensive_warning)
|
.setMessage(R.string.import_network_expensive_warning)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
|
@@ -13,6 +13,7 @@ import android.view.MenuItem
|
|||||||
import android.view.SubMenu
|
import android.view.SubMenu
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||||
@@ -460,6 +461,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val JSON_MIME_TYPE = "application/json"
|
val JSON_MIME_TYPE = MimeTypeMap.getSingleton()
|
||||||
|
.getMimeTypeFromExtension("json") ?: "application/octet-stream"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ package org.schabi.newpipe.player;
|
|||||||
|
|
||||||
import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu;
|
import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu;
|
||||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -84,7 +83,6 @@ public final class PlayQueueActivity extends AppCompatActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
assureCorrectAppLanguage(this);
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
||||||
|
|
||||||
|
@@ -44,7 +44,6 @@ import static org.schabi.newpipe.player.notification.NotificationConstants.ACTIO
|
|||||||
import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE;
|
import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE;
|
||||||
import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
|
import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
|
||||||
import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
|
import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
@@ -88,8 +87,8 @@ import org.schabi.newpipe.databinding.PlayerBinding;
|
|||||||
import org.schabi.newpipe.error.ErrorInfo;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.error.ErrorUtil;
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
|
||||||
import org.schabi.newpipe.extractor.Image;
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
@@ -120,9 +119,9 @@ import org.schabi.newpipe.player.ui.VideoPlayerUi;
|
|||||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.SerializedCache;
|
import org.schabi.newpipe.util.SerializedCache;
|
||||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||||
|
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -753,7 +752,6 @@ public final class Player implements PlaybackListener, Listener {
|
|||||||
toggleShuffleModeEnabled();
|
toggleShuffleModeEnabled();
|
||||||
break;
|
break;
|
||||||
case Intent.ACTION_CONFIGURATION_CHANGED:
|
case Intent.ACTION_CONFIGURATION_CHANGED:
|
||||||
assureCorrectAppLanguage(service);
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received");
|
Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received");
|
||||||
}
|
}
|
||||||
|
@@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
package org.schabi.newpipe.player;
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
@@ -91,7 +89,6 @@ public final class PlayerService extends MediaBrowserServiceCompat {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onCreate() called");
|
Log.d(TAG, "onCreate() called");
|
||||||
}
|
}
|
||||||
assureCorrectAppLanguage(this);
|
|
||||||
ThemeHelper.setTheme(this);
|
ThemeHelper.setTheme(this);
|
||||||
|
|
||||||
mediaBrowserImpl = new MediaBrowserImpl(this, this::notifyChildrenChanged);
|
mediaBrowserImpl = new MediaBrowserImpl(this, this::notifyChildrenChanged);
|
||||||
@@ -330,7 +327,6 @@ public final class PlayerService extends MediaBrowserServiceCompat {
|
|||||||
public BrowserRoot onGetRoot(@NonNull final String clientPackageName,
|
public BrowserRoot onGetRoot(@NonNull final String clientPackageName,
|
||||||
final int clientUid,
|
final int clientUid,
|
||||||
@Nullable final Bundle rootHints) {
|
@Nullable final Bundle rootHints) {
|
||||||
// TODO check if the accessing package has permission to view data
|
|
||||||
return mediaBrowserImpl.onGetRoot(clientPackageName, clientUid, rootHints);
|
return mediaBrowserImpl.onGetRoot(clientPackageName, clientUid, rootHints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,7 +2,6 @@ package org.schabi.newpipe.player.helper;
|
|||||||
|
|
||||||
import static org.schabi.newpipe.ktx.ViewUtils.animateRotation;
|
import static org.schabi.newpipe.ktx.ViewUtils.animateRotation;
|
||||||
import static org.schabi.newpipe.player.Player.DEBUG;
|
import static org.schabi.newpipe.player.Player.DEBUG;
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
import static org.schabi.newpipe.util.ThemeHelper.resolveDrawable;
|
import static org.schabi.newpipe.util.ThemeHelper.resolveDrawable;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
@@ -145,7 +144,6 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
||||||
assureCorrectAppLanguage(getContext());
|
|
||||||
Bridge.restoreInstanceState(this, savedInstanceState);
|
Bridge.restoreInstanceState(this, savedInstanceState);
|
||||||
|
|
||||||
binding = DialogPlaybackParameterBinding.inflate(getLayoutInflater());
|
binding = DialogPlaybackParameterBinding.inflate(getLayoutInflater());
|
||||||
|
@@ -33,11 +33,9 @@ import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
|||||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode;
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode;
|
||||||
import com.google.android.exoplayer2.ui.CaptionStyleCompat;
|
import com.google.android.exoplayer2.ui.CaptionStyleCompat;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||||
@@ -47,13 +45,14 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
|
|||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
|
import org.schabi.newpipe.util.Localization;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.DecimalFormatSymbols;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Formatter;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -62,11 +61,7 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public final class PlayerHelper {
|
public final class PlayerHelper {
|
||||||
private static final StringBuilder STRING_BUILDER = new StringBuilder();
|
private static final FormattersProvider FORMATTERS_PROVIDER = new FormattersProvider();
|
||||||
private static final Formatter STRING_FORMATTER =
|
|
||||||
new Formatter(STRING_BUILDER, Locale.getDefault());
|
|
||||||
private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x");
|
|
||||||
private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%");
|
|
||||||
|
|
||||||
@Retention(SOURCE)
|
@Retention(SOURCE)
|
||||||
@IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI,
|
@IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI,
|
||||||
@@ -89,9 +84,11 @@ public final class PlayerHelper {
|
|||||||
private PlayerHelper() {
|
private PlayerHelper() {
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
// region Exposed helpers
|
||||||
// Exposed helpers
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
public static void resetFormat() {
|
||||||
|
FORMATTERS_PROVIDER.reset();
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String getTimeString(final int milliSeconds) {
|
public static String getTimeString(final int milliSeconds) {
|
||||||
@@ -100,35 +97,24 @@ public final class PlayerHelper {
|
|||||||
final int hours = (milliSeconds % 86400000) / 3600000;
|
final int hours = (milliSeconds % 86400000) / 3600000;
|
||||||
final int days = (milliSeconds % (86400000 * 7)) / 86400000;
|
final int days = (milliSeconds % (86400000 * 7)) / 86400000;
|
||||||
|
|
||||||
STRING_BUILDER.setLength(0);
|
final Formatters formatters = FORMATTERS_PROVIDER.formatters();
|
||||||
return (days > 0
|
if (days > 0) {
|
||||||
? STRING_FORMATTER.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds)
|
return formatters.stringFormat("%d:%02d:%02d:%02d", days, hours, minutes, seconds);
|
||||||
: hours > 0
|
}
|
||||||
? STRING_FORMATTER.format("%d:%02d:%02d", hours, minutes, seconds)
|
|
||||||
: STRING_FORMATTER.format("%02d:%02d", minutes, seconds)
|
return hours > 0
|
||||||
).toString();
|
? formatters.stringFormat("%d:%02d:%02d", hours, minutes, seconds)
|
||||||
|
: formatters.stringFormat("%02d:%02d", minutes, seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String formatSpeed(final double speed) {
|
public static String formatSpeed(final double speed) {
|
||||||
return SPEED_FORMATTER.format(speed);
|
return FORMATTERS_PROVIDER.formatters().speed().format(speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String formatPitch(final double pitch) {
|
public static String formatPitch(final double pitch) {
|
||||||
return PITCH_FORMATTER.format(pitch);
|
return FORMATTERS_PROVIDER.formatters().pitch().format(pitch);
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static String subtitleMimeTypesOf(@NonNull final MediaFormat format) {
|
|
||||||
switch (format) {
|
|
||||||
case VTT:
|
|
||||||
return MimeTypes.TEXT_VTT;
|
|
||||||
case TTML:
|
|
||||||
return MimeTypes.APPLICATION_TTML;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized mime type: " + format.name());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -219,9 +205,8 @@ public final class PlayerHelper {
|
|||||||
? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
|
? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
// endregion
|
||||||
// Settings Resolution
|
// region Resolution
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) {
|
public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) {
|
||||||
return getPreferences(context)
|
return getPreferences(context)
|
||||||
@@ -405,9 +390,8 @@ public final class PlayerHelper {
|
|||||||
return Integer.parseInt(preferredIntervalBytes) * 1024;
|
return Integer.parseInt(preferredIntervalBytes) * 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
// endregion
|
||||||
// Private helpers
|
// region Private helpers
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private static SharedPreferences getPreferences(@NonNull final Context context) {
|
private static SharedPreferences getPreferences(@NonNull final Context context) {
|
||||||
@@ -427,9 +411,8 @@ public final class PlayerHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
// endregion
|
||||||
// Utils used by player
|
// region Utils used by player
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@RepeatMode
|
@RepeatMode
|
||||||
public static int nextRepeatMode(@RepeatMode final int repeatMode) {
|
public static int nextRepeatMode(@RepeatMode final int repeatMode) {
|
||||||
@@ -503,4 +486,43 @@ public final class PlayerHelper {
|
|||||||
player.getContext().getString(R.string.seek_duration_key),
|
player.getContext().getString(R.string.seek_duration_key),
|
||||||
player.getContext().getString(R.string.seek_duration_default_value))));
|
player.getContext().getString(R.string.seek_duration_default_value))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
// region Format
|
||||||
|
|
||||||
|
static class FormattersProvider {
|
||||||
|
private Formatters formatters;
|
||||||
|
|
||||||
|
public Formatters formatters() {
|
||||||
|
if (formatters == null) {
|
||||||
|
formatters = Formatters.create();
|
||||||
|
}
|
||||||
|
return formatters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
formatters = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Formatters(
|
||||||
|
Locale locale,
|
||||||
|
NumberFormat speed,
|
||||||
|
NumberFormat pitch) {
|
||||||
|
|
||||||
|
static Formatters create() {
|
||||||
|
final Locale locale = Localization.getAppLocale();
|
||||||
|
final DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale);
|
||||||
|
return new Formatters(
|
||||||
|
locale,
|
||||||
|
new DecimalFormat("0.##x", dfs),
|
||||||
|
new DecimalFormat("##%", dfs));
|
||||||
|
}
|
||||||
|
|
||||||
|
String stringFormat(final String format, final Object... args) {
|
||||||
|
return String.format(locale, format, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
}
|
}
|
||||||
|
@@ -159,6 +159,11 @@ public final class PlayerHolder {
|
|||||||
|
|
||||||
private boolean playAfterConnect = false;
|
private boolean playAfterConnect = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param playAfterConnection Sets the value of `playAfterConnect` to pass to the {@link
|
||||||
|
* PlayerServiceExtendedEventListener#onPlayerConnected(Player, boolean)} the next time it
|
||||||
|
* is called. The value of `playAfterConnect` will be reset to false after that.
|
||||||
|
*/
|
||||||
public void doPlayAfterConnect(final boolean playAfterConnection) {
|
public void doPlayAfterConnect(final boolean playAfterConnection) {
|
||||||
this.playAfterConnect = playAfterConnection;
|
this.playAfterConnect = playAfterConnection;
|
||||||
}
|
}
|
||||||
@@ -183,7 +188,6 @@ public final class PlayerHolder {
|
|||||||
playerService = localBinder.getService();
|
playerService = localBinder.getService();
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.onServiceConnected(playerService);
|
listener.onServiceConnected(playerService);
|
||||||
getPlayer().ifPresent(p -> listener.onPlayerConnected(p, playAfterConnect));
|
|
||||||
}
|
}
|
||||||
startPlayerListener();
|
startPlayerListener();
|
||||||
// ^ will call listener.onPlayerConnected() down the line if there is an active player
|
// ^ will call listener.onPlayerConnected() down the line if there is an active player
|
||||||
@@ -357,6 +361,8 @@ public final class PlayerHolder {
|
|||||||
listener.onPlayerDisconnected();
|
listener.onPlayerDisconnected();
|
||||||
} else {
|
} else {
|
||||||
listener.onPlayerConnected(player, serviceConnection.playAfterConnect);
|
listener.onPlayerConnected(player, serviceConnection.playAfterConnect);
|
||||||
|
// reset the value of playAfterConnect: if it was true before, it is now "consumed"
|
||||||
|
serviceConnection.playAfterConnect = false;
|
||||||
player.setFragmentListener(internalListener);
|
player.setFragmentListener(internalListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,9 @@ import android.support.v4.media.MediaBrowserCompat
|
|||||||
import android.support.v4.media.MediaDescriptionCompat
|
import android.support.v4.media.MediaDescriptionCompat
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.media.MediaBrowserServiceCompat
|
import androidx.media.MediaBrowserServiceCompat
|
||||||
|
import androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT
|
||||||
import androidx.media.MediaBrowserServiceCompat.Result
|
import androidx.media.MediaBrowserServiceCompat.Result
|
||||||
import androidx.media.utils.MediaConstants
|
import androidx.media.utils.MediaConstants
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
@@ -47,6 +49,7 @@ class MediaBrowserImpl(
|
|||||||
private val context: Context,
|
private val context: Context,
|
||||||
notifyChildrenChanged: Consumer<String>, // parentId
|
notifyChildrenChanged: Consumer<String>, // parentId
|
||||||
) {
|
) {
|
||||||
|
private val packageValidator = PackageValidator(context)
|
||||||
private val database = NewPipeDatabase.getInstance(context)
|
private val database = NewPipeDatabase.getInstance(context)
|
||||||
private var disposables = CompositeDisposable()
|
private var disposables = CompositeDisposable()
|
||||||
|
|
||||||
@@ -68,11 +71,22 @@ class MediaBrowserImpl(
|
|||||||
clientPackageName: String,
|
clientPackageName: String,
|
||||||
clientUid: Int,
|
clientUid: Int,
|
||||||
rootHints: Bundle?
|
rootHints: Bundle?
|
||||||
): MediaBrowserServiceCompat.BrowserRoot {
|
): MediaBrowserServiceCompat.BrowserRoot? {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onGetRoot($clientPackageName, $clientUid, $rootHints)")
|
Log.d(TAG, "onGetRoot($clientPackageName, $clientUid, $rootHints)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!packageValidator.isKnownCaller(clientPackageName, clientUid)) {
|
||||||
|
// this is a caller we can't trust (see PackageValidator's rules taken from uamp)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootHints?.getBoolean(EXTRA_RECENT, false) == true) {
|
||||||
|
// the system is asking for a root to do media resumption, but we can't handle that yet,
|
||||||
|
// see https://developer.android.com/media/implement/surfaces/mobile#mediabrowserservice_implementation
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
val extras = Bundle()
|
val extras = Bundle()
|
||||||
extras.putBoolean(
|
extras.putBoolean(
|
||||||
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true
|
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true
|
||||||
@@ -103,7 +117,7 @@ class MediaBrowserImpl(
|
|||||||
|
|
||||||
private fun onLoadChildren(parentId: String): Single<List<MediaBrowserCompat.MediaItem>> {
|
private fun onLoadChildren(parentId: String): Single<List<MediaBrowserCompat.MediaItem>> {
|
||||||
try {
|
try {
|
||||||
val parentIdUri = Uri.parse(parentId)
|
val parentIdUri = parentId.toUri()
|
||||||
val path = ArrayList(parentIdUri.pathSegments)
|
val path = ArrayList(parentIdUri.pathSegments)
|
||||||
|
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
@@ -185,7 +199,7 @@ class MediaBrowserImpl(
|
|||||||
builder
|
builder
|
||||||
.setMediaId(createMediaIdForInfoItem(playlist is PlaylistRemoteEntity, playlist.uid))
|
.setMediaId(createMediaIdForInfoItem(playlist is PlaylistRemoteEntity, playlist.uid))
|
||||||
.setTitle(playlist.orderingName)
|
.setTitle(playlist.orderingName)
|
||||||
.setIconUri(playlist.thumbnailUrl?.let { Uri.parse(it) })
|
.setIconUri(imageUriOrNullIfDisabled(playlist.thumbnailUrl))
|
||||||
|
|
||||||
val extras = Bundle()
|
val extras = Bundle()
|
||||||
extras.putString(
|
extras.putString(
|
||||||
@@ -212,7 +226,7 @@ class MediaBrowserImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
|
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
|
||||||
builder.setIconUri(Uri.parse(it))
|
builder.setIconUri(imageUriOrNullIfDisabled(it))
|
||||||
}
|
}
|
||||||
|
|
||||||
return MediaBrowserCompat.MediaItem(
|
return MediaBrowserCompat.MediaItem(
|
||||||
@@ -258,7 +272,7 @@ class MediaBrowserImpl(
|
|||||||
builder.setMediaId(createMediaIdForPlaylistIndex(false, playlistId, index))
|
builder.setMediaId(createMediaIdForPlaylistIndex(false, playlistId, index))
|
||||||
.setTitle(item.streamEntity.title)
|
.setTitle(item.streamEntity.title)
|
||||||
.setSubtitle(item.streamEntity.uploader)
|
.setSubtitle(item.streamEntity.uploader)
|
||||||
.setIconUri(Uri.parse(item.streamEntity.thumbnailUrl))
|
.setIconUri(imageUriOrNullIfDisabled(item.streamEntity.thumbnailUrl))
|
||||||
|
|
||||||
return MediaBrowserCompat.MediaItem(
|
return MediaBrowserCompat.MediaItem(
|
||||||
builder.build(),
|
builder.build(),
|
||||||
@@ -277,7 +291,7 @@ class MediaBrowserImpl(
|
|||||||
.setSubtitle(item.uploaderName)
|
.setSubtitle(item.uploaderName)
|
||||||
|
|
||||||
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
|
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
|
||||||
builder.setIconUri(Uri.parse(it))
|
builder.setIconUri(imageUriOrNullIfDisabled(it))
|
||||||
}
|
}
|
||||||
|
|
||||||
return MediaBrowserCompat.MediaItem(
|
return MediaBrowserCompat.MediaItem(
|
||||||
@@ -316,7 +330,7 @@ class MediaBrowserImpl(
|
|||||||
builder.setMediaId(mediaId)
|
builder.setMediaId(mediaId)
|
||||||
.setTitle(streamHistoryEntry.streamEntity.title)
|
.setTitle(streamHistoryEntry.streamEntity.title)
|
||||||
.setSubtitle(streamHistoryEntry.streamEntity.uploader)
|
.setSubtitle(streamHistoryEntry.streamEntity.uploader)
|
||||||
.setIconUri(Uri.parse(streamHistoryEntry.streamEntity.thumbnailUrl))
|
.setIconUri(imageUriOrNullIfDisabled(streamHistoryEntry.streamEntity.thumbnailUrl))
|
||||||
|
|
||||||
return MediaBrowserCompat.MediaItem(
|
return MediaBrowserCompat.MediaItem(
|
||||||
builder.build(),
|
builder.build(),
|
||||||
@@ -395,5 +409,13 @@ class MediaBrowserImpl(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG: String = MediaBrowserImpl::class.java.getSimpleName()
|
private val TAG: String = MediaBrowserImpl::class.java.getSimpleName()
|
||||||
|
|
||||||
|
fun imageUriOrNullIfDisabled(url: String?): Uri? {
|
||||||
|
return if (ImageStrategy.shouldLoadImages()) {
|
||||||
|
url?.toUri()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user