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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -68,6 +68,8 @@ import org.schabi.newpipe.util.SecondaryStreamHelper;
|
||||
import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
||||
import org.schabi.newpipe.util.AudioTrackAdapter;
|
||||
import org.schabi.newpipe.util.AudioTrackAdapter.AudioTracksWrapper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.File;
|
||||
@@ -95,12 +97,14 @@ public class DownloadDialog extends DialogFragment
|
||||
@State
|
||||
StreamInfo currentInfo;
|
||||
@State
|
||||
StreamSizeWrapper<AudioStream> wrappedAudioStreams;
|
||||
@State
|
||||
StreamSizeWrapper<VideoStream> wrappedVideoStreams;
|
||||
@State
|
||||
StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams;
|
||||
@State
|
||||
AudioTracksWrapper wrappedAudioTracks;
|
||||
@State
|
||||
int selectedAudioTrackIndex;
|
||||
@State
|
||||
int selectedVideoIndex; // set in the constructor
|
||||
@State
|
||||
int selectedAudioIndex = 0; // default to the first item
|
||||
@@ -117,6 +121,7 @@ public class DownloadDialog extends DialogFragment
|
||||
private Context context;
|
||||
private boolean askForSavePath;
|
||||
|
||||
private AudioTrackAdapter audioTrackAdapter;
|
||||
private StreamItemAdapter<AudioStream, Stream> audioStreamsAdapter;
|
||||
private StreamItemAdapter<VideoStream, AudioStream> videoStreamsAdapter;
|
||||
private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter;
|
||||
@@ -163,18 +168,26 @@ public class DownloadDialog extends DialogFragment
|
||||
public DownloadDialog(@NonNull final Context context, @NonNull final StreamInfo info) {
|
||||
this.currentInfo = info;
|
||||
|
||||
final List<AudioStream> audioStreams =
|
||||
getStreamsOfSpecifiedDelivery(info.getAudioStreams(), PROGRESSIVE_HTTP);
|
||||
final List<List<AudioStream>> groupedAudioStreams =
|
||||
ListHelper.getGroupedAudioStreams(context, audioStreams);
|
||||
this.wrappedAudioTracks = new AudioTracksWrapper(groupedAudioStreams, context);
|
||||
this.selectedAudioTrackIndex =
|
||||
ListHelper.getDefaultAudioTrackGroup(context, groupedAudioStreams);
|
||||
|
||||
// TODO: Adapt this code when the downloader support other types of stream deliveries
|
||||
final List<VideoStream> videoStreams = ListHelper.getSortedStreamVideosList(
|
||||
context,
|
||||
getStreamsOfSpecifiedDelivery(info.getVideoStreams(), PROGRESSIVE_HTTP),
|
||||
getStreamsOfSpecifiedDelivery(info.getVideoOnlyStreams(), PROGRESSIVE_HTTP),
|
||||
false,
|
||||
false
|
||||
// If there are multiple languages available, prefer streams without audio
|
||||
// to allow language selection
|
||||
wrappedAudioTracks.size() > 1
|
||||
);
|
||||
|
||||
this.wrappedVideoStreams = new StreamSizeWrapper<>(videoStreams, context);
|
||||
this.wrappedAudioStreams = new StreamSizeWrapper<>(
|
||||
getStreamsOfSpecifiedDelivery(info.getAudioStreams(), PROGRESSIVE_HTTP), context);
|
||||
this.wrappedSubtitleStreams = new StreamSizeWrapper<>(
|
||||
getStreamsOfSpecifiedDelivery(info.getSubtitles(), PROGRESSIVE_HTTP), context);
|
||||
|
||||
@@ -212,33 +225,9 @@ public class DownloadDialog extends DialogFragment
|
||||
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
|
||||
Icepick.restoreInstanceState(this, savedInstanceState);
|
||||
|
||||
final var secondaryStreams = new SparseArrayCompat<SecondaryStreamHelper<AudioStream>>(4);
|
||||
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
||||
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
if (!videoStreams.get(i).isVideoOnly()) {
|
||||
continue;
|
||||
}
|
||||
final AudioStream audioStream = SecondaryStreamHelper
|
||||
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
|
||||
|
||||
if (audioStream != null) {
|
||||
secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams,
|
||||
audioStream));
|
||||
} else if (DEBUG) {
|
||||
final MediaFormat mediaFormat = videoStreams.get(i).getFormat();
|
||||
if (mediaFormat != null) {
|
||||
Log.w(TAG, "No audio stream candidates for video format "
|
||||
+ mediaFormat.name());
|
||||
} else {
|
||||
Log.w(TAG, "No audio stream candidates for unknown video format");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.videoStreamsAdapter = new StreamItemAdapter<>(wrappedVideoStreams, secondaryStreams);
|
||||
this.audioStreamsAdapter = new StreamItemAdapter<>(wrappedAudioStreams);
|
||||
this.audioTrackAdapter = new AudioTrackAdapter(wrappedAudioTracks);
|
||||
this.subtitleStreamsAdapter = new StreamItemAdapter<>(wrappedSubtitleStreams);
|
||||
updateSecondaryStreams();
|
||||
|
||||
final Intent intent = new Intent(context, DownloadManagerService.class);
|
||||
context.startService(intent);
|
||||
@@ -265,6 +254,39 @@ public class DownloadDialog extends DialogFragment
|
||||
}, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the displayed video streams based on the selected audio track.
|
||||
*/
|
||||
private void updateSecondaryStreams() {
|
||||
final StreamSizeWrapper<AudioStream> audioStreams = getWrappedAudioStreams();
|
||||
final var secondaryStreams = new SparseArrayCompat<SecondaryStreamHelper<AudioStream>>(4);
|
||||
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
||||
wrappedVideoStreams.resetSizes();
|
||||
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
if (!videoStreams.get(i).isVideoOnly()) {
|
||||
continue;
|
||||
}
|
||||
final AudioStream audioStream = SecondaryStreamHelper
|
||||
.getAudioStreamFor(audioStreams.getStreamsList(), videoStreams.get(i));
|
||||
|
||||
if (audioStream != null) {
|
||||
secondaryStreams.append(i, new SecondaryStreamHelper<>(audioStreams, audioStream));
|
||||
} else if (DEBUG) {
|
||||
final MediaFormat mediaFormat = videoStreams.get(i).getFormat();
|
||||
if (mediaFormat != null) {
|
||||
Log.w(TAG, "No audio stream candidates for video format "
|
||||
+ mediaFormat.name());
|
||||
} else {
|
||||
Log.w(TAG, "No audio stream candidates for unknown video format");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.videoStreamsAdapter = new StreamItemAdapter<>(wrappedVideoStreams, secondaryStreams);
|
||||
this.audioStreamsAdapter = new StreamItemAdapter<>(audioStreams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
final ViewGroup container,
|
||||
@@ -285,13 +307,13 @@ public class DownloadDialog extends DialogFragment
|
||||
|
||||
dialogBinding.fileName.setText(FilenameUtils.createFilename(getContext(),
|
||||
currentInfo.getName()));
|
||||
selectedAudioIndex = ListHelper
|
||||
.getDefaultAudioFormat(getContext(), wrappedAudioStreams.getStreamsList());
|
||||
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(),
|
||||
getWrappedAudioStreams().getStreamsList());
|
||||
|
||||
selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());
|
||||
|
||||
dialogBinding.qualitySpinner.setOnItemSelectedListener(this);
|
||||
|
||||
dialogBinding.audioTrackSpinner.setOnItemSelectedListener(this);
|
||||
dialogBinding.videoAudioGroup.setOnCheckedChangeListener(this);
|
||||
|
||||
initToolbar(dialogBinding.toolbarLayout.toolbar);
|
||||
@@ -383,7 +405,7 @@ public class DownloadDialog extends DialogFragment
|
||||
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
||||
"Downloading video stream size",
|
||||
currentInfo.getServiceId()))));
|
||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams)
|
||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(getWrappedAudioStreams())
|
||||
.subscribe(result -> {
|
||||
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
|
||||
== R.id.audio_button) {
|
||||
@@ -405,14 +427,28 @@ public class DownloadDialog extends DialogFragment
|
||||
currentInfo.getServiceId()))));
|
||||
}
|
||||
|
||||
private void setupAudioTrackSpinner() {
|
||||
if (getContext() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dialogBinding.audioTrackSpinner.setAdapter(audioTrackAdapter);
|
||||
dialogBinding.audioTrackSpinner.setSelection(selectedAudioTrackIndex);
|
||||
}
|
||||
|
||||
private void setupAudioSpinner() {
|
||||
if (getContext() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dialogBinding.qualitySpinner.setAdapter(audioStreamsAdapter);
|
||||
dialogBinding.qualitySpinner.setSelection(selectedAudioIndex);
|
||||
dialogBinding.qualitySpinner.setVisibility(View.GONE);
|
||||
setRadioButtonsState(true);
|
||||
dialogBinding.audioStreamSpinner.setAdapter(audioStreamsAdapter);
|
||||
dialogBinding.audioStreamSpinner.setSelection(selectedAudioIndex);
|
||||
dialogBinding.audioStreamSpinner.setVisibility(View.VISIBLE);
|
||||
dialogBinding.audioTrackSpinner.setVisibility(
|
||||
wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE);
|
||||
dialogBinding.audioTrackPresentInVideoText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setupVideoSpinner() {
|
||||
@@ -422,7 +458,19 @@ public class DownloadDialog extends DialogFragment
|
||||
|
||||
dialogBinding.qualitySpinner.setAdapter(videoStreamsAdapter);
|
||||
dialogBinding.qualitySpinner.setSelection(selectedVideoIndex);
|
||||
dialogBinding.qualitySpinner.setVisibility(View.VISIBLE);
|
||||
setRadioButtonsState(true);
|
||||
dialogBinding.audioStreamSpinner.setVisibility(View.GONE);
|
||||
onVideoStreamSelected();
|
||||
}
|
||||
|
||||
private void onVideoStreamSelected() {
|
||||
final boolean isVideoOnly = videoStreamsAdapter.getItem(selectedVideoIndex).isVideoOnly();
|
||||
|
||||
dialogBinding.audioTrackSpinner.setVisibility(
|
||||
isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE);
|
||||
dialogBinding.audioTrackPresentInVideoText.setVisibility(
|
||||
!isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void setupSubtitleSpinner() {
|
||||
@@ -432,7 +480,11 @@ public class DownloadDialog extends DialogFragment
|
||||
|
||||
dialogBinding.qualitySpinner.setAdapter(subtitleStreamsAdapter);
|
||||
dialogBinding.qualitySpinner.setSelection(selectedSubtitleIndex);
|
||||
dialogBinding.qualitySpinner.setVisibility(View.VISIBLE);
|
||||
setRadioButtonsState(true);
|
||||
dialogBinding.audioStreamSpinner.setVisibility(View.GONE);
|
||||
dialogBinding.audioTrackSpinner.setVisibility(View.GONE);
|
||||
dialogBinding.audioTrackPresentInVideoText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
|
||||
@@ -550,18 +602,31 @@ public class DownloadDialog extends DialogFragment
|
||||
+ "parent = [" + parent + "], view = [" + view + "], "
|
||||
+ "position = [" + position + "], id = [" + id + "]");
|
||||
}
|
||||
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
|
||||
switch (parent.getId()) {
|
||||
case R.id.quality_spinner:
|
||||
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.video_button:
|
||||
selectedVideoIndex = position;
|
||||
onVideoStreamSelected();
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
selectedSubtitleIndex = position;
|
||||
break;
|
||||
}
|
||||
onItemSelectedSetFileName();
|
||||
break;
|
||||
case R.id.audio_track_spinner:
|
||||
final boolean trackChanged = selectedAudioTrackIndex != position;
|
||||
selectedAudioTrackIndex = position;
|
||||
if (trackChanged) {
|
||||
updateSecondaryStreams();
|
||||
fetchStreamsSize();
|
||||
}
|
||||
break;
|
||||
case R.id.audio_stream_spinner:
|
||||
selectedAudioIndex = position;
|
||||
break;
|
||||
case R.id.video_button:
|
||||
selectedVideoIndex = position;
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
selectedSubtitleIndex = position;
|
||||
break;
|
||||
}
|
||||
onItemSelectedSetFileName();
|
||||
}
|
||||
|
||||
private void onItemSelectedSetFileName() {
|
||||
@@ -607,6 +672,7 @@ public class DownloadDialog extends DialogFragment
|
||||
|
||||
protected void setupDownloadOptions() {
|
||||
setRadioButtonsState(false);
|
||||
setupAudioTrackSpinner();
|
||||
|
||||
final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0;
|
||||
final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0;
|
||||
@@ -657,6 +723,13 @@ public class DownloadDialog extends DialogFragment
|
||||
dialogBinding.subtitleButton.setEnabled(enabled);
|
||||
}
|
||||
|
||||
private StreamSizeWrapper<AudioStream> getWrappedAudioStreams() {
|
||||
if (selectedAudioTrackIndex < 0 || selectedAudioTrackIndex > wrappedAudioTracks.size()) {
|
||||
return StreamSizeWrapper.empty();
|
||||
}
|
||||
return wrappedAudioTracks.getTracksList().get(selectedAudioTrackIndex);
|
||||
}
|
||||
|
||||
private int getSubtitleIndexBy(@NonNull final List<SubtitlesStream> streams) {
|
||||
final Localization preferredLocalization = NewPipe.getPreferredLocalization();
|
||||
|
||||
@@ -697,7 +770,6 @@ public class DownloadDialog extends DialogFragment
|
||||
.setTitle(R.string.general_error)
|
||||
.setMessage(msg)
|
||||
.setNegativeButton(getString(R.string.ok), null)
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -910,7 +982,7 @@ public class DownloadDialog extends DialogFragment
|
||||
break;
|
||||
}
|
||||
|
||||
askDialog.create().show();
|
||||
askDialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -954,7 +1026,7 @@ public class DownloadDialog extends DialogFragment
|
||||
}
|
||||
});
|
||||
|
||||
askDialog.create().show();
|
||||
askDialog.show();
|
||||
}
|
||||
|
||||
private void continueSelectedDownload(@NonNull final StoredFileHelper storage) {
|
||||
@@ -1013,7 +1085,6 @@ public class DownloadDialog extends DialogFragment
|
||||
psName = Postprocessing.ALGORITHM_WEBM_MUXER;
|
||||
}
|
||||
|
||||
psArgs = null;
|
||||
final long videoSize = wrappedVideoStreams.getSizeInBytes(
|
||||
(VideoStream) selectedStream);
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player.PositionInfo;
|
||||
import com.google.android.exoplayer2.RenderersFactory;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.Tracks;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
@@ -77,7 +76,6 @@ import com.google.android.exoplayer2.text.CueGroup;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoSize;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Target;
|
||||
@@ -88,6 +86,7 @@ import org.schabi.newpipe.databinding.PlayerBinding;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
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;
|
||||
@@ -96,6 +95,7 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
|
||||
import org.schabi.newpipe.player.helper.AudioReactor;
|
||||
import org.schabi.newpipe.player.helper.CustomRenderersFactory;
|
||||
import org.schabi.newpipe.player.helper.LoadController;
|
||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
@@ -115,7 +115,6 @@ import org.schabi.newpipe.player.ui.PlayerUiList;
|
||||
import org.schabi.newpipe.player.ui.PopupPlayerUi;
|
||||
import org.schabi.newpipe.player.ui.VideoPlayerUi;
|
||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PicassoHelper;
|
||||
@@ -181,13 +180,18 @@ public final class Player implements PlaybackListener, Listener {
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
// play queue might be null e.g. while player is starting
|
||||
@Nullable private PlayQueue playQueue;
|
||||
@Nullable
|
||||
private PlayQueue playQueue;
|
||||
|
||||
@Nullable private MediaSourceManager playQueueManager;
|
||||
@Nullable
|
||||
private MediaSourceManager playQueueManager;
|
||||
|
||||
@Nullable private PlayQueueItem currentItem;
|
||||
@Nullable private MediaItemTag currentMetadata;
|
||||
@Nullable private Bitmap currentThumbnail;
|
||||
@Nullable
|
||||
private PlayQueueItem currentItem;
|
||||
@Nullable
|
||||
private MediaItemTag currentMetadata;
|
||||
@Nullable
|
||||
private Bitmap currentThumbnail;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Player
|
||||
@@ -196,12 +200,17 @@ public final class Player implements PlaybackListener, Listener {
|
||||
private ExoPlayer simpleExoPlayer;
|
||||
private AudioReactor audioReactor;
|
||||
|
||||
@NonNull private final DefaultTrackSelector trackSelector;
|
||||
@NonNull private final LoadController loadController;
|
||||
@NonNull private final RenderersFactory renderFactory;
|
||||
@NonNull
|
||||
private final DefaultTrackSelector trackSelector;
|
||||
@NonNull
|
||||
private final LoadController loadController;
|
||||
@NonNull
|
||||
private final DefaultRenderersFactory renderFactory;
|
||||
|
||||
@NonNull private final VideoPlaybackResolver videoResolver;
|
||||
@NonNull private final AudioPlaybackResolver audioResolver;
|
||||
@NonNull
|
||||
private final VideoPlaybackResolver videoResolver;
|
||||
@NonNull
|
||||
private final AudioPlaybackResolver audioResolver;
|
||||
|
||||
private final PlayerService service; //TODO try to remove and replace everything with context
|
||||
|
||||
@@ -226,24 +235,32 @@ public final class Player implements PlaybackListener, Listener {
|
||||
|
||||
private BroadcastReceiver broadcastReceiver;
|
||||
private IntentFilter intentFilter;
|
||||
@Nullable private PlayerServiceEventListener fragmentListener = null;
|
||||
@Nullable private PlayerEventListener activityListener = null;
|
||||
@Nullable
|
||||
private PlayerServiceEventListener fragmentListener = null;
|
||||
@Nullable
|
||||
private PlayerEventListener activityListener = null;
|
||||
|
||||
@NonNull private final SerialDisposable progressUpdateDisposable = new SerialDisposable();
|
||||
@NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable();
|
||||
@NonNull
|
||||
private final SerialDisposable progressUpdateDisposable = new SerialDisposable();
|
||||
@NonNull
|
||||
private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable();
|
||||
|
||||
// This is the only listener we need for thumbnail loading, since there is always at most only
|
||||
// one thumbnail being loaded at a time. This field is also here to maintain a strong reference,
|
||||
// which would otherwise be garbage collected since Picasso holds weak references to targets.
|
||||
@NonNull private final Target currentThumbnailTarget;
|
||||
@NonNull
|
||||
private final Target currentThumbnailTarget;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@NonNull private final Context context;
|
||||
@NonNull private final SharedPreferences prefs;
|
||||
@NonNull private final HistoryRecordManager recordManager;
|
||||
@NonNull
|
||||
private final Context context;
|
||||
@NonNull
|
||||
private final SharedPreferences prefs;
|
||||
@NonNull
|
||||
private final HistoryRecordManager recordManager;
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -263,7 +280,16 @@ public final class Player implements PlaybackListener, Listener {
|
||||
final PlayerDataSource dataSource = new PlayerDataSource(context,
|
||||
new DefaultBandwidthMeter.Builder(context).build());
|
||||
loadController = new LoadController();
|
||||
renderFactory = new DefaultRenderersFactory(context);
|
||||
|
||||
renderFactory = prefs.getBoolean(
|
||||
context.getString(
|
||||
R.string.always_use_exoplayer_set_output_surface_workaround_key), false)
|
||||
? new CustomRenderersFactory(context) : new DefaultRenderersFactory(context);
|
||||
|
||||
renderFactory.setEnableDecoderFallback(
|
||||
prefs.getBoolean(
|
||||
context.getString(
|
||||
R.string.use_exoplayer_decoder_fallback_key), false));
|
||||
|
||||
videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
|
||||
audioResolver = new AudioPlaybackResolver(context, dataSource);
|
||||
@@ -326,7 +352,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||
isAudioOnly = audioPlayerSelected();
|
||||
|
||||
if (intent.hasExtra(PLAYBACK_QUALITY)) {
|
||||
setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
|
||||
videoResolver.setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
|
||||
}
|
||||
|
||||
// Resolve enqueue intents
|
||||
@@ -334,7 +360,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||
playQueue.append(newQueue.getStreams());
|
||||
return;
|
||||
|
||||
// Resolve enqueue next intents
|
||||
// Resolve enqueue next intents
|
||||
} else if (intent.getBooleanExtra(ENQUEUE_NEXT, false) && playQueue != null) {
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
playQueue.append(newQueue.getStreams());
|
||||
@@ -520,16 +546,11 @@ public final class Player implements PlaybackListener, Listener {
|
||||
// Setup UIs
|
||||
UIs.call(PlayerUi::initPlayer);
|
||||
|
||||
// enable media tunneling
|
||||
if (DEBUG && PreferenceManager.getDefaultSharedPreferences(context)
|
||||
// Disable media tunneling if requested by the user from ExoPlayer settings
|
||||
if (!PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(context.getString(R.string.disable_media_tunneling_key), false)) {
|
||||
Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] "
|
||||
+ "media tunneling disabled in debug preferences");
|
||||
} else if (DeviceUtils.shouldSupportMediaTunneling()) {
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setTunnelingEnabled(true));
|
||||
} else if (DEBUG) {
|
||||
Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling");
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
@@ -911,7 +932,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||
|
||||
private Disposable getProgressUpdateDisposable() {
|
||||
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS,
|
||||
AndroidSchedulers.mainThread())
|
||||
AndroidSchedulers.mainThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> triggerProgressUpdate(),
|
||||
error -> Log.e(TAG, "Progress update failure: ", error));
|
||||
@@ -920,7 +941,6 @@ public final class Player implements PlaybackListener, Listener {
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback states
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -1242,6 +1262,9 @@ public final class Player implements PlaybackListener, Listener {
|
||||
}
|
||||
final StreamInfo previousInfo = Optional.ofNullable(currentMetadata)
|
||||
.flatMap(MediaItemTag::getMaybeStreamInfo).orElse(null);
|
||||
final MediaItemTag.AudioTrack previousAudioTrack =
|
||||
Optional.ofNullable(currentMetadata)
|
||||
.flatMap(MediaItemTag::getMaybeAudioTrack).orElse(null);
|
||||
currentMetadata = tag;
|
||||
|
||||
if (!currentMetadata.getErrors().isEmpty()) {
|
||||
@@ -1262,6 +1285,12 @@ public final class Player implements PlaybackListener, Listener {
|
||||
if (previousInfo == null || !previousInfo.getUrl().equals(info.getUrl())) {
|
||||
// only update with the new stream info if it has actually changed
|
||||
updateMetadataWith(info);
|
||||
} else if (previousAudioTrack == null
|
||||
|| tag.getMaybeAudioTrack()
|
||||
.map(t -> t.getSelectedAudioStreamIndex()
|
||||
!= previousAudioTrack.getSelectedAudioStreamIndex())
|
||||
.orElse(false)) {
|
||||
notifyAudioTrackUpdateToListeners();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1349,6 +1378,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||
// Errors
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
//region Errors
|
||||
|
||||
/**
|
||||
* Process exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}.
|
||||
* <p>There are multiple types of errors:</p>
|
||||
@@ -1375,8 +1405,9 @@ public final class Player implements PlaybackListener, Listener {
|
||||
* For any error above that is <b>not</b> explicitly <b>catchable</b>, the player will
|
||||
* create a notification so users are aware.
|
||||
* </ul>
|
||||
*
|
||||
* @see com.google.android.exoplayer2.Player.Listener#onPlayerError(PlaybackException)
|
||||
* */
|
||||
*/
|
||||
// Any error code not explicitly covered here are either unrelated to NewPipe use case
|
||||
// (e.g. DRM) or not recoverable (e.g. Decoder error). In both cases, the player should
|
||||
// shutdown.
|
||||
@@ -1758,6 +1789,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||
registerStreamViewed();
|
||||
|
||||
notifyMetadataUpdateToListeners();
|
||||
notifyAudioTrackUpdateToListeners();
|
||||
UIs.call(playerUi -> playerUi.onMetadataChanged(info));
|
||||
}
|
||||
|
||||
@@ -1886,6 +1918,12 @@ public final class Player implements PlaybackListener, Listener {
|
||||
.map(quality -> quality.getSortedVideoStreams()
|
||||
.get(quality.getSelectedVideoStreamIndex()));
|
||||
}
|
||||
|
||||
public Optional<AudioStream> getSelectedAudioStream() {
|
||||
return Optional.ofNullable(currentMetadata)
|
||||
.flatMap(MediaItemTag::getMaybeAudioTrack)
|
||||
.map(MediaItemTag.AudioTrack::getSelectedAudioStream);
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
||||
@@ -2017,6 +2055,15 @@ public final class Player implements PlaybackListener, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyAudioTrackUpdateToListeners() {
|
||||
if (fragmentListener != null) {
|
||||
fragmentListener.onAudioTrackUpdate();
|
||||
}
|
||||
if (activityListener != null) {
|
||||
activityListener.onAudioTrackUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void useVideoSource(final boolean videoEnabled) {
|
||||
if (playQueue == null || isAudioOnly == !videoEnabled || audioPlayerSelected()) {
|
||||
return;
|
||||
@@ -2113,7 +2160,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||
// because the stream source will be probably the same as the current played
|
||||
if (sourceType == SourceType.VIDEO_WITH_SEPARATED_AUDIO
|
||||
|| (sourceType == SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY
|
||||
&& isNullOrEmpty(streamInfo.getAudioStreams()))) {
|
||||
&& isNullOrEmpty(streamInfo.getAudioStreams()))) {
|
||||
// It's not needed to reload the play queue manager only if the content's stream type
|
||||
// is a video stream, a live stream or an ended live stream
|
||||
return !StreamTypeUtil.isVideo(streamType);
|
||||
@@ -2175,7 +2222,18 @@ public final class Player implements PlaybackListener, Listener {
|
||||
}
|
||||
|
||||
public void setPlaybackQuality(@Nullable final String quality) {
|
||||
saveStreamProgressState();
|
||||
setRecovery();
|
||||
videoResolver.setPlaybackQuality(quality);
|
||||
reloadPlayQueueManager();
|
||||
}
|
||||
|
||||
public void setAudioTrack(@Nullable final String audioTrackId) {
|
||||
saveStreamProgressState();
|
||||
setRecovery();
|
||||
videoResolver.setAudioTrack(audioTrackId);
|
||||
audioResolver.setAudioTrack(audioTrackId);
|
||||
reloadPlayQueueManager();
|
||||
}
|
||||
|
||||
|
||||
@@ -2253,7 +2311,7 @@ public final class Player implements PlaybackListener, Listener {
|
||||
|
||||
/**
|
||||
* Get the video renderer index of the current playing stream.
|
||||
*
|
||||
* <p>
|
||||
* This method returns the video renderer index of the current
|
||||
* {@link MappingTrackSelector.MappedTrackInfo} or {@link #RENDERER_UNAVAILABLE} if the current
|
||||
* {@link MappingTrackSelector.MappedTrackInfo} is null or if there is no video renderer index.
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.schabi.newpipe.player.resolver;
|
||||
|
||||
import static org.schabi.newpipe.util.ListHelper.getNonTorrentStreams;
|
||||
import static org.schabi.newpipe.util.ListHelper.getFilteredAudioStreams;
|
||||
import static org.schabi.newpipe.util.ListHelper.getPlayableStreams;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
@@ -28,6 +29,8 @@ public class AudioPlaybackResolver implements PlaybackResolver {
|
||||
private final Context context;
|
||||
@NonNull
|
||||
private final PlayerDataSource dataSource;
|
||||
@Nullable
|
||||
private String audioTrack;
|
||||
|
||||
public AudioPlaybackResolver(@NonNull final Context context,
|
||||
@NonNull final PlayerDataSource dataSource) {
|
||||
@@ -35,6 +38,13 @@ public class AudioPlaybackResolver implements PlaybackResolver {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a media source providing audio. If a service has no separate {@link AudioStream}s we
|
||||
* use a video stream as audio source to support audio background playback.
|
||||
*
|
||||
* @param info of the stream
|
||||
* @return the audio source to use or null if none could be found
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource resolve(@NonNull final StreamInfo info) {
|
||||
@@ -43,12 +53,27 @@ public class AudioPlaybackResolver implements PlaybackResolver {
|
||||
return liveSource;
|
||||
}
|
||||
|
||||
final Stream stream = getAudioSource(info);
|
||||
if (stream == null) {
|
||||
return null;
|
||||
}
|
||||
final List<AudioStream> audioStreams =
|
||||
getFilteredAudioStreams(context, info.getAudioStreams());
|
||||
final Stream stream;
|
||||
final MediaItemTag tag;
|
||||
|
||||
final MediaItemTag tag = StreamInfoTag.of(info);
|
||||
if (!audioStreams.isEmpty()) {
|
||||
final int audioIndex =
|
||||
ListHelper.getAudioFormatIndex(context, audioStreams, audioTrack);
|
||||
stream = getStreamForIndex(audioIndex, audioStreams);
|
||||
tag = StreamInfoTag.of(info, audioStreams, audioIndex);
|
||||
} else {
|
||||
final List<VideoStream> videoStreams =
|
||||
getPlayableStreams(info.getVideoStreams(), info.getServiceId());
|
||||
if (!videoStreams.isEmpty()) {
|
||||
final int index = ListHelper.getDefaultResolutionIndex(context, videoStreams);
|
||||
stream = getStreamForIndex(index, videoStreams);
|
||||
tag = StreamInfoTag.of(info);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return PlaybackResolver.buildMediaSource(
|
||||
@@ -59,29 +84,6 @@ public class AudioPlaybackResolver implements PlaybackResolver {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a stream to be played as audio. If a service has no separate {@link AudioStream}s we
|
||||
* use a video stream as audio source to support audio background playback.
|
||||
*
|
||||
* @param info of the stream
|
||||
* @return the audio source to use or null if none could be found
|
||||
*/
|
||||
@Nullable
|
||||
private Stream getAudioSource(@NonNull final StreamInfo info) {
|
||||
final List<AudioStream> audioStreams = getNonTorrentStreams(info.getAudioStreams());
|
||||
if (!audioStreams.isEmpty()) {
|
||||
final int index = ListHelper.getDefaultAudioFormat(context, audioStreams);
|
||||
return getStreamForIndex(index, audioStreams);
|
||||
} else {
|
||||
final List<VideoStream> videoStreams = getNonTorrentStreams(info.getVideoStreams());
|
||||
if (!videoStreams.isEmpty()) {
|
||||
final int index = ListHelper.getDefaultResolutionIndex(context, videoStreams);
|
||||
return getStreamForIndex(index, videoStreams);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Stream getStreamForIndex(final int index, @NonNull final List<? extends Stream> streams) {
|
||||
if (index >= 0 && index < streams.size()) {
|
||||
@@ -89,4 +91,13 @@ public class AudioPlaybackResolver implements PlaybackResolver {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAudioTrack() {
|
||||
return audioTrack;
|
||||
}
|
||||
|
||||
public void setAudioTrack(@Nullable final String audioLanguage) {
|
||||
this.audioTrack = audioLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +156,16 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
|
||||
cacheKey.append(audioStream.getAverageBitrate());
|
||||
}
|
||||
|
||||
if (audioStream.getAudioTrackId() != null) {
|
||||
cacheKey.append(" ");
|
||||
cacheKey.append(audioStream.getAudioTrackId());
|
||||
}
|
||||
|
||||
if (audioStream.getAudioLocale() != null) {
|
||||
cacheKey.append(" ");
|
||||
cacheKey.append(audioStream.getAudioLocale().getISO3Language());
|
||||
}
|
||||
|
||||
return cacheKey.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,9 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.android.exoplayer2.C.TIME_UNSET;
|
||||
import static org.schabi.newpipe.util.ListHelper.getFilteredAudioStreams;
|
||||
import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams;
|
||||
import static org.schabi.newpipe.util.ListHelper.getNonTorrentStreams;
|
||||
import static org.schabi.newpipe.util.ListHelper.getPlayableStreams;
|
||||
|
||||
public class VideoPlaybackResolver implements PlaybackResolver {
|
||||
private static final String TAG = VideoPlaybackResolver.class.getSimpleName();
|
||||
@@ -44,6 +45,8 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
||||
|
||||
@Nullable
|
||||
private String playbackQuality;
|
||||
@Nullable
|
||||
private String audioTrack;
|
||||
|
||||
public enum SourceType {
|
||||
LIVE_STREAM,
|
||||
@@ -72,21 +75,31 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
||||
|
||||
// Create video stream source
|
||||
final List<VideoStream> videoStreamsList = ListHelper.getSortedStreamVideosList(context,
|
||||
getNonTorrentStreams(info.getVideoStreams()),
|
||||
getNonTorrentStreams(info.getVideoOnlyStreams()), false, true);
|
||||
final int index;
|
||||
getPlayableStreams(info.getVideoStreams(), info.getServiceId()),
|
||||
getPlayableStreams(info.getVideoOnlyStreams(), info.getServiceId()), false, true);
|
||||
final List<AudioStream> audioStreamsList =
|
||||
getFilteredAudioStreams(context, info.getAudioStreams());
|
||||
|
||||
final int videoIndex;
|
||||
if (videoStreamsList.isEmpty()) {
|
||||
index = -1;
|
||||
videoIndex = -1;
|
||||
} else if (playbackQuality == null) {
|
||||
index = qualityResolver.getDefaultResolutionIndex(videoStreamsList);
|
||||
videoIndex = qualityResolver.getDefaultResolutionIndex(videoStreamsList);
|
||||
} else {
|
||||
index = qualityResolver.getOverrideResolutionIndex(videoStreamsList,
|
||||
videoIndex = qualityResolver.getOverrideResolutionIndex(videoStreamsList,
|
||||
getPlaybackQuality());
|
||||
}
|
||||
final MediaItemTag tag = StreamInfoTag.of(info, videoStreamsList, index);
|
||||
|
||||
final int audioIndex =
|
||||
ListHelper.getAudioFormatIndex(context, audioStreamsList, audioTrack);
|
||||
final MediaItemTag tag =
|
||||
StreamInfoTag.of(info, videoStreamsList, videoIndex, audioStreamsList, audioIndex);
|
||||
@Nullable final VideoStream video = tag.getMaybeQuality()
|
||||
.map(MediaItemTag.Quality::getSelectedVideoStream)
|
||||
.orElse(null);
|
||||
@Nullable final AudioStream audio = tag.getMaybeAudioTrack()
|
||||
.map(MediaItemTag.AudioTrack::getSelectedAudioStream)
|
||||
.orElse(null);
|
||||
|
||||
if (video != null) {
|
||||
try {
|
||||
@@ -99,14 +112,9 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
||||
}
|
||||
}
|
||||
|
||||
// Create optional audio stream source
|
||||
final List<AudioStream> audioStreams = getNonTorrentStreams(info.getAudioStreams());
|
||||
final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get(
|
||||
ListHelper.getDefaultAudioFormat(context, audioStreams));
|
||||
|
||||
// Use the audio stream if there is no video stream, or
|
||||
// merge with audio stream in case if video does not contain audio
|
||||
if (audio != null && (video == null || video.isVideoOnly())) {
|
||||
if (audio != null && (video == null || video.isVideoOnly() || audioTrack != null)) {
|
||||
try {
|
||||
final MediaSource audioSource = PlaybackResolver.buildMediaSource(
|
||||
dataSource, audio, info, PlaybackResolver.cacheKeyOf(info, audio), tag);
|
||||
@@ -179,6 +187,15 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
||||
this.playbackQuality = playbackQuality;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAudioTrack() {
|
||||
return audioTrack;
|
||||
}
|
||||
|
||||
public void setAudioTrack(@Nullable final String audioLanguage) {
|
||||
this.audioTrack = audioLanguage;
|
||||
}
|
||||
|
||||
public interface QualityResolver {
|
||||
int getDefaultResolutionIndex(List<VideoStream> sortedVideos);
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.PlayerBinding;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
@@ -78,6 +79,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper;
|
||||
import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
@@ -108,7 +110,8 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
|
||||
protected PlayerBinding binding;
|
||||
private final Handler controlsVisibilityHandler = new Handler(Looper.getMainLooper());
|
||||
@Nullable private SurfaceHolderCallback surfaceHolderCallback;
|
||||
@Nullable
|
||||
private SurfaceHolderCallback surfaceHolderCallback;
|
||||
boolean surfaceIsSetup = false;
|
||||
|
||||
|
||||
@@ -117,11 +120,13 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static final int POPUP_MENU_ID_QUALITY = 69;
|
||||
private static final int POPUP_MENU_ID_AUDIO_TRACK = 70;
|
||||
private static final int POPUP_MENU_ID_PLAYBACK_SPEED = 79;
|
||||
private static final int POPUP_MENU_ID_CAPTION = 89;
|
||||
|
||||
protected boolean isSomePopupMenuVisible = false;
|
||||
private PopupMenu qualityPopupMenu;
|
||||
private PopupMenu audioTrackPopupMenu;
|
||||
protected PopupMenu playbackSpeedPopupMenu;
|
||||
private PopupMenu captionPopupMenu;
|
||||
|
||||
@@ -146,7 +151,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
//region Constructor, setup, destroy
|
||||
|
||||
protected VideoPlayerUi(@NonNull final Player player,
|
||||
@NonNull final PlayerBinding playerBinding) {
|
||||
@NonNull final PlayerBinding playerBinding) {
|
||||
super(player);
|
||||
binding = playerBinding;
|
||||
setupFromView();
|
||||
@@ -173,6 +178,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
R.style.DarkPopupMenu);
|
||||
|
||||
qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView);
|
||||
audioTrackPopupMenu = new PopupMenu(themeWrapper, binding.audioTrackTextView);
|
||||
playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed);
|
||||
captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView);
|
||||
|
||||
@@ -190,6 +196,8 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
|
||||
protected void initListeners() {
|
||||
binding.qualityTextView.setOnClickListener(makeOnClickListener(this::onQualityClicked));
|
||||
binding.audioTrackTextView.setOnClickListener(
|
||||
makeOnClickListener(this::onAudioTracksClicked));
|
||||
binding.playbackSpeed.setOnClickListener(makeOnClickListener(this::onPlaybackSpeedClicked));
|
||||
|
||||
binding.playbackSeekBar.setOnSeekBarChangeListener(this);
|
||||
@@ -266,6 +274,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
|
||||
protected void deinitListeners() {
|
||||
binding.qualityTextView.setOnClickListener(null);
|
||||
binding.audioTrackTextView.setOnClickListener(null);
|
||||
binding.playbackSpeed.setOnClickListener(null);
|
||||
binding.playbackSeekBar.setOnSeekBarChangeListener(null);
|
||||
binding.captionTextView.setOnClickListener(null);
|
||||
@@ -419,6 +428,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
binding.topControls.setPaddingRelative(controlsPad, playerTopPad, controlsPad, 0);
|
||||
binding.bottomControls.setPaddingRelative(controlsPad, 0, controlsPad, 0);
|
||||
binding.qualityTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad);
|
||||
binding.audioTrackTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad);
|
||||
binding.playbackSpeed.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad);
|
||||
binding.playbackSpeed.setMinimumWidth(buttonsMinWidth);
|
||||
binding.captionTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad);
|
||||
@@ -524,6 +534,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
|
||||
/**
|
||||
* Sets the current duration into the corresponding elements.
|
||||
*
|
||||
* @param currentProgress the current progress, in milliseconds
|
||||
*/
|
||||
private void updatePlayBackElementsCurrentDuration(final int currentProgress) {
|
||||
@@ -536,6 +547,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
|
||||
/**
|
||||
* Sets the video duration time into all control components (e.g. seekbar).
|
||||
*
|
||||
* @param duration the video duration, in milliseconds
|
||||
*/
|
||||
private void setVideoDurationToControls(final int duration) {
|
||||
@@ -984,6 +996,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
private void updateStreamRelatedViews() {
|
||||
player.getCurrentStreamInfo().ifPresent(info -> {
|
||||
binding.qualityTextView.setVisibility(View.GONE);
|
||||
binding.audioTrackTextView.setVisibility(View.GONE);
|
||||
binding.playbackSpeed.setVisibility(View.GONE);
|
||||
|
||||
binding.playbackEndTime.setVisibility(View.GONE);
|
||||
@@ -1019,6 +1032,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
}
|
||||
|
||||
buildQualityMenu();
|
||||
buildAudioTrackMenu();
|
||||
|
||||
binding.qualityTextView.setVisibility(View.VISIBLE);
|
||||
binding.surfaceView.setVisibility(View.VISIBLE);
|
||||
@@ -1067,6 +1081,34 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
.ifPresent(s -> binding.qualityTextView.setText(s.getResolution()));
|
||||
}
|
||||
|
||||
private void buildAudioTrackMenu() {
|
||||
if (audioTrackPopupMenu == null) {
|
||||
return;
|
||||
}
|
||||
audioTrackPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_AUDIO_TRACK);
|
||||
|
||||
final List<AudioStream> availableStreams = Optional.ofNullable(player.getCurrentMetadata())
|
||||
.flatMap(MediaItemTag::getMaybeAudioTrack)
|
||||
.map(MediaItemTag.AudioTrack::getAudioStreams)
|
||||
.orElse(null);
|
||||
if (availableStreams == null || availableStreams.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < availableStreams.size(); i++) {
|
||||
final AudioStream audioStream = availableStreams.get(i);
|
||||
audioTrackPopupMenu.getMenu().add(POPUP_MENU_ID_AUDIO_TRACK, i, Menu.NONE,
|
||||
Localization.audioTrackName(context, audioStream));
|
||||
}
|
||||
|
||||
player.getSelectedAudioStream()
|
||||
.ifPresent(s -> binding.audioTrackTextView.setText(
|
||||
Localization.audioTrackName(context, s)));
|
||||
binding.audioTrackTextView.setVisibility(View.VISIBLE);
|
||||
audioTrackPopupMenu.setOnMenuItemClickListener(this);
|
||||
audioTrackPopupMenu.setOnDismissListener(this);
|
||||
}
|
||||
|
||||
private void buildPlaybackSpeedMenu() {
|
||||
if (playbackSpeedPopupMenu == null) {
|
||||
return;
|
||||
@@ -1175,6 +1217,11 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
.ifPresent(binding.qualityTextView::setText);
|
||||
}
|
||||
|
||||
private void onAudioTracksClicked() {
|
||||
audioTrackPopupMenu.show();
|
||||
isSomePopupMenuVisible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an item of the quality selector or the playback speed selector is selected.
|
||||
*/
|
||||
@@ -1187,26 +1234,10 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
}
|
||||
|
||||
if (menuItem.getGroupId() == POPUP_MENU_ID_QUALITY) {
|
||||
final int menuItemIndex = menuItem.getItemId();
|
||||
@Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata();
|
||||
if (currentMetadata == null || currentMetadata.getMaybeQuality().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final MediaItemTag.Quality quality = currentMetadata.getMaybeQuality().get();
|
||||
final List<VideoStream> availableStreams = quality.getSortedVideoStreams();
|
||||
final int selectedStreamIndex = quality.getSelectedVideoStreamIndex();
|
||||
if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
player.saveStreamProgressState(); //TODO added, check if good
|
||||
final String newResolution = availableStreams.get(menuItemIndex).getResolution();
|
||||
player.setRecovery();
|
||||
player.setPlaybackQuality(newResolution);
|
||||
player.reloadPlayQueueManager();
|
||||
|
||||
binding.qualityTextView.setText(menuItem.getTitle());
|
||||
onQualityItemClick(menuItem);
|
||||
return true;
|
||||
} else if (menuItem.getGroupId() == POPUP_MENU_ID_AUDIO_TRACK) {
|
||||
onAudioTrackItemClick(menuItem);
|
||||
return true;
|
||||
} else if (menuItem.getGroupId() == POPUP_MENU_ID_PLAYBACK_SPEED) {
|
||||
final int speedIndex = menuItem.getItemId();
|
||||
@@ -1219,6 +1250,47 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onQualityItemClick(@NonNull final MenuItem menuItem) {
|
||||
final int menuItemIndex = menuItem.getItemId();
|
||||
@Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata();
|
||||
if (currentMetadata == null || currentMetadata.getMaybeQuality().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MediaItemTag.Quality quality = currentMetadata.getMaybeQuality().get();
|
||||
final List<VideoStream> availableStreams = quality.getSortedVideoStreams();
|
||||
final int selectedStreamIndex = quality.getSelectedVideoStreamIndex();
|
||||
if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String newResolution = availableStreams.get(menuItemIndex).getResolution();
|
||||
player.setPlaybackQuality(newResolution);
|
||||
|
||||
binding.qualityTextView.setText(menuItem.getTitle());
|
||||
}
|
||||
|
||||
private void onAudioTrackItemClick(@NonNull final MenuItem menuItem) {
|
||||
final int menuItemIndex = menuItem.getItemId();
|
||||
@Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata();
|
||||
if (currentMetadata == null || currentMetadata.getMaybeAudioTrack().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MediaItemTag.AudioTrack audioTrack =
|
||||
currentMetadata.getMaybeAudioTrack().get();
|
||||
final List<AudioStream> availableStreams = audioTrack.getAudioStreams();
|
||||
final int selectedStreamIndex = audioTrack.getSelectedAudioStreamIndex();
|
||||
if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String newAudioTrack = availableStreams.get(menuItemIndex).getAudioTrackId();
|
||||
player.setAudioTrack(newAudioTrack);
|
||||
|
||||
binding.audioTrackTextView.setText(menuItem.getTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when some popup menu is dismissed.
|
||||
*/
|
||||
|
||||
@@ -182,7 +182,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
importDatabase(file, lastImportDataUri))
|
||||
.setNegativeButton(R.string.cancel, (d, id) ->
|
||||
d.cancel())
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -223,20 +222,19 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
// if settings file exist, ask if it should be imported.
|
||||
if (manager.extractSettings(file)) {
|
||||
final AlertDialog.Builder alert = new AlertDialog.Builder(requireContext());
|
||||
alert.setTitle(R.string.import_settings);
|
||||
|
||||
alert.setNegativeButton(R.string.cancel, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
finishImport(importDataUri);
|
||||
});
|
||||
alert.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
manager.loadSharedPreferences(PreferenceManager
|
||||
.getDefaultSharedPreferences(requireContext()));
|
||||
finishImport(importDataUri);
|
||||
});
|
||||
alert.show();
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.import_settings)
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
finishImport(importDataUri);
|
||||
})
|
||||
.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
manager.loadSharedPreferences(PreferenceManager
|
||||
.getDefaultSharedPreferences(requireContext()));
|
||||
finishImport(importDataUri);
|
||||
})
|
||||
.show();
|
||||
} else {
|
||||
finishImport(importDataUri);
|
||||
}
|
||||
|
||||
@@ -170,11 +170,11 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
|
||||
}
|
||||
|
||||
private void showMessageDialog(@StringRes final int title, @StringRes final int message) {
|
||||
final AlertDialog.Builder msg = new AlertDialog.Builder(ctx);
|
||||
msg.setTitle(title);
|
||||
msg.setMessage(message);
|
||||
msg.setPositiveButton(getString(R.string.ok), null);
|
||||
msg.show();
|
||||
new AlertDialog.Builder(ctx)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(getString(R.string.ok), null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class ExoPlayerSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable final Bundle savedInstanceState,
|
||||
@Nullable final String rootKey) {
|
||||
addPreferencesFromResourceRegistry();
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,6 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
||||
disposables.add(getWholeStreamHistoryDisposable(context, recordManager));
|
||||
disposables.add(getRemoveOrphanedRecordsDisposable(context, recordManager));
|
||||
}))
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -144,7 +143,6 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||
.setPositiveButton(R.string.delete, ((dialog, which) ->
|
||||
disposables.add(getDeletePlaybackStatesDisposable(context, recordManager))))
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -156,7 +154,6 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||
.setPositiveButton(R.string.delete, ((dialog, which) ->
|
||||
disposables.add(getDeleteSearchHistoryDisposable(context, recordManager))))
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,25 @@ public final class SettingMigrations {
|
||||
}
|
||||
};
|
||||
|
||||
public static final Migration MIGRATION_4_5 = new Migration(4, 5) {
|
||||
@Override
|
||||
protected void migrate(final Context context) {
|
||||
final boolean brightness = sp.getBoolean("brightness_gesture_control", true);
|
||||
final boolean volume = sp.getBoolean("volume_gesture_control", true);
|
||||
|
||||
final SharedPreferences.Editor editor = sp.edit();
|
||||
|
||||
editor.putString(context.getString(R.string.right_gesture_control_key),
|
||||
context.getString(volume
|
||||
? R.string.volume_control_key : R.string.none_control_key));
|
||||
editor.putString(context.getString(R.string.left_gesture_control_key),
|
||||
context.getString(brightness
|
||||
? R.string.brightness_control_key : R.string.none_control_key));
|
||||
|
||||
editor.apply();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* List of all implemented migrations.
|
||||
* <p>
|
||||
@@ -119,12 +138,13 @@ public final class SettingMigrations {
|
||||
MIGRATION_1_2,
|
||||
MIGRATION_2_3,
|
||||
MIGRATION_3_4,
|
||||
MIGRATION_4_5,
|
||||
};
|
||||
|
||||
/**
|
||||
* Version number for preferences. Must be incremented every time a migration is necessary.
|
||||
*/
|
||||
public static final int VERSION = 4;
|
||||
public static final int VERSION = 5;
|
||||
|
||||
|
||||
public static void initMigrations(final Context context, final boolean isFirstRun) {
|
||||
|
||||
@@ -40,6 +40,7 @@ public final class SettingsResourceRegistry {
|
||||
add(PlayerNotificationSettingsFragment.class, R.xml.player_notification_settings);
|
||||
add(UpdateSettingsFragment.class, R.xml.update_settings);
|
||||
add(VideoAudioSettingsFragment.class, R.xml.video_audio_settings);
|
||||
add(ExoPlayerSettingsFragment.class, R.xml.exoplayer_settings);
|
||||
}
|
||||
|
||||
private SettingRegistryEntry add(
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A list adapter for groups of {@link AudioStream}s (audio tracks).
|
||||
*/
|
||||
public class AudioTrackAdapter extends BaseAdapter {
|
||||
private final AudioTracksWrapper tracksWrapper;
|
||||
|
||||
public AudioTrackAdapter(final AudioTracksWrapper tracksWrapper) {
|
||||
this.tracksWrapper = tracksWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return tracksWrapper.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AudioStream> getItem(final int position) {
|
||||
return tracksWrapper.getTracksList().get(position).getStreamsList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(final int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, final View convertView, final ViewGroup parent) {
|
||||
final var context = parent.getContext();
|
||||
final View view;
|
||||
if (convertView == null) {
|
||||
view = LayoutInflater.from(context).inflate(
|
||||
R.layout.stream_quality_item, parent, false);
|
||||
} else {
|
||||
view = convertView;
|
||||
}
|
||||
|
||||
final ImageView woSoundIconView = view.findViewById(R.id.wo_sound_icon);
|
||||
final TextView formatNameView = view.findViewById(R.id.stream_format_name);
|
||||
final TextView qualityView = view.findViewById(R.id.stream_quality);
|
||||
final TextView sizeView = view.findViewById(R.id.stream_size);
|
||||
|
||||
final List<AudioStream> streams = getItem(position);
|
||||
final AudioStream stream = streams.get(0);
|
||||
|
||||
woSoundIconView.setVisibility(View.GONE);
|
||||
sizeView.setVisibility(View.VISIBLE);
|
||||
|
||||
if (stream.getAudioTrackId() != null) {
|
||||
formatNameView.setText(stream.getAudioTrackId());
|
||||
}
|
||||
qualityView.setText(Localization.audioTrackName(context, stream));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public static class AudioTracksWrapper implements Serializable {
|
||||
private final List<StreamSizeWrapper<AudioStream>> tracksList;
|
||||
|
||||
public AudioTracksWrapper(@NonNull final List<List<AudioStream>> groupedAudioStreams,
|
||||
@Nullable final Context context) {
|
||||
this.tracksList = groupedAudioStreams.stream().map(streams ->
|
||||
new StreamSizeWrapper<>(streams, context)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<StreamSizeWrapper<AudioStream>> getTracksList() {
|
||||
return tracksList;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return tracksList.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,22 +36,6 @@ public final class DeviceUtils {
|
||||
private static Boolean isTV = null;
|
||||
private static Boolean isFireTV = null;
|
||||
|
||||
/*
|
||||
* Devices that do not support media tunneling
|
||||
*/
|
||||
// Formuler Z8 Pro, Z8, CC, Z Alpha, Z+ Neo
|
||||
private static final boolean HI3798MV200 = Build.VERSION.SDK_INT == 24
|
||||
&& Build.DEVICE.equals("Hi3798MV200");
|
||||
// Zephir TS43UHD-2
|
||||
private static final boolean CVT_MT5886_EU_1G = Build.VERSION.SDK_INT == 24
|
||||
&& Build.DEVICE.equals("cvt_mt5886_eu_1g");
|
||||
// Hilife TV
|
||||
private static final boolean REALTEKATV = Build.VERSION.SDK_INT == 25
|
||||
&& Build.DEVICE.equals("RealtekATV");
|
||||
// Philips QM16XE
|
||||
private static final boolean QM16XE_U = Build.VERSION.SDK_INT == 23
|
||||
&& Build.DEVICE.equals("QM16XE_U");
|
||||
|
||||
private DeviceUtils() {
|
||||
}
|
||||
|
||||
@@ -211,18 +195,6 @@ public final class DeviceUtils {
|
||||
context.getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
/**
|
||||
* Some devices have broken tunneled video playback but claim to support it.
|
||||
* See https://github.com/TeamNewPipe/NewPipe/issues/5911
|
||||
* @return false if affected device
|
||||
*/
|
||||
public static boolean shouldSupportMediaTunneling() {
|
||||
return !HI3798MV200
|
||||
&& !CVT_MT5886_EU_1G
|
||||
&& !REALTEKATV
|
||||
&& !QM16XE_U;
|
||||
}
|
||||
|
||||
public static boolean isLandscape(final Context context) {
|
||||
return context.getResources().getDisplayMetrics().heightPixels < context.getResources()
|
||||
.getDisplayMetrics().widthPixels;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.ConnectivityManager;
|
||||
@@ -13,6 +15,7 @@ import androidx.preference.PreferenceManager;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.AudioTrackType;
|
||||
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
|
||||
import org.schabi.newpipe.extractor.stream.Stream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
@@ -23,6 +26,7 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
@@ -36,19 +40,40 @@ public final class ListHelper {
|
||||
// Audio format in order of quality. 0=lowest quality, n=highest quality
|
||||
private static final List<MediaFormat> AUDIO_FORMAT_QUALITY_RANKING =
|
||||
List.of(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A);
|
||||
// Audio format in order of efficiency. 0=most efficient, n=least efficient
|
||||
// Audio format in order of efficiency. 0=least efficient, n=most efficient
|
||||
private static final List<MediaFormat> AUDIO_FORMAT_EFFICIENCY_RANKING =
|
||||
List.of(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3);
|
||||
List.of(MediaFormat.MP3, MediaFormat.M4A, MediaFormat.WEBMA);
|
||||
// Use a Set for better performance
|
||||
private static final Set<String> HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p");
|
||||
// Audio track types in order of priotity. 0=lowest, n=highest
|
||||
private static final List<AudioTrackType> AUDIO_TRACK_TYPE_RANKING =
|
||||
List.of(AudioTrackType.DESCRIPTIVE, AudioTrackType.DUBBED, AudioTrackType.ORIGINAL);
|
||||
// Audio track types in order of priotity when descriptive audio is preferred.
|
||||
private static final List<AudioTrackType> AUDIO_TRACK_TYPE_RANKING_DESCRIPTIVE =
|
||||
List.of(AudioTrackType.ORIGINAL, AudioTrackType.DUBBED, AudioTrackType.DESCRIPTIVE);
|
||||
|
||||
/**
|
||||
* List of supported YouTube Itag ids.
|
||||
* The original order is kept.
|
||||
* @see {@link org.schabi.newpipe.extractor.services.youtube.ItagItem#ITAG_LIST}
|
||||
*/
|
||||
private static final List<Integer> SUPPORTED_ITAG_IDS =
|
||||
List.of(
|
||||
17, 36, // video v3GPP
|
||||
18, 34, 35, 59, 78, 22, 37, 38, // video MPEG4
|
||||
43, 44, 45, 46, // video webm
|
||||
171, 172, 139, 140, 141, 249, 250, 251, // audio
|
||||
160, 133, 134, 135, 212, 136, 298, 137, 299, 266, // video only
|
||||
278, 242, 243, 244, 245, 246, 247, 248, 271, 272, 302, 303, 308, 313, 315
|
||||
);
|
||||
|
||||
private ListHelper() { }
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
* @param context Android app context
|
||||
* @param videoStreams list of the video streams to check
|
||||
* @return index of the video stream with the default index
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
*/
|
||||
public static int getDefaultResolutionIndex(final Context context,
|
||||
final List<VideoStream> videoStreams) {
|
||||
@@ -58,11 +83,11 @@ public final class ListHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
* @param context Android app context
|
||||
* @param videoStreams list of the video streams to check
|
||||
* @param defaultResolution the default resolution to look for
|
||||
* @return index of the video stream with the default index
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
*/
|
||||
public static int getResolutionIndex(final Context context,
|
||||
final List<VideoStream> videoStreams,
|
||||
@@ -71,10 +96,10 @@ public final class ListHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
* @param context Android app context
|
||||
* @param videoStreams list of the video streams to check
|
||||
* @param context Android app context
|
||||
* @param videoStreams list of the video streams to check
|
||||
* @return index of the video stream with the default index
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
*/
|
||||
public static int getPopupDefaultResolutionIndex(final Context context,
|
||||
final List<VideoStream> videoStreams) {
|
||||
@@ -84,11 +109,11 @@ public final class ListHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
* @param context Android app context
|
||||
* @param videoStreams list of the video streams to check
|
||||
* @param defaultResolution the default resolution to look for
|
||||
* @return index of the video stream with the default index
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
*/
|
||||
public static int getPopupResolutionIndex(final Context context,
|
||||
final List<VideoStream> videoStreams,
|
||||
@@ -98,16 +123,36 @@ public final class ListHelper {
|
||||
|
||||
public static int getDefaultAudioFormat(final Context context,
|
||||
final List<AudioStream> audioStreams) {
|
||||
final MediaFormat defaultFormat = getDefaultFormat(context,
|
||||
R.string.default_audio_format_key, R.string.default_audio_format_value);
|
||||
return getAudioIndexByHighestRank(audioStreams,
|
||||
getAudioTrackComparator(context).thenComparing(getAudioFormatComparator(context)));
|
||||
}
|
||||
|
||||
// If the user has chosen to limit resolution to conserve mobile data
|
||||
// usage then we should also limit our audio usage.
|
||||
if (isLimitingDataUsage(context)) {
|
||||
return getMostCompactAudioIndex(defaultFormat, audioStreams);
|
||||
} else {
|
||||
return getHighestQualityAudioIndex(defaultFormat, audioStreams);
|
||||
public static int getDefaultAudioTrackGroup(final Context context,
|
||||
final List<List<AudioStream>> groupedAudioStreams) {
|
||||
if (groupedAudioStreams == null || groupedAudioStreams.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final Comparator<AudioStream> cmp = getAudioTrackComparator(context);
|
||||
final List<AudioStream> highestRanked = groupedAudioStreams.stream()
|
||||
.max((o1, o2) -> cmp.compare(o1.get(0), o2.get(0)))
|
||||
.orElse(null);
|
||||
return groupedAudioStreams.indexOf(highestRanked);
|
||||
}
|
||||
|
||||
public static int getAudioFormatIndex(final Context context,
|
||||
final List<AudioStream> audioStreams,
|
||||
@Nullable final String trackId) {
|
||||
if (trackId != null) {
|
||||
for (int i = 0; i < audioStreams.size(); i++) {
|
||||
final AudioStream s = audioStreams.get(i);
|
||||
if (s.getAudioTrackId() != null
|
||||
&& s.getAudioTrackId().equals(trackId)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return getDefaultAudioFormat(context, audioStreams);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +166,7 @@ public final class ListHelper {
|
||||
*/
|
||||
@NonNull
|
||||
public static <S extends Stream> List<S> getStreamsOfSpecifiedDelivery(
|
||||
final List<S> streamList,
|
||||
@Nullable final List<S> streamList,
|
||||
final DeliveryMethod deliveryMethod) {
|
||||
return getFilteredStreamList(streamList,
|
||||
stream -> stream.getDeliveryMethod() == deliveryMethod);
|
||||
@@ -136,23 +181,31 @@ public final class ListHelper {
|
||||
*/
|
||||
@NonNull
|
||||
public static <S extends Stream> List<S> getUrlAndNonTorrentStreams(
|
||||
final List<S> streamList) {
|
||||
@Nullable final List<S> streamList) {
|
||||
return getFilteredStreamList(streamList,
|
||||
stream -> stream.isUrl() && stream.getDeliveryMethod() != DeliveryMethod.TORRENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Stream} list which only contains non-torrent streams.
|
||||
* Return a {@link Stream} list which only contains streams which can be played by the player.
|
||||
* <br>
|
||||
* Some formats are not supported. For more info, see {@link #SUPPORTED_ITAG_IDS}.
|
||||
* Torrent streams are also removed, because they cannot be retrieved.
|
||||
*
|
||||
* @param streamList the original stream list
|
||||
* @param <S> the item type's class that extends {@link Stream}
|
||||
* @return a stream list which only contains non-torrent streams
|
||||
* @param streamList the original stream list
|
||||
* @param serviceId
|
||||
* @return a stream list which only contains streams that can be played the player
|
||||
*/
|
||||
@NonNull
|
||||
public static <S extends Stream> List<S> getNonTorrentStreams(
|
||||
final List<S> streamList) {
|
||||
public static <S extends Stream> List<S> getPlayableStreams(
|
||||
@Nullable final List<S> streamList, final int serviceId) {
|
||||
final int youtubeServiceId = YouTube.getServiceId();
|
||||
return getFilteredStreamList(streamList,
|
||||
stream -> stream.getDeliveryMethod() != DeliveryMethod.TORRENT);
|
||||
stream -> stream.getDeliveryMethod() != DeliveryMethod.TORRENT
|
||||
&& (serviceId != youtubeServiceId
|
||||
|| stream.getItagItem() == null
|
||||
|| SUPPORTED_ITAG_IDS.contains(stream.getItagItem().id)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,6 +239,90 @@ public final class ListHelper {
|
||||
videoOnlyStreams, ascendingOrder, preferVideoOnlyStreams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the list of audio streams and return a list with the preferred stream for
|
||||
* each audio track. Streams are sorted with the preferred language in the first position.
|
||||
*
|
||||
* @param context the context to search for the track to give preference
|
||||
* @param audioStreams the list of audio streams
|
||||
* @return the sorted, filtered list
|
||||
*/
|
||||
public static List<AudioStream> getFilteredAudioStreams(
|
||||
@NonNull final Context context,
|
||||
@Nullable final List<AudioStream> audioStreams) {
|
||||
if (audioStreams == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final HashMap<String, AudioStream> collectedStreams = new HashMap<>();
|
||||
|
||||
final Comparator<AudioStream> cmp = getAudioFormatComparator(context);
|
||||
|
||||
for (final AudioStream stream : audioStreams) {
|
||||
if (stream.getDeliveryMethod() == DeliveryMethod.TORRENT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String trackId = Objects.toString(stream.getAudioTrackId(), "");
|
||||
|
||||
final AudioStream presentStream = collectedStreams.get(trackId);
|
||||
if (presentStream == null || cmp.compare(stream, presentStream) > 0) {
|
||||
collectedStreams.put(trackId, stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter unknown audio tracks if there are multiple tracks
|
||||
if (collectedStreams.size() > 1) {
|
||||
collectedStreams.remove("");
|
||||
}
|
||||
|
||||
// Sort collected streams by name
|
||||
return collectedStreams.values().stream().sorted(getAudioTrackNameComparator(context))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Group the list of audioStreams by their track ID and sort the resulting list by track name.
|
||||
*
|
||||
* @param context app context to get track names for sorting
|
||||
* @param audioStreams list of audio streams
|
||||
* @return list of audio streams lists representing individual tracks
|
||||
*/
|
||||
public static List<List<AudioStream>> getGroupedAudioStreams(
|
||||
@NonNull final Context context,
|
||||
@Nullable final List<AudioStream> audioStreams) {
|
||||
if (audioStreams == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final HashMap<String, List<AudioStream>> collectedStreams = new HashMap<>();
|
||||
|
||||
for (final AudioStream stream : audioStreams) {
|
||||
final String trackId = Objects.toString(stream.getAudioTrackId(), "");
|
||||
if (collectedStreams.containsKey(trackId)) {
|
||||
collectedStreams.get(trackId).add(stream);
|
||||
} else {
|
||||
final List<AudioStream> list = new ArrayList<>();
|
||||
list.add(stream);
|
||||
collectedStreams.put(trackId, list);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter unknown audio tracks if there are multiple tracks
|
||||
if (collectedStreams.size() > 1) {
|
||||
collectedStreams.remove("");
|
||||
}
|
||||
|
||||
// Sort tracks alphabetically, sort track streams by quality
|
||||
final Comparator<AudioStream> nameCmp = getAudioTrackNameComparator(context);
|
||||
final Comparator<AudioStream> formatCmp = getAudioFormatComparator(context);
|
||||
|
||||
return collectedStreams.values().stream()
|
||||
.sorted((o1, o2) -> nameCmp.compare(o1.get(0), o2.get(0)))
|
||||
.map(streams -> streams.stream().sorted(formatCmp).collect(Collectors.toList()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -199,7 +336,7 @@ public final class ListHelper {
|
||||
* @return a new stream list filtered using the given predicate
|
||||
*/
|
||||
private static <S extends Stream> List<S> getFilteredStreamList(
|
||||
final List<S> streamList,
|
||||
@Nullable final List<S> streamList,
|
||||
final Predicate<S> streamListPredicate) {
|
||||
if (streamList == null) {
|
||||
return Collections.emptyList();
|
||||
@@ -210,7 +347,7 @@ public final class ListHelper {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static String computeDefaultResolution(final Context context, final int key,
|
||||
private static String computeDefaultResolution(@NonNull final Context context, final int key,
|
||||
final int value) {
|
||||
final SharedPreferences preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context);
|
||||
@@ -300,8 +437,8 @@ public final class ListHelper {
|
||||
// Filter out higher resolutions (or not if high resolutions should always be shown)
|
||||
.filter(stream -> showHigherResolutions
|
||||
|| !HIGH_RESOLUTION_LIST.contains(stream.getResolution()
|
||||
// Replace any frame rate with nothing
|
||||
.replaceAll("p\\d+$", "p")))
|
||||
// Replace any frame rate with nothing
|
||||
.replaceAll("p\\d+$", "p")))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final HashMap<String, VideoStream> hashMap = new HashMap<>();
|
||||
@@ -351,72 +488,22 @@ public final class ListHelper {
|
||||
return videoStreams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio from the list with the highest quality.
|
||||
* Format will be ignored if it yields no results.
|
||||
*
|
||||
* @param format The target format type or null if it doesn't matter
|
||||
* @param audioStreams List of audio streams
|
||||
* @return Index of audio stream that produces the most compact results or -1 if not found
|
||||
*/
|
||||
static int getHighestQualityAudioIndex(@Nullable final MediaFormat format,
|
||||
@Nullable final List<AudioStream> audioStreams) {
|
||||
return getAudioIndexByHighestRank(format, audioStreams,
|
||||
// Compares descending (last = highest rank)
|
||||
getAudioStreamComparator(AUDIO_FORMAT_QUALITY_RANKING));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio from the list with the lowest bitrate and most efficient format.
|
||||
* Format will be ignored if it yields no results.
|
||||
*
|
||||
* @param format The target format type or null if it doesn't matter
|
||||
* @param audioStreams List of audio streams
|
||||
* @return Index of audio stream that produces the most compact results or -1 if not found
|
||||
*/
|
||||
static int getMostCompactAudioIndex(@Nullable final MediaFormat format,
|
||||
@Nullable final List<AudioStream> audioStreams) {
|
||||
return getAudioIndexByHighestRank(format, audioStreams,
|
||||
// The "reversed()" is important -> Compares ascending (first = highest rank)
|
||||
getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING).reversed());
|
||||
}
|
||||
|
||||
private static Comparator<AudioStream> getAudioStreamComparator(
|
||||
final List<MediaFormat> formatRanking) {
|
||||
return Comparator.nullsLast(Comparator.comparingInt(AudioStream::getAverageBitrate))
|
||||
.thenComparingInt(stream -> formatRanking.indexOf(stream.getFormat()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio-stream from the list with the highest rank, depending on the comparator.
|
||||
* Format will be ignored if it yields no results.
|
||||
*
|
||||
* @param targetedFormat The target format type or null if it doesn't matter
|
||||
* @param audioStreams List of audio streams
|
||||
* @param comparator The comparator used for determining the max/best/highest ranked value
|
||||
* @param audioStreams List of audio streams
|
||||
* @param comparator The comparator used for determining the max/best/highest ranked value
|
||||
* @return Index of audio stream that produces the highest ranked result or -1 if not found
|
||||
*/
|
||||
private static int getAudioIndexByHighestRank(@Nullable final MediaFormat targetedFormat,
|
||||
@Nullable final List<AudioStream> audioStreams,
|
||||
final Comparator<AudioStream> comparator) {
|
||||
static int getAudioIndexByHighestRank(@Nullable final List<AudioStream> audioStreams,
|
||||
final Comparator<AudioStream> comparator) {
|
||||
if (audioStreams == null || audioStreams.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final AudioStream highestRankedAudioStream = audioStreams.stream()
|
||||
.filter(audioStream -> targetedFormat == null
|
||||
|| audioStream.getFormat() == targetedFormat)
|
||||
.max(comparator)
|
||||
.orElse(null);
|
||||
|
||||
if (highestRankedAudioStream == null) {
|
||||
// Fallback: Ignore targetedFormat if not null
|
||||
if (targetedFormat != null) {
|
||||
return getAudioIndexByHighestRank(null, audioStreams, comparator);
|
||||
}
|
||||
// targetedFormat is already null -> return -1
|
||||
return -1;
|
||||
}
|
||||
.max(comparator).orElse(null);
|
||||
|
||||
return audioStreams.indexOf(highestRankedAudioStream);
|
||||
}
|
||||
@@ -604,4 +691,149 @@ public final class ListHelper {
|
||||
|
||||
return manager.isActiveNetworkMetered();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link Comparator} to compare {@link AudioStream}s by their format and bitrate.
|
||||
*
|
||||
* <p>The prefered stream will be ordered last.</p>
|
||||
*
|
||||
* @param context app context
|
||||
* @return Comparator
|
||||
*/
|
||||
private static Comparator<AudioStream> getAudioFormatComparator(
|
||||
final @NonNull Context context) {
|
||||
final MediaFormat defaultFormat = getDefaultFormat(context,
|
||||
R.string.default_audio_format_key, R.string.default_audio_format_value);
|
||||
return getAudioFormatComparator(defaultFormat, isLimitingDataUsage(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link Comparator} to compare {@link AudioStream}s by their format and bitrate.
|
||||
*
|
||||
* <p>The prefered stream will be ordered last.</p>
|
||||
*
|
||||
* @param defaultFormat the default format to look for
|
||||
* @param limitDataUsage choose low bitrate audio stream
|
||||
* @return Comparator
|
||||
*/
|
||||
static Comparator<AudioStream> getAudioFormatComparator(
|
||||
@Nullable final MediaFormat defaultFormat, final boolean limitDataUsage) {
|
||||
final List<MediaFormat> formatRanking = limitDataUsage
|
||||
? AUDIO_FORMAT_EFFICIENCY_RANKING : AUDIO_FORMAT_QUALITY_RANKING;
|
||||
|
||||
Comparator<AudioStream> bitrateComparator =
|
||||
Comparator.comparingInt(AudioStream::getAverageBitrate);
|
||||
if (limitDataUsage) {
|
||||
bitrateComparator = bitrateComparator.reversed();
|
||||
}
|
||||
|
||||
return Comparator.comparing(AudioStream::getFormat, (o1, o2) -> {
|
||||
if (defaultFormat != null) {
|
||||
return Boolean.compare(o1 == defaultFormat, o2 == defaultFormat);
|
||||
}
|
||||
return 0;
|
||||
}).thenComparing(bitrateComparator).thenComparingInt(
|
||||
stream -> formatRanking.indexOf(stream.getFormat()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link Comparator} to compare {@link AudioStream}s by their tracks.
|
||||
*
|
||||
* <p>Tracks will be compared this order:</p>
|
||||
* <ol>
|
||||
* <li>If {@code preferOriginalAudio}: use original audio</li>
|
||||
* <li>Language matches {@code preferredLanguage}</li>
|
||||
* <li>
|
||||
* Track type ranks highest in this order:
|
||||
* <i>Original</i> > <i>Dubbed</i> > <i>Descriptive</i>
|
||||
* <p>If {@code preferDescriptiveAudio}:
|
||||
* <i>Descriptive</i> > <i>Dubbed</i> > <i>Original</i></p>
|
||||
* </li>
|
||||
* <li>Language is English</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>The prefered track will be ordered last.</p>
|
||||
*
|
||||
* @param context App context
|
||||
* @return Comparator
|
||||
*/
|
||||
private static Comparator<AudioStream> getAudioTrackComparator(
|
||||
@NonNull final Context context) {
|
||||
final SharedPreferences preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final Locale preferredLanguage = Localization.getPreferredLocale(context);
|
||||
final boolean preferOriginalAudio =
|
||||
preferences.getBoolean(context.getString(R.string.prefer_original_audio_key),
|
||||
false);
|
||||
final boolean preferDescriptiveAudio =
|
||||
preferences.getBoolean(context.getString(R.string.prefer_descriptive_audio_key),
|
||||
false);
|
||||
|
||||
return getAudioTrackComparator(preferredLanguage, preferOriginalAudio,
|
||||
preferDescriptiveAudio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link Comparator} to compare {@link AudioStream}s by their tracks.
|
||||
*
|
||||
* <p>Tracks will be compared this order:</p>
|
||||
* <ol>
|
||||
* <li>If {@code preferOriginalAudio}: use original audio</li>
|
||||
* <li>Language matches {@code preferredLanguage}</li>
|
||||
* <li>
|
||||
* Track type ranks highest in this order:
|
||||
* <i>Original</i> > <i>Dubbed</i> > <i>Descriptive</i>
|
||||
* <p>If {@code preferDescriptiveAudio}:
|
||||
* <i>Descriptive</i> > <i>Dubbed</i> > <i>Original</i></p>
|
||||
* </li>
|
||||
* <li>Language is English</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>The prefered track will be ordered last.</p>
|
||||
*
|
||||
* @param preferredLanguage Preferred audio stream language
|
||||
* @param preferOriginalAudio Get the original audio track regardless of its language
|
||||
* @param preferDescriptiveAudio Prefer the descriptive audio track if available
|
||||
* @return Comparator
|
||||
*/
|
||||
static Comparator<AudioStream> getAudioTrackComparator(
|
||||
final Locale preferredLanguage,
|
||||
final boolean preferOriginalAudio,
|
||||
final boolean preferDescriptiveAudio) {
|
||||
final String langCode = preferredLanguage.getISO3Language();
|
||||
final List<AudioTrackType> trackTypeRanking = preferDescriptiveAudio
|
||||
? AUDIO_TRACK_TYPE_RANKING_DESCRIPTIVE : AUDIO_TRACK_TYPE_RANKING;
|
||||
|
||||
return Comparator.comparing(AudioStream::getAudioTrackType, (o1, o2) -> {
|
||||
if (preferOriginalAudio) {
|
||||
return Boolean.compare(
|
||||
o1 == AudioTrackType.ORIGINAL, o2 == AudioTrackType.ORIGINAL);
|
||||
}
|
||||
return 0;
|
||||
}).thenComparing(AudioStream::getAudioLocale,
|
||||
Comparator.nullsFirst(Comparator.comparing(
|
||||
locale -> locale.getISO3Language().equals(langCode))))
|
||||
.thenComparing(AudioStream::getAudioTrackType,
|
||||
Comparator.nullsFirst(Comparator.comparingInt(trackTypeRanking::indexOf)))
|
||||
.thenComparing(AudioStream::getAudioLocale,
|
||||
Comparator.nullsFirst(Comparator.comparing(
|
||||
locale -> locale.getISO3Language().equals(
|
||||
Locale.ENGLISH.getISO3Language()))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link Comparator} to compare {@link AudioStream}s by their languages and track types
|
||||
* for alphabetical sorting.
|
||||
*
|
||||
* @param context app context for localization
|
||||
* @return Comparator
|
||||
*/
|
||||
private static Comparator<AudioStream> getAudioTrackNameComparator(
|
||||
@NonNull final Context context) {
|
||||
final Locale appLoc = Localization.getAppLocale(context);
|
||||
|
||||
return Comparator.comparing(AudioStream::getAudioLocale, Comparator.nullsLast(
|
||||
Comparator.comparing(locale -> locale.getDisplayName(appLoc))))
|
||||
.thenComparing(AudioStream::getAudioTrackType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.PluralsRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.math.MathUtils;
|
||||
@@ -21,6 +22,8 @@ import org.ocpsoft.prettytime.units.Decade;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.AudioTrackType;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
@@ -261,6 +264,52 @@ public final class Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the localized name of an audio track.
|
||||
*
|
||||
* <p>Examples of results returned by this method:</p>
|
||||
* <ul>
|
||||
* <li>English (original)</li>
|
||||
* <li>English (descriptive)</li>
|
||||
* <li>Spanish (dubbed)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param context the context used to get the app language
|
||||
* @param track an {@link AudioStream} of the track
|
||||
* @return the localized name of the audio track
|
||||
*/
|
||||
public static String audioTrackName(final Context context, final AudioStream track) {
|
||||
final String name;
|
||||
if (track.getAudioLocale() != null) {
|
||||
name = track.getAudioLocale().getDisplayLanguage(getAppLocale(context));
|
||||
} else if (track.getAudioTrackName() != null) {
|
||||
name = track.getAudioTrackName();
|
||||
} else {
|
||||
name = context.getString(R.string.unknown_audio_track);
|
||||
}
|
||||
|
||||
if (track.getAudioTrackType() != null) {
|
||||
final String trackType = audioTrackType(context, track.getAudioTrackType());
|
||||
if (trackType != null) {
|
||||
return context.getString(R.string.audio_track_name, name, trackType);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String audioTrackType(final Context context, final AudioTrackType trackType) {
|
||||
switch (trackType) {
|
||||
case ORIGINAL:
|
||||
return context.getString(R.string.audio_track_type_original);
|
||||
case DUBBED:
|
||||
return context.getString(R.string.audio_track_type_dubbed);
|
||||
case DESCRIPTIVE:
|
||||
return context.getString(R.string.audio_track_type_descriptive);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Pretty Time
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@@ -325,11 +325,11 @@ public final class NavigationHelper {
|
||||
if (context instanceof Activity) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setMessage(R.string.no_player_found)
|
||||
.setPositiveButton(R.string.install,
|
||||
(dialog, which) -> ShareUtils.installApp(context,
|
||||
.setPositiveButton(R.string.install, (dialog, which) ->
|
||||
ShareUtils.installApp(context,
|
||||
context.getString(R.string.vlc_package)))
|
||||
.setNegativeButton(R.string.cancel, (dialog, which)
|
||||
-> Log.i("NavigationHelper", "You unlocked a secret unicorn."))
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) ->
|
||||
Log.i("NavigationHelper", "You unlocked a secret unicorn."))
|
||||
.show();
|
||||
} else {
|
||||
Toast.makeText(context, R.string.no_player_found_toast, Toast.LENGTH_LONG).show();
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public final class PendingIntentCompat {
|
||||
private PendingIntentCompat() {
|
||||
}
|
||||
|
||||
private static int addImmutableFlag(final int flags) {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
? flags | PendingIntent.FLAG_IMMUTABLE : flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PendingIntent} to start an activity. It is immutable on API level 23 and
|
||||
* greater.
|
||||
*
|
||||
* @param context The context in which the activity should be started.
|
||||
* @param requestCode The request code
|
||||
* @param intent The Intent of the activity to be launched.
|
||||
* @param flags The flags for the intent.
|
||||
* @return The pending intent.
|
||||
* @see PendingIntent#getActivity(Context, int, Intent, int)
|
||||
*/
|
||||
@NonNull
|
||||
public static PendingIntent getActivity(@NonNull final Context context, final int requestCode,
|
||||
@NonNull final Intent intent, final int flags) {
|
||||
return PendingIntent.getActivity(context, requestCode, intent, addImmutableFlag(flags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PendingIntent} to start a service. It is immutable on API level 23 and
|
||||
* greater.
|
||||
*
|
||||
* @param context The context in which the service should be started.
|
||||
* @param requestCode The request code
|
||||
* @param intent The Intent of the service to be launched.
|
||||
* @param flags The flags for the intent.
|
||||
* @return The pending intent.
|
||||
* @see PendingIntent#getService(Context, int, Intent, int)
|
||||
*/
|
||||
@NonNull
|
||||
public static PendingIntent getService(@NonNull final Context context, final int requestCode,
|
||||
@NonNull final Intent intent, final int flags) {
|
||||
return PendingIntent.getService(context, requestCode, intent, addImmutableFlag(flags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PendingIntent} to perform a broadcast. It is immutable on API level 23 and
|
||||
* greater.
|
||||
*
|
||||
* @param context The context in which the broadcast should be performed.
|
||||
* @param requestCode The request code
|
||||
* @param intent The Intent to be broadcast.
|
||||
* @param flags The flags for the intent.
|
||||
* @return The pending intent.
|
||||
* @see PendingIntent#getBroadcast(Context, int, Intent, int)
|
||||
*/
|
||||
@NonNull
|
||||
public static PendingIntent getBroadcast(@NonNull final Context context, final int requestCode,
|
||||
@NonNull final Intent intent, final int flags) {
|
||||
return PendingIntent.getBroadcast(context, requestCode, intent, addImmutableFlag(flags));
|
||||
}
|
||||
}
|
||||
@@ -224,6 +224,8 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
||||
public static class StreamSizeWrapper<T extends Stream> implements Serializable {
|
||||
private static final StreamSizeWrapper<Stream> EMPTY =
|
||||
new StreamSizeWrapper<>(Collections.emptyList(), null);
|
||||
private static final int SIZE_UNSET = -2;
|
||||
|
||||
private final List<T> streamsList;
|
||||
private final long[] streamSizes;
|
||||
private final String unknownSize;
|
||||
@@ -235,7 +237,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
||||
this.unknownSize = context == null
|
||||
? "--.-" : context.getString(R.string.unknown_content);
|
||||
|
||||
Arrays.fill(streamSizes, -2);
|
||||
resetSizes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,7 +253,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
||||
final Callable<Boolean> fetchAndSet = () -> {
|
||||
boolean hasChanged = false;
|
||||
for (final X stream : streamsWrapper.getStreamsList()) {
|
||||
if (streamsWrapper.getSizeInBytes(stream) > -2) {
|
||||
if (streamsWrapper.getSizeInBytes(stream) > SIZE_UNSET) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -269,6 +271,10 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
||||
.onErrorReturnItem(true);
|
||||
}
|
||||
|
||||
public void resetSizes() {
|
||||
Arrays.fill(streamSizes, SIZE_UNSET);
|
||||
}
|
||||
|
||||
public static <X extends Stream> StreamSizeWrapper<X> empty() {
|
||||
//noinspection unchecked
|
||||
return (StreamSizeWrapper<X>) EMPTY;
|
||||
|
||||
@@ -61,11 +61,12 @@ public final class KoreUtils {
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
if (!tryOpenIntentInApp(context, intent)) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setMessage(R.string.kore_not_found)
|
||||
.setPositiveButton(R.string.install, (dialog, which) -> installKore(context))
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> { });
|
||||
builder.create().show();
|
||||
new AlertDialog.Builder(context)
|
||||
.setMessage(R.string.kore_not_found)
|
||||
.setPositiveButton(R.string.install, (dialog, which) ->
|
||||
installKore(context))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ public final class InternalUrlsHandler {
|
||||
.setTitle(R.string.player_stream_failure)
|
||||
.setMessage(
|
||||
ErrorPanelHelper.Companion.getExceptionDescription(throwable))
|
||||
.setPositiveButton(R.string.ok, (v, b) -> { })
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}));
|
||||
return true;
|
||||
|
||||
@@ -54,12 +54,12 @@ public class DownloadInitializer extends Thread {
|
||||
long lowestSize = Long.MAX_VALUE;
|
||||
|
||||
for (int i = 0; i < mMission.urls.length && mMission.running; i++) {
|
||||
mConn = mMission.openConnection(mMission.urls[i], true, -1, -1);
|
||||
mConn = mMission.openConnection(mMission.urls[i], true, 0, 0);
|
||||
mMission.establishConnection(mId, mConn);
|
||||
dispose();
|
||||
|
||||
if (Thread.interrupted()) return;
|
||||
long length = Utility.getContentLength(mConn);
|
||||
long length = Utility.getTotalContentLength(mConn);
|
||||
|
||||
if (i == 0) {
|
||||
httpCode = mConn.getResponseCode();
|
||||
@@ -84,14 +84,14 @@ public class DownloadInitializer extends Thread {
|
||||
}
|
||||
} else {
|
||||
// ask for the current resource length
|
||||
mConn = mMission.openConnection(true, -1, -1);
|
||||
mConn = mMission.openConnection(true, 0, 0);
|
||||
mMission.establishConnection(mId, mConn);
|
||||
dispose();
|
||||
|
||||
if (!mMission.running || Thread.interrupted()) return;
|
||||
|
||||
httpCode = mConn.getResponseCode();
|
||||
mMission.length = Utility.getContentLength(mConn);
|
||||
mMission.length = Utility.getTotalContentLength(mConn);
|
||||
}
|
||||
|
||||
if (mMission.length == 0 || httpCode == 204) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.annotation.StringRes;
|
||||
import androidx.collection.SparseArrayCompat;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationCompat.Builder;
|
||||
import androidx.core.app.PendingIntentCompat;
|
||||
import androidx.core.app.ServiceCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
@@ -43,7 +44,6 @@ import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
|
||||
import org.schabi.newpipe.streams.io.StoredFileHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.PendingIntentCompat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -146,7 +146,7 @@ public class DownloadManagerService extends Service {
|
||||
|
||||
mOpenDownloadList = PendingIntentCompat.getActivity(this, 0,
|
||||
openDownloadListIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
PendingIntent.FLAG_UPDATE_CURRENT, false);
|
||||
|
||||
icLauncher = BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher);
|
||||
|
||||
@@ -487,7 +487,7 @@ public class DownloadManagerService extends Service {
|
||||
private PendingIntent makePendingIntent(String action) {
|
||||
Intent intent = new Intent(this, DownloadManagerService.class).setAction(action);
|
||||
return PendingIntentCompat.getService(this, intent.hashCode(), intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
PendingIntent.FLAG_UPDATE_CURRENT, false);
|
||||
}
|
||||
|
||||
private void manageLock(boolean acquire) {
|
||||
|
||||
@@ -538,7 +538,6 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
|
||||
|
||||
builder.setNegativeButton(R.string.ok, (dialog, which) -> dialog.cancel())
|
||||
.setTitle(mission.storage.getName())
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -211,12 +211,11 @@ public class MissionsFragment extends Fragment {
|
||||
.setTitle(R.string.clear_download_history)
|
||||
.setMessage(R.string.confirm_prompt)
|
||||
// Intentionally misusing buttons' purpose in order to achieve good order
|
||||
.setNegativeButton(R.string.clear_download_history,
|
||||
(dialog, which) -> mAdapter.clearFinishedDownloads(false))
|
||||
.setNegativeButton(R.string.clear_download_history, (dialog, which) ->
|
||||
mAdapter.clearFinishedDownloads(false))
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete_downloaded_files,
|
||||
(dialog, which) -> showDeleteDownloadedFilesConfirmationPrompt())
|
||||
.create()
|
||||
.setPositiveButton(R.string.delete_downloaded_files, (dialog, which) ->
|
||||
showDeleteDownloadedFilesConfirmationPrompt())
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -225,9 +224,8 @@ public class MissionsFragment extends Fragment {
|
||||
new AlertDialog.Builder(mContext)
|
||||
.setTitle(R.string.delete_downloaded_files_confirm)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ok,
|
||||
(dialog, which) -> mAdapter.clearFinishedDownloads(true))
|
||||
.create()
|
||||
.setPositiveButton(R.string.ok, (dialog, which) ->
|
||||
mAdapter.clearFinishedDownloads(true))
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package us.shandian.giga.util;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.DrawableRes;
|
||||
@@ -29,8 +26,10 @@ import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
import okio.ByteString;
|
||||
import us.shandian.giga.get.DownloadMission;
|
||||
|
||||
public class Utility {
|
||||
|
||||
@@ -232,6 +231,28 @@ public class Utility {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content length of the entire file even if the HTTP response is partial
|
||||
* (response code 206).
|
||||
* @param connection http connection
|
||||
* @return content length
|
||||
*/
|
||||
public static long getTotalContentLength(final HttpURLConnection connection) {
|
||||
try {
|
||||
if (connection.getResponseCode() == 206) {
|
||||
final String rangeStr = connection.getHeaderField("Content-Range");
|
||||
final String bytesStr = rangeStr.split("/", 2)[1];
|
||||
return Long.parseLong(bytesStr);
|
||||
} else {
|
||||
return getContentLength(connection);
|
||||
}
|
||||
} catch (Exception err) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String pad(int number) {
|
||||
return number < 10 ? ("0" + number) : String.valueOf(number);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 342 B |
|
Before Width: | Height: | Size: 252 B |
|
Before Width: | Height: | Size: 486 B |
|
Before Width: | Height: | Size: 230 B |
|
Before Width: | Height: | Size: 220 B |
|
Before Width: | Height: | Size: 327 B |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 487 B |
|
Before Width: | Height: | Size: 345 B |
|
Before Width: | Height: | Size: 646 B |
|
Before Width: | Height: | Size: 804 B |
|
Before Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 687 B |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/defaultIconTint"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z" />
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/defaultIconTint"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M11,18L11,6l-8.5,6 8.5,6zM11.5,12l8.5,6L20,6l-8.5,6z" />
|
||||
</vector>
|
||||
@@ -1,15 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/defaultIconTint"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:name="flip"
|
||||
android:pivotX="12"
|
||||
android:scaleX="-1">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/defaultIconTint"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z" />
|
||||
</vector>
|
||||
@@ -267,23 +267,21 @@
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/detail_uploader_thumbnail_view"
|
||||
android:id="@+id/detail_sub_channel_thumbnail_view"
|
||||
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
||||
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||
android:contentDescription="@string/detail_sub_channel_thumbnail_view_description"
|
||||
android:src="@drawable/placeholder_person"
|
||||
app:shapeAppearance="@style/CircularImageView" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/detail_sub_channel_thumbnail_view"
|
||||
android:id="@+id/detail_uploader_thumbnail_view"
|
||||
android:layout_width="@dimen/video_item_detail_sub_channel_image_size"
|
||||
android:layout_height="@dimen/video_item_detail_sub_channel_image_size"
|
||||
android:layout_gravity="bottom|right"
|
||||
android:contentDescription="@string/detail_sub_channel_thumbnail_view_description"
|
||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||
android:src="@drawable/placeholder_person"
|
||||
android:visibility="gone"
|
||||
app:shapeAppearance="@style/CircularImageView"
|
||||
tools:visibility="visible" />
|
||||
app:shapeAppearance="@style/CircularImageView" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
@@ -71,11 +71,45 @@
|
||||
android:minWidth="150dp"
|
||||
tools:listitem="@layout/stream_quality_item" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/audio_track_spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/quality_spinner"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:minWidth="150dp"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/audio_stream_spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/audio_track_spinner"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:minWidth="150dp"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/audio_track_present_in_video_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/audio_stream_spinner"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/audio_track_present_in_video"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/threads_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/quality_spinner"
|
||||
android:layout_below="@+id/audio_track_present_in_video_text"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
|
||||
@@ -254,24 +254,22 @@
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/detail_uploader_thumbnail_view"
|
||||
android:id="@+id/detail_sub_channel_thumbnail_view"
|
||||
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
||||
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||
android:contentDescription="@string/detail_sub_channel_thumbnail_view_description"
|
||||
android:src="@drawable/placeholder_person"
|
||||
app:shapeAppearance="@style/CircularImageView" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/detail_sub_channel_thumbnail_view"
|
||||
android:id="@+id/detail_uploader_thumbnail_view"
|
||||
android:layout_width="@dimen/video_item_detail_sub_channel_image_size"
|
||||
android:layout_height="@dimen/video_item_detail_sub_channel_image_size"
|
||||
android:layout_gravity="bottom|right"
|
||||
android:contentDescription="@string/detail_sub_channel_thumbnail_view_description"
|
||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||
android:src="@drawable/placeholder_person"
|
||||
android:visibility="gone"
|
||||
app:shapeAppearance="@style/CircularImageView"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:visibility="visible" />
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
@@ -157,6 +157,22 @@
|
||||
tools:text="The Video Artist LONG very LONG very Long" />
|
||||
</LinearLayout>
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/audioTrackTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:minWidth="0dp"
|
||||
android:padding="@dimen/player_main_buttons_padding"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
tools:ignore="HardcodedText,RtlHardcoded"
|
||||
tools:visibility="visible"
|
||||
tools:text="English (Original)" />
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/qualityTextView"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -18,6 +18,14 @@
|
||||
android:visible="true"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_audio_track"
|
||||
android:tooltipText="@string/audio_track"
|
||||
android:visible="false"
|
||||
app:showAsAction="ifRoom">
|
||||
<menu />
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_mute"
|
||||
android:icon="@drawable/ic_volume_off"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<string name="light_theme_title">فاتح</string>
|
||||
<string name="network_error">خطأ في الشبكة</string>
|
||||
<string name="no_player_found">لم يتم العثور على مشغل بث. تثبيت VLC؟</string>
|
||||
<string name="open_in_browser">فتح في متصفح الويب</string>
|
||||
<string name="open_in_browser">فتح في المتصفح</string>
|
||||
<string name="play_audio">الصوت</string>
|
||||
<string name="play_with_kodi_title">تشغيل بواسطة كودي</string>
|
||||
<string name="search">البحث</string>
|
||||
@@ -46,7 +46,7 @@
|
||||
<string name="general_error">خطأ</string>
|
||||
<string name="parsing_error">تعذر تحليل الموقع</string>
|
||||
<string name="youtube_signature_deobfuscation_error">تعذر فك تشفير توقيع رابط الفيديو</string>
|
||||
<string name="main_bg_subtitle">اضغط على العدسة المكبرة للبدء.</string>
|
||||
<string name="main_bg_subtitle">اضغط على \"العدسة المكبرة\" للبدء.</string>
|
||||
<string name="subscribe_button_title">اشتراك</string>
|
||||
<string name="subscribed_button_title">مشترك</string>
|
||||
<string name="tab_subscriptions">الاشتراكات</string>
|
||||
@@ -91,7 +91,7 @@
|
||||
<string name="show_age_restricted_content_title">محتوى مقيد للبالغين</string>
|
||||
<string name="duration_live">بث مباشر</string>
|
||||
<string name="error_report_title">تقرير عن المشكلة</string>
|
||||
<string name="disabled">متوقف</string>
|
||||
<string name="disabled">معطل</string>
|
||||
<string name="clear">تنظيف</string>
|
||||
<string name="best_resolution">أفضل دقة</string>
|
||||
<string name="undo">تراجع</string>
|
||||
@@ -133,7 +133,7 @@
|
||||
<string name="no_videos">لاتوجد فيديوهات</string>
|
||||
<string name="start">ابدأ</string>
|
||||
<string name="pause">إيقاف مؤقت</string>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="delete">احذف</string>
|
||||
<string name="checksum">التوقيع</string>
|
||||
<string name="ok">حسناً</string>
|
||||
<string name="msg_name">اسم الملف</string>
|
||||
@@ -158,9 +158,9 @@
|
||||
<string name="contribution_title">ساهم</string>
|
||||
<string name="contribution_encouragement">إذا كانت لديك أفكار؛ أو ترجمة، أو تغييرات تخص التصميم، أو تنظيف و تحسين الشفرة البرمجية، أو تعديلات عميقة عليها، فتذكر أنّ مساعدتك دائما موضع ترحيب. وكلما أتممنا شيئا كلما كان ذلك أفضل!</string>
|
||||
<string name="view_on_github">عرض على GitHub</string>
|
||||
<string name="donation_title">تبرع</string>
|
||||
<string name="donation_title">تبرَّع</string>
|
||||
<string name="donation_encouragement">يتم تطوير NewPipe من قبل متطوعين يقضون وقت فراغهم لتقديم أفضل تجربة لك. حان الوقت لرد المساعدة مع المطورين وجعل NewPipe أكثر و أفضل بينما يستمتعون بفنجان من القهوة.</string>
|
||||
<string name="give_back">تبرع</string>
|
||||
<string name="give_back">رد الجميل</string>
|
||||
<string name="website_title">موقع الويب</string>
|
||||
<string name="website_encouragement">قم بزيارة موقع NewPipe لمزيد من المعلومات والمستجدات.</string>
|
||||
<string name="app_license_title">تراخيص NewPipe</string>
|
||||
@@ -174,7 +174,7 @@
|
||||
<string name="trending">الشائعة</string>
|
||||
<string name="top_50">أفضل ٥٠</string>
|
||||
<string name="new_and_hot">جديد وساخن</string>
|
||||
<string name="play_queue_remove">حذف</string>
|
||||
<string name="play_queue_remove">أحذف</string>
|
||||
<string name="play_queue_stream_detail">التفاصيل</string>
|
||||
<string name="play_queue_audio_settings">إعدادات الصوت</string>
|
||||
<string name="start_here_on_popup">بدأ التشغيل في نافذة منبثقة</string>
|
||||
@@ -346,13 +346,9 @@
|
||||
<string name="users">المستخدمين</string>
|
||||
<string name="unsubscribe">إلغاء الاشتراك</string>
|
||||
<string name="tab_choose">اختر علامة التبويب</string>
|
||||
<string name="volume_gesture_control_summary">استخدم إيماءات التحكم في صوت المشغّل</string>
|
||||
<string name="brightness_gesture_control_title">التحكم بإيماءات السطوع</string>
|
||||
<string name="brightness_gesture_control_summary">استخدام الإيماءات للتحكم بسطوع المشغّل</string>
|
||||
<string name="settings_category_updates_title">التحديثات</string>
|
||||
<string name="file_deleted">تم حذف الملف</string>
|
||||
<string name="app_update_notification_channel_name">تنبيه تحديث التطبيق</string>
|
||||
<string name="volume_gesture_control_title">إيماء التحكم بالصوت</string>
|
||||
<string name="events">الأحداث</string>
|
||||
<string name="app_update_notification_channel_description">إشعارات لإصدار NewPipe الجديد</string>
|
||||
<string name="download_to_sdcard_error_title">وحدة التخزين الخارجية غير متوفرة</string>
|
||||
@@ -675,7 +671,6 @@
|
||||
<string name="off">إيقاف</string>
|
||||
<string name="on">تشغيل</string>
|
||||
<string name="tablet_mode_title">وضع الجهاز اللوحي</string>
|
||||
<string name="feed_toggle_show_played_items">إظهار العناصر التي تمت مشاهدتها</string>
|
||||
<string name="comments_are_disabled">تم تعطيل التعليقات</string>
|
||||
<string name="dont_show">لا تظهر</string>
|
||||
<string name="low_quality_smaller">جودة منخفضة (أصغر)</string>
|
||||
@@ -729,7 +724,6 @@
|
||||
<string name="detail_pinned_comment_view_description">تعليق مثبت</string>
|
||||
<string name="leak_canary_not_available">LeakCanary غير متوفر</string>
|
||||
<string name="progressive_load_interval_exoplayer_default">الافتراضي ExoPlayer</string>
|
||||
<string name="progressive_load_interval_summary">تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل</string>
|
||||
<string name="settings_category_player_notification_summary">تكوين إشعار مشغل البث الحالي</string>
|
||||
<string name="notifications">الإشعارات</string>
|
||||
<string name="loading_stream_details">تحميل تفاصيل البث…</string>
|
||||
@@ -766,9 +760,6 @@
|
||||
<string name="unknown_format">تنسيق غير معروف</string>
|
||||
<string name="unknown_quality">جودة غير معروفة</string>
|
||||
<string name="progressive_load_interval_title">حجم الفاصل الزمني لتحميل التشغيل</string>
|
||||
<string name="feed_toggle_show_future_items">عرض العناصر المستقبلية</string>
|
||||
<string name="feed_toggle_hide_future_items">إخفاء العناصر المستقبلية</string>
|
||||
<string name="feed_toggle_hide_played_items">إخفاء العناصر التي تمت مشاهدتها</string>
|
||||
<string name="faq_title">أسئلة مكررة</string>
|
||||
<string name="faq_description">إذا كنت تواجه مشكلة في استخدام التطبيق ، فتأكد من مراجعة هذه الإجابات للأسئلة الشائعة!</string>
|
||||
<string name="faq">مشاهدة على الموقع</string>
|
||||
@@ -793,4 +784,33 @@
|
||||
<string name="remove_duplicates_title">إزالة التكرارات؟</string>
|
||||
<string name="feed_hide_streams_title">إظهار التدفقات التالية</string>
|
||||
<string name="feed_show_watched">شاهدت بالكامل</string>
|
||||
<string name="left_gesture_control_title">إجراء الإيماءة اليسرى</string>
|
||||
<string name="right_gesture_control_title">اجراء الإيماءة اليمنى</string>
|
||||
<string name="brightness">السطوع</string>
|
||||
<string name="none">بدون</string>
|
||||
<string name="left_gesture_control_summary">اختر إيماءة للنصف الأيسر من شاشة المشغل</string>
|
||||
<string name="right_gesture_control_summary">اختر إيماءة للنصف الأيمن من شاشة المشغل</string>
|
||||
<string name="volume">مستوى الصوت</string>
|
||||
<string name="progressive_load_interval_summary">قم بتغيير حجم الفاصل الزمني للتحميل على المحتويات التدريجية (حاليا %s). قد تؤدي القيمة المنخفضة إلى تسريع التحميل الأولي</string>
|
||||
<string name="prefer_descriptive_audio_title">تفضل الصوت الوصفي</string>
|
||||
<string name="play_queue_audio_track">الصوت : %s</string>
|
||||
<string name="audio_track">المسار الصوتي</string>
|
||||
<string name="audio_track_present_in_video">يجب أن يكون هناك مسار صوتي موجود بالفعل في هذا البث</string>
|
||||
<string name="select_audio_track_external_players">حدد مسار الصوت للمشغلات الخارجية</string>
|
||||
<string name="unknown_audio_track">غير معروف</string>
|
||||
<string name="settings_category_exoplayer_title">إعدادات ExoPlayer</string>
|
||||
<string name="settings_category_exoplayer_summary">إدارة بعض إعدادات ExoPlayer. تتطلب هذه التغييرات إعادة تشغيل المشغل لتصبح سارية المفعول</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_title">استخدم دائمًا الحل البديل لإعداد سطح إخراج فيديو ExoPlayer</string>
|
||||
<string name="audio_track_name">%s %s</string>
|
||||
<string name="audio_track_type_original">الافتراضي</string>
|
||||
<string name="audio_track_type_dubbed">مدبلجة</string>
|
||||
<string name="audio_track_type_descriptive">وصفي</string>
|
||||
<string name="prefer_original_audio_summary">حدد المسار الصوتي الأصلي بغض النظر عن اللغة</string>
|
||||
<string name="prefer_original_audio_title">تفضيل الصوت الأصلي</string>
|
||||
<string name="prefer_descriptive_audio_summary">حدد مسارًا صوتيًا يحتوي على أوصاف للأشخاص ضعاف البصر إذا كان ذلك متاحًا</string>
|
||||
<string name="use_exoplayer_decoder_fallback_title">استخدم ميزة فك ترميز وحدة فك التشفير الاحتياطية في ExoPlayer</string>
|
||||
<string name="use_exoplayer_decoder_fallback_summary">قم بتمكين هذا الخيار إذا كانت لديك مشكلات في تهيئة وحدة فك التشفير ، والتي تعود إلى أجهزة فك التشفير ذات الأولوية الأقل إذا فشلت تهيئة وحدات فك التشفير الأولية. قد ينتج عن ذلك أداء تشغيل ضعيف مقارنة باستخدام وحدات فك التشفير الأساسية</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_summary">يقوم هذا الحل البديل بتحرير وإعادة إنشاء نماذج برامج ترميز الفيديو عند حدوث تغيير في السطح، بدلا من تعيين السطح إلى برنامج الترميز مباشرة. تم استخدام هذا الإعداد بالفعل بواسطة ExoPlayer على بعض الأجهزة التي تعاني من هذه المشكلة ، وهذا الإعداد له تأثير فقط على Android 6 والإصدارات الأحدث
|
||||
\n
|
||||
\nقد يؤدي تمكين هذا الخيار إلى منع أخطاء التشغيل عند تبديل مشغل الفيديو الحالي أو التبديل إلى وضع ملء الشاشة</string>
|
||||
</resources>
|
||||
@@ -4,35 +4,35 @@
|
||||
<string name="upload_date_text">%1$s tarixində yayımlanıb</string>
|
||||
<string name="no_player_found">Yayım oynadıcı tapılmadı. \"VLC\" quraşdırılsın\?</string>
|
||||
<string name="no_player_found_toast">Yayım oynadıcı tapılmadı (Oynatmaq üçün VLC quraşdıra bilərsiniz).</string>
|
||||
<string name="install">Yüklə</string>
|
||||
<string name="install">Quraşdır</string>
|
||||
<string name="cancel">Ləğv et</string>
|
||||
<string name="open_in_browser">Brauzerdə aç</string>
|
||||
<string name="share">Paylaş</string>
|
||||
<string name="download">Endir</string>
|
||||
<string name="controls_download_desc">Yayım faylını endir</string>
|
||||
<string name="download">Yüklə</string>
|
||||
<string name="controls_download_desc">Yayım faylın yüklə</string>
|
||||
<string name="search">Axtarış</string>
|
||||
<string name="settings">Tənzimləmələr</string>
|
||||
<string name="did_you_mean">Bunu demək istəyirdiniz: \"%1$s\"\?</string>
|
||||
<string name="did_you_mean">\"%1$s\" nəzərdə tuturdunuz\?</string>
|
||||
<string name="share_dialog_title">ilə paylaş</string>
|
||||
<string name="use_external_video_player_title">Xarici video oynadıcı istifadə et</string>
|
||||
<string name="use_external_video_player_summary">Bəzi ayırdetmələrdə səsi silir</string>
|
||||
<string name="use_external_video_player_summary">Bəzi formatlarda səsi silir</string>
|
||||
<string name="use_external_audio_player_title">Xarici səs oynadıcı istifadə et</string>
|
||||
<string name="subscribe_button_title">Abunə Ol</string>
|
||||
<string name="subscribed_button_title">Abunə olundu</string>
|
||||
<string name="channel_unsubscribed">Kanal abunəliyi ləğv edildi</string>
|
||||
<string name="show_info">Məlumat göstər</string>
|
||||
<string name="tab_subscriptions">Abunələr</string>
|
||||
<string name="tab_bookmarks">Əlfəcinlənmiş Pleylistlər</string>
|
||||
<string name="tab_subscriptions">Abunəliklər</string>
|
||||
<string name="tab_bookmarks">Əlfəcinlənmiş Oynatma Siyahıları</string>
|
||||
<string name="fragment_feed_title">Yeniliklər</string>
|
||||
<string name="controls_background_title">Fon</string>
|
||||
<string name="download_path_title">Video endirmə qovluğu</string>
|
||||
<string name="download_path_summary">Endirilmiş video fayllar burada saxlanılır</string>
|
||||
<string name="download_path_dialog_title">Video fayllar üçün endirmə qovluğu seç</string>
|
||||
<string name="download_path_audio_title">Səs endirmə qovluğu</string>
|
||||
<string name="download_path_audio_summary">Endirilmiş səs faylları burada saxlanılır</string>
|
||||
<string name="download_path_audio_dialog_title">Səs faylları üçün endirmə qovluğu seç</string>
|
||||
<string name="default_resolution_title">Standart ayırdetmə</string>
|
||||
<string name="show_higher_resolutions_title">Daha böyük ayırdetmələr göstər</string>
|
||||
<string name="download_path_title">Video yükləmə qovluğu</string>
|
||||
<string name="download_path_summary">Yüklənilmiş video fayllar burada saxlanılır</string>
|
||||
<string name="download_path_dialog_title">Video fayllar üçün yükləmə qovluğu seç</string>
|
||||
<string name="download_path_audio_title">Səs yükləmə qovluğu</string>
|
||||
<string name="download_path_audio_summary">Yüklənilmiş səs faylları burada saxlanılır</string>
|
||||
<string name="download_path_audio_dialog_title">Səs faylları üçün yükləmə qovluğu seç</string>
|
||||
<string name="default_resolution_title">Standart format</string>
|
||||
<string name="show_higher_resolutions_title">Daha böyük formatlar göstər</string>
|
||||
<string name="play_with_kodi_title">\"Kodi\" ilə Oynat</string>
|
||||
<string name="kore_not_found">Çatışmayan \"Kore\" tətbiqi yüklənilsin\?</string>
|
||||
<string name="show_play_with_kodi_title">\"Kodi ilə Oynat\" seçimini göstər</string>
|
||||
@@ -61,10 +61,6 @@
|
||||
<string name="enable_search_history_title">Axtarış tarixçəsi</string>
|
||||
<string name="show_search_suggestions_summary">Axtarış zamanı göstərmək üçün təklifləri seç</string>
|
||||
<string name="show_search_suggestions_title">Axtarış təklifləri</string>
|
||||
<string name="brightness_gesture_control_summary">Oynadıcı parlaqlığını nizamlamaq üçün jestlər istifadə et</string>
|
||||
<string name="brightness_gesture_control_title">Parlaqlıq jesti idarəetməsi</string>
|
||||
<string name="volume_gesture_control_summary">Oynadıcı səsini nizamlamaq üçün jestlər istifadə et</string>
|
||||
<string name="volume_gesture_control_title">Səs səviyyəsi jesti idarəetməsi</string>
|
||||
<string name="auto_queue_toggle">Avto-növbələ</string>
|
||||
<string name="auto_queue_title">Növbəti Yayımı Avto-növbələ</string>
|
||||
<string name="metadata_cache_wipe_complete_notice">Üst məlumat keşi silindi</string>
|
||||
@@ -89,13 +85,13 @@
|
||||
<string name="notification_action_1_title">İkinci fəaliyyət düyməsi</string>
|
||||
<string name="notification_action_0_title">Birinci fəaliyyət düyməsi</string>
|
||||
<string name="show_higher_resolutions_summary">Yalnız bəzi cihazlar 2K/4K videoları oynada bilir</string>
|
||||
<string name="default_popup_resolution_title">Standart ani görüntü ayırdetməsi</string>
|
||||
<string name="default_popup_resolution_title">Standart ani görüntü formatı</string>
|
||||
<string name="controls_add_to_playlist_title">Əlavə Et</string>
|
||||
<string name="controls_popup_title">Ani Görüntü</string>
|
||||
<string name="tab_choose">Paneli Seç</string>
|
||||
<string name="subscription_update_failed">Abunəliyi yeniləmək alınmadı</string>
|
||||
<string name="subscription_change_failed">Abunəliyi dəyişmək alınmadı</string>
|
||||
<string name="search_showing_result_for">Nəticələr göstərilir: %s</string>
|
||||
<string name="search_showing_result_for">%s üçün nəticələr göstərilir</string>
|
||||
<string name="channels">Kanallar</string>
|
||||
<string name="video_detail_by">%s tərəfindən</string>
|
||||
<string name="youtube_restricted_mode_enabled_title">YouTube\'un \"Məhdud Rejimi\"ni aç</string>
|
||||
@@ -429,21 +425,21 @@
|
||||
<string name="show_thumbnail_summary">Həm kilid ekranı fonu, həm də bildirişlər üçün miniatür istifadə et</string>
|
||||
<string name="recent">Ən Yeni</string>
|
||||
<string name="georestricted_content">Bu məzmun ölkənizdə mövcud deyil.</string>
|
||||
<string name="paid_content">Bu məzmun yalnız ödəniş etmiş istifadəçilər üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlana və ya endirilə bilməz.</string>
|
||||
<string name="paid_content">Bu məzmun yalnız ödəniş etmiş istifadəçilər üçün əlçatandır, beləliklə, NewPipe tərəfindən yayımlana və ya yüklənilə bilməz.</string>
|
||||
<string name="auto_device_theme_title">Avtomatik (cihaz teması)</string>
|
||||
<string name="night_theme_summary">Sevimli gecə temanızı seçin — %s</string>
|
||||
<string name="detail_pinned_comment_view_description">Sabitlənmiş şərh</string>
|
||||
<string name="notifications_disabled">Bildirişlər deaktiv edilib</string>
|
||||
<string name="detail_pinned_comment_view_description">Sancaqlanmış şərh</string>
|
||||
<string name="notifications_disabled">Bildirişlər qeyri-aktivdir</string>
|
||||
<string name="get_notified">Bildiriş al</string>
|
||||
<string name="you_successfully_subscribed">Artıq bu kanala abunə oldunuz</string>
|
||||
<string name="enumeration_comma">,</string>
|
||||
<string name="toggle_all">Hamısını dəyişdir</string>
|
||||
<string name="toggle_all">Hamısın dəyişdir</string>
|
||||
<string name="msg_name">Fayl adı</string>
|
||||
<string name="recaptcha_solve">Həll et</string>
|
||||
<string name="subscriptions_export_unsuccessful">Abunəlikləri ixrac etmək mümkün olmadı</string>
|
||||
<plurals name="watching">
|
||||
<item quantity="one">%s izləyici</item>
|
||||
<item quantity="other">%s izləyici</item>
|
||||
<item quantity="one">%s baxıcı</item>
|
||||
<item quantity="other">%s baxıcı</item>
|
||||
</plurals>
|
||||
<string name="manual_update_description">Yeni versiyaları əl ilə yoxla</string>
|
||||
<plurals name="listening">
|
||||
@@ -489,14 +485,12 @@
|
||||
<string name="feed_load_error_terminated">Müəllifin hesabı bağlanıb.
|
||||
\nNewPipe gələcəkdə bu axını yükləyə bilməyəcək.
|
||||
\nBu kanaldan abunəliyi çıxarmaq istəyirsiniz\?</string>
|
||||
<string name="feed_toggle_show_played_items">Baxılan elementləri göstər</string>
|
||||
<string name="featured">Seçilmiş</string>
|
||||
<string name="featured">Seçilən</string>
|
||||
<string name="drawer_close">Çəkməcəni Bağla</string>
|
||||
<string name="video_player">Video oynadıcı</string>
|
||||
<string name="hash_channel_description">Video fayl xülasəsi prosesi üçün bildirişlər</string>
|
||||
<string name="on">Aç</string>
|
||||
<string name="notification_scale_to_square_image_title">Miniatürü 1:1 görünüş nisbətinə kəs</string>
|
||||
<string name="progressive_load_interval_summary">Yükləmə intervalı həcmini dəyişdir (hazırda %s). Daha aşağı dəyər ilkin video yükləməni sürətləndirə bilər. Dəyişikliklər oynadıcını yenidən başlatmağı tələb edir</string>
|
||||
<string name="show_meta_info_summary">Yayım yaradıcısı, məzmunu və ya axtarış sorğusu haqqında əlavə məlumat olan üst məlumat qutularını gizlətmək üçün söndür</string>
|
||||
<string name="auto_queue_summary">Əlaqəli yayımı əlavə etməklə (təkrarlanmayan) sonlanacaq oynatma növbəsini davam etdir</string>
|
||||
<string name="remote_search_suggestions">Kənar axtarış təklifləri</string>
|
||||
@@ -524,8 +518,8 @@
|
||||
<string name="enable_queue_limit_desc">Eyni vaxtda ancaq bir endirmə həyata keçiriləcək</string>
|
||||
<string name="account_terminated">Hesab ləğv edildi</string>
|
||||
<string name="service_provides_reason">%s bu səbəbi təmin edir:</string>
|
||||
<string name="download_has_started">Endirmə başladı</string>
|
||||
<string name="description_select_disable">Açıqlamadakı mətni seçməyi deaktiv et</string>
|
||||
<string name="download_has_started">Yükləmə başladı</string>
|
||||
<string name="description_select_disable">Açıqlamadakı mətni seçməyi qeyri-aktiv et</string>
|
||||
<string name="metadata_category">Kateqoriya</string>
|
||||
<string name="metadata_privacy_internal">Daxili</string>
|
||||
<string name="description_select_enable">Açıqlamadakı mətni seçməyi aktivləşdir</string>
|
||||
@@ -550,7 +544,7 @@
|
||||
<item quantity="one">Endirmə tamamlandı</item>
|
||||
<item quantity="other">%s endirmə tamamlandı</item>
|
||||
</plurals>
|
||||
<string name="progressive_load_interval_exoplayer_default">Standart ExoPlayer</string>
|
||||
<string name="progressive_load_interval_exoplayer_default">ExoPlayer standartı</string>
|
||||
<string name="feed_use_dedicated_fetch_method_title">Mövcud olduqda xüsusi axından al</string>
|
||||
<string name="remove_watched_popup_title">Baxılmış videolar silinsin\?</string>
|
||||
<string name="remove_watched">İzləniləni sil</string>
|
||||
@@ -622,7 +616,7 @@
|
||||
<string name="export_to">Bura ixrac et</string>
|
||||
<string name="import_file_title">Faylı idxal et</string>
|
||||
<string name="subscriptions_import_unsuccessful">Abunəlikləri idxal etmək mümkün olmadı</string>
|
||||
<string name="start_accept_privacy_policy">Avropa Ümumi Məlumat Mühafizəsi Qaydasına (GDPR) riayət etmək üçün diqqətinizi NewPipe məxfilik siyasətinə cəlb edirik. Zəhmət olmasa, diqqətlə oxuyun. Xəta hesabatın bizə göndərmək üçün qəbul etməlisiniz.</string>
|
||||
<string name="start_accept_privacy_policy">Avropa Ümumi Məlumat Mühafizəsi Qaydasına (GDPR) riayət etmək üçün diqqətinizi NewPipe məxfilik siyasətinə cəlb edirik. Zəhmət olmasa, diqqətlə oxuyun. Xəta məlumatın bizə göndərmək üçün qəbul etməlisiniz.</string>
|
||||
<string name="overwrite_unrelated_warning">Bu adda fayl artıq mövcuddur</string>
|
||||
<string name="download_already_pending">Bu adla gözlənilən bir endirmə var</string>
|
||||
<string name="error_path_creation">Təyinat qovluğu yaradıla bilməz</string>
|
||||
@@ -673,16 +667,16 @@
|
||||
<string name="loading_stream_details">Yayım təfərrüatları yüklənir…</string>
|
||||
<string name="disable_media_tunneling_title">Media tunelini deaktiv et</string>
|
||||
<string name="crash_the_app">Tətbiq çökdü</string>
|
||||
<string name="import_youtube_instructions">YouTube abunəliklərini Google takeout\'dan
|
||||
\nidxal edin:
|
||||
<string name="import_youtube_instructions">YouTube abunəliklərin Google Takeout-dan
|
||||
\nidxal et:
|
||||
\n
|
||||
\n1. Bu URL\'ə keçin: %1$s
|
||||
\n2. Soruşulduqda daxil olun
|
||||
\n3.\"Bütün Məlumatlar Daxildir\",sonra \"Heçbirini Seçmə\", yalnız \"abunəliklər\"i seçin və \"Oldu\" kliklə
|
||||
\n4. \"Növbəti addım\"üzərinə klikləyin, sonra isə \"İxrac Yarat\" üzərinə klikləyin
|
||||
\n5. Görünəndən sonra \"Endir\"düyməsini basın
|
||||
\n6. Aşağıdakı FAYLI İDXAL ET düyməsinə klikləyin və endirilmiş .zip faylını seçin
|
||||
\n7. [Əgər .zip faylı idxalı uğursuz olsa] .csv faylını çıxarın(adətən\"YouTubevəYouTubeMusic/subscriptions/subscriptions.csv\" altında),aşağıda İDXAL EDİLƏN FAYL-ı klikləyin və çıxarılmış csv faylını seçin</string>
|
||||
\n1. %1$s URL\'ə keçin:
|
||||
\n2. Soruşulduqda daxil ol
|
||||
\n3. \"Bütün Məlumatlar Daxildir\",sonra \"Hamısın Seçmə\", yalnız \"abunəlikləri\" seç və \"Oldu\" kliklə
|
||||
\n4. \"Növbəti addım\"üzərinə kliklə, sonra isə \"İxrac Yarat\" üzərinə kliklə
|
||||
\n5. Görünəndən sonra, \"Endirin\"düyməsin bas
|
||||
\n6. Aşağıda FAYLI İDXAL ET düyməsin kliklə və yüklənilmiş (.zip) faylın seç
|
||||
\n7. [Əgər .zip faylı idxalı uğursuz olsa] .csv faylın çıxar(adətən\"YouTubeandYouTubeMusic/subscriptions/subscriptions.csv\" altında),aşağıda FAYLI İDXAL ET-ə kliklə və çıxarılan csv faylın seç</string>
|
||||
<string name="playback_speed_control">Oynatma Sürəti Nizamlamaları</string>
|
||||
<string name="unhook_checkbox">Ayır (pozuntuya səbəb ola bilər)</string>
|
||||
<string name="show_error">Xətanı göstər</string>
|
||||
@@ -702,21 +696,18 @@
|
||||
<string name="no_appropriate_file_manager_message_android_10">Bu əməliyyat üçün uyğun fayl meneceri tapılmadı.
|
||||
\nZəhmət olmasa ,Yaddaş Giriş Çərçivəsinə uyğun fayl menecerini quraşdırın</string>
|
||||
<string name="youtube_music_premium_content">Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.</string>
|
||||
<string name="description_select_note">İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə və keçidlər kliklənməyə bilər.</string>
|
||||
<string name="description_select_note">İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə və linklər kliklənməyə bilər.</string>
|
||||
<string name="notification_scale_to_square_image_summary">Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 görünüş nisbətinə qədər kəs</string>
|
||||
<string name="notification_actions_summary">Aşağıdakı hər bir bildiriş fəaliyyətini üzərinə toxunaraq redaktə et. Sağdakı təsdiq qutularından istifadə edərək yığcam bildirişdə göstərmək üçün onların üçünü seç</string>
|
||||
<string name="invalid_source">Belə fayl/məzmun mənbəyi yoxdur</string>
|
||||
<string name="selected_stream_external_player_not_supported">Seçilmiş yayım xarici oynadıcılar tərəfindən dəstəklənmir</string>
|
||||
<string name="selected_stream_external_player_not_supported">Seçilən yayım xarici oynadıcılar tərəfindən dəstəklənmir</string>
|
||||
<string name="streams_not_yet_supported_removed">Yükləyici tərəfindən hələ dəstəklənməyən yayımlar göstərilmir</string>
|
||||
<string name="no_audio_streams_available_for_external_players">Xarici oynadıcılar üçün heç bir səs yayımı yoxdur</string>
|
||||
<string name="no_video_streams_available_for_external_players">Xarici oynadıcılar üçün heç bir video yayımı yoxdur</string>
|
||||
<string name="no_audio_streams_available_for_external_players">Xarici oynadıcılar üçün mövcud səs yayımı yoxdur</string>
|
||||
<string name="no_video_streams_available_for_external_players">Xarici oynadıcılar üçün mövcud video yayımı yoxdur</string>
|
||||
<string name="select_quality_external_players">Xarici oynadıcılar üçün keyfiyyət seç</string>
|
||||
<string name="unknown_format">Naməlum format</string>
|
||||
<string name="unknown_quality">Naməlum keyfiyyət</string>
|
||||
<string name="progressive_load_interval_title">Oynatma yükləmə intervalı həcmi</string>
|
||||
<string name="feed_toggle_show_future_items">Gələcək elementləri göstər</string>
|
||||
<string name="feed_toggle_hide_played_items">Baxılan elementləri gizlət</string>
|
||||
<string name="feed_toggle_hide_future_items">Gələcək elementləri gizlət</string>
|
||||
<string name="faq_description">Tətbiqi istifadə etməkdə çətinlik çəkirsinizsə, ümumi suallara bu cavabları yoxladığınıza əmin olun!</string>
|
||||
<string name="faq_title">Tez-tez soruşulan suallar</string>
|
||||
<string name="faq">Veb Saytında bax</string>
|
||||
@@ -741,4 +732,33 @@
|
||||
<string name="feed_show_partially_watched">Qismən baxılıb</string>
|
||||
<string name="remove_duplicates_message">Bu pleylistdəki bütün dublikat yayımları silmək istəyirsiniz\?</string>
|
||||
<string name="feed_show_upcoming">Yaxınlaşan</string>
|
||||
<string name="left_gesture_control_title">Sol jest hərəkəti</string>
|
||||
<string name="right_gesture_control_summary">Oynadıcı ekranının sağ yarısı üçün jest seç</string>
|
||||
<string name="right_gesture_control_title">Sağ jest hərəkəti</string>
|
||||
<string name="brightness">Parlaqlıq</string>
|
||||
<string name="volume">Səs səviyyəsi</string>
|
||||
<string name="none">Heç biri</string>
|
||||
<string name="left_gesture_control_summary">Oynadıcı ekranının sol yarısı üçün jest seç</string>
|
||||
<string name="prefer_original_audio_title">Orijinal səsə üstünlük ver</string>
|
||||
<string name="prefer_original_audio_summary">Dildən asılı olmayaraq orijinal səs axını seç</string>
|
||||
<string name="prefer_descriptive_audio_title">Təsviri səsə üstünlük ver</string>
|
||||
<string name="prefer_descriptive_audio_summary">Varsa, görmə qabiliyyəti zəifləyən insanlar üçün təsviri olan səs axını seçin</string>
|
||||
<string name="play_queue_audio_track">Səs: %s</string>
|
||||
<string name="audio_track">Səs axını</string>
|
||||
<string name="audio_track_present_in_video">Səs axını bu yayımda olmalıdır</string>
|
||||
<string name="select_audio_track_external_players">Xarici oynadıcılar üçün səs axını seç</string>
|
||||
<string name="unknown_audio_track">Naməlum</string>
|
||||
<string name="settings_category_exoplayer_title">ExoPlayer tənzimləmələri</string>
|
||||
<string name="settings_category_exoplayer_summary">Bəzi ExoPlayer tənzimləmələrin idarə et. Bu dəyişiklikləri təsirli etmək üçün oynadıcını yenidən başlatmaq tələb olunur</string>
|
||||
<string name="use_exoplayer_decoder_fallback_title">ExoPlayer-in çözücü xüsusiyyətin istifadə et</string>
|
||||
<string name="use_exoplayer_decoder_fallback_summary">Əsas çözücüləri işlətmə uğursuz olarsa, çözücü işlətmək probleminiz varsa (daha aşağı prioritetli çözücülərə düşür), bu seçimi aktiv edin. Bu, əsas çözücülərdən istifadə ilə müqayisədə zəif oynatma performansı ilə nəticələnə bilər</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Bu həll yolu səthi kodlayıcıya birbaşa tənzimləmək əvəzinə, səth dəyişikliyi olarsa video kodlayıcıları buraxır və yenidən işlədir. Artıq ExoPlayer tərəfindən bu problemi olan bəzi cihazlarda istifadə olunur, bu tənzimləmənin təsiri yalnız Android 6 və daha yüksəkdə var.
|
||||
\n
|
||||
\nBu seçimi aktivləşdirmə cari video oynadıcı dəyişdiriləndə və ya tam ekrana keçəndə oynatma xətalarının qarşısını ala bilər</string>
|
||||
<string name="audio_track_name">%s %s</string>
|
||||
<string name="audio_track_type_original">orijinal</string>
|
||||
<string name="audio_track_type_dubbed">dublyaj edilib</string>
|
||||
<string name="audio_track_type_descriptive">təsviri</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_title">Həmişə ExoPlayer-in video çıxış səthi tənzimləməsin istifadə et</string>
|
||||
<string name="progressive_load_interval_summary">Qabaqcıl məzmunda yükləmə aralığı həcmin dəyişdir (hazırda %s). Daha aşağı dəyər onların ilkin yüklənilməsin sürətləndirə bilər</string>
|
||||
</resources>
|
||||
@@ -150,8 +150,6 @@
|
||||
<string name="playlists">Llistes de reproducción</string>
|
||||
<string name="tracks">Pistes</string>
|
||||
<string name="users">Usuarios</string>
|
||||
<string name="volume_gesture_control_summary">Usa xestos pa controlar el volume del reproductor</string>
|
||||
<string name="brightness_gesture_control_summary">Usa xestos pa controlar el brillu del reproductor</string>
|
||||
<string name="restore_defaults">Reafitamientu de valores</string>
|
||||
<string name="subscribers_count_not_available">El númberu de soscriptores nun ta disponible</string>
|
||||
<string name="updates_setting_title">Anovamientos</string>
|
||||
@@ -226,8 +224,6 @@
|
||||
</plurals>
|
||||
<string name="preferred_player_fetcher_notification_message">Cargando\'l conteníu solicitáu</string>
|
||||
<string name="privacy_policy_title">Política de privacidá de NewPipe</string>
|
||||
<string name="volume_gesture_control_title">Control per xestos del volume</string>
|
||||
<string name="brightness_gesture_control_title">Control per xestos del brillu</string>
|
||||
<string name="error_file_creation">El ficheru nun pue crease</string>
|
||||
<string name="error_http_no_content">El sirvidor nun unvia datos</string>
|
||||
<string name="localization_changes_requires_app_restart">La llingua va camudar namás que se reanicie l\'aplicación.</string>
|
||||
|
||||
@@ -17,10 +17,6 @@
|
||||
<string name="no_player_found">Hech qanday translatsiya pleyeri topilmadi. VLC o\'rnatilsinmi\?</string>
|
||||
<string name="upload_date_text">%1$s tomonidan e‘lon qilingan</string>
|
||||
<string name="main_bg_subtitle">Boshlash uchun \"Izlash\" tugmasini bosing</string>
|
||||
<string name="volume_gesture_control_summary">Player tovushini boshqarish uchun imo-ishoralardan foydalanish</string>
|
||||
<string name="brightness_gesture_control_summary">Player yorqinligini boshqarish uchun imo-ishoralardan foydalaning</string>
|
||||
<string name="brightness_gesture_control_title">Yorqinlik ishoralarini boshqarish</string>
|
||||
<string name="volume_gesture_control_title">Ovoz balandligini ishoralarni boshqarish</string>
|
||||
<string name="auto_queue_toggle">Avto-navbat</string>
|
||||
<string name="auto_queue_summary">Tegishli stream qo\'shib, ijro etish navbatini tugatishni (takrorlanmaydigan) davom ettirish</string>
|
||||
<string name="auto_queue_title">avtomatik navbat next stream</string>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="main_bg_subtitle">Націсніце «Пошук», каб пачаць.</string>
|
||||
<string name="upload_date_text">Апублікавана %1$s</string>
|
||||
<string name="no_player_found">Патокавы прайгравальнік не знойдзены. Усталяваць VLC\?</string>
|
||||
<string name="no_player_found_toast">Патокавы плэер не знойдзены (вы можаце ўсталяваць VLC).</string>
|
||||
<string name="no_player_found_toast">Патокавы плэер не знойдзены (вы можаце ўсталяваць VLC каб прайграць).</string>
|
||||
<string name="install">Усталяваць</string>
|
||||
<string name="cancel">Скасаваць</string>
|
||||
<string name="open_in_browser">Адкрыць у браўзеры</string>
|
||||
@@ -338,10 +338,6 @@
|
||||
<string name="minimize_on_exit_popup_description">Плэер у акне</string>
|
||||
<string name="unsubscribe">Адпісацца</string>
|
||||
<string name="tab_choose">Абярыце ўкладку</string>
|
||||
<string name="volume_gesture_control_title">Жэст гучнасці</string>
|
||||
<string name="volume_gesture_control_summary">Мяняць гучнасць плэера жэстамі</string>
|
||||
<string name="brightness_gesture_control_title">Жэст яркасці</string>
|
||||
<string name="brightness_gesture_control_summary">Мяняць яркасць плэера жэстамі</string>
|
||||
<string name="settings_category_updates_title">Абнаўленні</string>
|
||||
<string name="file_deleted">Файл выдалены</string>
|
||||
<string name="app_update_notification_channel_name">Апавяшчэнне аб абнаўленні праграмы</string>
|
||||
@@ -503,8 +499,6 @@
|
||||
</plurals>
|
||||
<string name="feed_group_dialog_empty_selection">Падпіскі не выбраны</string>
|
||||
<string name="feed_oldest_subscription_update">Апошняе абнаўленне: %s</string>
|
||||
<string name="feed_toggle_show_played_items">Паказаць прагледжаныя матэрыялы</string>
|
||||
<string name="feed_toggle_hide_played_items">Схаваць прагледжаныя матэрыялы</string>
|
||||
<string name="auto_device_theme_title">Аўтаматычна (тэма прылады)</string>
|
||||
<string name="night_theme_summary">Выберыце ўлюбёную начную тэму - %s</string>
|
||||
<string name="description_select_enable">Дазвол вылучэння тэксту ў апісанні</string>
|
||||
@@ -521,8 +515,6 @@
|
||||
<string name="select_quality_external_players">Выберыце якасць для знешніх плэераў</string>
|
||||
<string name="unknown_quality">Невядомая якасць</string>
|
||||
<string name="unknown_format">Невядомы фармат</string>
|
||||
<string name="feed_toggle_show_future_items">Паказаць наступны матэрыял</string>
|
||||
<string name="feed_toggle_hide_future_items">Схаваць наступныя матэрыялы</string>
|
||||
<string name="sort">Сартаваць</string>
|
||||
<string name="new_seek_duration_toast">З-за абмежаванняў ExoPlayer працягласць пошуку была ўсталявана на %d сякундаў</string>
|
||||
<string name="chapters">Раздзелы</string>
|
||||
@@ -592,7 +584,7 @@
|
||||
<item quantity="many">%d хвілінаў</item>
|
||||
<item quantity="other">%d хвілінаў</item>
|
||||
</plurals>
|
||||
<string name="progressive_load_interval_summary">Змяніць памер інтэрвалу загрузкі (зараз %s). Меншае значэнне можа паскорыць пачатковую загрузку відэа. Змены патрабуюць перазапуск плэера</string>
|
||||
<string name="progressive_load_interval_summary">Змяніць памер інтэрвалу загрузкі прагрэсіўнага змесціва (у цяперашні час %s). Меншае значэнне можа паскорыць іх першапачатковую загрузку</string>
|
||||
<string name="show_description_summary">Выключыце, каб схаваць апісанне відэа і дадатковую інфармацыю</string>
|
||||
<string name="local_search_suggestions">Прапановы лакальнага пошуку</string>
|
||||
<string name="settings_category_player_notification_summary">Наладзіць апавяшчэнне аб бягучым прайграванні патоку</string>
|
||||
@@ -765,4 +757,32 @@
|
||||
<string name="metadata_privacy">Прыватнасць</string>
|
||||
<string name="metadata_language">Мова</string>
|
||||
<string name="metadata_support">Падтрымка</string>
|
||||
<string name="left_gesture_control_title">Дзеянне левага жэсту</string>
|
||||
<string name="right_gesture_control_title">Дзеянне правага жэсту</string>
|
||||
<string name="brightness">Яркасць</string>
|
||||
<string name="none">Нічога</string>
|
||||
<string name="left_gesture_control_summary">Выбраць жэст для левай часткі экрана прайгравання</string>
|
||||
<string name="volume">Гук</string>
|
||||
<string name="right_gesture_control_summary">Выбраць жэст для правай часткі экрана прайгравання</string>
|
||||
<string name="prefer_original_audio_summary">Выбіраць зыходную гукавую дарожку незалежна ад мовы</string>
|
||||
<string name="prefer_original_audio_title">Аддаць перавагу арыгінальнаму гуку</string>
|
||||
<string name="prefer_descriptive_audio_title">Аддаць перавагу апісальнаму гуку</string>
|
||||
<string name="prefer_descriptive_audio_summary">Выберыце гукавую дарожку з апісаннем для людзей са слабым зрокам, калі яна ёсць</string>
|
||||
<string name="play_queue_audio_track">Аўдыё: %s</string>
|
||||
<string name="audio_track">Гукавая дарожка</string>
|
||||
<string name="select_audio_track_external_players">Выберыце гукавую дарожку для знешніх прайгравальнікаў</string>
|
||||
<string name="unknown_audio_track">Невядомая</string>
|
||||
<string name="settings_category_exoplayer_title">Налады ExoPlayer</string>
|
||||
<string name="use_exoplayer_decoder_fallback_title">Выкарыстоўваць функцыю рэзервовага дэкодэра ExoPlayer</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_title">Заўсёды выкарыстоўваць спосаб абыходу налад паверхні відэавываду ExoPlayer</string>
|
||||
<string name="audio_track_name">%s %s</string>
|
||||
<string name="audio_track_type_original">арыгінальны</string>
|
||||
<string name="audio_track_type_dubbed">дубляваны</string>
|
||||
<string name="audio_track_type_descriptive">апісальны</string>
|
||||
<string name="audio_track_present_in_video">Гукавая дарожка ўжо павінна прысутнічаць у гэтай плыні</string>
|
||||
<string name="use_exoplayer_decoder_fallback_summary">Уключыце гэту опцыю, калі ў вас ёсць праблемы з ініцыялізацыяй дэкодэра, якая вяртаецца да дэкодэраў з больш нізкім прыярытэтам, калі ініцыялізацыя асноўных дэкодэраў не ўдаецца. Гэта можа прывесці да нізкай прадукцыйнасці прайгравання, чым пры выкарыстанні асноўных дэкодэраў</string>
|
||||
<string name="settings_category_exoplayer_summary">Кіраванне некаторымі наладамі ExoPlayer. Каб гэтыя змены ўступілі ў сілу, патрабуецца перазапуск гульца</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Гэты абыходны шлях вызваляе і паўторна стварае відэакодэкі, калі адбываецца змяненне паверхні, замест таго, каб усталёўваць паверхню непасрэдна для кодэка. ExoPlayer ужо выкарыстоўваецца на некаторых прыладах з гэтай праблемай, гэты параметр мае ўплыў толькі на прыладах з Android 6 і вышэй
|
||||
\n
|
||||
\nУключэнне гэтай опцыі можа прадухіліць памылкі прайгравання пры пераключэнні бягучага відэаплэера або пераключэнні ў поўнаэкранны рэжым</string>
|
||||
</resources>
|
||||
@@ -335,10 +335,6 @@
|
||||
<string name="enable_disposed_exceptions_summary">Насили докладването на неизпращаеми Rx изключения извън фрагмента или кръговрата на активност след приключване</string>
|
||||
<string name="unhook_checkbox">Откачи (може да предизвика промени)</string>
|
||||
<string name="unsubscribe">Отписване</string>
|
||||
<string name="volume_gesture_control_title">Контрол на звука с жестове</string>
|
||||
<string name="volume_gesture_control_summary">Използвай жестове за контрол на звука</string>
|
||||
<string name="brightness_gesture_control_title">Контрол на яркостта с жестове</string>
|
||||
<string name="brightness_gesture_control_summary">Използвай жестове за контрол на яркостта</string>
|
||||
<string name="file_deleted">Файлът е изтрит</string>
|
||||
<string name="events">Събития</string>
|
||||
<string name="show_comments_title">Показвай коментари</string>
|
||||
@@ -550,7 +546,6 @@
|
||||
<string name="show_image_indicators_summary">Показвай цветни Picasso-панделки в горната част на изображенията като индикатор за техния произход (червен – от мрежата, син – от диска и червен – от паметта)</string>
|
||||
<string name="auto_device_theme_title">Автоматична (тази на устройството)</string>
|
||||
<string name="notification_scale_to_square_image_summary">Мащабиране на миниатюрата в известието от 16:9 към 1:1 формат (възможни са изкривявания)</string>
|
||||
<string name="feed_toggle_show_played_items">Покажи гледани</string>
|
||||
<string name="select_a_playlist">Избете плейлист</string>
|
||||
<string name="notifications">Известия</string>
|
||||
<string name="clear_cookie_title">Изчистване на бисквитките от reCAPTCHA</string>
|
||||
|
||||
4
app/src/main/res/values-bm/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="main_bg_subtitle">ߥߊߘߏ ߛߐ߲߬ߞߌ߲߫ ߞߵߊ߬ ߘߊߡߌ߬ߣߊ߬.</string>
|
||||
</resources>
|
||||
@@ -147,10 +147,6 @@
|
||||
<string name="metadata_cache_wipe_title">ক্যাশ করা মেটাডেটা মোছ</string>
|
||||
<string name="metadata_cache_wipe_complete_notice">মেটাডেটা ক্যাশ মোছা হয়েছে</string>
|
||||
<string name="auto_queue_title">পরবর্তী স্ট্রিম স্বয়ংক্রিয়ংভাবে সংযোজন করো</string>
|
||||
<string name="brightness_gesture_control_summary">প্লেয়ারের উজ্জ্বলতা নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো</string>
|
||||
<string name="brightness_gesture_control_title">উজ্জ্বলতার নিয়ন্ত্রণ সংকেত</string>
|
||||
<string name="volume_gesture_control_summary">প্লেয়ারের ভলিউম নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো</string>
|
||||
<string name="volume_gesture_control_title">ভলিউম সংকেত নিয়ন্ত্রণ</string>
|
||||
<string name="missions_header_finished">সম্পন্ন</string>
|
||||
<string name="list">তালিকা</string>
|
||||
<string name="enable_playback_state_lists_title">তালিকা আকারে সাজাও</string>
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
<string name="download_dialog_title">ডাউনলোড</string>
|
||||
<string name="enable_watch_history_title">ইতিহাস</string>
|
||||
<string name="enable_search_history_title">খোজ ইতিহাস</string>
|
||||
<string name="brightness_gesture_control_summary">প্লেয়ারের উজ্জ্বলতা নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো</string>
|
||||
<string name="brightness_gesture_control_title">উজ্জ্বলতার নিয়ন্ত্রণ সংকেত</string>
|
||||
<string name="volume_gesture_control_summary">প্লেয়ারের ভলিউম নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো</string>
|
||||
<string name="volume_gesture_control_title">ভলিউম সংকেত নিয়ন্ত্রণ</string>
|
||||
<string name="auto_queue_title">পরবর্তী স্ট্রিম স্বয়ংক্রিয়ংভাবে সংযোজন করুন</string>
|
||||
<string name="metadata_cache_wipe_complete_notice">মেটাডেটা ক্যাশ মুছে ফেলা হয়েছে</string>
|
||||
<string name="metadata_cache_wipe_summary">সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো</string>
|
||||
|
||||
@@ -220,10 +220,6 @@
|
||||
<string name="enable_search_history_title">খোজ ইতিহাস</string>
|
||||
<string name="show_search_suggestions_summary">সার্চ করার সময় দেখানোর জন্য সাজেশন বেছে নিন</string>
|
||||
<string name="show_search_suggestions_title">সার্চ পরামর্শ</string>
|
||||
<string name="brightness_gesture_control_summary">প্লেয়ারের উজ্জ্বলতা নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো</string>
|
||||
<string name="brightness_gesture_control_title">উজ্জ্বলতার নিয়ন্ত্রণ সংকেত</string>
|
||||
<string name="volume_gesture_control_summary">প্লেয়ারের ভলিউম নিয়ন্ত্রণ করতে সংকেত ব্যবহার করো</string>
|
||||
<string name="volume_gesture_control_title">ভলিউম সংকেত নিয়ন্ত্রণ</string>
|
||||
<string name="auto_queue_title">পরবর্তী স্ট্রিম স্বয়ংক্রিয়ংভাবে সংযোজন করুন</string>
|
||||
<string name="metadata_cache_wipe_complete_notice">মেটাডেটা ক্যাশ মুছে ফেলা হয়েছে</string>
|
||||
<string name="metadata_cache_wipe_summary">সব ক্যাশড ওয়েবপেজ ডেটা মুছে ফেলো</string>
|
||||
@@ -570,7 +566,6 @@
|
||||
<string name="import_network_expensive_warning">মনে রাখবে এই ক্রিয়ার নেটওয়ার্ক খরচ বেশি হতে পারে।
|
||||
\n
|
||||
\nচালিয়ে যাবে\?</string>
|
||||
<string name="feed_toggle_show_played_items">দেখা ভুক্তি দেখাও</string>
|
||||
<string name="tablet_mode_title">ট্যাবলেট অবস্থা</string>
|
||||
<string name="off">বন্ধ</string>
|
||||
<string name="on">চালু</string>
|
||||
@@ -636,12 +631,9 @@
|
||||
<item quantity="other">%1$sটি ডাউনলোড মুছা হয়েছে</item>
|
||||
</plurals>
|
||||
<string name="show_crash_the_player_title">\"চালক বন্ধ করো\" দেখাও</string>
|
||||
<string name="feed_toggle_hide_played_items">দেখা ভুক্তি লুকাও</string>
|
||||
<string name="notifications_disabled">বিজ্ঞপ্তি নিষ্ক্রিয়</string>
|
||||
<string name="feed_toggle_hide_future_items">ভবিষ্যৎ ভুক্তি লুকাও</string>
|
||||
<string name="create_error_notification">ত্রুটি বিজ্ঞপ্তি বানাও</string>
|
||||
<string name="main_page_content_swipe_remove">ভুক্তি মুছতে ডানে-বামে সরাও</string>
|
||||
<string name="loading_stream_details">সম্প্রচার বিষয়ক তথ্য প্রক্রিয়ারত…</string>
|
||||
<string name="feed_toggle_show_future_items">ভবিষ্যৎ ভুক্তি দেখাও</string>
|
||||
<string name="progressive_load_interval_title">প্লেব্যাক লোড বিরতির আকার</string>
|
||||
</resources>
|
||||
@@ -70,7 +70,6 @@
|
||||
<string name="popup_remember_size_pos_summary">Zapamtite posljednju veličinu i položaj iskočnog prozora</string>
|
||||
<string name="use_inexact_seek_title">Koristite brzo neprecizno premotavanje</string>
|
||||
<string name="use_inexact_seek_summary">Neprecizno premotavanje dozvoljava pokretaču brže premotavanje s gorom preciznošću. Premotavanje za 5, 15 ili 25 sekundi ne radi s ovim</string>
|
||||
<string name="progressive_load_interval_summary">Promijenite veličinu intervala za učitavanje (trenutačno %s). Niža vrijednost bi vam moglo ubrzat učitavanje videa. Trebate te ponovno učitati pokretač za promjenu.</string>
|
||||
<string name="clear_queue_confirmation_summary">Prebacivanje sa jednog pokretača na drugi bi van moglo zamijeniti pokretni red</string>
|
||||
<string name="show_comments_summary">Isključite da sakrijete komentare</string>
|
||||
<string name="clear_queue_confirmation_title">Pitajte za potvrdu prije isčišćavanja reda</string>
|
||||
@@ -89,13 +88,9 @@
|
||||
<string name="no_player_found_toast">Nijedan medijski prijenosnik nije nađen na vašem uređaju (možete VLC instalisati da bi ste ga pokrenili).</string>
|
||||
<string name="metadata_cache_wipe_summary">Uklonite sve keširane podatke web stranica</string>
|
||||
<string name="auto_queue_title">Automatski sljedeći prijenos u red stavite</string>
|
||||
<string name="volume_gesture_control_title">Pokretna kontrola zvučne glasnine</string>
|
||||
<string name="auto_queue_summary">Nastavite završni (ne-ponavljajući) reprodukcijski red privlakom srodnog prijenosa</string>
|
||||
<string name="auto_queue_toggle">Automatsko redanje</string>
|
||||
<string name="volume_gesture_control_summary">Koristite pokrete za kontrolu jačine zvuka pokretača</string>
|
||||
<string name="brightness_gesture_control_title">Kontrola osvetljenja sa pokretima</string>
|
||||
<string name="show_search_suggestions_title">Prijedlozi za pretragu</string>
|
||||
<string name="brightness_gesture_control_summary">Koristite pokrete za kontrolu svjetline pokretača</string>
|
||||
<string name="show_search_suggestions_summary">Odaberite prijedloge koje želite prikazati prilikom pretrage</string>
|
||||
<string name="local_search_suggestions">Lokalni prijedlozi za pretraživanje</string>
|
||||
<string name="remote_search_suggestions">Razdaljeni prijedlozi za pretraživanje</string>
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
<string name="show_higher_resolutions_title">Mostra resolucions superiors</string>
|
||||
<string name="show_higher_resolutions_summary">Només alguns dispositius poden reproduir vídeos en 2K/4K</string>
|
||||
<string name="play_with_kodi_title">Reprodueix amb el Kodi</string>
|
||||
<string name="kore_not_found">No s\'ha trobat l\'aplicació Kode. Voleu instal·lar-la\?</string>
|
||||
<string name="kore_not_found">No s\'ha trobat l\'aplicació Kore. Voleu instal·lar-la\?</string>
|
||||
<string name="show_play_with_kodi_title">Mostra «Reprodueix amb el Kodi»</string>
|
||||
<string name="show_play_with_kodi_summary">Mostra una opció per reproduir un vídeo amb el centre multimèdia Kodi</string>
|
||||
<string name="popup_remember_size_pos_title">Reproductor emergent intel·ligent</string>
|
||||
@@ -333,10 +333,6 @@
|
||||
<string name="tracks">Pistes</string>
|
||||
<string name="users">Usuaris</string>
|
||||
<string name="tab_choose">Trieu una pestanya</string>
|
||||
<string name="volume_gesture_control_title">Control de volum per gestos</string>
|
||||
<string name="volume_gesture_control_summary">Fes servir gestos per controlar el volum del reproductor</string>
|
||||
<string name="brightness_gesture_control_title">Control de brillantor per gestos</string>
|
||||
<string name="brightness_gesture_control_summary">Fes servir gestos per controlar la brillantor del reproductor</string>
|
||||
<string name="settings_category_updates_title">Actualitzacions</string>
|
||||
<string name="file_deleted">S\'ha eliminat el fitxer</string>
|
||||
<string name="download_to_sdcard_error_title">L\'emmagatzematge extern no està disponible</string>
|
||||
@@ -574,8 +570,8 @@
|
||||
<string name="notification_action_2_title">Tercer botó d\'acció</string>
|
||||
<string name="notification_action_1_title">Segon botó d\'acció</string>
|
||||
<string name="notification_action_0_title">Primer botó d\'acció</string>
|
||||
<string name="notification_scale_to_square_image_summary">Escala la miniatura del vídeo mostrat a la notificació de 16:9 a raó d\'1:1 (pot causar deformacions)</string>
|
||||
<string name="notification_scale_to_square_image_title">Escala a raó d\'1:1</string>
|
||||
<string name="notification_scale_to_square_image_summary">Retalla la miniatura del vídeo mostrat a la notificació de 16:9 a 1:1</string>
|
||||
<string name="notification_scale_to_square_image_title">Retalla la miniatura amb una relació d\'aspecte 1:1</string>
|
||||
<string name="search_showing_result_for">Mostrant resultats per a: %s</string>
|
||||
<string name="chapters">Capítols</string>
|
||||
<string name="description_tab_description">Descripció</string>
|
||||
@@ -624,7 +620,6 @@
|
||||
<string name="auto_device_theme_title">Automàtic (tema del dispositiu)</string>
|
||||
<string name="service_provides_reason">%s dóna aquesta raó:</string>
|
||||
<string name="account_terminated">Usuari suspes</string>
|
||||
<string name="feed_toggle_show_played_items">Mostra contingut visualitzat</string>
|
||||
<string name="feed_load_error_terminated">El compte de l\'autor ha estat esborrat.
|
||||
\nNewPipe no serà capaç de carregar aquest fil en el futur.
|
||||
\nUs voleu desubscriure d\'aquest canal\?</string>
|
||||
@@ -702,4 +697,8 @@
|
||||
<string name="sort">Ordenar</string>
|
||||
<string name="settings_category_player_notification_summary">Configura la notificació de reproducció actual.</string>
|
||||
<string name="progressive_load_interval_summary">Canvia la mida de l\'interval de càrrega (actualment %s). Un valor inferior pot accelerar la càrrega inicial del vídeo. Els canvis requereixen un reinici del jugador.</string>
|
||||
<string name="ignore_hardware_media_buttons_title">Ignora els esdeveniments dels botons de reproducció físics</string>
|
||||
<string name="ignore_hardware_media_buttons_summary">Útil, per exemple, si feu servir uns auriculars amb els botons físicament trencats</string>
|
||||
<string name="left_gesture_control_summary">Trieu un gest per la part esquerra de la pantalla</string>
|
||||
<string name="progressive_load_interval_title">Mida de l\'interval de càrrega de reproducció</string>
|
||||
</resources>
|
||||
@@ -148,7 +148,6 @@
|
||||
<string name="show_error">پیشاندانی کێشە</string>
|
||||
<string name="feed_update_threshold_option_always_update">هەردهم نوێ بكرێتهوه</string>
|
||||
<string name="subscriptions_import_unsuccessful">ناتوانرێت بەژدارییەکان هاورده بكرێنهوه</string>
|
||||
<string name="brightness_gesture_control_title">کۆنترۆڵی ڕووناکی بەجوڵەی پەنجە</string>
|
||||
<string name="settings_category_appearance_title">دیمهن</string>
|
||||
<string name="import_data_title">هاوردە كردنی داتابهیس</string>
|
||||
<string name="enable_search_history_title">مێژووی گهڕان</string>
|
||||
@@ -172,7 +171,6 @@
|
||||
<string name="show_original_time_ago_summary">دەقە بنچینەییەکان لە خزمەتگوزارییەکانەوە لە بابەتی پەخشەکاندا دیار دەبن</string>
|
||||
<string name="download_dialog_title">دابهزاندن</string>
|
||||
<string name="caption_setting_title">ژێرنووسەکان</string>
|
||||
<string name="volume_gesture_control_title">کۆنترۆڵی دەنگ بەجوڵەی پەنجە</string>
|
||||
<string name="auto_queue_title">خستنه نۆبهتی-خۆكاری پهخشی دواتر</string>
|
||||
<string name="external_player_unsupported_link_type">لێدهره دەرەکییەکان پشتگیری ئەم جۆرە بەستەرانە ناکەن</string>
|
||||
<string name="permission_denied">کردار ڕەتکرایەوە لەلایەن سیستەمەوە</string>
|
||||
@@ -329,7 +327,6 @@
|
||||
<string name="subscription_change_failed">ناتوانیت گۆڕانكاری لهم بهژدارییهدا بكهیت</string>
|
||||
<string name="default_resolution_title">قهبارهی بنەڕەتی</string>
|
||||
<string name="minimize_on_exit_popup_description">بچووککردنەوە بۆ پەنجەرە</string>
|
||||
<string name="brightness_gesture_control_summary">جوڵەی پەنجەت لەسەر ڕوونما بەکاربهێنە بۆ گۆڕینی ئاستی ڕووناکی ڕوونما</string>
|
||||
<string name="songs">گۆرانییەکان</string>
|
||||
<string name="controls_download_desc">دابهزاندنی فایلی پەخش</string>
|
||||
<string name="list_view_mode">شێوازی پیشاندانی خشتە</string>
|
||||
@@ -344,7 +341,6 @@
|
||||
<string name="file">فایل</string>
|
||||
<string name="error_http_not_found">نەدۆزرایەوە</string>
|
||||
<string name="post_processing">چارەسەردەکرێت</string>
|
||||
<string name="volume_gesture_control_summary">جوڵەی پەنجەت لەسەر ڕوونما بەکاربهێنە بۆ گۆڕینی ئاستی دەنگ</string>
|
||||
<string name="main_page_content">بابەتی پەڕەی سەرەکی</string>
|
||||
<string name="feed_group_dialog_select_subscriptions">دیار کردنی بەژدارییەکان</string>
|
||||
<string name="import_file_title">هاوردهكردنی فایل</string>
|
||||
@@ -618,7 +614,6 @@
|
||||
<string name="recent">دواین</string>
|
||||
<string name="show_thumbnail_summary">وێنۆچكهكه بۆ پاشبنهمای ڕوونماداخراو و پەیامەکان بهكاردەهێنرێن</string>
|
||||
<string name="show_thumbnail_title">پیشاندانی وێنۆچكه</string>
|
||||
<string name="feed_toggle_show_played_items">تەماشاکراوەکان پیشان بدرێن</string>
|
||||
<string name="downloads_storage_ask_summary_no_saf_notice">بۆ دابهزاندنی ههر بابهتێك پرست پێ دهكرێت لهبارهی شوێنی دابهزاندنیان</string>
|
||||
<string name="disable_media_tunneling_title">ناكاراكردنی تونێلكردنی میدیا</string>
|
||||
<string name="show_age_restricted_content_summary">ئهو بابهتانهی نهگونجاون بۆ منداڵان پیشان بدرێن كه سنووری تهمهن دهیانگرێتهوه (وهك +18)</string>
|
||||
@@ -700,7 +695,6 @@
|
||||
<string name="delete_downloaded_files_confirm">هەموو فایلە دابەزێنراوەکان لە دیسک بسڕدرێتەوە؟</string>
|
||||
<string name="notifications_disabled">پەیامەکان ناکاراکراون</string>
|
||||
<string name="get_notified">پەیامم بکە</string>
|
||||
<string name="progressive_load_interval_summary">"قەبارەی نێوان بارکردنەکە بگۆڕە (لە ئێستادا %s) . بەهایەکی کەمتر لەوانەیە بارکردنی ڤیدیۆی سەرەتایی خێراتر بکات. گۆڕانکارییەکان پێویستیان بە داگیرساندنەوەی لێدەر هەیە"</string>
|
||||
<string name="percent">لەسەدا</string>
|
||||
<string name="semitone">نیمچەتەن</string>
|
||||
<string name="progressive_load_interval_exoplayer_default">بنەڕەتی ExoPlayer</string>
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
<string name="settings_category_player_behavior_title">Chování</string>
|
||||
<string name="settings_category_history_title">Historie a mezipaměť</string>
|
||||
<string name="popup_playing_toast">Přehrávání v okně</string>
|
||||
<string name="disabled">Vypnuto</string>
|
||||
<string name="disabled">Zakázáno</string>
|
||||
<string name="clear">Vyčistit</string>
|
||||
<string name="best_resolution">Nejlepší rozlišení</string>
|
||||
<string name="undo">Vrátit</string>
|
||||
@@ -153,7 +153,7 @@
|
||||
<string name="title_activity_about">O NewPipe</string>
|
||||
<string name="title_licenses">Licence třetích stran</string>
|
||||
<string name="copyright" formatted="true">© %1$s od %2$s pod %3$s</string>
|
||||
<string name="tab_about">O aplikaci a ČKD</string>
|
||||
<string name="tab_about">O aplikaci a FAQ</string>
|
||||
<string name="tab_licenses">Licence</string>
|
||||
<string name="app_description">Svobodné a nenáročné streamování v Androidu.</string>
|
||||
<string name="view_on_github">Zobraz na GitHubu</string>
|
||||
@@ -337,10 +337,6 @@
|
||||
<string name="minimize_on_exit_popup_description">Minimalizovat přehrávač do vyskakovacího okna</string>
|
||||
<string name="unsubscribe">Přestat odebírat</string>
|
||||
<string name="tab_choose">Zvolit panel</string>
|
||||
<string name="volume_gesture_control_title">Ovládání hlasitosti gesty</string>
|
||||
<string name="volume_gesture_control_summary">Používat gesta pro ovládání hlasitosti přehrávače</string>
|
||||
<string name="brightness_gesture_control_title">Ovládání jasu gesty</string>
|
||||
<string name="brightness_gesture_control_summary">Používat gesta pro ovládání jasu přehrávače</string>
|
||||
<string name="settings_category_updates_title">Aktualizace</string>
|
||||
<string name="file_deleted">Soubor smazán</string>
|
||||
<string name="app_update_notification_channel_name">Oznámení o aktualizaci aplikace</string>
|
||||
@@ -360,8 +356,8 @@
|
||||
<string name="grid">Mřížka</string>
|
||||
<string name="auto">Automaticky</string>
|
||||
<string name="app_update_available_notification_title">Aktualizace NewPipe je k dispozici!</string>
|
||||
<string name="missions_header_finished">Hotovo</string>
|
||||
<string name="missions_header_pending">Vyčkávání</string>
|
||||
<string name="missions_header_finished">Dokončeno</string>
|
||||
<string name="missions_header_pending">Čekání</string>
|
||||
<string name="paused">Pozastaveno</string>
|
||||
<string name="queued">ve frontě</string>
|
||||
<string name="post_processing">zpracování</string>
|
||||
@@ -370,7 +366,7 @@
|
||||
<string name="download_failed">Stahování se nezdařilo</string>
|
||||
<string name="generate_unique_name">Vytvořit jedinečný název</string>
|
||||
<string name="overwrite">Přepsat</string>
|
||||
<string name="overwrite_unrelated_warning">Stažený soubor s tímto názvem již existuje</string>
|
||||
<string name="overwrite_unrelated_warning">Soubor s tímto názvem již existuje</string>
|
||||
<string name="overwrite_finished_warning">Stažený soubor s tímto názvem již existuje</string>
|
||||
<string name="download_already_running">Stahování s tímto názvem již probíhá</string>
|
||||
<string name="show_error">Zobrazit chybu</string>
|
||||
@@ -550,7 +546,7 @@
|
||||
<string name="never">Nikdy</string>
|
||||
<string name="wifi_only">Pouze na Wi-Fi</string>
|
||||
<string name="autoplay_summary">Automaticky zahájit přehrávání — %s</string>
|
||||
<string name="title_activity_play_queue">Přehrát frontu</string>
|
||||
<string name="title_activity_play_queue">Fronta přehravání</string>
|
||||
<string name="unsupported_url_dialog_message">Nelze rozpoznat zadanou adresu URL. Otevřít pomocí jiné aplikace\?</string>
|
||||
<string name="auto_queue_toggle">Automatické přehravání</string>
|
||||
<string name="clear_queue_confirmation_description">Fronta aktivního přehrávače bude smazána</string>
|
||||
@@ -645,7 +641,6 @@
|
||||
<string name="off">Vypnuto</string>
|
||||
<string name="on">Zapnuto</string>
|
||||
<string name="tablet_mode_title">Režim tabletu</string>
|
||||
<string name="feed_toggle_show_played_items">Zobrazit zhlédnuté položky</string>
|
||||
<string name="dont_show">Nezobrazovat</string>
|
||||
<string name="low_quality_smaller">Nízká kvalita (menší)</string>
|
||||
<string name="high_quality_larger">Vysoká kvalita (větší)</string>
|
||||
@@ -691,7 +686,6 @@
|
||||
<string name="show_error_snackbar">Zobrazit krátké oznámení o chybě</string>
|
||||
<string name="detail_pinned_comment_view_description">Připnutý komentář</string>
|
||||
<string name="crash_the_player">Shodit přehrávač</string>
|
||||
<string name="progressive_load_interval_summary">Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače</string>
|
||||
<string name="leak_canary_not_available">LeakCanary není dostupné</string>
|
||||
<string name="progressive_load_interval_exoplayer_default">Výchozí ExoPlayer</string>
|
||||
<string name="settings_category_player_notification_summary">Nastavit oznámení o právě přehrávaném streamu</string>
|
||||
@@ -724,12 +718,9 @@
|
||||
<string name="no_audio_streams_available_for_external_players">U externích přehrávačů nejsou dostupné žádné zvukové streamy</string>
|
||||
<string name="unknown_format">Neznámý formát</string>
|
||||
<string name="unknown_quality">Neznámá kvalita</string>
|
||||
<string name="feed_toggle_show_future_items">Zobrazit nadcházející položky</string>
|
||||
<string name="streams_not_yet_supported_removed">Streamy, které zatím nejsou podporovány systémem stahování, nebudou zobrazeny</string>
|
||||
<string name="select_quality_external_players">Vyberte kvalitu pro externí přehrávače</string>
|
||||
<string name="no_video_streams_available_for_external_players">U externích přehrávačů nejsou k dispozici žádné videostreamy</string>
|
||||
<string name="feed_toggle_hide_played_items">Skrýt zhlédnuté položky</string>
|
||||
<string name="feed_toggle_hide_future_items">Skrýt nadcházející položky</string>
|
||||
<string name="faq_title">Často kladené dotazy</string>
|
||||
<string name="faq_description">Pokud máte potíže s používáním aplikace, přečtěte si tyto odpovědi na časté otázky!</string>
|
||||
<string name="faq">Zobrazit na webu</string>
|
||||
@@ -754,4 +745,33 @@
|
||||
<string name="feed_show_upcoming">Nadcházející</string>
|
||||
<string name="remove_duplicates">Odstranit duplicity</string>
|
||||
<string name="remove_duplicates_message">Chcete odstranit všechny duplicitní streamy v tomto playlistu\?</string>
|
||||
<string name="left_gesture_control_title">Akce levého gesta</string>
|
||||
<string name="right_gesture_control_title">Akce pravého gesta</string>
|
||||
<string name="volume">Hlasitost</string>
|
||||
<string name="none">Žádné</string>
|
||||
<string name="left_gesture_control_summary">Vyberte gesto pro levou polovinu obrazovky přehrávače</string>
|
||||
<string name="brightness">Jas</string>
|
||||
<string name="right_gesture_control_summary">Vyberte gesto pro pravou polovinu obrazovky přehrávače</string>
|
||||
<string name="progressive_load_interval_summary">Změna velikosti intervalu načítání progresivního obsahu (aktuálně %s). Nižší hodnota může urychlit jejich počáteční načítání</string>
|
||||
<string name="prefer_original_audio_title">Upřednostňovat původní zvuk</string>
|
||||
<string name="prefer_original_audio_summary">Vybrat původní zvukovou stopu bez ohledu na jazyk</string>
|
||||
<string name="prefer_descriptive_audio_title">Upřednostňovat popisný zvuk</string>
|
||||
<string name="prefer_descriptive_audio_summary">Vybrat zvukovou stopu s popisem pro zrakově postižené, pokud je k dispozici</string>
|
||||
<string name="unknown_audio_track">Neznámá</string>
|
||||
<string name="settings_category_exoplayer_title">Nastavení přehrávače ExoPlayer</string>
|
||||
<string name="settings_category_exoplayer_summary">Správa některých nastavení přehrávače ExoPlayer. Tyto změny vyžadují restartování přehrávače, aby se projevily</string>
|
||||
<string name="use_exoplayer_decoder_fallback_title">Použít funkci náhradního dekodéru přehrávače ExoPlayer</string>
|
||||
<string name="use_exoplayer_decoder_fallback_summary">Tuto možnost povolte, pokud máte problémy s inicializací dekodéru. V případě selhání inicializace primárních dekodérů se přehrávač vrátí zpět k dekodérům s nižší prioritou. To může mít za následek nižší výkon přehrávání než při použití primárních dekodérů</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_title">Vždy použít nastavení povrchu výstupu videa v přehrávači ExoPlayer</string>
|
||||
<string name="audio_track_name">%s %s</string>
|
||||
<string name="audio_track_type_original">původní</string>
|
||||
<string name="audio_track_type_dubbed">dabovaná</string>
|
||||
<string name="audio_track_type_descriptive">popisná</string>
|
||||
<string name="audio_track">Zvuková stopa</string>
|
||||
<string name="audio_track_present_in_video">V tomto streamu by již měla být přítomna zvuková stopa</string>
|
||||
<string name="select_audio_track_external_players">Vyberte zvukovou stopu pro externí přehrávače</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Toto obejití uvolní a znovu nainstaluje kodeky videa, když dojde ke změně povrchu, místo aby se povrch nastavil přímo na kodek. Toto nastavení, které již bylo použito v přehrávači ExoPlayer na některých zařízeních s tímto problémem, má vliv pouze na Android 6 a vyšší.
|
||||
\n
|
||||
\nPovolení této možnosti může zabránit chybám při přehrávání při přepnutí aktuálního přehrávače videa nebo při přepnutí na celou obrazovku</string>
|
||||
<string name="play_queue_audio_track">Zvuk: %s</string>
|
||||
</resources>
|
||||
@@ -65,10 +65,6 @@
|
||||
<string name="metadata_cache_wipe_complete_notice">Metadata-cache slettet</string>
|
||||
<string name="auto_queue_title">Føj automatisk næste stream til køen</string>
|
||||
<string name="auto_queue_summary">Fortsæt en afspilningskø, der afsluttes (ikke-gentagende), ved at tilføje en lignende stream</string>
|
||||
<string name="volume_gesture_control_title">Juster lydstyrke ved hjælp af fingerbevægelser</string>
|
||||
<string name="volume_gesture_control_summary">Brug fingerbevægelser til at kontrollere afspillerens lydstyrke</string>
|
||||
<string name="brightness_gesture_control_title">Styr lysstyrken med fingerbevægelser</string>
|
||||
<string name="brightness_gesture_control_summary">Brug fingerbevægelser til at justere afspillerens lysstyrke</string>
|
||||
<string name="show_search_suggestions_title">Søgeforslag</string>
|
||||
<string name="show_search_suggestions_summary">Vælg forslagene, der vises, når der søges</string>
|
||||
<string name="enable_search_history_title">Søgehistorik</string>
|
||||
@@ -462,7 +458,6 @@
|
||||
<item quantity="one">Download fuldført</item>
|
||||
<item quantity="other">%s downloads fuldført</item>
|
||||
</plurals>
|
||||
<string name="progressive_load_interval_summary">Ændr indlæsningsintervallets størrelse (som nu er på %s). En lavere værdi kan øge videoindlæsningshastigheden. Ændringer kræver en genstart af afspiller</string>
|
||||
<string name="clear_queue_confirmation_description">Den aktive spilleliste bliver udskiftet</string>
|
||||
<string name="clear_queue_confirmation_summary">Hvis du skifter fra en spiller til en anden, kan din kø blive erstattet</string>
|
||||
<string name="show_meta_info_title">Vis metainformation</string>
|
||||
@@ -551,7 +546,6 @@
|
||||
<string name="feed_update_threshold_summary">Tid siden sidste opdatering for at et abonnoment bliver forældet - %s</string>
|
||||
<string name="feed_update_threshold_option_always_update">Altid opdater</string>
|
||||
<string name="feed_group_dialog_select_subscriptions">Vælg abonnementer</string>
|
||||
<string name="feed_toggle_show_played_items">Vis sete elementer</string>
|
||||
<string name="georestricted_content">Dette indhold er ikke tilgængeligt i dit land.</string>
|
||||
<string name="video_detail_by">Af %s</string>
|
||||
<string name="remove_watched_popup_warning">Videoer, der er blevet set før og efter, at de er blevet tilføjet til spillelisten, vil blive fjernet.
|
||||
@@ -680,7 +674,6 @@
|
||||
<string name="metadata_thumbnail_url">URL til miniaturebillede</string>
|
||||
<string name="off">Fra</string>
|
||||
<string name="tablet_mode_title">Tablet-tilstand</string>
|
||||
<string name="feed_toggle_hide_future_items">Skjul fremtidige elementer</string>
|
||||
<string name="youtube_music_premium_content">Denne video er kun tilgængelig for YouTube Music Premium-medlemmer, så den kan ikke streames eller downloades af NewPipe.</string>
|
||||
<string name="downloads_storage_use_saf_summary">\"Storage Access Framework\" gør det muligt at downloade til et eksternt SD-kort</string>
|
||||
<string name="enable_disposed_exceptions_summary">Fremtving indberetning af ikke-leverbare Rx-undtagelser uden for fragmentets eller aktivitetens livscyklus efter bortskaffelse</string>
|
||||
@@ -710,7 +703,6 @@
|
||||
<string name="app_update_unavailable_toast">Du kører den nyeste version af NewPipe</string>
|
||||
<string name="new_seek_duration_toast">På grund af ExoPlayer-begrænsninger blev søgetiden sat til %d sekunder</string>
|
||||
<string name="feed_group_show_only_ungrouped_subscriptions">Vis kun ikke-grupperede abonnementer</string>
|
||||
<string name="feed_toggle_hide_played_items">Skjul sete elementer</string>
|
||||
<string name="playlist_page_summary">Side med spillelister</string>
|
||||
<string name="select_night_theme_toast">Du kan vælge dit foretrukne nattema nedenfor</string>
|
||||
<string name="night_theme_summary">Vælg dit foretrukne nattema - %s</string>
|
||||
@@ -724,11 +716,10 @@
|
||||
<string name="toggle_all">Skift alle</string>
|
||||
<string name="no_audio_streams_available_for_external_players">Ingen lydstreams er tilgængelige for eksterne afspillere</string>
|
||||
<string name="select_quality_external_players">Vælg kvalitet til eksterne afspillere</string>
|
||||
<string name="feed_toggle_show_future_items">Vis fremtidige elementer</string>
|
||||
<string name="sort">Sortér</string>
|
||||
<string name="ignore_hardware_media_buttons_title">Ignorer hardware medie knapper</string>
|
||||
<string name="ignore_hardware_media_buttons_summary">Brugbart f.eks. hvis du bruger et headset med ødelagte fysiske knapper</string>
|
||||
<string name="duplicate_in_playlist">Playlists der er grået ud, indeholder allerede dette objekt.</string>
|
||||
<string name="unset_playlist_thumbnail">Inaktiver permanent thumbnail</string>
|
||||
<string name="msg_failed_to_copy">Fejlede at kopiere til udklipsholderen</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -318,7 +318,7 @@
|
||||
<string name="read_privacy_policy">Datenschutzbestimmungen lesen</string>
|
||||
<string name="accept">Akzeptieren</string>
|
||||
<string name="decline">Ablehnen</string>
|
||||
<string name="start_accept_privacy_policy">Um der europäischen Datenschutz-Grundverordnung (DSGVO) gerecht zu werden, weisen wir hiermit auf NewPipe\'s Datenschutzerklärung hin. Bitte lies sie sorgfältig durch.
|
||||
<string name="start_accept_privacy_policy">Um der europäischen Datenschutz-Grundverordnung (DSGVO) gerecht zu werden, weisen wir hiermit auf NewPipes Datenschutzerklärung hin. Bitte lies sie sorgfältig durch.
|
||||
\nDu musst den Datenschutzrichtlinien zustimmen, um den Fehlerbericht an uns zu senden.</string>
|
||||
<string name="limit_data_usage_none_description">Unbegrenzt</string>
|
||||
<string name="limit_mobile_data_usage_title">Auflösung bei Verwendung mobiler Daten begrenzen</string>
|
||||
@@ -336,10 +336,6 @@
|
||||
<string name="users">Benutzer</string>
|
||||
<string name="unsubscribe">Deabonnieren</string>
|
||||
<string name="tab_choose">Tab wählen</string>
|
||||
<string name="volume_gesture_control_title">Gestensteuerung für Lautstärke</string>
|
||||
<string name="volume_gesture_control_summary">Gesten verwenden, um die Lautstärke einzustellen</string>
|
||||
<string name="brightness_gesture_control_title">Gestensteuerung für Helligkeit</string>
|
||||
<string name="brightness_gesture_control_summary">Gesten verwenden, um die Helligkeit einzustellen</string>
|
||||
<string name="settings_category_updates_title">Aktualisierungen</string>
|
||||
<string name="settings_category_player_notification_title">Wiedergabebenachrichtigung</string>
|
||||
<string name="settings_category_player_notification_summary">Konfiguriert die Benachrichtigung zum aktuell abgespielten Stream</string>
|
||||
@@ -642,7 +638,6 @@
|
||||
<string name="on">An</string>
|
||||
<string name="tablet_mode_title">Tablet-Modus</string>
|
||||
<string name="off">Aus</string>
|
||||
<string name="feed_toggle_show_played_items">Angesehene Elemente anzeigen</string>
|
||||
<string name="mark_as_watched">Als gesehen markieren</string>
|
||||
<string name="detail_heart_img_view_description">Vom Ersteller mit Herz versehen</string>
|
||||
<string name="show_image_indicators_summary">Farbige Picasso-Bänder über den Bildern anzeigen, die deren Quelle angeben: rot für Netzwerk, blau für Festplatte und grün für Speicher</string>
|
||||
@@ -682,7 +677,6 @@
|
||||
\nBitte installiere einen Storage Access Framework kompatiblen Dateimanager</string>
|
||||
<string name="detail_pinned_comment_view_description">Angehefteter Kommentar</string>
|
||||
<string name="leak_canary_not_available">LeakCanary ist nicht verfügbar</string>
|
||||
<string name="progressive_load_interval_summary">Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players</string>
|
||||
<string name="progressive_load_interval_exoplayer_default">ExoPlayer Standard</string>
|
||||
<string name="notifications">Benachrichtigungen</string>
|
||||
<string name="streams_notification_channel_description">Benachrichtigen über neue abonnierbare Streams</string>
|
||||
@@ -714,9 +708,6 @@
|
||||
<string name="streams_not_yet_supported_removed">Streams, die der Downloader noch nicht unterstützt, werden nicht angezeigt</string>
|
||||
<string name="selected_stream_external_player_not_supported">Der ausgewählte Stream wird von externen Playern nicht unterstützt</string>
|
||||
<string name="progressive_load_interval_title">Größe des Ladeintervalls für die Wiedergabe</string>
|
||||
<string name="feed_toggle_show_future_items">Zukünftige Elemente anzeigen</string>
|
||||
<string name="feed_toggle_hide_played_items">Angesehene Elemente ausblenden</string>
|
||||
<string name="feed_toggle_hide_future_items">Zukünftige Elemente ausblenden</string>
|
||||
<string name="faq">Auf der Webseite ansehen</string>
|
||||
<string name="faq_title">Häufig gestellte Fragen</string>
|
||||
<string name="faq_description">Wenn du Probleme bei der Verwendung der App hast, lies bitte die Antworten auf häufig gestellte Fragen!</string>
|
||||
@@ -741,4 +732,33 @@
|
||||
<string name="feed_show_upcoming">Demnächst</string>
|
||||
<string name="feed_show_watched">Vollständig angeschaut</string>
|
||||
<string name="feed_show_partially_watched">Teilweise angeschaut</string>
|
||||
<string name="left_gesture_control_summary">Geste für die linke Hälfte des Player-Bildschirms auswählen</string>
|
||||
<string name="right_gesture_control_summary">Geste für die rechte Hälfte des Player-Bildschirms auswählen</string>
|
||||
<string name="none">Keine</string>
|
||||
<string name="right_gesture_control_title">Rechte Gestenaktion</string>
|
||||
<string name="left_gesture_control_title">Linke Gestenaktion</string>
|
||||
<string name="brightness">Helligkeit</string>
|
||||
<string name="volume">Lautstärke</string>
|
||||
<string name="progressive_load_interval_summary">Ändere die Größe des Ladeintervalls für progressive Inhalte (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden der Inhalte beschleunigen.</string>
|
||||
<string name="prefer_original_audio_title">Originalton bevorzugen</string>
|
||||
<string name="prefer_original_audio_summary">Wähle die Originaltonspur unabhängig von der Sprache</string>
|
||||
<string name="prefer_descriptive_audio_title">Beschreibendes Audio bevorzugen</string>
|
||||
<string name="prefer_descriptive_audio_summary">Wähle eine Audiospur mit Beschreibungen für sehbehinderte Menschen, falls verfügbar</string>
|
||||
<string name="play_queue_audio_track">Audio: %s</string>
|
||||
<string name="audio_track">Audiospur</string>
|
||||
<string name="settings_category_exoplayer_summary">Verwalte einige ExoPlayer-Einstellungen. Diese Änderungen erfordern einen Neustart des Players, um wirksam zu werden</string>
|
||||
<string name="use_exoplayer_decoder_fallback_title">Verwende die Decoder-Fallback-Funktion von ExoPlayer</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_title">Verwende immer die ExoPlayer-Einstellung für die Videoausgangsoberfläche als Umgehung</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Dieser Workaround gibt die Video-Codecs frei und instanziiert sie neu, wenn sich die Oberfläche ändert, anstatt die Oberfläche direkt auf den Codec zu setzen. Diese Einstellung wird bereits von ExoPlayer auf einigen Geräten mit diesem Problem verwendet und hat nur Auswirkungen auf Android 6 und höher
|
||||
\n
|
||||
\nDas Aktivieren dieser Option kann Wiedergabefehler beim Wechsel des aktuellen Videoplayers oder beim Wechsel zum Vollbildmodus verhindern</string>
|
||||
<string name="audio_track_type_original">Original</string>
|
||||
<string name="audio_track_type_dubbed">Synchronisiert</string>
|
||||
<string name="audio_track_type_descriptive">Beschreibend</string>
|
||||
<string name="select_audio_track_external_players">Audiospur für externe Player auswählen</string>
|
||||
<string name="audio_track_present_in_video">In diesem Stream sollte bereits eine Audiospur vorhanden sein</string>
|
||||
<string name="use_exoplayer_decoder_fallback_summary">Aktiviere diese Option, wenn du Probleme mit der Decoderinitialisierung hast, die auf Decoder mit niedrigerer Priorität zurückgreift, wenn die Initialisierung des primären Decoders fehlschlägt. Dies kann zu einer schlechteren Wiedergabeleistung führen als bei der Verwendung von Primärdecodern</string>
|
||||
<string name="unknown_audio_track">Unbekannt</string>
|
||||
<string name="settings_category_exoplayer_title">ExoPlayer-Einstellungen</string>
|
||||
<string name="audio_track_name">%s %s</string>
|
||||
</resources>
|
||||
@@ -96,7 +96,7 @@
|
||||
<string name="metadata_cache_wipe_title">Εκκαθάριση προσωρινά αποθηκευμένων μεταδεδομένων</string>
|
||||
<string name="metadata_cache_wipe_summary">Αφαίρεση όλων των προσωρινά αποθηκευμένων δεδομένων ιστοσελίδων</string>
|
||||
<string name="metadata_cache_wipe_complete_notice">Η προσωρινή μνήμη μεταδεδομένων εκκαθαρίστηκε</string>
|
||||
<string name="auto_queue_title">Αυτόματη πρόσθεση της επόμενης ροής στην ουρά</string>
|
||||
<string name="auto_queue_title">Αυτόματη προσθήκη της επόμενης ροής στην ουρά</string>
|
||||
<string name="auto_queue_summary">Συνέχεια της τρέχουσας (μη επαναλαμβανόμενης) ουράς μετά τη λήξη της, με την προσθήκη μιας σχετικής ροής</string>
|
||||
<string name="show_search_suggestions_summary">Επιλογή των προτάσεων που εμφανίζονται κατά την αναζήτηση</string>
|
||||
<string name="enable_search_history_summary">Αποθήκευση αναζητήσεων στη συσκευή</string>
|
||||
@@ -334,10 +334,6 @@
|
||||
<string name="minimize_on_exit_popup_description">Ελαχιστοποίηση σε αναδυόμενο παράθυρο</string>
|
||||
<string name="unsubscribe">Απεγγραφή</string>
|
||||
<string name="tab_choose">Επιλογή Καρτέλας</string>
|
||||
<string name="volume_gesture_control_title">Έλεγχος ήχου με χειρονομιές</string>
|
||||
<string name="volume_gesture_control_summary">Χρησιμοποιήστε χειρονομίες για τον έλεγχο της έντασης του ήχου</string>
|
||||
<string name="brightness_gesture_control_title">Έλεγχος φωτεινότητας με χειρονομίες</string>
|
||||
<string name="brightness_gesture_control_summary">Χρησιμοποιήστε χειρονομίες για τον έλεγχο φωτεινότητας</string>
|
||||
<string name="settings_category_updates_title">Ενημερώσεις</string>
|
||||
<string name="events">Συμβάντα</string>
|
||||
<string name="file_deleted">Το αρχείο διαγράφηκε</string>
|
||||
@@ -635,7 +631,6 @@
|
||||
<string name="off">Ανενεργό</string>
|
||||
<string name="on">Ενεργό</string>
|
||||
<string name="tablet_mode_title">Κατάσταση tablet</string>
|
||||
<string name="feed_toggle_show_played_items">Εμφάνιση αναπαραχθέντων</string>
|
||||
<string name="comments_are_disabled">Τα σχόλια είναι απενεργοποιημένα</string>
|
||||
<string name="dont_show">Απόκρυψη</string>
|
||||
<string name="low_quality_smaller">Χαμηλή ποιότητα (μικρότερο)</string>
|
||||
@@ -681,7 +676,6 @@
|
||||
<string name="detail_pinned_comment_view_description">Καρφιτσωμένο σχόλιο</string>
|
||||
<string name="leak_canary_not_available">Το LeakCanary δεν είναι διαθέσιμο</string>
|
||||
<string name="progressive_load_interval_exoplayer_default">Εξ\' ορισμού ExoPlayer</string>
|
||||
<string name="progressive_load_interval_summary">Αλλάξτε το μέγεθος του διαστήματος φόρτωσης (επί του παρόντος είναι %s). Μια χαμηλότερη τιμή μπορεί να επιταχύνει την αρχική φόρτωση βίντεο. Οι αλλαγές απαιτούν επανεκκίνηση της εφαρμογής</string>
|
||||
<string name="notifications">Ειδοποιήσεις</string>
|
||||
<plurals name="new_streams">
|
||||
<item quantity="one">%s νέα ροή</item>
|
||||
@@ -714,9 +708,6 @@
|
||||
<string name="unknown_format">Άγνωστος τύπος αρχείου</string>
|
||||
<string name="unknown_quality">Άγνωστη ποιότητα</string>
|
||||
<string name="progressive_load_interval_title">Μέγεθος διαστήματος φόρτωσης αναπαραγωγής</string>
|
||||
<string name="feed_toggle_show_future_items">Εμφάνιση μελλοντικών αντικειμένων</string>
|
||||
<string name="feed_toggle_hide_played_items">Απόκρυψη θεαθέντων</string>
|
||||
<string name="feed_toggle_hide_future_items">Απόκρυψη μελλοντικών αντικειμένων</string>
|
||||
<string name="faq_title">Συχνές ερωτήσεις</string>
|
||||
<string name="faq">Προβολή στην ιστοσελίδα</string>
|
||||
<string name="faq_description">Εάν αντιμετωπίζετε προβλήματα με τη χρήση της εφαρμογής, φροντίστε να ελέγξετε αυτές τις απαντήσεις σε συνήθεις ερωτήσεις!</string>
|
||||
@@ -741,4 +732,33 @@
|
||||
<string name="remove_duplicates_message">Θέλετε να καταργήσετε όλες τις διπλότυπες ροές σε αυτήν τη λίστα αναπαραγωγής;</string>
|
||||
<string name="remove_duplicates">Αφαίρεση διπλοτύπων</string>
|
||||
<string name="feed_show_partially_watched">Θεαθέντα μερικώς</string>
|
||||
<string name="left_gesture_control_summary">Επιλέξτε χειρονομία για το αριστερό μισό της οθόνης του προγράμματος αναπαραγωγής</string>
|
||||
<string name="left_gesture_control_title">Ενέργεια αριστερής χειρονομίας</string>
|
||||
<string name="right_gesture_control_summary">Επιλέξτε χειρονομία για το δεξί μισό της οθόνης του προγράμματος αναπαραγωγής</string>
|
||||
<string name="right_gesture_control_title">Ενέργεια δεξιάς χειρονομίας</string>
|
||||
<string name="brightness">Φωτεινότητα</string>
|
||||
<string name="volume">Ένταση</string>
|
||||
<string name="none">Καμία</string>
|
||||
<string name="prefer_original_audio_title">Προτίμηση πρωτότυπου ήχου</string>
|
||||
<string name="prefer_original_audio_summary">Επιλογή του πρωτότυπου κομματιού ήχου ανεξάρτητα από τη γλώσσα</string>
|
||||
<string name="prefer_descriptive_audio_title">Προτίμηση του περιγραφικού ήχου</string>
|
||||
<string name="play_queue_audio_track">Ήχος: %s</string>
|
||||
<string name="audio_track">Κομμάτι ήχου</string>
|
||||
<string name="audio_track_present_in_video">Ένα κομμάτι ήχου θα πρέπει να υπάρχει ήδη σε αυτήν τη ροή</string>
|
||||
<string name="select_audio_track_external_players">Επιλογή ήχου για εξωτερικές συσκευές αναπαραγωγής</string>
|
||||
<string name="unknown_audio_track">Άγνωστο</string>
|
||||
<string name="settings_category_exoplayer_title">Ρυθμίσεις ExoPlayer</string>
|
||||
<string name="settings_category_exoplayer_summary">Διαχειριστείτε ορισμένες ρυθμίσεις του ExoPlayer. Αυτές οι αλλαγές απαιτούν επανεκκίνηση του προγράμματος αναπαραγωγής για να τεθεί σε ισχύ</string>
|
||||
<string name="use_exoplayer_decoder_fallback_title">Χρησιμοποιήστε την δυνατότητα εναλλακτικού αποκωδικοποιητή του ExoPlayer</string>
|
||||
<string name="use_exoplayer_decoder_fallback_summary">Ενεργοποιήστε αυτήν την επιλογή εάν αντιμετωπίζετε προβλήματα με την προετοιμασία του αποκωδικοποιητή, η οποία επιστρέφει σε αποκωδικοποιητές χαμηλότερης προτεραιότητας εάν αποτύχει η προετοιμασία του πρωτεύοντος αποκωδικοποιητή. Αυτό μπορεί να έχει ως αποτέλεσμα κακή απόδοση αναπαραγωγής από ότι όταν χρησιμοποιείτε κύριους αποκωδικοποιητές</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_title">Χρησιμοποιείτε πάντα τον εναλλακτικό τρόπο ρύθμισης της επιφάνειας εξόδου βίντεο του ExoPlayer</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Αυτή η λύση απελευθερώνει και επαναφέρει τους κωδικοποιητές βίντεο όταν συμβαίνει μια αλλαγή επιφάνειας, αντί να ρυθμίζει την επιφάνεια απευθείας στον κωδικοποιητή. Χρησιμοποιείται ήδη από το ExoPlayer σε ορισμένες συσκευές με αυτό το πρόβλημα, αυτή η ρύθμιση έχει επίδραση μόνο σε Android 6 και νεότερη έκδοση.
|
||||
\n
|
||||
\nΗ ενεργοποίηση αυτής της επιλογής μπορεί να αποτρέψει σφάλματα αναπαραγωγής κατά την εναλλαγή του τρέχοντος προγράμματος αναπαραγωγής βίντεο ή τη μετάβαση σε πλήρη οθόνη</string>
|
||||
<string name="audio_track_name">%s %s</string>
|
||||
<string name="audio_track_type_original">αρχικό</string>
|
||||
<string name="audio_track_type_dubbed">μεταγλωττισμένο</string>
|
||||
<string name="audio_track_type_descriptive">περιγραφικό</string>
|
||||
<string name="progressive_load_interval_summary">Αλλάξτε το μέγεθος του διαστήματος φόρτωσης σε προοδευτικά περιεχόμενα (προς το παρόν %s). Μια χαμηλότερη τιμή μπορεί να επιταχύνει την αρχική τους φόρτωση</string>
|
||||
<string name="prefer_descriptive_audio_summary">Επιλογή ήχου με περιγραφές για άτομα με προβλήματα όρασης, εάν είναι διαθέσιμος</string>
|
||||
</resources>
|
||||
@@ -202,10 +202,6 @@
|
||||
<string name="users">Uzantoj</string>
|
||||
<string name="unsubscribe">Malaboni</string>
|
||||
<string name="tab_choose">Elektu ongleton</string>
|
||||
<string name="volume_gesture_control_title">Kontrolo de volumena gesto</string>
|
||||
<string name="volume_gesture_control_summary">Uzi gestojn por kontroli la volumon</string>
|
||||
<string name="brightness_gesture_control_title">Kontrolo de gesto de brilo</string>
|
||||
<string name="brightness_gesture_control_summary">Uzi gestojn por kontroli la brilon</string>
|
||||
<string name="settings_category_updates_title">Ĝisdatigoj</string>
|
||||
<string name="file_deleted">Dosiero forviŝita</string>
|
||||
<string name="app_update_notification_channel_name">Sciigo por ĝisdatigi apon</string>
|
||||
|
||||
@@ -390,10 +390,6 @@
|
||||
<string name="downloads_storage_use_saf_summary">El \'Sistema de Acceso al Almacenamiento\' permite descargar en una tarjeta SD externa</string>
|
||||
<string name="unsubscribe">Desuscribirse</string>
|
||||
<string name="tab_choose">Elija la pestaña</string>
|
||||
<string name="volume_gesture_control_title">Control de volumen por gestos</string>
|
||||
<string name="volume_gesture_control_summary">Usar gestos para controlar el volumen del reproductor</string>
|
||||
<string name="brightness_gesture_control_title">Control de brillo por gestos</string>
|
||||
<string name="brightness_gesture_control_summary">Usar gestos para controlar el brillo del reproductor</string>
|
||||
<string name="settings_category_updates_title">Actualizaciones</string>
|
||||
<string name="events">Eventos</string>
|
||||
<string name="app_update_notification_channel_name">Notificación de actualización de la aplicación</string>
|
||||
@@ -648,7 +644,6 @@
|
||||
<string name="off">Apagado</string>
|
||||
<string name="on">Encendido</string>
|
||||
<string name="tablet_mode_title">Modo tableta</string>
|
||||
<string name="feed_toggle_show_played_items">Mostrar elementos ya vistos</string>
|
||||
<string name="dont_show">No mostrar</string>
|
||||
<string name="low_quality_smaller">Baja calidad (más pequeño)</string>
|
||||
<string name="high_quality_larger">Alta calidad (más grande)</string>
|
||||
@@ -696,7 +691,6 @@
|
||||
<string name="detail_pinned_comment_view_description">Comentario fijado</string>
|
||||
<string name="leak_canary_not_available">LeakCanary no está disponible</string>
|
||||
<string name="progressive_load_interval_exoplayer_default">ExoPlayer valor por defecto</string>
|
||||
<string name="progressive_load_interval_summary">Cambie el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial de video. Los cambios requieren un reinicio del reproductor</string>
|
||||
<string name="notifications">Notificaciones</string>
|
||||
<string name="streams_notification_channel_name">Nuevos streams</string>
|
||||
<string name="settings_category_player_notification_title">Notificación del reproductor</string>
|
||||
@@ -729,10 +723,7 @@
|
||||
<string name="select_quality_external_players">Elija la calidad para reproductores externos</string>
|
||||
<string name="unknown_format">Formato desconocido</string>
|
||||
<string name="unknown_quality">Calidad desconocida</string>
|
||||
<string name="feed_toggle_show_future_items">Mostrar elementos futuros</string>
|
||||
<string name="progressive_load_interval_title">Tamaño del intervalo de carga de reproducción</string>
|
||||
<string name="feed_toggle_hide_played_items">Ocultar los elementos mirados</string>
|
||||
<string name="feed_toggle_hide_future_items">Ocultar elementos futuros</string>
|
||||
<string name="faq">Ver en la página web</string>
|
||||
<string name="faq_title">Preguntas frecuentes</string>
|
||||
<string name="faq_description">Si tienes problemas al usar la aplicación, ¡Asegúrate de verificar estas respuestas a preguntas comunes!</string>
|
||||
@@ -757,4 +748,33 @@
|
||||
<string name="remove_duplicates">Eliminar los duplicados</string>
|
||||
<string name="feed_show_watched">Completamente visto</string>
|
||||
<string name="feed_show_partially_watched">Parcialmente visto</string>
|
||||
<string name="left_gesture_control_title">Acción del gesto en la izquierda</string>
|
||||
<string name="right_gesture_control_title">Acción del gesto a la derecha</string>
|
||||
<string name="brightness">Brillo</string>
|
||||
<string name="volume">Volumen</string>
|
||||
<string name="none">Ninguno</string>
|
||||
<string name="left_gesture_control_summary">Elige un gesto para la mitad izquierda de la pantalla del reproductor</string>
|
||||
<string name="right_gesture_control_summary">Elige un gesto para la mitad derecha de la pantalla del reproductor</string>
|
||||
<string name="prefer_original_audio_title">Prefiero el audio original</string>
|
||||
<string name="prefer_original_audio_summary">Selecciona la pista de audio original independientemente del idioma</string>
|
||||
<string name="prefer_descriptive_audio_title">Prefiero un audio descriptivo</string>
|
||||
<string name="prefer_descriptive_audio_summary">Selecciona una pista de audio con descripciones para personas con discapacidad visual, si está disponible</string>
|
||||
<string name="play_queue_audio_track">Audio: %s</string>
|
||||
<string name="audio_track">Pista de audio</string>
|
||||
<string name="audio_track_present_in_video">Ya debería existir una pista de audio en esta transmisión</string>
|
||||
<string name="select_audio_track_external_players">Selecciona una pista de audio para reproductores externos</string>
|
||||
<string name="unknown_audio_track">Desconocido</string>
|
||||
<string name="use_exoplayer_decoder_fallback_title">Utilice la función de respaldo del decodificador de ExoPlayer</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_title">Utiliza siempre la configuración de ExoPlayer para la interfaz de salida del video como una solución alternativa</string>
|
||||
<string name="audio_track_name">%s %s</string>
|
||||
<string name="audio_track_type_original">original</string>
|
||||
<string name="audio_track_type_dubbed">doblado</string>
|
||||
<string name="audio_track_type_descriptive">descriptivo</string>
|
||||
<string name="progressive_load_interval_summary">Cambia el tamaño del intervalo de carga en contenidos progresivos (actualmente %s). Un valor más bajo puede acelerar la carga inicial</string>
|
||||
<string name="settings_category_exoplayer_title">Ajustes para ExoPlayer</string>
|
||||
<string name="settings_category_exoplayer_summary">Gestiona algunos ajustes de ExoPlayer. Estos cambios requieren reiniciar el reproductor para que surtan efecto</string>
|
||||
<string name="use_exoplayer_decoder_fallback_summary">Habilite esta opción si tiene problemas con la inicialización del decodificador recurriendo a decodificadores de menor prioridad si el decodificador principal no se inicializa. Esto puede dar como resultado un rendimiento de reproducción más bajo que cuando se usan decodificadores primarios</string>
|
||||
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Esta solución alternativa libera los códecs de video y los vuelve a instanciar cuando cambia la máscara, en lugar de configurar la máscara directamente en el códec. ExoPlayer ya usa esta configuración en algunos dispositivos con este problema y solo afecta a Android 6 y versiones posteriores
|
||||
\n
|
||||
\nHabilitar esta opción puede evitar errores de reproducción al cambiar el reproductor de video actual o cambiar al modo de pantalla completa</string>
|
||||
</resources>
|
||||