mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-11-06 03:12:30 +01:00
Compare commits
115 Commits
v0.25.1
...
fix/peertu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27b2d5de70 | ||
|
|
5c7a9a52f5 | ||
|
|
c1f0a945c0 | ||
|
|
e33bb676f9 | ||
|
|
30724dbc50 | ||
|
|
e765343162 | ||
|
|
62ce0b0408 | ||
|
|
3bbc606694 | ||
|
|
56eec9fed1 | ||
|
|
ea0d798ea0 | ||
|
|
5716d51112 | ||
|
|
d845a158f0 | ||
|
|
1a2fbd8122 | ||
|
|
8bdeed8f28 | ||
|
|
3c87462203 | ||
|
|
3622438a9d | ||
|
|
1848892ff8 | ||
|
|
72c6ed2804 | ||
|
|
42de2c7033 | ||
|
|
6bcc8691fa | ||
|
|
6cf13ed8fb | ||
|
|
ad75db40df | ||
|
|
4e3bf3c2f9 | ||
|
|
1925687f18 | ||
|
|
577301c4eb | ||
|
|
c87b42de1c | ||
|
|
c8e8915c2e | ||
|
|
17cdedfa85 | ||
|
|
677bb4070f | ||
|
|
fe82029dc7 | ||
|
|
0ab9961908 | ||
|
|
ecbf5d5ead | ||
|
|
df430badbc | ||
|
|
8639972a54 | ||
|
|
41038f452d | ||
|
|
2f31ea8864 | ||
|
|
e831059162 | ||
|
|
e109e8cf1c | ||
|
|
f1524b6aba | ||
|
|
51ee6f87e0 | ||
|
|
0bb3e7cb86 | ||
|
|
4bf063645a | ||
|
|
9866eab60f | ||
|
|
10c42de2f1 | ||
|
|
e1fd25fb71 | ||
|
|
2315b082ff | ||
|
|
023f6166ab | ||
|
|
d89a3c6c4d | ||
|
|
fb00ee8cf9 | ||
|
|
22671ca16c | ||
|
|
4e837e838d | ||
|
|
ed1781133c | ||
|
|
60fc662a26 | ||
|
|
43b0167a3a | ||
|
|
8519897089 | ||
|
|
60a5d02018 | ||
|
|
c377ffbce8 | ||
|
|
b567d428ad | ||
|
|
da30e539df | ||
|
|
f74d794b2a | ||
|
|
69ef4a987e | ||
|
|
78e1e0508e | ||
|
|
6d98ad7abc | ||
|
|
70b3ba310a | ||
|
|
2edc223e77 | ||
|
|
e18a6b09f8 | ||
|
|
f8c3ec4be7 | ||
|
|
ba3afd1e35 | ||
|
|
20f0011921 | ||
|
|
acebabd028 | ||
|
|
6243f34946 | ||
|
|
787758a436 | ||
|
|
a02b92fd59 | ||
|
|
a6ff85a208 | ||
|
|
41da8fc05f | ||
|
|
a4a9957a15 | ||
|
|
29318c64ed | ||
|
|
74bd28cbd9 | ||
|
|
365bb2d0e4 | ||
|
|
c08538d25d | ||
|
|
140ea8642c | ||
|
|
445d364193 | ||
|
|
4bb45c001d | ||
|
|
7350b1f32e | ||
|
|
4a33ee6045 | ||
|
|
704e9bd7b6 | ||
|
|
d2735607b8 | ||
|
|
3c72992c39 | ||
|
|
7689d1d15c | ||
|
|
65d8589e7a | ||
|
|
32cec6c9a7 | ||
|
|
72ca52a29b | ||
|
|
2ded8c7cc1 | ||
|
|
759a9080a8 | ||
|
|
2ba649949f | ||
|
|
c8d54ec6c7 | ||
|
|
96e9242431 | ||
|
|
3c74cb3439 | ||
|
|
7a8116b2cf | ||
|
|
d010384c88 | ||
|
|
39a5c8bdfb | ||
|
|
694418d30d | ||
|
|
ed06f559ae | ||
|
|
fdd3b03fe5 | ||
|
|
dbd6e4d11f | ||
|
|
61a14765f3 | ||
|
|
9b8ffdd2aa | ||
|
|
ef0a4cf8b2 | ||
|
|
7aed2eed8a | ||
|
|
87a88e4df7 | ||
|
|
366c39d4c6 | ||
|
|
77649d388c | ||
|
|
dba53d23aa | ||
|
|
208887d538 | ||
|
|
de7872d8f2 |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -1,3 +1,5 @@
|
||||
### Please do **not** open pull requests for *new features* now, as we are planning to rewrite large chunks of the code. Only bugfix PRs will be accepted. More details will be announced soon!
|
||||
|
||||
NewPipe contribution guidelines
|
||||
===============================
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
name: Question
|
||||
description: Ask about anything NewPipe-related
|
||||
labels: [question, needs triage]
|
||||
labels: [question]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this issue! :hugs:
|
||||
Thanks for taking the time to fill out this form! :hugs:
|
||||
|
||||
Note that you can also ask questions on our [IRC channel](https://web.libera.chat/#newpipe).
|
||||
|
||||
@@ -14,7 +14,7 @@ body:
|
||||
attributes:
|
||||
label: "Checklist"
|
||||
options:
|
||||
- label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
|
||||
- label: "I made sure that there are *no existing issues or discussions* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
|
||||
required: true
|
||||
- label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my question isn't listed."
|
||||
required: true
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ❓ Question
|
||||
url: https://github.com/TeamNewPipe/NewPipe/discussions/new?category=questions
|
||||
about: Ask about anything NewPipe-related
|
||||
- name: 💬 IRC
|
||||
url: https://web.libera.chat/#newpipe
|
||||
about: Chat with us via IRC for quick Q/A
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -28,7 +28,7 @@
|
||||
#### APK testing
|
||||
<!-- Use a new, meaningfully named branch. The name is used as a suffix for the app ID to allow installing and testing multiple versions of NewPipe, e.g. "commentfix", if your PR implements a bugfix for comments. (No names like "patch-0" and "feature-1".) -->
|
||||
<!-- Remove the following line if you directly link the APK created by the CI pipeline. Directly linking is preferred if you need to let users test.-->
|
||||
The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR.
|
||||
The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR. You can find more info and a video demonstration [on this wiki page](https://github.com/TeamNewPipe/NewPipe/wiki/Download-APK-for-PR).
|
||||
|
||||
#### Due diligence
|
||||
- [ ] I read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md).
|
||||
|
||||
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
@@ -42,12 +42,14 @@ jobs:
|
||||
- name: create and checkout branch
|
||||
# push events already checked out the branch
|
||||
if: github.event_name == 'pull_request'
|
||||
run: git checkout -B ${{ github.head_ref }}
|
||||
env:
|
||||
BRANCH: ${{ github.head_ref }}
|
||||
run: git checkout -B "$BRANCH"
|
||||
|
||||
- name: set up JDK 11
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
distribution: "temurin"
|
||||
cache: 'gradle'
|
||||
|
||||
@@ -66,8 +68,13 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
# api-level 19 is min sdk, but throws errors related to desugaring
|
||||
api-level: [ 21, 29 ]
|
||||
include:
|
||||
- api-level: 21
|
||||
target: default
|
||||
arch: x86
|
||||
- api-level: 33
|
||||
target: google_apis # emulator API 33 only exists with Google APIs
|
||||
arch: x86_64
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -75,10 +82,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: set up JDK 11
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
distribution: "temurin"
|
||||
cache: 'gradle'
|
||||
|
||||
@@ -86,8 +93,8 @@ jobs:
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
# workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
|
||||
emulator-build: 7425822
|
||||
target: ${{ matrix.target }}
|
||||
arch: ${{ matrix.arch }}
|
||||
script: ./gradlew connectedCheck --stacktrace
|
||||
|
||||
- name: Upload test report when tests fail # because the printed out stacktrace (console) is too short, see also #7553
|
||||
@@ -108,10 +115,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11 # Sonar requires JDK 11
|
||||
java-version: 17
|
||||
distribution: "temurin"
|
||||
cache: 'gradle'
|
||||
|
||||
|
||||
103
.github/workflows/image-minimizer.js
vendored
103
.github/workflows/image-minimizer.js
vendored
@@ -30,10 +30,12 @@ module.exports = async ({github, context}) => {
|
||||
}
|
||||
|
||||
// Regex for finding images (simple variant) 
|
||||
const REGEX_IMAGE_LOOKUP = /\!\[(.*)\]\((https:\/\/[-a-z0-9]+\.githubusercontent\.com\/\d+\/[-0-9a-f]{32,512}\.(jpg|gif|png))\)/gm;
|
||||
const REGEX_USER_CONTENT_IMAGE_LOOKUP = /\!\[(.*)\]\((https:\/\/[-a-z0-9]+\.githubusercontent\.com\/\d+\/[-0-9a-f]{32,512}\.(jpg|gif|png))\)/gm;
|
||||
const REGEX_ASSETS_IMAGE_LOCKUP = /\!\[(.*)\]\((https:\/\/github\.com\/[-\w\d]+\/[-\w\d]+\/assets\/\d+\/[\-0-9a-f]{32,512})\)/gm;
|
||||
|
||||
// Check if we found something
|
||||
let foundSimpleImages = REGEX_IMAGE_LOOKUP.test(initialBody);
|
||||
let foundSimpleImages = REGEX_USER_CONTENT_IMAGE_LOOKUP.test(initialBody)
|
||||
|| REGEX_ASSETS_IMAGE_LOCKUP.test(initialBody);
|
||||
if (!foundSimpleImages) {
|
||||
console.log('Found no simple images to process');
|
||||
return;
|
||||
@@ -47,53 +49,8 @@ module.exports = async ({github, context}) => {
|
||||
var wasMatchModified = false;
|
||||
|
||||
// Try to find and replace the images with minimized ones
|
||||
let newBody = await replaceAsync(initialBody, REGEX_IMAGE_LOOKUP, async (match, g1, g2) => {
|
||||
console.log(`Found match '${match}'`);
|
||||
|
||||
if (g1.endsWith(IGNORE_ALT_NAME_END)) {
|
||||
console.log(`Ignoring match '${match}': IGNORE_ALT_NAME_END`);
|
||||
return match;
|
||||
}
|
||||
|
||||
let probeAspectRatio = 0;
|
||||
let shouldModify = false;
|
||||
try {
|
||||
console.log(`Probing ${g2}`);
|
||||
let probeResult = await probe(g2);
|
||||
if (probeResult == null) {
|
||||
throw 'No probeResult';
|
||||
}
|
||||
if (probeResult.hUnits != 'px') {
|
||||
throw `Unexpected probeResult.hUnits (expected px but got ${probeResult.hUnits})`;
|
||||
}
|
||||
if (probeResult.height <= 0) {
|
||||
throw `Unexpected probeResult.height (height is invalid: ${probeResult.height})`;
|
||||
}
|
||||
if (probeResult.wUnits != 'px') {
|
||||
throw `Unexpected probeResult.wUnits (expected px but got ${probeResult.wUnits})`;
|
||||
}
|
||||
if (probeResult.width <= 0) {
|
||||
throw `Unexpected probeResult.width (width is invalid: ${probeResult.width})`;
|
||||
}
|
||||
console.log(`Probing resulted in ${probeResult.width}x${probeResult.height}px`);
|
||||
|
||||
probeAspectRatio = probeResult.width / probeResult.height;
|
||||
shouldModify = probeResult.height > IMG_MAX_HEIGHT_PX && probeAspectRatio < MIN_ASPECT_RATIO;
|
||||
} catch(e) {
|
||||
console.log('Probing failed:', e);
|
||||
// Immediately abort
|
||||
return match;
|
||||
}
|
||||
|
||||
if (shouldModify) {
|
||||
wasMatchModified = true;
|
||||
console.log(`Modifying match '${match}'`);
|
||||
return `<img alt="${g1}" src="${g2}" width=${Math.min(600, (IMG_MAX_HEIGHT_PX * probeAspectRatio).toFixed(0))} />`;
|
||||
}
|
||||
|
||||
console.log(`Match '${match}' is ok/will not be modified`);
|
||||
return match;
|
||||
});
|
||||
let newBody = await replaceAsync(initialBody, REGEX_USER_CONTENT_IMAGE_LOOKUP, minimizeAsync);
|
||||
newBody = await replaceAsync(newBody, REGEX_ASSETS_IMAGE_LOCKUP, minimizeAsync);
|
||||
|
||||
if (!wasMatchModified) {
|
||||
console.log('Nothing was modified. Skipping update');
|
||||
@@ -129,4 +86,52 @@ module.exports = async ({github, context}) => {
|
||||
const data = await Promise.all(promises);
|
||||
return str.replace(regex, () => data.shift());
|
||||
}
|
||||
|
||||
async function minimizeAsync(match, g1, g2) {
|
||||
console.log(`Found match '${match}'`);
|
||||
|
||||
if (g1.endsWith(IGNORE_ALT_NAME_END)) {
|
||||
console.log(`Ignoring match '${match}': IGNORE_ALT_NAME_END`);
|
||||
return match;
|
||||
}
|
||||
|
||||
let probeAspectRatio = 0;
|
||||
let shouldModify = false;
|
||||
try {
|
||||
console.log(`Probing ${g2}`);
|
||||
let probeResult = await probe(g2);
|
||||
if (probeResult == null) {
|
||||
throw 'No probeResult';
|
||||
}
|
||||
if (probeResult.hUnits != 'px') {
|
||||
throw `Unexpected probeResult.hUnits (expected px but got ${probeResult.hUnits})`;
|
||||
}
|
||||
if (probeResult.height <= 0) {
|
||||
throw `Unexpected probeResult.height (height is invalid: ${probeResult.height})`;
|
||||
}
|
||||
if (probeResult.wUnits != 'px') {
|
||||
throw `Unexpected probeResult.wUnits (expected px but got ${probeResult.wUnits})`;
|
||||
}
|
||||
if (probeResult.width <= 0) {
|
||||
throw `Unexpected probeResult.width (width is invalid: ${probeResult.width})`;
|
||||
}
|
||||
console.log(`Probing resulted in ${probeResult.width}x${probeResult.height}px`);
|
||||
|
||||
probeAspectRatio = probeResult.width / probeResult.height;
|
||||
shouldModify = probeResult.height > IMG_MAX_HEIGHT_PX && probeAspectRatio < MIN_ASPECT_RATIO;
|
||||
} catch(e) {
|
||||
console.log('Probing failed:', e);
|
||||
// Immediately abort
|
||||
return match;
|
||||
}
|
||||
|
||||
if (shouldModify) {
|
||||
wasMatchModified = true;
|
||||
console.log(`Modifying match '${match}'`);
|
||||
return `<img alt="${g1}" src="${g2}" width=${Math.min(600, (IMG_MAX_HEIGHT_PX * probeAspectRatio).toFixed(0))} />`;
|
||||
}
|
||||
|
||||
console.log(`Match '${match}' is ok/will not be modified`);
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
27
README.md
27
README.md
@@ -1,3 +1,6 @@
|
||||
<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>
|
||||
<h4 align="center">Please do <b>not</b> open pull requests for <i>new features</i> now, only bugfix PRs will be accepted.</h4>
|
||||
|
||||
<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>
|
||||
<h4 align="center">A libre lightweight streaming front-end for Android.</h4>
|
||||
@@ -25,18 +28,18 @@
|
||||
|
||||
## Screenshots
|
||||
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/00.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/00.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/01.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/02.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/03.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/04.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/05.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/06.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/07.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/08.png)
|
||||
<br/><br/>
|
||||
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/09.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/09.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/10.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/10.png)
|
||||
|
||||
### Supported Services
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ plugins {
|
||||
id "kotlin-kapt"
|
||||
id "kotlin-parcelize"
|
||||
id "checkstyle"
|
||||
id "org.sonarqube" version "3.5.0.2730"
|
||||
id "org.sonarqube" version "4.0.0.2929"
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -80,13 +80,13 @@ android {
|
||||
// Flag to enable support for the new language APIs
|
||||
coreLibraryDesugaringEnabled true
|
||||
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
encoding 'utf-8'
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11
|
||||
jvmTarget = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -96,17 +96,25 @@ android {
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
// remove two files which belong to jsoup
|
||||
// no idea how they ended up in the META-INF dir...
|
||||
excludes += ['META-INF/README.md', 'META-INF/CHANGES']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
checkstyleVersion = '10.3.1'
|
||||
checkstyleVersion = '10.12.1'
|
||||
|
||||
androidxLifecycleVersion = '2.5.1'
|
||||
androidxRoomVersion = '2.4.3'
|
||||
androidxWorkVersion = '2.7.1'
|
||||
|
||||
icepickVersion = '3.2.0'
|
||||
exoPlayerVersion = '2.18.5'
|
||||
exoPlayerVersion = '2.18.7'
|
||||
googleAutoServiceVersion = '1.0.1'
|
||||
groupieVersion = '2.10.1'
|
||||
markwonVersion = '4.6.2'
|
||||
@@ -114,7 +122,6 @@ ext {
|
||||
leakCanaryVersion = '2.9.1'
|
||||
stethoVersion = '1.6.0'
|
||||
mockitoVersion = '4.0.0'
|
||||
assertJVersion = '3.23.1'
|
||||
}
|
||||
|
||||
configurations {
|
||||
@@ -156,6 +163,7 @@ task runKtlint(type: JavaExec) {
|
||||
getMainClass().set("com.pinterest.ktlint.Main")
|
||||
classpath = configurations.ktlint
|
||||
args "src/**/*.kt"
|
||||
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
|
||||
}
|
||||
|
||||
task formatKtlint(type: JavaExec) {
|
||||
@@ -164,6 +172,7 @@ task formatKtlint(type: JavaExec) {
|
||||
getMainClass().set("com.pinterest.ktlint.Main")
|
||||
classpath = configurations.ktlint
|
||||
args "-F", "src/**/*.kt"
|
||||
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
@@ -183,7 +192,7 @@ sonar {
|
||||
|
||||
dependencies {
|
||||
/** Desugaring **/
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||
|
||||
/** NewPipe libraries **/
|
||||
// You can use a local version by uncommenting a few lines in settings.gradle
|
||||
@@ -191,7 +200,7 @@ dependencies {
|
||||
// name and the commit hash with the commit hash of the (pushed) commit you want to test
|
||||
// This works thanks to JitPack: https://jitpack.io/
|
||||
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.22.6'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:8495ad619e'
|
||||
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
|
||||
|
||||
/** Checkstyle **/
|
||||
@@ -205,7 +214,7 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.core:core-ktx:1.10.0'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.4.1'
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}"
|
||||
@@ -231,10 +240,10 @@ dependencies {
|
||||
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
||||
|
||||
// HTML parser
|
||||
implementation "org.jsoup:jsoup:1.15.3"
|
||||
implementation "org.jsoup:jsoup:1.16.1"
|
||||
|
||||
// HTTP client
|
||||
implementation "com.squareup.okhttp3:okhttp:4.10.0"
|
||||
implementation "com.squareup.okhttp3:okhttp:4.11.0"
|
||||
|
||||
// Media player
|
||||
implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}"
|
||||
@@ -263,13 +272,13 @@ dependencies {
|
||||
implementation "io.noties.markwon:linkify:${markwonVersion}"
|
||||
|
||||
// Crash reporting
|
||||
implementation "ch.acra:acra-core:5.9.7"
|
||||
implementation "ch.acra:acra-core:5.10.1"
|
||||
|
||||
// Properly restarting
|
||||
implementation 'com.jakewharton:process-phoenix:2.1.2'
|
||||
|
||||
// Reactive extensions for Java VM
|
||||
implementation "io.reactivex.rxjava3:rxjava:3.1.5"
|
||||
implementation "io.reactivex.rxjava3:rxjava:3.1.6"
|
||||
implementation "io.reactivex.rxjava3:rxandroid:3.0.2"
|
||||
// RxJava binding APIs for Android UI widgets
|
||||
implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
|
||||
@@ -291,10 +300,10 @@ dependencies {
|
||||
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
|
||||
testImplementation "org.mockito:mockito-inline:${mockitoVersion}"
|
||||
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.3"
|
||||
androidTestImplementation "androidx.test:runner:1.4.0"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.5"
|
||||
androidTestImplementation "androidx.test:runner:1.5.2"
|
||||
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
|
||||
androidTestImplementation "org.assertj:assertj-core:${assertJVersion}"
|
||||
androidTestImplementation "org.assertj:assertj-core:3.23.1"
|
||||
}
|
||||
|
||||
static String getGitWorkingBranch() {
|
||||
@@ -313,6 +322,7 @@ static String getGitWorkingBranch() {
|
||||
}
|
||||
}
|
||||
|
||||
// fix reproducible builds
|
||||
project.afterEvaluate {
|
||||
tasks.compileReleaseArtProfile.doLast {
|
||||
outputs.files.each { file ->
|
||||
|
||||
36
app/proguard-rules.pro
vendored
36
app/proguard-rules.pro
vendored
@@ -1,32 +1,18 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /home/the-scrabi/bin/Android/Sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
# https://developer.android.com/build/shrink-code
|
||||
|
||||
## Helps debug release versions
|
||||
-dontobfuscate
|
||||
|
||||
## Rules for NewPipeExtractor
|
||||
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
|
||||
|
||||
-keep class org.mozilla.javascript.** { *; }
|
||||
|
||||
-keep class org.mozilla.classfile.ClassFileWriter
|
||||
-keep class com.google.android.exoplayer2.** { *; }
|
||||
|
||||
-dontwarn org.mozilla.javascript.tools.**
|
||||
|
||||
# Rules for icepick. Copy paste from https://github.com/frankiesardo/icepick
|
||||
## Rules for ExoPlayer
|
||||
-keep class com.google.android.exoplayer2.** { *; }
|
||||
|
||||
## Rules for Icepick. Copy pasted from https://github.com/frankiesardo/icepick
|
||||
-dontwarn icepick.**
|
||||
-keep class icepick.** { *; }
|
||||
-keep class **$$Icepick { *; }
|
||||
@@ -35,11 +21,11 @@
|
||||
}
|
||||
-keepnames class * { @icepick.State *;}
|
||||
|
||||
## Rules for OkHttp. Copy paste from https://github.com/square/okhttp
|
||||
## Rules for OkHttp. Copy pasted from https://github.com/square/okhttp
|
||||
-dontwarn okhttp3.**
|
||||
-dontwarn okio.**
|
||||
##
|
||||
|
||||
## See https://github.com/TeamNewPipe/NewPipe/pull/1441
|
||||
-keepclassmembers class * implements java.io.Serializable {
|
||||
static final long serialVersionUID;
|
||||
!static !transient <fields>;
|
||||
@@ -47,5 +33,5 @@
|
||||
private void readObject(java.io.ObjectInputStream);
|
||||
}
|
||||
|
||||
# for some reason NotificationModeConfigFragment wasn't kept (only referenced in a preference xml)
|
||||
## For some reason NotificationModeConfigFragment wasn't kept (only referenced in a preference xml)
|
||||
-keep class org.schabi.newpipe.settings.notifications.** { *; }
|
||||
|
||||
@@ -357,15 +357,16 @@
|
||||
<data android:host="eduvid.org" />
|
||||
<data android:host="framatube.org" />
|
||||
<data android:host="media.assassinate-you.net" />
|
||||
<data android:host="media.fsfe.org" />
|
||||
<data android:host="peertube.co.uk" />
|
||||
<data android:host="peertube.cpy.re" />
|
||||
<data android:host="peertube.mastodon.host" />
|
||||
<data android:host="peertube.fr" />
|
||||
<data android:host="tilvids.com" />
|
||||
<data android:host="video.ploud.fr" />
|
||||
<data android:host="video.lqdn.fr" />
|
||||
<data android:host="peertube.mastodon.host" />
|
||||
<data android:host="peertube.stream" />
|
||||
<data android:host="skeptikon.fr" />
|
||||
<data android:host="media.fsfe.org" />
|
||||
<data android:host="tilvids.com" />
|
||||
<data android:host="video.lqdn.fr" />
|
||||
<data android:host="video.ploud.fr" />
|
||||
|
||||
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
|
||||
<data android:pathPrefix="/w/" /> <!-- short video URLs -->
|
||||
|
||||
@@ -63,6 +63,7 @@ import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding;
|
||||
import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
|
||||
@@ -258,8 +259,15 @@ public class MainActivity extends AppCompatActivity {
|
||||
private boolean drawerItemSelected(final MenuItem item) {
|
||||
switch (item.getGroupId()) {
|
||||
case R.id.menu_services_group:
|
||||
changeService(item);
|
||||
break;
|
||||
if (item.getItemId() == ServiceList.PeerTube.getServiceId()
|
||||
&& DeviceUtils.isTv(getApplicationContext())
|
||||
&& !item.isActionViewExpanded()) {
|
||||
((Spinner) item.getActionView()).performClick();
|
||||
return true;
|
||||
} else {
|
||||
changeService(item);
|
||||
break;
|
||||
}
|
||||
case R.id.menu_tabs_group:
|
||||
try {
|
||||
tabSelected(item);
|
||||
@@ -383,8 +391,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
|
||||
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
|
||||
|
||||
// peertube specifics
|
||||
if (s.getServiceId() == 3) {
|
||||
// PeerTube specifics
|
||||
if (s == ServiceList.PeerTube) {
|
||||
enhancePeertubeMenu(menuItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
@@ -19,7 +20,6 @@ import com.grack.nanojson.JsonParser
|
||||
import com.grack.nanojson.JsonParserException
|
||||
import org.schabi.newpipe.extractor.downloader.Response
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
||||
import org.schabi.newpipe.util.PendingIntentCompat
|
||||
import org.schabi.newpipe.util.ReleaseVersionUtil.coerceUpdateCheckExpiry
|
||||
import org.schabi.newpipe.util.ReleaseVersionUtil.isLastUpdateCheckExpired
|
||||
import org.schabi.newpipe.util.ReleaseVersionUtil.isReleaseApk
|
||||
@@ -60,7 +60,7 @@ class NewVersionWorker(
|
||||
val intent = Intent(Intent.ACTION_VIEW, apkLocationUrl?.toUri())
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
val pendingIntent = PendingIntentCompat.getActivity(
|
||||
applicationContext, 0, intent, 0
|
||||
applicationContext, 0, intent, 0, false
|
||||
)
|
||||
val channelId = applicationContext.getString(R.string.app_update_notification_channel_id)
|
||||
val notificationBuilder = NotificationCompat.Builder(applicationContext, channelId)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -176,9 +176,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
ShareUtils.openUrlInApp(this, ERROR_GITHUB_ISSUE_URL);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.decline, (dialog, which) -> {
|
||||
// do nothing
|
||||
})
|
||||
.setNegativeButton(R.string.decline, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.util.PendingIntentCompat
|
||||
|
||||
/**
|
||||
* This class contains all of the methods that should be used to let the user know that an error has
|
||||
@@ -118,7 +118,8 @@ class ErrorUtil {
|
||||
context,
|
||||
0,
|
||||
getErrorActivityIntent(context, errorInfo),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ 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.squareup.picasso.Callback;
|
||||
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.R;
|
||||
@@ -162,8 +161,12 @@ public final class VideoDetailFragment
|
||||
private boolean showRelatedItems;
|
||||
private boolean showDescription;
|
||||
private String selectedTabTag;
|
||||
@AttrRes @NonNull final List<Integer> tabIcons = new ArrayList<>();
|
||||
@StringRes @NonNull final List<Integer> tabContentDescriptions = new ArrayList<>();
|
||||
@AttrRes
|
||||
@NonNull
|
||||
final List<Integer> tabIcons = new ArrayList<>();
|
||||
@StringRes
|
||||
@NonNull
|
||||
final List<Integer> tabContentDescriptions = new ArrayList<>();
|
||||
private boolean tabSettingsChanged = false;
|
||||
private int lastAppBarVerticalOffset = Integer.MAX_VALUE; // prevents useless updates
|
||||
|
||||
@@ -645,27 +648,6 @@ public final class VideoDetailFragment
|
||||
}
|
||||
}
|
||||
|
||||
private void initThumbnailViews(@NonNull final StreamInfo info) {
|
||||
PicassoHelper.loadDetailsThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailThumbnailImageView, new Callback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
// nothing to do, the image was loaded correctly into the thumbnail
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(final Exception e) {
|
||||
showSnackBarError(new ErrorInfo(e, UserAction.LOAD_IMAGE,
|
||||
info.getThumbnailUrl(), info));
|
||||
}
|
||||
});
|
||||
|
||||
PicassoHelper.loadAvatar(info.getSubChannelAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailSubChannelThumbnailView);
|
||||
PicassoHelper.loadAvatar(info.getUploaderAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailUploaderThumbnailView);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// OwnStack
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -1040,20 +1022,10 @@ public final class VideoDetailFragment
|
||||
player.setRecovery();
|
||||
}
|
||||
|
||||
if (!useExternalAudioPlayer) {
|
||||
openNormalBackgroundPlayer(append);
|
||||
if (useExternalAudioPlayer) {
|
||||
showExternalAudioPlaybackDialog();
|
||||
} else {
|
||||
final List<AudioStream> audioStreams = getUrlAndNonTorrentStreams(
|
||||
currentInfo.getAudioStreams());
|
||||
final int index = ListHelper.getDefaultAudioFormat(activity, audioStreams);
|
||||
|
||||
if (index == -1) {
|
||||
Toast.makeText(activity, R.string.no_audio_streams_available_for_external_players,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
startOnExternalPlayer(activity, currentInfo, audioStreams.get(index));
|
||||
openNormalBackgroundPlayer(append);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1106,7 +1078,7 @@ public final class VideoDetailFragment
|
||||
|
||||
if (PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
|
||||
showExternalPlaybackDialog();
|
||||
showExternalVideoPlaybackDialog();
|
||||
} else {
|
||||
replaceQueueIfUserConfirms(this::openMainPlayer);
|
||||
}
|
||||
@@ -1486,12 +1458,9 @@ public final class VideoDetailFragment
|
||||
binding.detailSubChannelThumbnailView.setVisibility(View.GONE);
|
||||
|
||||
if (!isEmpty(info.getSubChannelName())) {
|
||||
displayBothUploaderAndSubChannel(info, activity);
|
||||
} else if (!isEmpty(info.getUploaderName())) {
|
||||
displayUploaderAsSubChannel(info, activity);
|
||||
displayBothUploaderAndSubChannel(info);
|
||||
} else {
|
||||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||
binding.detailUploaderThumbnailView.setVisibility(View.GONE);
|
||||
displayUploaderAsSubChannel(info);
|
||||
}
|
||||
|
||||
final Drawable buddyDrawable =
|
||||
@@ -1565,7 +1534,8 @@ public final class VideoDetailFragment
|
||||
binding.detailSecondaryControlPanel.setVisibility(View.GONE);
|
||||
|
||||
checkUpdateProgressInfo(info);
|
||||
initThumbnailViews(info);
|
||||
PicassoHelper.loadDetailsThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailThumbnailImageView);
|
||||
showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
|
||||
binding.detailMetaInfoSeparator, disposables);
|
||||
|
||||
@@ -1602,27 +1572,30 @@ public final class VideoDetailFragment
|
||||
noVideoStreams ? R.drawable.ic_headset_shadow : R.drawable.ic_play_arrow_shadow);
|
||||
}
|
||||
|
||||
private void displayUploaderAsSubChannel(final StreamInfo info, final Context context) {
|
||||
private void displayUploaderAsSubChannel(final StreamInfo info) {
|
||||
binding.detailSubChannelTextView.setText(info.getUploaderName());
|
||||
binding.detailSubChannelTextView.setVisibility(View.VISIBLE);
|
||||
binding.detailSubChannelTextView.setSelected(true);
|
||||
|
||||
if (info.getUploaderSubscriberCount() > -1) {
|
||||
binding.detailUploaderTextView.setText(
|
||||
Localization.shortSubscriberCount(context, info.getUploaderSubscriberCount()));
|
||||
Localization.shortSubscriberCount(activity, info.getUploaderSubscriberCount()));
|
||||
binding.detailUploaderTextView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(info.getUploaderAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailSubChannelThumbnailView);
|
||||
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
||||
binding.detailUploaderThumbnailView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void displayBothUploaderAndSubChannel(final StreamInfo info, final Context context) {
|
||||
private void displayBothUploaderAndSubChannel(final StreamInfo info) {
|
||||
binding.detailSubChannelTextView.setText(info.getSubChannelName());
|
||||
binding.detailSubChannelTextView.setVisibility(View.VISIBLE);
|
||||
binding.detailSubChannelTextView.setSelected(true);
|
||||
|
||||
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
||||
|
||||
final StringBuilder subText = new StringBuilder();
|
||||
if (!isEmpty(info.getUploaderName())) {
|
||||
subText.append(
|
||||
@@ -1633,7 +1606,7 @@ public final class VideoDetailFragment
|
||||
subText.append(Localization.DOT_SEPARATOR);
|
||||
}
|
||||
subText.append(
|
||||
Localization.shortSubscriberCount(context, info.getUploaderSubscriberCount()));
|
||||
Localization.shortSubscriberCount(activity, info.getUploaderSubscriberCount()));
|
||||
}
|
||||
|
||||
if (subText.length() > 0) {
|
||||
@@ -1643,6 +1616,13 @@ public final class VideoDetailFragment
|
||||
} else {
|
||||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(info.getSubChannelAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailSubChannelThumbnailView);
|
||||
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
||||
PicassoHelper.loadAvatar(info.getUploaderAvatarUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailUploaderThumbnailView);
|
||||
binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void openDownloadDialog() {
|
||||
@@ -2013,7 +1993,10 @@ public final class VideoDetailFragment
|
||||
restoreDefaultBrightness();
|
||||
} else {
|
||||
// Do not restore if user has disabled brightness gesture
|
||||
if (!PlayerHelper.isBrightnessGestureEnabled(activity)) {
|
||||
if (!PlayerHelper.getActionForRightGestureSide(activity)
|
||||
.equals(getString(R.string.brightness_control_key))
|
||||
&& !PlayerHelper.getActionForLeftGestureSide(activity)
|
||||
.equals(getString(R.string.brightness_control_key))) {
|
||||
return;
|
||||
}
|
||||
// Restore already saved brightness level
|
||||
@@ -2106,10 +2089,11 @@ public final class VideoDetailFragment
|
||||
.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||
onAllow.run();
|
||||
dialog.dismiss();
|
||||
}).show();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showExternalPlaybackDialog() {
|
||||
private void showExternalVideoPlaybackDialog() {
|
||||
if (currentInfo == null) {
|
||||
return;
|
||||
}
|
||||
@@ -2156,6 +2140,43 @@ public final class VideoDetailFragment
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showExternalAudioPlaybackDialog() {
|
||||
if (currentInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<AudioStream> audioStreams = getUrlAndNonTorrentStreams(
|
||||
currentInfo.getAudioStreams());
|
||||
final List<AudioStream> audioTracks =
|
||||
ListHelper.getFilteredAudioStreams(activity, audioStreams);
|
||||
|
||||
if (audioTracks.isEmpty()) {
|
||||
Toast.makeText(activity, R.string.no_audio_streams_available_for_external_players,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} else if (audioTracks.size() == 1) {
|
||||
startOnExternalPlayer(activity, currentInfo, audioTracks.get(0));
|
||||
} else {
|
||||
final int selectedAudioStream =
|
||||
ListHelper.getDefaultAudioFormat(activity, audioTracks);
|
||||
final CharSequence[] trackNames = audioTracks.stream()
|
||||
.map(audioStream -> Localization.audioTrackName(activity, audioStream))
|
||||
.toArray(CharSequence[]::new);
|
||||
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.select_audio_track_external_players)
|
||||
.setNeutralButton(R.string.open_in_browser, (dialog, i) ->
|
||||
ShareUtils.openUrlInBrowser(requireActivity(), url))
|
||||
.setSingleChoiceItems(trackNames, selectedAudioStream, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ok, (dialog, i) -> {
|
||||
final int index = ((AlertDialog) dialog).getListView()
|
||||
.getCheckedItemPosition();
|
||||
startOnExternalPlayer(activity, currentInfo, audioTracks.get(index));
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove unneeded information while waiting for a next task
|
||||
* */
|
||||
|
||||
@@ -264,8 +264,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
final boolean isThumbnailPermanent = localPlaylistManager
|
||||
.getIsPlaylistThumbnailPermanent(selectedItem.uid);
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
|
||||
final ArrayList<String> items = new ArrayList<>();
|
||||
items.add(rename);
|
||||
items.add(delete);
|
||||
@@ -289,7 +287,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
}
|
||||
};
|
||||
|
||||
builder.setItems(items.toArray(new String[0]), action).create().show();
|
||||
new AlertDialog.Builder(activity)
|
||||
.setItems(items.toArray(new String[0]), action)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showRenameDialog(final PlaylistMetadataEntry selectedItem) {
|
||||
@@ -299,14 +299,13 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
dialogBinding.dialogEditText.setText(selectedItem.name);
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setView(dialogBinding.getRoot())
|
||||
new AlertDialog.Builder(activity)
|
||||
.setView(dialogBinding.getRoot())
|
||||
.setPositiveButton(R.string.rename_playlist, (dialog, which) ->
|
||||
changeLocalPlaylistName(
|
||||
selectedItem.uid,
|
||||
dialogBinding.dialogEditText.getText().toString()))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,6 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||
}
|
||||
}
|
||||
.setPositiveButton(resources.getString(R.string.ok), null)
|
||||
.create()
|
||||
.show()
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_item_feed_toggle_played_items) {
|
||||
@@ -254,22 +253,18 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||
viewModel.getShowFutureItemsFromPreferences()
|
||||
)
|
||||
|
||||
val builder = AlertDialog.Builder(context!!)
|
||||
builder.setTitle(R.string.feed_hide_streams_title)
|
||||
builder.setMultiChoiceItems(dialogItems, checkedDialogItems) { _, which, isChecked ->
|
||||
checkedDialogItems[which] = isChecked
|
||||
}
|
||||
|
||||
builder.setPositiveButton(R.string.ok) { _, _ ->
|
||||
viewModel.setSaveShowPlayedItems(checkedDialogItems[0])
|
||||
|
||||
viewModel.setSaveShowPartiallyPlayedItems(checkedDialogItems[1])
|
||||
|
||||
viewModel.setSaveShowFutureItems(checkedDialogItems[2])
|
||||
}
|
||||
builder.setNegativeButton(R.string.cancel, null)
|
||||
|
||||
builder.create().show()
|
||||
AlertDialog.Builder(context!!)
|
||||
.setTitle(R.string.feed_hide_streams_title)
|
||||
.setMultiChoiceItems(dialogItems, checkedDialogItems) { _, which, isChecked ->
|
||||
checkedDialogItems[which] = isChecked
|
||||
}
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
viewModel.setSaveShowPlayedItems(checkedDialogItems[0])
|
||||
viewModel.setSaveShowPartiallyPlayedItems(checkedDialogItems[1])
|
||||
viewModel.setSaveShowFutureItems(checkedDialogItems[2])
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onDestroyOptionsMenu() {
|
||||
@@ -490,15 +485,13 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.feed_load_error)
|
||||
.setPositiveButton(
|
||||
R.string.unsubscribe
|
||||
) { _, _ ->
|
||||
SubscriptionManager(requireContext()).deleteSubscription(
|
||||
subscriptionEntity.serviceId, subscriptionEntity.url
|
||||
).subscribe()
|
||||
.setPositiveButton(R.string.unsubscribe) { _, _ ->
|
||||
SubscriptionManager(requireContext())
|
||||
.deleteSubscription(subscriptionEntity.serviceId, subscriptionEntity.url)
|
||||
.subscribe()
|
||||
handleItemsErrors(nextItemsErrors)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
|
||||
var message = getString(R.string.feed_load_error_account_info, subscriptionEntity.name)
|
||||
if (cause is AccountTerminatedException) {
|
||||
@@ -515,7 +508,8 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||
message += "\n" + cause.message
|
||||
}
|
||||
}
|
||||
builder.setMessage(message).create().show()
|
||||
builder.setMessage(message)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun updateRelativeTimeViews() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.squareup.picasso.Picasso
|
||||
@@ -19,7 +20,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.local.feed.service.FeedUpdateInfo
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.NavigationHelper
|
||||
import org.schabi.newpipe.util.PendingIntentCompat
|
||||
import org.schabi.newpipe.util.PicassoHelper
|
||||
|
||||
/**
|
||||
@@ -76,7 +76,8 @@ class NotificationHelper(val context: Context) {
|
||||
NavigationHelper
|
||||
.getChannelIntent(context, data.listInfo.serviceId, data.listInfo.url)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
0
|
||||
0,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
@@ -42,7 +43,6 @@ import org.schabi.newpipe.extractor.ListInfo
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
|
||||
import org.schabi.newpipe.util.PendingIntentCompat
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class FeedLoadService : Service() {
|
||||
@@ -152,8 +152,8 @@ class FeedLoadService : Service() {
|
||||
private lateinit var notificationBuilder: NotificationCompat.Builder
|
||||
|
||||
private fun createNotification(): NotificationCompat.Builder {
|
||||
val cancelActionIntent =
|
||||
PendingIntentCompat.getBroadcast(this, NOTIFICATION_ID, Intent(ACTION_CANCEL), 0)
|
||||
val cancelActionIntent = PendingIntentCompat
|
||||
.getBroadcast(this, NOTIFICATION_ID, Intent(ACTION_CANCEL), 0, false)
|
||||
|
||||
return NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
|
||||
.setOngoing(true)
|
||||
|
||||
@@ -5,7 +5,6 @@ import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||
import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.InputType;
|
||||
@@ -358,14 +357,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setMessage(R.string.remove_watched_popup_warning)
|
||||
.setTitle(R.string.remove_watched_popup_title)
|
||||
.setPositiveButton(R.string.ok,
|
||||
(DialogInterface d, int id) -> removeWatchedStreams(false))
|
||||
.setPositiveButton(R.string.ok, (d, id) ->
|
||||
removeWatchedStreams(false))
|
||||
.setNeutralButton(
|
||||
R.string.remove_watched_popup_yes_and_partially_watched_videos,
|
||||
(DialogInterface d, int id) -> removeWatchedStreams(true))
|
||||
(d, id) -> removeWatchedStreams(true))
|
||||
.setNegativeButton(R.string.cancel,
|
||||
(DialogInterface d, int id) -> d.cancel())
|
||||
.create()
|
||||
(d, id) -> d.cancel())
|
||||
.show();
|
||||
}
|
||||
} else if (item.getItemId() == R.id.menu_item_remove_duplicates) {
|
||||
@@ -560,15 +558,14 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
dialogBinding.dialogEditText.setSelection(dialogBinding.dialogEditText.getText().length());
|
||||
dialogBinding.dialogEditText.setText(name);
|
||||
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext())
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.rename_playlist)
|
||||
.setView(dialogBinding.getRoot())
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.rename, (dialogInterface, i) ->
|
||||
changePlaylistName(dialogBinding.dialogEditText.getText().toString()));
|
||||
|
||||
dialogBuilder.show();
|
||||
changePlaylistName(dialogBinding.dialogEditText.getText().toString()))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void changePlaylistName(final String title) {
|
||||
@@ -634,15 +631,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
}
|
||||
|
||||
private void openRemoveDuplicatesDialog() {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this.getActivity());
|
||||
|
||||
builder.setTitle(R.string.remove_duplicates_title)
|
||||
new AlertDialog.Builder(this.getActivity())
|
||||
.setTitle(R.string.remove_duplicates_title)
|
||||
.setMessage(R.string.remove_duplicates_message)
|
||||
.setPositiveButton(R.string.ok,
|
||||
(dialog, i) -> removeDuplicatesInPlaylist())
|
||||
.setNeutralButton(R.string.cancel, null);
|
||||
|
||||
builder.create().show();
|
||||
.setPositiveButton(R.string.ok, (dialog, i) ->
|
||||
removeDuplicatesInPlaylist())
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void removeDuplicatesInPlaylist() {
|
||||
|
||||
@@ -352,7 +352,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setCustomTitle(dialogTitleBinding.root)
|
||||
.setItems(commands, actions)
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SeekBar;
|
||||
@@ -27,11 +28,13 @@ import com.google.android.exoplayer2.PlaybackParameters;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||
import org.schabi.newpipe.player.mediaitem.MediaItemTag;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
@@ -44,6 +47,9 @@ import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class PlayQueueActivity extends AppCompatActivity
|
||||
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener,
|
||||
View.OnClickListener, PlaybackParameterDialog.Callback {
|
||||
@@ -52,6 +58,8 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
|
||||
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
|
||||
|
||||
private static final int MENU_ID_AUDIO_TRACK = 71;
|
||||
|
||||
private Player player;
|
||||
|
||||
private boolean serviceBound;
|
||||
@@ -97,6 +105,7 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
this.menu = m;
|
||||
getMenuInflater().inflate(R.menu.menu_play_queue, m);
|
||||
getMenuInflater().inflate(R.menu.menu_play_queue_bg, m);
|
||||
buildAudioTrackMenu();
|
||||
onMaybeMuteChanged();
|
||||
// to avoid null reference
|
||||
if (player != null) {
|
||||
@@ -153,6 +162,12 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
NavigationHelper.playOnBackgroundPlayer(this, player.getPlayQueue(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getGroupId() == MENU_ID_AUDIO_TRACK) {
|
||||
onAudioTrackClick(item.getItemId());
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@@ -591,4 +606,69 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
item.setIcon(player.isMuted() ? R.drawable.ic_volume_off : R.drawable.ic_volume_up);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioTrackUpdate() {
|
||||
buildAudioTrackMenu();
|
||||
}
|
||||
|
||||
private void buildAudioTrackMenu() {
|
||||
if (menu == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MenuItem audioTrackSelector = menu.findItem(R.id.action_audio_track);
|
||||
final List<AudioStream> availableStreams =
|
||||
Optional.ofNullable(player.getCurrentMetadata())
|
||||
.flatMap(MediaItemTag::getMaybeAudioTrack)
|
||||
.map(MediaItemTag.AudioTrack::getAudioStreams)
|
||||
.orElse(null);
|
||||
final Optional<AudioStream> selectedAudioStream = player.getSelectedAudioStream();
|
||||
|
||||
if (availableStreams == null || availableStreams.size() < 2
|
||||
|| selectedAudioStream.isEmpty()) {
|
||||
audioTrackSelector.setVisible(false);
|
||||
} else {
|
||||
final SubMenu audioTrackMenu = audioTrackSelector.getSubMenu();
|
||||
audioTrackMenu.clear();
|
||||
|
||||
for (int i = 0; i < availableStreams.size(); i++) {
|
||||
final AudioStream audioStream = availableStreams.get(i);
|
||||
audioTrackMenu.add(MENU_ID_AUDIO_TRACK, i, Menu.NONE,
|
||||
Localization.audioTrackName(this, audioStream));
|
||||
}
|
||||
|
||||
final AudioStream s = selectedAudioStream.get();
|
||||
final String trackName = Localization.audioTrackName(this, s);
|
||||
audioTrackSelector.setTitle(
|
||||
getString(R.string.play_queue_audio_track, trackName));
|
||||
|
||||
final String shortName = s.getAudioLocale() != null
|
||||
? s.getAudioLocale().getLanguage() : trackName;
|
||||
audioTrackSelector.setTitleCondensed(
|
||||
shortName.substring(0, Math.min(shortName.length(), 2)));
|
||||
audioTrackSelector.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an item from the audio track selector is selected.
|
||||
*
|
||||
* @param itemId index of the selected item
|
||||
*/
|
||||
private void onAudioTrackClick(final int itemId) {
|
||||
if (player.getCurrentMetadata() == null) {
|
||||
return;
|
||||
}
|
||||
player.getCurrentMetadata().getMaybeAudioTrack().ifPresent(audioTrack -> {
|
||||
final List<AudioStream> availableStreams = audioTrack.getAudioStreams();
|
||||
final int selectedStreamIndex = audioTrack.getSelectedAudioStreamIndex();
|
||||
if (selectedStreamIndex == itemId || availableStreams.size() <= itemId) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String newAudioTrack = availableStreams.get(itemId).getAudioTrackId();
|
||||
player.setAudioTrack(newAudioTrack);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,5 +11,6 @@ public interface PlayerEventListener {
|
||||
PlaybackParameters parameters);
|
||||
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
|
||||
void onMetadataUpdate(StreamInfo info, PlayQueue queue);
|
||||
default void onAudioTrackUpdate() { }
|
||||
void onServiceStopped();
|
||||
}
|
||||
|
||||
@@ -193,18 +193,20 @@ class MainPlayerGestureListener(
|
||||
isMoving = true
|
||||
|
||||
// -- Brightness and Volume control --
|
||||
val isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(player.context)
|
||||
val isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(player.context)
|
||||
if (isBrightnessGestureEnabled && isVolumeGestureEnabled) {
|
||||
if (getDisplayHalfPortion(initialEvent) === DisplayPortion.LEFT_HALF) {
|
||||
onScrollBrightness(distanceY)
|
||||
} else /* DisplayPortion.RIGHT_HALF */ {
|
||||
onScrollVolume(distanceY)
|
||||
if (getDisplayHalfPortion(initialEvent) == DisplayPortion.RIGHT_HALF) {
|
||||
when (PlayerHelper.getActionForRightGestureSide(player.context)) {
|
||||
player.context.getString(R.string.volume_control_key) ->
|
||||
onScrollVolume(distanceY)
|
||||
player.context.getString(R.string.brightness_control_key) ->
|
||||
onScrollBrightness(distanceY)
|
||||
}
|
||||
} else {
|
||||
when (PlayerHelper.getActionForLeftGestureSide(player.context)) {
|
||||
player.context.getString(R.string.volume_control_key) ->
|
||||
onScrollVolume(distanceY)
|
||||
player.context.getString(R.string.brightness_control_key) ->
|
||||
onScrollBrightness(distanceY)
|
||||
}
|
||||
} else if (isBrightnessGestureEnabled) {
|
||||
onScrollBrightness(distanceY)
|
||||
} else if (isVolumeGestureEnabled) {
|
||||
onScrollVolume(distanceY)
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.schabi.newpipe.player.helper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
|
||||
/**
|
||||
* A {@link MediaCodecVideoRenderer} which always enable the output surface workaround that
|
||||
* ExoPlayer enables on several devices which are known to implement
|
||||
* {@link android.media.MediaCodec#setOutputSurface(android.view.Surface)
|
||||
* MediaCodec.setOutputSurface(Surface)} incorrectly.
|
||||
*
|
||||
* <p>
|
||||
* See {@link MediaCodecVideoRenderer#codecNeedsSetOutputSurfaceWorkaround(String)} for more
|
||||
* details.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This custom {@link MediaCodecVideoRenderer} may be useful in the case a device is affected by
|
||||
* this issue but is not present in ExoPlayer's list.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This class has only effect on devices with Android 6 and higher, as the {@code setOutputSurface}
|
||||
* method is only implemented in these Android versions and the method used as a workaround is
|
||||
* always applied on older Android versions (releasing and re-instantiating video codec instances).
|
||||
* </p>
|
||||
*/
|
||||
public final class CustomMediaCodecVideoRenderer extends MediaCodecVideoRenderer {
|
||||
|
||||
@SuppressWarnings({"checkstyle:ParameterNumber", "squid:S107"})
|
||||
public CustomMediaCodecVideoRenderer(final Context context,
|
||||
final MediaCodecAdapter.Factory codecAdapterFactory,
|
||||
final MediaCodecSelector mediaCodecSelector,
|
||||
final long allowedJoiningTimeMs,
|
||||
final boolean enableDecoderFallback,
|
||||
@Nullable final Handler eventHandler,
|
||||
@Nullable final VideoRendererEventListener eventListener,
|
||||
final int maxDroppedFramesToNotify) {
|
||||
super(context, codecAdapterFactory, mediaCodecSelector, allowedJoiningTimeMs,
|
||||
enableDecoderFallback, eventHandler, eventListener, maxDroppedFramesToNotify);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean codecNeedsSetOutputSurfaceWorkaround(final String name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.schabi.newpipe.player.helper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A {@link DefaultRenderersFactory} which only uses {@link CustomMediaCodecVideoRenderer} as an
|
||||
* implementation of video codec renders.
|
||||
*
|
||||
* <p>
|
||||
* As no ExoPlayer extension is currently used, the reflection code used by ExoPlayer to try to
|
||||
* load video extension libraries is not needed in our case and has been removed. This should be
|
||||
* changed in the case an extension is shipped with the app, such as the AV1 one.
|
||||
* </p>
|
||||
*/
|
||||
public final class CustomRenderersFactory extends DefaultRenderersFactory {
|
||||
|
||||
public CustomRenderersFactory(final Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("checkstyle:ParameterNumber")
|
||||
@Override
|
||||
protected void buildVideoRenderers(final Context context,
|
||||
@ExtensionRendererMode final int extensionRendererMode,
|
||||
final MediaCodecSelector mediaCodecSelector,
|
||||
final boolean enableDecoderFallback,
|
||||
final Handler eventHandler,
|
||||
final VideoRendererEventListener eventListener,
|
||||
final long allowedVideoJoiningTimeMs,
|
||||
final ArrayList<Renderer> out) {
|
||||
out.add(new CustomMediaCodecVideoRenderer(context, getCodecAdapterFactory(),
|
||||
mediaCodecSelector, allowedVideoJoiningTimeMs, enableDecoderFallback, eventHandler,
|
||||
eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
|
||||
}
|
||||
}
|
||||
@@ -228,14 +228,16 @@ public final class PlayerHelper {
|
||||
.getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), false);
|
||||
}
|
||||
|
||||
public static boolean isVolumeGestureEnabled(@NonNull final Context context) {
|
||||
public static String getActionForRightGestureSide(@NonNull final Context context) {
|
||||
return getPreferences(context)
|
||||
.getBoolean(context.getString(R.string.volume_gesture_control_key), true);
|
||||
.getString(context.getString(R.string.right_gesture_control_key),
|
||||
context.getString(R.string.default_right_gesture_control_value));
|
||||
}
|
||||
|
||||
public static boolean isBrightnessGestureEnabled(@NonNull final Context context) {
|
||||
public static String getActionForLeftGestureSide(@NonNull final Context context) {
|
||||
return getPreferences(context)
|
||||
.getBoolean(context.getString(R.string.brightness_gesture_control_key), true);
|
||||
.getString(context.getString(R.string.left_gesture_control_key),
|
||||
context.getString(R.string.default_left_gesture_control_value));
|
||||
}
|
||||
|
||||
public static boolean isStartMainPlayerFullscreenEnabled(@NonNull final Context context) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.google.android.exoplayer2.MediaItem.RequestMetadata;
|
||||
import com.google.android.exoplayer2.MediaMetadata;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
@@ -55,6 +56,11 @@ public interface MediaItemTag {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
default Optional<AudioTrack> getMaybeAudioTrack() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
<T> Optional<T> getMaybeExtras(@NonNull Class<T> type);
|
||||
|
||||
<T> MediaItemTag withExtras(@NonNull T extra);
|
||||
@@ -128,4 +134,37 @@ public interface MediaItemTag {
|
||||
? null : sortedVideoStreams.get(selectedVideoStreamIndex);
|
||||
}
|
||||
}
|
||||
|
||||
final class AudioTrack {
|
||||
@NonNull
|
||||
private final List<AudioStream> audioStreams;
|
||||
private final int selectedAudioStreamIndex;
|
||||
|
||||
private AudioTrack(@NonNull final List<AudioStream> audioStreams,
|
||||
final int selectedAudioStreamIndex) {
|
||||
this.audioStreams = audioStreams;
|
||||
this.selectedAudioStreamIndex = selectedAudioStreamIndex;
|
||||
}
|
||||
|
||||
static AudioTrack of(@NonNull final List<AudioStream> audioStreams,
|
||||
final int selectedAudioStreamIndex) {
|
||||
return new AudioTrack(audioStreams, selectedAudioStreamIndex);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<AudioStream> getAudioStreams() {
|
||||
return audioStreams;
|
||||
}
|
||||
|
||||
public int getSelectedAudioStreamIndex() {
|
||||
return selectedAudioStreamIndex;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AudioStream getSelectedAudioStream() {
|
||||
return selectedAudioStreamIndex < 0
|
||||
|| selectedAudioStreamIndex >= audioStreams.size()
|
||||
? null : audioStreams.get(selectedAudioStreamIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.schabi.newpipe.player.mediaitem;
|
||||
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
@@ -25,25 +26,41 @@ public final class StreamInfoTag implements MediaItemTag {
|
||||
@Nullable
|
||||
private final MediaItemTag.Quality quality;
|
||||
@Nullable
|
||||
private final MediaItemTag.AudioTrack audioTrack;
|
||||
@Nullable
|
||||
private final Object extras;
|
||||
|
||||
private StreamInfoTag(@NonNull final StreamInfo streamInfo,
|
||||
@Nullable final MediaItemTag.Quality quality,
|
||||
@Nullable final MediaItemTag.AudioTrack audioTrack,
|
||||
@Nullable final Object extras) {
|
||||
this.streamInfo = streamInfo;
|
||||
this.quality = quality;
|
||||
this.audioTrack = audioTrack;
|
||||
this.extras = extras;
|
||||
}
|
||||
|
||||
public static StreamInfoTag of(@NonNull final StreamInfo streamInfo,
|
||||
@NonNull final List<VideoStream> sortedVideoStreams,
|
||||
final int selectedVideoStreamIndex) {
|
||||
final int selectedVideoStreamIndex,
|
||||
@NonNull final List<AudioStream> audioStreams,
|
||||
final int selectedAudioStreamIndex) {
|
||||
final Quality quality = Quality.of(sortedVideoStreams, selectedVideoStreamIndex);
|
||||
return new StreamInfoTag(streamInfo, quality, null);
|
||||
final AudioTrack audioTrack =
|
||||
AudioTrack.of(audioStreams, selectedAudioStreamIndex);
|
||||
return new StreamInfoTag(streamInfo, quality, audioTrack, null);
|
||||
}
|
||||
|
||||
public static StreamInfoTag of(@NonNull final StreamInfo streamInfo,
|
||||
@NonNull final List<AudioStream> audioStreams,
|
||||
final int selectedAudioStreamIndex) {
|
||||
final AudioTrack audioTrack =
|
||||
AudioTrack.of(audioStreams, selectedAudioStreamIndex);
|
||||
return new StreamInfoTag(streamInfo, null, audioTrack, null);
|
||||
}
|
||||
|
||||
public static StreamInfoTag of(@NonNull final StreamInfo streamInfo) {
|
||||
return new StreamInfoTag(streamInfo, null, null);
|
||||
return new StreamInfoTag(streamInfo, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,6 +120,12 @@ public final class StreamInfoTag implements MediaItemTag {
|
||||
return Optional.ofNullable(quality);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Optional<AudioTrack> getMaybeAudioTrack() {
|
||||
return Optional.ofNullable(audioTrack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getMaybeExtras(@NonNull final Class<T> type) {
|
||||
return Optional.ofNullable(extras).map(type::cast);
|
||||
@@ -110,6 +133,6 @@ public final class StreamInfoTag implements MediaItemTag {
|
||||
|
||||
@Override
|
||||
public StreamInfoTag withExtras(@NonNull final Object extra) {
|
||||
return new StreamInfoTag(streamInfo, quality, extra);
|
||||
return new StreamInfoTag(streamInfo, quality, audioTrack, extra);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.app.PendingIntentCompat;
|
||||
import androidx.core.app.ServiceCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
@@ -21,7 +22,6 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.player.Player;
|
||||
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PendingIntentCompat;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -134,7 +134,7 @@ public final class NotificationUtil {
|
||||
.setColorized(player.getPrefs().getBoolean(
|
||||
player.getContext().getString(R.string.notification_colorize_key), true))
|
||||
.setDeleteIntent(PendingIntentCompat.getBroadcast(player.getContext(),
|
||||
NOTIFICATION_ID, new Intent(ACTION_CLOSE), FLAG_UPDATE_CURRENT));
|
||||
NOTIFICATION_ID, new Intent(ACTION_CLOSE), FLAG_UPDATE_CURRENT, false));
|
||||
|
||||
// set the initial value for the video thumbnail, updatable with updateNotificationThumbnail
|
||||
setLargeIcon(builder);
|
||||
@@ -152,7 +152,7 @@ public final class NotificationUtil {
|
||||
|
||||
// also update content intent, in case the user switched players
|
||||
notificationBuilder.setContentIntent(PendingIntentCompat.getActivity(player.getContext(),
|
||||
NOTIFICATION_ID, getIntentForNotification(), FLAG_UPDATE_CURRENT));
|
||||
NOTIFICATION_ID, getIntentForNotification(), FLAG_UPDATE_CURRENT, false));
|
||||
notificationBuilder.setContentTitle(player.getVideoTitle());
|
||||
notificationBuilder.setContentText(player.getUploaderName());
|
||||
notificationBuilder.setTicker(player.getVideoTitle());
|
||||
@@ -335,7 +335,7 @@ public final class NotificationUtil {
|
||||
final String intentAction) {
|
||||
return new NotificationCompat.Action(drawable, player.getContext().getString(title),
|
||||
PendingIntentCompat.getBroadcast(player.getContext(), NOTIFICATION_ID,
|
||||
new Intent(intentAction), FLAG_UPDATE_CURRENT));
|
||||
new Intent(intentAction), FLAG_UPDATE_CURRENT, false));
|
||||
}
|
||||
|
||||
private Intent getIntentForNotification() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user