.
diff --git a/README.md b/README.md
index 7f81c3fa6..0d9808907 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,23 @@
-# See https://github.com/TeamNewPipe/NewPipe/issues/4918
-No active development is going to occur here!
+# FoxPipe
-
-
-NewPipe
+A NewPipe fork with the old UI having separate Players handling 3 seperate Queues
+
+FoxPipe
A libre lightweight streaming frontend for Android.
-
-
+
-
-
-
-
+
-Screenshots • Description • Features • Updates • Contribution • Donate • License
-Website • Blog • FAQ • Press
+Screenshots • Description • Features • Installation and updates • Contribution • License
+Website • FAQ • Press
WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.
-PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.
+PUTTING NEWPIPE OR ANY FORK OF IT INTO THE GOOGLE PLAY STORE VIOLATES THEIR TERMS AND CONDITIONS.
## Screenshots
@@ -31,17 +26,21 @@ No active development is going to occur here!
[ ](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
[ ](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
[ ](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
-[ ](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
[ ](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
-[ ](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
-[ ](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
[ ](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
[ ](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
[ ](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
+
## Description
+This repository contains old preunified version 0.19.8 of [NewPipe](https://github.com/TeamNewPipe/NewPipe/releases/tag/v0.19.8) with legacy version of [NewPipeExtractor](https://github.com/ShareASmile/NewPipeExtractor) dependency.
+
+The application itself heavily relies on the extractor component which is responsible for proper parsing of various video/audio streams, including Youtube site. The old NewPipe version 0.19.8 depends on old extractor version which is practically deprecated and can't handle current Youtube (and similar?) streams,thus rendering the application useless for daily use.
+FoxPipe in this repository uses the updated version of NewPipeExtractor for legacy devices and resolves the forementioned issue, thus making it possible to use old NewPipe version 0.19.8 based FoxPipe with some bug fixes & features added along with updated extractor version. You don't need a YouTube account to use NewPipe, it is a copylefted libre software.
-NewPipe does not use any Google framework libraries, nor the YouTube API. Websites are only parsed to fetch required info, so this app can be used on devices without Google services installed. Also, you don't need a YouTube account to use NewPipe, which is copylefted libre software.
+## Motivation
+
+Not so long ago, NewPipe project implemented a new UI elements for video streams. Personally, I didn't like that change. I wanted to keep using the old UI having separate Players(Video, Popup & Background) handling three seperate Queues simultaneously instead.
### Features
@@ -51,8 +50,7 @@ NewPipe does not use any Google framework libraries, nor the YouTube API. Websit
* Listen to YouTube videos
* Popup mode (floating player)
* Select streaming player to watch video with
-* Download videos
-* Download audio only
+* Make Personalised Watch Later Playlists
* Open a video in Kodi
* Show next/related videos
* Search YouTube in a specific language
@@ -73,11 +71,6 @@ NewPipe does not use any Google framework libraries, nor the YouTube API. Websit
* Livestream support
* Show comments
-### Coming Features
-
-* Cast to UPnP and Cast
-* … and many more
-
### Supported Services
NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/documentation/) provide more info on how a new service can be added to the app and the extractor. Please get in touch with us if you intend to add a new one. Currently supported services are:
@@ -86,20 +79,17 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
* SoundCloud \[beta\]
* media.ccc.de \[beta\]
* PeerTube instances \[beta\]
+* Bandcamp \[beta\]
-## Updates
-When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can:
- * Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods.
- * Download the APK from [releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it.
- * Update via F-droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, then push the update to users.
+
+
-When you install an APK from one of these options, it will be incompatible with an APK from one of the other options. This is due to different signing keys being used. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app, and are independent. F-Droid and GitHub use different signing keys, and building an APK debug excludes a key. The signing key issue is being discussed in issue [#1981](https://github.com/TeamNewPipe/NewPipe/issues/1981), and may be fixed by setting up our own repository on F-Droid.
+## Installation and updates
+You can install NewPipe using one of the following methods:
+ 1. Download the APK from [Github Releases](https://github.com/ShareASmile/FoxPipe/releases) and install it.
+ 2. Build a debug APK yourself or install from [actions](https://github.com/ShareASmile/FoxPipe/actions) This is the fastest way to get new features on your device.
-In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality was broken and F-Droid doesn't have the update yet), we recommend following this procedure:
-1. Back up your data via "Settings>Content>Export Database" so you keep your history, subscriptions, and playlists
-2. Uninstall NewPipe
-3. Download the APK from the new source and install it
-4. Import the data from step 1 via "Settings>Content>Import Database"
+We recommend method 1 for most users. 2. Building a debug APK using method 2 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app.
## Contribution
Whether you have ideas, translations, design changes, code cleaning, or real heavy code changes, help is always welcome.
@@ -107,37 +97,17 @@ The more is done the better it gets!
If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md).
-## Donate
-If you like NewPipe we'd be happy about a donation. You can either send bitcoin or donate via Bountysource or Liberapay. For further info on donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate).
-
-
-
-
-
- 16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh
-
-
-
-
-
-
-
-
-
-
-
-
## Privacy Policy
-The NewPipe project aims to provide a private, anonymous experience for using media web services.
-Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report, or comment in our blog. You can find the document [here](https://newpipe.schabi.org/legal/privacy/).
+The FoxPipe project aims to provide a private, anonymous experience for using media web services.
+Therefore, the app does not collect any data without your consent.
## License
-[](http://www.gnu.org/licenses/gpl-3.0.en.html)
+[](https://www.gnu.org/licenses/gpl-3.0.en.html)
NewPipe is Free Software: You can use, study share and improve it at your
will. Specifically you can redistribute and/or modify it under the terms of the
[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as
published by the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
+(at your option) any later version.
diff --git a/app/.gitignore b/app/.gitignore
deleted file mode 100644
index 53edac5e4..000000000
--- a/app/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-.gitignore
-/build
-*.iml
diff --git a/app/build.gradle b/app/build.gradle
index d4889cf70..58f72bc54 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,19 +2,19 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
-apply plugin: 'checkstyle'
android {
- compileSdkVersion 29
- buildToolsVersion '29.0.3'
+ compileSdkVersion 30
+ buildToolsVersion '30.0.3'
defaultConfig {
- applicationId "org.schabi.newpipe"
- resValue "string", "app_name", "NewPipe"
+ applicationId "com.soulwin.foxpipe"
+ resValue "string", "app_name", "FoxPipe"
minSdkVersion 19
+ //noinspection ExpiredTargetSdkVersion
targetSdkVersion 29
- versionCode 993
- versionName "0.22.3.2"
+ versionCode 956
+ versionName "0.19.8.3"
multiDexEnabled true
@@ -34,15 +34,15 @@ android {
// suffix the app id and the app name with git branch name
def workingBranch = getGitWorkingBranch()
- def normalizedWorkingBranch = workingBranch.replaceAll("[^A-Za-z]+", "").toLowerCase()
+ def normalizedWorkingBranch = workingBranch.replaceFirst("^[^A-Za-z]+", "").replaceAll("[^0-9A-Za-z]+", "")
if (normalizedWorkingBranch.isEmpty() || workingBranch == "master" || workingBranch == "dev") {
// default values when branch name could not be determined or is master or dev
applicationIdSuffix ".debug"
- resValue "string", "app_name", "NewPipe Debug"
+ resValue "string", "app_name", "FoxPipe debug"
} else {
applicationIdSuffix ".debug." + normalizedWorkingBranch
- resValue "string", "app_name", "NewPipe " + workingBranch
- archivesBaseName = 'NewPipe_' + normalizedWorkingBranch
+ resValue "string", "app_name", "FoxPipe " + workingBranch
+ archivesBaseName = 'FoxPipe_' + normalizedWorkingBranch
}
}
@@ -51,11 +51,9 @@ android {
// TODO: update Gradle version
release {
minifyEnabled true
- shrinkResources false // disabled to fix F-Droid's reproducible build
+ shrinkResources true // could be disabled to fix F-Droid's reproducible build
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
archivesBaseName = 'app'
- applicationIdSuffix ".preunified"
- resValue "string", "app_name", "NewPipe " + "preunified"
}
}
@@ -64,6 +62,9 @@ android {
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
+ // suppress false warning ("Resource IDs will be non-final in Android Gradle Plugin version
+ // 5.0, avoid using them in switch case statements"), which affects only library projects
+ disable 'NonConstantResourceId'
}
compileOptions {
@@ -91,7 +92,6 @@ android {
ext {
icepickVersion = '3.2.0'
- checkstyleVersion = '8.38'
stethoVersion = '1.5.1'
leakCanaryVersion = '2.5'
exoPlayerVersion = '2.11.8'
@@ -99,127 +99,139 @@ ext {
androidxRoomVersion = '2.2.5'
groupieVersion = '2.8.1'
markwonVersion = '4.6.0'
+ googleAutoServiceVersion = '1.0-rc7'
}
configurations {
- checkstyle
ktlint
}
-checkstyle {
- configFile rootProject.file('checkstyle.xml')
- ignoreFailures false
- showViolations true
- toolVersion = checkstyleVersion
-}
-
-task runCheckstyle(type: Checkstyle) {
- source 'src'
- include '**/*.java'
- exclude '**/gen/**'
- exclude '**/R.java'
- exclude '**/BuildConfig.java'
- exclude 'main/java/us/shandian/giga/**'
-
- classpath = configurations.checkstyle
-
- showViolations true
-
- reports {
- xml.enabled true
- html.enabled true
- }
-}
+def outputDir = "${project.buildDir}/reports/ktlint/"
+def inputFiles = project.fileTree(dir: "src", include: "**/*.kt")
task runKtlint(type: JavaExec) {
+ inputs.files(inputFiles)
+ outputs.dir(outputDir)
main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint
args "src/**/*.kt"
}
task formatKtlint(type: JavaExec) {
+ inputs.files(inputFiles)
+ outputs.dir(outputDir)
main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint
args "-F", "src/**/*.kt"
}
afterEvaluate {
+ if (!System.properties.containsKey('skipFormatKtlint')) {
+ preDebugBuild.dependsOn formatKtlint
+ }
preDebugBuild.dependsOn runKtlint
}
dependencies {
+/** Desugaring **/
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
-
- implementation "frankiesardo:icepick:${icepickVersion}"
- kapt "frankiesardo:icepick-processor:${icepickVersion}"
-
- checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
+/** Checkstyle **/
ktlint "com.pinterest:ktlint:0.40.0"
- debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
- debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
-
- debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
- implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
-
- implementation "androidx.multidex:multidex:2.0.1"
-
- testImplementation 'junit:junit:4.13.2'
- testImplementation 'org.mockito:mockito-core:3.3.3'
+/** Kotlin **/
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}"
- androidTestImplementation "androidx.test.ext:junit:1.1.1"
- androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
- androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0", {
- exclude module: 'support-annotations'
- }
-
- implementation 'com.github.TeamNewPipe:NewPipeExtractor:eb07d70a2ce03bee3cc74fc33b2e4173e1c21436'
+/** AndroidX **/
+ implementation 'androidx.appcompat:appcompat:1.1.0' //no change
+ implementation 'androidx.cardview:cardview:1.0.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
+ implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
+ implementation "androidx.lifecycle:lifecycle-extensions:${androidxLifecycleVersion}"
+ implementation 'androidx.media:media:1.2.1'
+ implementation 'androidx.multidex:multidex:2.0.1'
+ implementation 'androidx.preference:preference:1.1.1'
+ implementation 'androidx.recyclerview:recyclerview:1.1.0'
+ implementation "androidx.room:room-runtime:${androidxRoomVersion}"
+ implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
+ kapt "androidx.room:room-compiler:${androidxRoomVersion}"
+ implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
+ implementation 'com.google.android.material:material:1.2.1'
+
+/** NewPipe libraries **/
+ // You can use a local version by uncommenting a few lines in settings.gradle
+ // Or you can use a commit you pushed to GitHub by just replacing TeamNewPipe with your GitHub
+ // name and the commit hash with the commit hash of the (pushed) commit you want to test
+ // This works thanks to JitPack: https://jitpack.io/
+ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
+ implementation 'com.github.RSoulwin:NewPipeExtractor:d731ca24559172813df0fdebcbf10239cc26d6fc'
+
+/** Third-party libraries **/
+ // Instance state boilerplate elimination
+ implementation "frankiesardo:icepick:${icepickVersion}"
+ kapt "frankiesardo:icepick-processor:${icepickVersion}"
- implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
+ // HTML parser
implementation "org.jsoup:jsoup:1.13.1"
- //noinspection GradleDependency --> do not update okhttp above 3.12.xx to keep supporting Android 4.4 users
+ // HTTP client
+ //noinspection GradleDependency --> do not update okhttp beyond 3.12.x to keep supporting Android 4.4 users
implementation "com.squareup.okhttp3:okhttp:3.12.13"
+ // Media player
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
- implementation "com.google.android.material:material:1.1.0"
-
- implementation "androidx.appcompat:appcompat:1.1.0"
- implementation "androidx.preference:preference:1.1.1"
- implementation "androidx.recyclerview:recyclerview:1.1.0"
- implementation "androidx.cardview:cardview:1.0.0"
- implementation "androidx.constraintlayout:constraintlayout:1.1.3"
-
- implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
- implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
- implementation "androidx.lifecycle:lifecycle-extensions:${androidxLifecycleVersion}"
-
- implementation "androidx.room:room-runtime:${androidxRoomVersion}"
- implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
- kapt "androidx.room:room-compiler:${androidxRoomVersion}"
+ // Metadata generator for service descriptors
+ compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}"
+ kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}"
+ // Manager for complex RecyclerView layouts
implementation "com.xwray:groupie:${groupieVersion}"
implementation "com.xwray:groupie-kotlin-android-extensions:${groupieVersion}"
+ // Circular ImageView
implementation "de.hdodenhof:circleimageview:3.1.0"
+ // Image loading
implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5"
+ // Markdown library for Android
implementation "io.noties.markwon:core:${markwonVersion}"
implementation "io.noties.markwon:linkify:${markwonVersion}"
+ // File picker
implementation "com.nononsenseapps:filepicker:4.2.1"
- implementation "ch.acra:acra-core:5.5.0"
+ // Crash reporting
+ implementation "ch.acra:acra-core:5.7.0"
+ // Reactive extensions for Java VM
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
+ // RxJava binding APIs for Android UI widgets
implementation "com.jakewharton.rxbinding2:rxbinding:2.2.0"
- implementation "org.ocpsoft.prettytime:prettytime:4.0.5.Final"
+ // Date and time formatting
+ implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final"
+
+/** Debugging **/
+ // Memory leak detection
+ debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
+ implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
+ // Debug bridge for Android
+ debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
+ debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
+
+/** Testing **/
+ testImplementation 'junit:junit:4.13.2'
+ testImplementation 'org.mockito:mockito-core:3.6.0'
+
+ androidTestImplementation "androidx.test.ext:junit:1.1.2"
+ androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
+ androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0", {
+ exclude module: 'support-annotations'
+ }
}
static String getGitWorkingBranch() {
diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt
index e37eb5db9..239c46f7a 100644
--- a/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt
+++ b/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt
@@ -31,49 +31,62 @@ class AppDatabaseTest {
}
@get:Rule
- val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory())
+ val testHelper = MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory()
+ )
@Test
fun migrateDatabaseFrom2to3() {
val databaseInV2 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_2)
databaseInV2.run {
- insert("streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
- // put("uid", null)
- put("service_id", DEFAULT_SERVICE_ID)
- put("url", DEFAULT_URL)
- put("title", DEFAULT_TITLE)
- put("stream_type", DEFAULT_TYPE.name)
- put("duration", DEFAULT_DURATION)
- put("uploader", DEFAULT_UPLOADER_NAME)
- put("thumbnail_url", DEFAULT_THUMBNAIL)
- })
- insert("streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
- // put("uid", null)
- put("service_id", DEFAULT_SECOND_SERVICE_ID)
- put("url", DEFAULT_SECOND_URL)
- // put("title", null)
- // put("stream_type", null)
- // put("duration", null)
- // put("uploader", null)
- // put("thumbnail_url", null)
- })
- insert("streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
- // put("uid", null)
- put("service_id", DEFAULT_SERVICE_ID)
- // put("url", null)
- // put("title", null)
- // put("stream_type", null)
- // put("duration", null)
- // put("uploader", null)
- // put("thumbnail_url", null)
- })
+ insert(
+ "streams", SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ // put("uid", null)
+ put("service_id", DEFAULT_SERVICE_ID)
+ put("url", DEFAULT_URL)
+ put("title", DEFAULT_TITLE)
+ put("stream_type", DEFAULT_TYPE.name)
+ put("duration", DEFAULT_DURATION)
+ put("uploader", DEFAULT_UPLOADER_NAME)
+ put("thumbnail_url", DEFAULT_THUMBNAIL)
+ }
+ )
+ insert(
+ "streams", SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ // put("uid", null)
+ put("service_id", DEFAULT_SECOND_SERVICE_ID)
+ put("url", DEFAULT_SECOND_URL)
+ // put("title", null)
+ // put("stream_type", null)
+ // put("duration", null)
+ // put("uploader", null)
+ // put("thumbnail_url", null)
+ }
+ )
+ insert(
+ "streams", SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ // put("uid", null)
+ put("service_id", DEFAULT_SERVICE_ID)
+ // put("url", null)
+ // put("title", null)
+ // put("stream_type", null)
+ // put("duration", null)
+ // put("uploader", null)
+ // put("thumbnail_url", null)
+ }
+ )
close()
}
- testHelper.runMigrationsAndValidate(AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
- true, Migrations.MIGRATION_2_3)
+ testHelper.runMigrationsAndValidate(
+ AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
+ true, Migrations.MIGRATION_2_3
+ )
val migratedDatabaseV3 = getMigratedDatabase()
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
@@ -110,9 +123,11 @@ class AppDatabaseTest {
}
private fun getMigratedDatabase(): AppDatabase {
- val database: AppDatabase = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
- AppDatabase::class.java, AppDatabase.DATABASE_NAME)
- .build()
+ val database: AppDatabase = Room.databaseBuilder(
+ ApplicationProvider.getApplicationContext(),
+ AppDatabase::class.java, AppDatabase.DATABASE_NAME
+ )
+ .build()
testHelper.closeWhenFinished(database)
return database
}
diff --git a/app/src/debug/java/org/schabi/newpipe/DebugApp.kt b/app/src/debug/java/org/schabi/newpipe/DebugApp.kt
index 5cfde80b8..24f9fdf3f 100644
--- a/app/src/debug/java/org/schabi/newpipe/DebugApp.kt
+++ b/app/src/debug/java/org/schabi/newpipe/DebugApp.kt
@@ -1,6 +1,5 @@
package org.schabi.newpipe
-import android.content.Context
import androidx.multidex.MultiDex
import androidx.preference.PreferenceManager
import com.facebook.stetho.Stetho
@@ -11,29 +10,38 @@ import okhttp3.OkHttpClient
import org.schabi.newpipe.extractor.downloader.Downloader
class DebugApp : App() {
- override fun attachBaseContext(base: Context) {
- super.attachBaseContext(base)
- MultiDex.install(this)
- }
-
override fun onCreate() {
super.onCreate()
initStetho()
// Give each object 10 seconds to be GC'ed, before LeakCanary gets nosy on it
AppWatcher.config = AppWatcher.config.copy(watchDurationMillis = 10000)
- LeakCanary.config = LeakCanary.config.copy(dumpHeap = PreferenceManager
- .getDefaultSharedPreferences(this).getBoolean(getString(
- R.string.allow_heap_dumping_key), false))
+ LeakCanary.config = LeakCanary.config.copy(
+ dumpHeap = PreferenceManager
+ .getDefaultSharedPreferences(this).getBoolean(
+ getString(
+ R.string.allow_heap_dumping_key
+ ),
+ false
+ )
+ )
}
override fun getDownloader(): Downloader {
- val downloader = DownloaderImpl.init(OkHttpClient.Builder()
- .addNetworkInterceptor(StethoInterceptor()))
+ val downloader = DownloaderImpl.init(
+ OkHttpClient.Builder()
+ .addNetworkInterceptor(StethoInterceptor())
+ )
setCookiesToDownloader(downloader)
return downloader
}
+ override fun initACRA() {
+ // install MultiDex before initializing ACRA
+ MultiDex.install(this)
+ super.initACRA()
+ }
+
private fun initStetho() {
// Create an InitializerBuilder
val initializerBuilder = Stetho.newInitializerBuilder(this)
@@ -43,7 +51,8 @@ class DebugApp : App() {
// Enable command line interface
initializerBuilder.enableDumpapp(
- Stetho.defaultDumperPluginsProvider(applicationContext))
+ Stetho.defaultDumperPluginsProvider(applicationContext)
+ )
// Use the InitializerBuilder to generate an Initializer
val initializer = initializerBuilder.build()
@@ -54,6 +63,6 @@ class DebugApp : App() {
override fun isDisposedRxExceptionsReported(): Boolean {
return PreferenceManager.getDefaultSharedPreferences(this)
- .getBoolean(getString(R.string.allow_disposed_exceptions_key), false)
+ .getBoolean(getString(R.string.allow_disposed_exceptions_key), false)
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 68c7ea338..f1f4365c6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,7 +1,8 @@
+ package="org.schabi.newpipe"
+ android:installLocation="auto">
@@ -13,6 +14,9 @@
+
-
+
+
+
@@ -206,28 +215,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -239,23 +226,13 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
@@ -283,7 +260,7 @@
-
+
@@ -314,26 +291,62 @@
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/epl1.html b/app/src/main/assets/epl1.html
new file mode 100644
index 000000000..7123552dd
--- /dev/null
+++ b/app/src/main/assets/epl1.html
@@ -0,0 +1,245 @@
+
+
+
+
+
+
+ Eclipse Public License - Version 1.0
+
+
+
+
+
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+ PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
+ DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
+ AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial
+ code and documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+i) changes to the Program, and
+ii) additions to the Program;
+where such changes and/or additions to the Program
+ originate from and are distributed by that particular Contributor. A
+ Contribution 'originates' from a Contributor if it was added to the
+ Program by such Contributor itself or anyone acting on such
+ Contributor's behalf. Contributions do not include additions to the
+ Program which: (i) are separate modules of software distributed in
+ conjunction with the Program under their own license agreement, and (ii)
+ are not derivative works of the Program.
+
+"Contributor" means any person or entity that distributes
+ the Program.
+
+"Licensed Patents" mean patent claims licensable by a
+ Contributor which are necessarily infringed by the use or sale of its
+ Contribution alone or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance
+ with this Agreement.
+
+"Recipient" means anyone who receives the Program under
+ this Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each
+ Contributor hereby grants Recipient a non-exclusive, worldwide,
+ royalty-free copyright license to reproduce, prepare derivative works
+ of, publicly display, publicly perform, distribute and sublicense the
+ Contribution of such Contributor, if any, and such derivative works, in
+ source code and object code form.
+
+b) Subject to the terms of this Agreement, each
+ Contributor hereby grants Recipient a non-exclusive, worldwide,
+ royalty-free patent license under Licensed Patents to make, use, sell,
+ offer to sell, import and otherwise transfer the Contribution of such
+ Contributor, if any, in source code and object code form. This patent
+ license shall apply to the combination of the Contribution and the
+ Program if, at the time the Contribution is added by the Contributor,
+ such addition of the Contribution causes such combination to be covered
+ by the Licensed Patents. The patent license shall not apply to any other
+ combinations which include the Contribution. No hardware per se is
+ licensed hereunder.
+
+c) Recipient understands that although each Contributor
+ grants the licenses to its Contributions set forth herein, no assurances
+ are provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity. Each
+ Contributor disclaims any liability to Recipient for claims brought by
+ any other entity based on infringement of intellectual property rights
+ or otherwise. As a condition to exercising the rights and licenses
+ granted hereunder, each Recipient hereby assumes sole responsibility to
+ secure any other intellectual property rights needed, if any. For
+ example, if a third party patent license is required to allow Recipient
+ to distribute the Program, it is Recipient's responsibility to acquire
+ that license before distributing the Program.
+
+d) Each Contributor represents that to its knowledge it
+ has sufficient copyright rights in its Contribution, if any, to grant
+ the copyright license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code
+ form under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this
+ Agreement; and
+
+b) its license agreement:
+
+i) effectively disclaims on behalf of all Contributors
+ all warranties and conditions, express and implied, including warranties
+ or conditions of title and non-infringement, and implied warranties or
+ conditions of merchantability and fitness for a particular purpose;
+
+ii) effectively excludes on behalf of all Contributors
+ all liability for damages, including direct, indirect, special,
+ incidental and consequential damages, such as lost profits;
+
+iii) states that any provisions which differ from this
+ Agreement are offered by that Contributor alone and not by any other
+ party; and
+
+iv) states that source code for the Program is available
+ from such Contributor, and informs licensees how to obtain it in a
+ reasonable manner on or through a medium customarily used for software
+ exchange.
+
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+
+b) a copy of this Agreement must be included with each
+ copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained
+ within the Program.
+
+Each Contributor must identify itself as the originator of its
+ Contribution, if any, in a manner that reasonably allows subsequent
+ Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+ responsibilities with respect to end users, business partners and the
+ like. While this license is intended to facilitate the commercial use of
+ the Program, the Contributor who includes the Program in a commercial
+ product offering should do so in a manner which does not create
+ potential liability for other Contributors. Therefore, if a Contributor
+ includes the Program in a commercial product offering, such Contributor
+ ("Commercial Contributor") hereby agrees to defend and
+ indemnify every other Contributor ("Indemnified Contributor")
+ against any losses, damages and costs (collectively "Losses")
+ arising from claims, lawsuits and other legal actions brought by a third
+ party against the Indemnified Contributor to the extent caused by the
+ acts or omissions of such Commercial Contributor in connection with its
+ distribution of the Program in a commercial product offering. The
+ obligations in this section do not apply to any claims or Losses
+ relating to any actual or alleged intellectual property infringement. In
+ order to qualify, an Indemnified Contributor must: a) promptly notify
+ the Commercial Contributor in writing of such claim, and b) allow the
+ Commercial Contributor to control, and cooperate with the Commercial
+ Contributor in, the defense and any related settlement negotiations. The
+ Indemnified Contributor may participate in any such claim at its own
+ expense.
+
+For example, a Contributor might include the Program in a commercial
+ product offering, Product X. That Contributor is then a Commercial
+ Contributor. If that Commercial Contributor then makes performance
+ claims, or offers warranties related to Product X, those performance
+ claims and warranties are such Commercial Contributor's responsibility
+ alone. Under this section, the Commercial Contributor would have to
+ defend claims against the other Contributors related to those
+ performance claims and warranties, and if a court requires any other
+ Contributor to pay any damages as a result, the Commercial Contributor
+ must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+ PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
+ ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+ OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+ responsible for determining the appropriateness of using and
+ distributing the Program and assumes all risks associated with its
+ exercise of rights under this Agreement , including but not limited to
+ the risks and costs of program errors, compliance with applicable laws,
+ damage to or loss of data, programs or equipment, and unavailability or
+ interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
+ NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+ WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+ DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+ HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+ applicable law, it shall not affect the validity or enforceability of
+ the remainder of the terms of this Agreement, and without further action
+ by the parties hereto, such provision shall be reformed to the minimum
+ extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity
+ (including a cross-claim or counterclaim in a lawsuit) alleging that the
+ Program itself (excluding combinations of the Program with other
+ software or hardware) infringes such Recipient's patent(s), then such
+ Recipient's rights granted under Section 2(b) shall terminate as of the
+ date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+ fails to comply with any of the material terms or conditions of this
+ Agreement and does not cure such failure in a reasonable period of time
+ after becoming aware of such noncompliance. If all Recipient's rights
+ under this Agreement terminate, Recipient agrees to cease use and
+ distribution of the Program as soon as reasonably practicable. However,
+ Recipient's obligations under this Agreement and any licenses granted by
+ Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this
+ Agreement, but in order to avoid inconsistency the Agreement is
+ copyrighted and may only be modified in the following manner. The
+ Agreement Steward reserves the right to publish new versions (including
+ revisions) of this Agreement from time to time. No one other than the
+ Agreement Steward has the right to modify this Agreement. The Eclipse
+ Foundation is the initial Agreement Steward. The Eclipse Foundation may
+ assign the responsibility to serve as the Agreement Steward to a
+ suitable separate entity. Each new version of the Agreement will be
+ given a distinguishing version number. The Program (including
+ Contributions) may always be distributed subject to the version of the
+ Agreement under which it was received. In addition, after a new version
+ of the Agreement is published, Contributor may elect to distribute the
+ Program (including its Contributions) under the new version. Except as
+ expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
+ rights or licenses to the intellectual property of any Contributor under
+ this Agreement, whether expressly, by implication, estoppel or
+ otherwise. All rights in the Program not expressly granted under this
+ Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+ the intellectual property laws of the United States of America. No party
+ to this Agreement will bring a legal action under this Agreement more
+ than one year after the cause of action arose. Each party waives its
+ rights to a jury trial in any resulting litigation.
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/gpl_2.html b/app/src/main/assets/gpl_2.html
deleted file mode 100644
index 0e1b8827e..000000000
--- a/app/src/main/assets/gpl_2.html
+++ /dev/null
@@ -1,400 +0,0 @@
-
-
-
-
-
- GNU General Public License v2.0 - GNU Project - Free Software Foundation (FSF)
-
-
-
-
-
-Version 2, June 1991
-
-
-
-Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-
-Everyone is permitted to copy and distribute verbatim copies
-of this license document, but changing it is not allowed.
-
-
-
-
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
-your programs, too.
-
-
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
-
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
-
-
-
-
-
-
-0.
- This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
-
-
-1.
- You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
-
-
-2.
- You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-
-
-
-
- a)
- You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
-
-
- b)
- You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
-
-
- c)
- If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-
-
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-
-
-3.
- You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
-
-
-
-
-
-
-
- a)
- Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
-
-
- b)
- Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
-
-
- c)
- Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-
-
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major softwareComponents (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-
-
-4.
- You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-
-
-5.
- You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-
-
-6.
- Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
-
-
-7.
- If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-
-
-8.
- If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-
-
-9.
- The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
-
-
-10.
- If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
-
-NO WARRANTY
-
-
-11.
- BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
-
-
-12.
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
-
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index deb0cf1ee..a91098a4e 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -70,6 +70,7 @@ public class App extends MultiDexApplication {
private static final Class extends ReportSenderFactory>[]
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
private static App app;
+ public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
public static App getApp() {
return app;
@@ -78,7 +79,6 @@ public static App getApp() {
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(base);
-
initACRA();
}
@@ -107,7 +107,7 @@ public void onCreate() {
configureRxJavaErrorHandler();
// Check for new version
- new CheckForNewAppVersionTask().execute();
+ //new CheckForNewAppVersionTask().execute();
}
protected Downloader getDownloader() {
@@ -120,7 +120,7 @@ protected void setCookiesToDownloader(final DownloaderImpl downloader) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
getApplicationContext());
final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
- downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, ""));
+ downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, null));
downloader.updateYoutubeRestrictedModeCookies(getApplicationContext());
}
@@ -201,10 +201,17 @@ private ImageLoaderConfiguration getImageLoaderConfigurations(final int memoryCa
.build();
}
- private void initACRA() {
+ /**
+ * Called in {@link #attachBaseContext(Context)} after calling the {@code super} method.
+ * Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA.
+ */
+ protected void initACRA() {
+ if (ACRA.isACRASenderServiceProcess()) {
+ return;
+ }
+
try {
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
- .setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
.setBuildConfigClass(BuildConfig.class)
.build();
ACRA.init(this, acraConfig);
@@ -220,7 +227,7 @@ private void initACRA() {
}
public void initNotificationChannel() {
- if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java
index 6878a360a..01f7ab240 100644
--- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java
+++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java
@@ -11,11 +11,11 @@
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.AsyncTask;
-import android.preference.PreferenceManager;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
+import androidx.preference.PreferenceManager;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
index 95d3c2b7c..442488dc4 100644
--- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
+++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
@@ -2,10 +2,10 @@
import android.content.Context;
import android.os.Build;
-import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.downloader.Request;
@@ -43,7 +43,7 @@
public final class DownloaderImpl extends Downloader {
public static final String USER_AGENT
- = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
+ = "Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0";
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY
= "youtube_restricted_mode_key";
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
diff --git a/app/src/main/java/org/schabi/newpipe/ExitActivity.java b/app/src/main/java/org/schabi/newpipe/ExitActivity.java
index 94eff9560..65e180434 100644
--- a/app/src/main/java/org/schabi/newpipe/ExitActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/ExitActivity.java
@@ -42,7 +42,7 @@ public static void exitAndRemoveFromRecentApps(final Activity activity) {
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (Build.VERSION.SDK_INT >= 21) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
finishAndRemoveTask();
} else {
finish();
diff --git a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java
index ca61c9655..c2897cff1 100644
--- a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java
+++ b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java
@@ -4,7 +4,7 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 37d6d62f5..c801e63e6 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -27,7 +27,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -46,10 +45,12 @@
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.ActivityCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
+import androidx.preference.PreferenceManager;
import com.google.android.material.navigation.NavigationView;
@@ -99,6 +100,7 @@ public class MainActivity extends AppCompatActivity {
private static final int ITEM_ID_BOOKMARKS = -3;
private static final int ITEM_ID_DOWNLOADS = -4;
private static final int ITEM_ID_HISTORY = -5;
+ private static final int ITEM_ID_BG_PLAYER = -6;
private static final int ITEM_ID_SETTINGS = 0;
private static final int ITEM_ID_ABOUT = 1;
@@ -115,10 +117,11 @@ protected void onCreate(final Bundle savedInstanceState) {
+ "savedInstanceState = [" + savedInstanceState + "]");
}
- // enable TLS1.1/1.2 for kitkat devices, to fix download and play for mediaCCC sources
+ // enable TLS1.1/1.2 for kitkat devices, to fix download and play for media.ccc.de sources
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
TLSSocketFactoryCompat.setAsDefault();
}
+
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
assureCorrectAppLanguage(this);
@@ -163,7 +166,7 @@ private void setupDrawer() throws Exception {
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
- R.string.tab_subscriptions)
+ R.string.tab_subscriptions)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
@@ -177,6 +180,9 @@ private void setupDrawer() throws Exception {
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
+ drawerItems.getMenu()
+ .add(R.id.menu_tabs_group, ITEM_ID_BG_PLAYER, ORDER, R.string.background_player)
+ .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_play_arrow));
//Settings and About
drawerItems.getMenu()
@@ -204,7 +210,7 @@ public void onDrawerClosed(final View drawerView) {
toggleServices();
}
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
- new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
+ ActivityCompat.recreate(MainActivity.this);
}
}
});
@@ -261,6 +267,9 @@ private void tabSelected(final MenuItem item) throws ExtractionException {
case ITEM_ID_HISTORY:
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
break;
+ case ITEM_ID_BG_PLAYER:
+ NavigationHelper.openBackgroundPlayer(this);
+ break;
default:
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
StreamingService service = NewPipe.getService(currentServiceId);
@@ -479,10 +488,7 @@ protected void onResume() {
Log.d(TAG, "Theme has changed, recreating activity...");
}
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
- // https://stackoverflow.com/questions/10844112/
- // Briefly, let the activity resume
- // properly posting the recreate call to end of the message queue
- new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
+ ActivityCompat.recreate(this);
}
if (sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) {
diff --git a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
index 40ea4fd58..24ea1f8f7 100644
--- a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
@@ -53,6 +53,16 @@ public class ReCaptchaActivity extends AppCompatActivity {
public static final String YT_URL = "https://www.youtube.com";
public static final String RECAPTCHA_COOKIES_KEY = "recaptcha_cookies";
+ public static String sanitizeRecaptchaUrl(@Nullable final String url) {
+ if (url == null || url.trim().isEmpty()) {
+ return YT_URL; // YouTube is the most likely service to have thrown a recaptcha
+ } else {
+ // remove "pbj=1" parameter from YouYube urls, as it makes the page JSON and not HTML
+ return url.replace("&pbj=1", "").replace("pbj=1&", "").replace("?pbj=1", "");
+ }
+ }
+
+
private WebView webView;
private String foundCookies = "";
@@ -64,11 +74,7 @@ protected void onCreate(final Bundle savedInstanceState) {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
- String url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
- if (url == null || url.isEmpty()) {
- url = YT_URL;
- }
-
+ final String url = sanitizeRecaptchaUrl(getIntent().getStringExtra(RECAPTCHA_URL_EXTRA));
// set return to Cancel by default
setResult(RESULT_CANCELED);
@@ -78,6 +84,7 @@ protected void onCreate(final Bundle savedInstanceState) {
// enable Javascript
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
+ webSettings.setUserAgentString(DownloaderImpl.USER_AGENT);
webView.setWebViewClient(new WebViewClient() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@@ -115,8 +122,7 @@ public void onPageFinished(final WebView view, final String url) {
webView.clearHistory();
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- cookieManager.removeAllCookies(aBoolean -> {
- });
+ cookieManager.removeAllCookies(value -> { });
} else {
cookieManager.removeAllCookie();
}
diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
index 39f6b217d..9343c576d 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
@@ -8,7 +8,6 @@
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -28,6 +27,7 @@
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.app.NotificationCompat;
import androidx.fragment.app.FragmentManager;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.Info;
@@ -50,6 +50,7 @@
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
+import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.urlfinder.UrlFinder;
import org.schabi.newpipe.views.FocusOverlayView;
@@ -159,27 +160,36 @@ private void handleUrl(final String url) {
if (result) {
onSuccess();
} else {
- onError();
+ showUnsupportedUrlDialog(url);
}
- }, this::handleError));
+ }, throwable -> handleError(throwable, url)));
}
- private void handleError(final Throwable error) {
- error.printStackTrace();
+ private void handleError(final Throwable throwable, final String url) {
+ throwable.printStackTrace();
- if (error instanceof ExtractionException) {
- Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
+ if (throwable instanceof ExtractionException) {
+ showUnsupportedUrlDialog(url);
} else {
- ExtractorHelper.handleGeneralException(this, -1, null, error,
+ ExtractorHelper.handleGeneralException(this, -1, url, throwable,
UserAction.SOMETHING_ELSE, null);
+ finish();
}
-
- finish();
}
- private void onError() {
- Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
- finish();
+ private void showUnsupportedUrlDialog(final String url) {
+ final Context context = getThemeWrapperContext();
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.unsupported_url)
+ .setMessage(R.string.unsupported_url_dialog_message)
+ .setIcon(ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_share))
+ .setPositiveButton(R.string.open_in_browser,
+ (dialog, which) -> ShareUtils.openUrlInBrowser(this, url))
+ .setNegativeButton(R.string.share,
+ (dialog, which) -> ShareUtils.shareUrl(this, "", url)) // no subject
+ .setNeutralButton(R.string.cancel, null)
+ .setOnDismissListener(dialog -> finish())
+ .show();
}
protected void onSuccess() {
@@ -459,7 +469,7 @@ private void handleChoice(final String selectedChoiceKey) {
startActivity(intent);
finish();
- }, this::handleError)
+ }, throwable -> handleError(throwable, currentUrl))
);
return;
}
@@ -492,11 +502,9 @@ private void openDownloadDialog() {
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.show(fm, "downloadDialog");
fm.executePendingTransactions();
- downloadDialog.getDialog().setOnDismissListener(dialog -> {
- finish();
- });
+ downloadDialog.getDialog().setOnDismissListener(dialog -> finish());
}, (@NonNull Throwable throwable) -> {
- onError();
+ showUnsupportedUrlDialog(currentUrl);
});
}
diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
index b5be2dde6..08cf96d85 100644
--- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
@@ -30,38 +30,47 @@ public class AboutActivity extends AppCompatActivity {
/**
* List of all software components.
*/
- private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
- new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai",
- "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
- new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
- "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
- new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley",
- "https://github.com/jhy/jsoup", StandardLicenses.MIT),
- new SoftwareComponent("Rhino", "2015", "Mozilla",
- "https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
+ private static final SoftwareComponent[] SOFTWARE_COMPONENTS = {
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin",
- "http://www.acra.ch", StandardLicenses.APACHE2),
- new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
- "https://github.com/nostra13/Android-Universal-Image-Loader",
- StandardLicenses.APACHE2),
+ "https://github.com/ACRA/acra", StandardLicenses.APACHE2),
+ new SoftwareComponent("AndroidX", "2005 - 2011", "The Android Open Source Project",
+ "https://developer.android.com/jetpack", StandardLicenses.APACHE2),
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof",
- "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
- new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
- "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
- new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc",
+ "https://github.com/hdodenhof/CircleImageView",
+ StandardLicenses.APACHE2),
+ new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google, Inc.",
"https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
- new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors",
+ new SoftwareComponent("GigaGet", "2014 - 2015", "Peter Cai",
+ "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3),
+ new SoftwareComponent("Groupie", "2016", "Lisa Wray",
+ "https://github.com/lisawray/groupie", StandardLicenses.MIT),
+ new SoftwareComponent("Icepick", "2015", "Frankie Sardo",
+ "https://github.com/frankiesardo/icepick", StandardLicenses.EPL1),
+ new SoftwareComponent("Jsoup", "2009 - 2020", "Jonathan Hedley",
+ "https://github.com/jhy/jsoup", StandardLicenses.MIT),
+ new SoftwareComponent("Markwon", "2019", "Dimitry Ivanov",
+ "https://github.com/noties/Markwon", StandardLicenses.APACHE2),
+ new SoftwareComponent("Material Components for Android", "2016 - 2020", "Google, Inc.",
+ "https://github.com/material-components/material-components-android",
+ StandardLicenses.APACHE2),
+ new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
+ "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
+ new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
+ "https://github.com/spacecowboy/NoNonsense-FilePicker",
+ StandardLicenses.MPL2),
+ new SoftwareComponent("OkHttp", "2019", "Square, Inc.",
+ "https://square.github.io/okhttp/", StandardLicenses.APACHE2),
+ new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
+ "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
+ new SoftwareComponent("RxAndroid", "2015", "The RxAndroid authors",
"https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
+ new SoftwareComponent("RxBinding", "2015", "Jake Wharton",
+ "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors",
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
- new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton",
- "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
- new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
- "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
- new SoftwareComponent("Markwon", "2017 - 2020", "Noties",
- "https://github.com/noties/Markwon", StandardLicenses.APACHE2),
- new SoftwareComponent("Groupie", "2016", "Lisa Wray",
- "https://github.com/lisawray/groupie", StandardLicenses.MIT)
+ new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
+ "https://github.com/nostra13/Android-Universal-Image-Loader",
+ StandardLicenses.APACHE2),
};
/**
@@ -103,6 +112,7 @@ protected void onCreate(final Bundle savedInstanceState) {
tabLayout.setupWithViewPager(mViewPager);
}
+
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
int id = item.getItemId();
@@ -120,7 +130,8 @@ public boolean onOptionsItemSelected(final MenuItem item) {
* A placeholder fragment containing a simple view.
*/
public static class AboutFragment extends Fragment {
- public AboutFragment() { }
+ public AboutFragment() {
+ }
/**
* Created a new instance of this fragment for the given section number.
diff --git a/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java b/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java
index 75a7a8613..60b1e168c 100644
--- a/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java
+++ b/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.java
@@ -4,8 +4,6 @@
* Class containing information about standard software licenses.
*/
public final class StandardLicenses {
- public static final License GPL2
- = new License("GNU General Public License, Version 2.0", "GPLv2", "gpl_2.html");
public static final License GPL3
= new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html");
public static final License APACHE2
@@ -14,6 +12,8 @@ public final class StandardLicenses {
= new License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html");
public static final License MIT
= new License("MIT License", "MIT", "mit.html");
+ public static final License EPL1
+ = new License("Eclipse Public License, Version 1.0", "EPL 1.0", "epl1.html");
private StandardLicenses() { }
}
diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt
index 74f5b369e..d443770cc 100644
--- a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt
@@ -7,18 +7,19 @@ import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.reactivex.Flowable
-import java.util.Date
import org.schabi.newpipe.database.feed.model.FeedEntity
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
+import java.util.Date
@Dao
abstract class FeedDAO {
@Query("DELETE FROM feed")
abstract fun deleteAll(): Int
- @Query("""
+ @Query(
+ """
SELECT s.* FROM streams s
INNER JOIN feed f
@@ -27,10 +28,12 @@ abstract class FeedDAO {
ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC
LIMIT 500
- """)
+ """
+ )
abstract fun getAllStreams(): Flowable>
- @Query("""
+ @Query(
+ """
SELECT s.* FROM streams s
INNER JOIN feed f
@@ -46,10 +49,12 @@ abstract class FeedDAO {
ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC
LIMIT 500
- """)
+ """
+ )
abstract fun getAllStreamsFromGroup(groupId: Long): Flowable>
- @Query("""
+ @Query(
+ """
DELETE FROM feed WHERE
feed.stream_id IN (
@@ -60,10 +65,12 @@ abstract class FeedDAO {
WHERE s.upload_date < :date
)
- """)
+ """
+ )
abstract fun unlinkStreamsOlderThan(date: Date)
- @Query("""
+ @Query(
+ """
DELETE FROM feed
WHERE feed.subscription_id = :subscriptionId
@@ -76,7 +83,8 @@ abstract class FeedDAO {
WHERE s.stream_type = "LIVE_STREAM" OR s.stream_type = "AUDIO_LIVE_STREAM"
)
- """)
+ """
+ )
abstract fun unlinkOldLivestreams(subscriptionId: Long)
@Insert(onConflict = OnConflictStrategy.IGNORE)
@@ -100,12 +108,14 @@ abstract class FeedDAO {
}
}
- @Query("""
+ @Query(
+ """
SELECT MIN(lu.last_updated) FROM feed_last_updated lu
INNER JOIN feed_group_subscription_join fgs
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
- """)
+ """
+ )
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable>
@Query("SELECT MIN(last_updated) FROM feed_last_updated")
@@ -114,7 +124,8 @@ abstract class FeedDAO {
@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
abstract fun notLoadedCount(): Flowable
- @Query("""
+ @Query(
+ """
SELECT COUNT(*) FROM subscriptions s
INNER JOIN feed_group_subscription_join fgs
@@ -124,20 +135,24 @@ abstract class FeedDAO {
ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL
- """)
+ """
+ )
abstract fun notLoadedCountForGroup(groupId: Long): Flowable
- @Query("""
+ @Query(
+ """
SELECT s.* FROM subscriptions s
LEFT JOIN feed_last_updated lu
ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
- """)
+ """
+ )
abstract fun getAllOutdated(outdatedThreshold: Date): Flowable>
- @Query("""
+ @Query(
+ """
SELECT s.* FROM subscriptions s
INNER JOIN feed_group_subscription_join fgs
@@ -147,6 +162,7 @@ abstract class FeedDAO {
ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
- """)
+ """
+ )
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: Date): Flowable>
}
diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedEntity.kt b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedEntity.kt
index 8a1eb65d4..beeedc62b 100644
--- a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedEntity.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedEntity.kt
@@ -10,21 +10,24 @@ import org.schabi.newpipe.database.feed.model.FeedEntity.Companion.SUBSCRIPTION_
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
-@Entity(tableName = FEED_TABLE,
- primaryKeys = [STREAM_ID, SUBSCRIPTION_ID],
- indices = [Index(SUBSCRIPTION_ID)],
- foreignKeys = [
- ForeignKey(
- entity = StreamEntity::class,
- parentColumns = [StreamEntity.STREAM_ID],
- childColumns = [STREAM_ID],
- onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true),
- ForeignKey(
- entity = SubscriptionEntity::class,
- parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
- childColumns = [SUBSCRIPTION_ID],
- onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true)
- ]
+@Entity(
+ tableName = FEED_TABLE,
+ primaryKeys = [STREAM_ID, SUBSCRIPTION_ID],
+ indices = [Index(SUBSCRIPTION_ID)],
+ foreignKeys = [
+ ForeignKey(
+ entity = StreamEntity::class,
+ parentColumns = [StreamEntity.STREAM_ID],
+ childColumns = [STREAM_ID],
+ onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
+ ),
+ ForeignKey(
+ entity = SubscriptionEntity::class,
+ parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
+ childColumns = [SUBSCRIPTION_ID],
+ onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
+ )
+ ]
)
data class FeedEntity(
@ColumnInfo(name = STREAM_ID)
diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupEntity.kt b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupEntity.kt
index e772168fd..1dd26946a 100644
--- a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupEntity.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupEntity.kt
@@ -9,8 +9,8 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.SORT_ORD
import org.schabi.newpipe.local.subscription.FeedGroupIcon
@Entity(
- tableName = FEED_GROUP_TABLE,
- indices = [Index(SORT_ORDER)]
+ tableName = FEED_GROUP_TABLE,
+ indices = [Index(SORT_ORDER)]
)
data class FeedGroupEntity(
@PrimaryKey(autoGenerate = true)
diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupSubscriptionEntity.kt b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupSubscriptionEntity.kt
index eac6bddee..40f7d203b 100644
--- a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupSubscriptionEntity.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupSubscriptionEntity.kt
@@ -11,22 +11,24 @@ import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity.Compan
import org.schabi.newpipe.database.subscription.SubscriptionEntity
@Entity(
- tableName = FEED_GROUP_SUBSCRIPTION_TABLE,
- primaryKeys = [GROUP_ID, SUBSCRIPTION_ID],
- indices = [Index(SUBSCRIPTION_ID)],
- foreignKeys = [
- ForeignKey(
- entity = FeedGroupEntity::class,
- parentColumns = [FeedGroupEntity.ID],
- childColumns = [GROUP_ID],
- onDelete = CASCADE, onUpdate = CASCADE, deferred = true),
+ tableName = FEED_GROUP_SUBSCRIPTION_TABLE,
+ primaryKeys = [GROUP_ID, SUBSCRIPTION_ID],
+ indices = [Index(SUBSCRIPTION_ID)],
+ foreignKeys = [
+ ForeignKey(
+ entity = FeedGroupEntity::class,
+ parentColumns = [FeedGroupEntity.ID],
+ childColumns = [GROUP_ID],
+ onDelete = CASCADE, onUpdate = CASCADE, deferred = true
+ ),
- ForeignKey(
- entity = SubscriptionEntity::class,
- parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
- childColumns = [SUBSCRIPTION_ID],
- onDelete = CASCADE, onUpdate = CASCADE, deferred = true)
- ]
+ ForeignKey(
+ entity = SubscriptionEntity::class,
+ parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
+ childColumns = [SUBSCRIPTION_ID],
+ onDelete = CASCADE, onUpdate = CASCADE, deferred = true
+ )
+ ]
)
data class FeedGroupSubscriptionEntity(
@ColumnInfo(name = GROUP_ID)
diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedLastUpdatedEntity.kt b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedLastUpdatedEntity.kt
index 78b2550a5..78adf5f96 100644
--- a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedLastUpdatedEntity.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedLastUpdatedEntity.kt
@@ -4,20 +4,21 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
-import java.util.Date
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
import org.schabi.newpipe.database.subscription.SubscriptionEntity
+import java.util.Date
@Entity(
- tableName = FEED_LAST_UPDATED_TABLE,
- foreignKeys = [
- ForeignKey(
- entity = SubscriptionEntity::class,
- parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
- childColumns = [SUBSCRIPTION_ID],
- onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true)
- ]
+ tableName = FEED_LAST_UPDATED_TABLE,
+ foreignKeys = [
+ ForeignKey(
+ entity = SubscriptionEntity::class,
+ parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
+ childColumns = [SUBSCRIPTION_ID],
+ onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
+ )
+ ]
)
data class FeedLastUpdatedEntity(
@PrimaryKey
diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java
index c716a2d91..8a9cab182 100644
--- a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java
+++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java
@@ -20,6 +20,9 @@
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
+import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS;
+import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_TIME;
+import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
@Dao
public abstract class StreamHistoryDAO implements HistoryDAO {
@@ -73,6 +76,12 @@ public Flowable> listByService(final int serviceId) {
+ " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT
+ " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")"
- + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID)
+ + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+
+ + " LEFT JOIN "
+ + "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ + STREAM_PROGRESS_TIME
+ + " FROM " + STREAM_STATE_TABLE + " )"
+ + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS)
public abstract Flowable> getStatistics();
}
diff --git a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt
index c653e6c6f..e52046552 100644
--- a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt
@@ -2,8 +2,8 @@ package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo
import androidx.room.Embedded
-import java.util.Date
import org.schabi.newpipe.database.stream.model.StreamEntity
+import java.util.Date
data class StreamHistoryEntry(
@Embedded
@@ -25,6 +25,6 @@ data class StreamHistoryEntry(
fun hasEqualValues(other: StreamHistoryEntry): Boolean {
return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId &&
- accessDate.compareTo(other.accessDate) == 0
+ accessDate.compareTo(other.accessDate) == 0
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt
index c349a3761..aff6205f2 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt
@@ -5,12 +5,17 @@ import androidx.room.Embedded
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
+import org.schabi.newpipe.database.stream.model.StreamStateEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
+import kotlin.jvm.Throws
-class PlaylistStreamEntry(
+data class PlaylistStreamEntry(
@Embedded
val streamEntity: StreamEntity,
+ @ColumnInfo(name = StreamStateEntity.STREAM_PROGRESS_TIME, defaultValue = "0")
+ val progressTime: Long,
+
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
val streamId: Long,
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
index 2c0f7e506..a41f36d72 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
@@ -24,6 +24,9 @@
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
+import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS;
+import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_TIME;
+import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
@Dao
public abstract class PlaylistStreamDAO implements BasicDAO {
@@ -58,6 +61,13 @@ public Flowable> listByService(final int serviceId) {
// then merge with the stream metadata
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+
+ + " LEFT JOIN "
+ + "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ + STREAM_PROGRESS_TIME
+ + " FROM " + STREAM_STATE_TABLE + " )"
+ + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS
+
+ " ORDER BY " + JOIN_INDEX + " ASC")
public abstract Flowable> getOrderedStreamsOf(long playlistId);
diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt b/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt
index dde1f0392..b4f9a3f3a 100644
--- a/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt
@@ -2,16 +2,20 @@ package org.schabi.newpipe.database.stream
import androidx.room.ColumnInfo
import androidx.room.Embedded
-import java.util.Date
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
+import org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_TIME
import org.schabi.newpipe.extractor.stream.StreamInfoItem
+import java.util.Date
class StreamStatisticsEntry(
@Embedded
val streamEntity: StreamEntity,
+ @ColumnInfo(name = STREAM_PROGRESS_TIME, defaultValue = "0")
+ val progressTime: Long,
+
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
val streamId: Long,
diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt
index 921c08b46..d254b34ff 100644
--- a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt
@@ -7,13 +7,13 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.Flowable
-import java.util.Date
import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
+import java.util.Date
@Dao
abstract class StreamDAO : BasicDAO {
@@ -35,10 +35,12 @@ abstract class StreamDAO : BasicDAO {
@Insert(onConflict = OnConflictStrategy.IGNORE)
internal abstract fun silentInsertAllInternal(streams: List): List
- @Query("""
+ @Query(
+ """
SELECT uid, stream_type, textual_upload_date, upload_date, is_upload_date_approximation, duration
FROM streams WHERE url = :url AND service_id = :serviceId
- """)
+ """
+ )
internal abstract fun getMinimalStreamForCompare(serviceId: Int, url: String): StreamCompareFeed?
@Transaction
@@ -79,7 +81,7 @@ abstract class StreamDAO : BasicDAO {
private fun compareAndUpdateStream(newerStream: StreamEntity) {
val existentMinimalStream = getMinimalStreamForCompare(newerStream.serviceId, newerStream.url)
- ?: throw IllegalStateException("Stream cannot be null just after insertion.")
+ ?: throw IllegalStateException("Stream cannot be null just after insertion.")
newerStream.uid = existentMinimalStream.uid
val isNewerStreamLive = newerStream.streamType == AUDIO_LIVE_STREAM || newerStream.streamType == LIVE_STREAM
@@ -88,7 +90,7 @@ abstract class StreamDAO : BasicDAO {
// Use the existent upload date if the newer stream does not have a better precision
// (i.e. is an approximation). This is done to prevent unnecessary changes.
val hasBetterPrecision =
- newerStream.uploadDate != null && newerStream.isUploadDateApproximation != true
+ newerStream.uploadDate != null && newerStream.isUploadDateApproximation != true
if (existentMinimalStream.uploadDate != null && !hasBetterPrecision) {
newerStream.uploadDate = existentMinimalStream.uploadDate
newerStream.textualUploadDate = existentMinimalStream.textualUploadDate
@@ -101,7 +103,8 @@ abstract class StreamDAO : BasicDAO {
}
}
- @Query("""
+ @Query(
+ """
DELETE FROM streams WHERE
NOT EXISTS (SELECT 1 FROM stream_history sh
@@ -112,7 +115,8 @@ abstract class StreamDAO : BasicDAO {
AND NOT EXISTS (SELECT 1 FROM feed f
WHERE f.stream_id = streams.uid)
- """)
+ """
+ )
abstract fun deleteOrphans(): Int
/**
diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt
index d13f5cc2d..7d9b76e02 100644
--- a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt
@@ -5,9 +5,6 @@ import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
-import java.io.Serializable
-import java.util.Calendar
-import java.util.Date
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL
@@ -16,11 +13,15 @@ import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.player.playqueue.PlayQueueItem
+import java.io.Serializable
+import java.util.Calendar
+import java.util.Date
-@Entity(tableName = STREAM_TABLE,
- indices = [
- Index(value = [STREAM_SERVICE_ID, STREAM_URL], unique = true)
- ]
+@Entity(
+ tableName = STREAM_TABLE,
+ indices = [
+ Index(value = [STREAM_SERVICE_ID, STREAM_URL], unique = true)
+ ]
)
data class StreamEntity(
@PrimaryKey(autoGenerate = true)
@@ -63,27 +64,27 @@ data class StreamEntity(
@Ignore
constructor(item: StreamInfoItem) : this(
- serviceId = item.serviceId, url = item.url, title = item.name,
- streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
- thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
- textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.date()?.time,
- isUploadDateApproximation = item.uploadDate?.isApproximation
+ serviceId = item.serviceId, url = item.url, title = item.name,
+ streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
+ thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
+ textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.date()?.time,
+ isUploadDateApproximation = item.uploadDate?.isApproximation
)
@Ignore
constructor(info: StreamInfo) : this(
- serviceId = info.serviceId, url = info.url, title = info.name,
- streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
- thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
- textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.date()?.time,
- isUploadDateApproximation = info.uploadDate?.isApproximation
+ serviceId = info.serviceId, url = info.url, title = info.name,
+ streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
+ thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
+ textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.date()?.time,
+ isUploadDateApproximation = info.uploadDate?.isApproximation
)
@Ignore
constructor(item: PlayQueueItem) : this(
- serviceId = item.serviceId, url = item.url, title = item.title,
- streamType = item.streamType, duration = item.duration, uploader = item.uploader,
- thumbnailUrl = item.thumbnailUrl
+ serviceId = item.serviceId, url = item.url, title = item.title,
+ streamType = item.streamType, duration = item.duration, uploader = item.uploader,
+ thumbnailUrl = item.thumbnailUrl
)
fun toStreamInfoItem(): StreamInfoItem {
@@ -95,8 +96,11 @@ data class StreamEntity(
if (viewCount != null) item.viewCount = viewCount as Long
item.textualUploadDate = textualUploadDate
item.uploadDate = uploadDate?.let {
- DateWrapper(Calendar.getInstance().apply { time = it }, isUploadDateApproximation
- ?: false)
+ DateWrapper(
+ Calendar.getInstance().apply { time = it },
+ isUploadDateApproximation
+ ?: false
+ )
}
return item
diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java
index d275d9a71..1ce834a82 100644
--- a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java
@@ -22,6 +22,9 @@
public class StreamStateEntity {
public static final String STREAM_STATE_TABLE = "stream_state";
public static final String JOIN_STREAM_ID = "stream_id";
+ // This additional field is required for the SQL query because 'stream_id' is used
+ // for some other joins already
+ public static final String JOIN_STREAM_ID_ALIAS = "stream_id_alias";
public static final String STREAM_PROGRESS_TIME = "progress_time";
/**
diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionDAO.kt b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionDAO.kt
index 60dd343b9..f0805bb49 100644
--- a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionDAO.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionDAO.kt
@@ -20,16 +20,19 @@ abstract class SubscriptionDAO : BasicDAO {
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
abstract override fun getAll(): Flowable>
- @Query("""
+ @Query(
+ """
SELECT * FROM subscriptions
WHERE name LIKE '%' || :filter || '%'
ORDER BY name COLLATE NOCASE ASC
- """)
+ """
+ )
abstract fun getSubscriptionsFiltered(filter: String): Flowable>
- @Query("""
+ @Query(
+ """
SELECT * FROM subscriptions s
LEFT JOIN feed_group_subscription_join fgs
@@ -38,12 +41,14 @@ abstract class SubscriptionDAO : BasicDAO {
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
ORDER BY name COLLATE NOCASE ASC
- """)
+ """
+ )
abstract fun getSubscriptionsOnlyUngrouped(
currentGroupId: Long
): Flowable>
- @Query("""
+ @Query(
+ """
SELECT * FROM subscriptions s
LEFT JOIN feed_group_subscription_join fgs
@@ -53,7 +58,8 @@ abstract class SubscriptionDAO : BasicDAO {
AND s.name LIKE '%' || :filter || '%'
ORDER BY name COLLATE NOCASE ASC
- """)
+ """
+ )
abstract fun getSubscriptionsOnlyUngroupedFiltered(
currentGroupId: Long,
filter: String
diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
index e46ded40d..f45da5d75 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
@@ -14,6 +14,7 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.AndroidTvUtils;
+import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
@@ -87,6 +88,9 @@ public boolean onOptionsItemSelected(final MenuItem item) {
case android.R.id.home:
onBackPressed();
return true;
+ case R.id.action_settings:
+ NavigationHelper.openSettings(this);
+ return true;
default:
return super.onOptionsItemSelected(item);
}
diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
index 9b083f1b3..2e93f000a 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -10,7 +10,6 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
-import android.preference.PreferenceManager;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -34,6 +33,7 @@
import androidx.appcompat.widget.Toolbar;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.DialogFragment;
+import androidx.preference.PreferenceManager;
import com.nononsenseapps.filepicker.Utils;
@@ -295,9 +295,9 @@ public void onViewCreated(@NonNull final View view, @Nullable final Bundle saved
initToolbar(view.findViewById(R.id.toolbar));
setupDownloadOptions();
- prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
- int threads = prefs.getInt(getString(R.string.default_download_threads), 3);
+ int threads = prefs.getInt(getString(R.string.default_download_threads), 5);
threadsCountTextView.setText(String.valueOf(threads));
threadsSeekBar.setProgress(threads - 1);
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@@ -516,7 +516,23 @@ protected void setupDownloadOptions() {
videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
subtitleButton.setVisibility(isSubtitleStreamsAvailable ? View.VISIBLE : View.GONE);
- if (isVideoStreamsAvailable) {
+ prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
+ final String defaultMedia = prefs.getString(getString(R.string.last_used_download_type),
+ getString(R.string.last_download_type_video_key));
+
+ if (isVideoStreamsAvailable
+ && (defaultMedia.equals(getString(R.string.last_download_type_video_key)))) {
+ videoButton.setChecked(true);
+ setupVideoSpinner();
+ } else if (isAudioStreamsAvailable
+ && (defaultMedia.equals(getString(R.string.last_download_type_audio_key)))) {
+ audioButton.setChecked(true);
+ setupAudioSpinner();
+ } else if (isSubtitleStreamsAvailable
+ && (defaultMedia.equals(getString(R.string.last_download_type_subtitle_key)))) {
+ subtitleButton.setChecked(true);
+ setupSubtitleSpinner();
+ } else if (isVideoStreamsAvailable) {
videoButton.setChecked(true);
setupVideoSpinner();
} else if (isAudioStreamsAvailable) {
@@ -592,9 +608,10 @@ private void showErrorActivity(final Exception e) {
}
private void prepareSelectedDownload() {
- StoredDirectoryHelper mainStorage;
- MediaFormat format;
- String mime;
+ final StoredDirectoryHelper mainStorage;
+ final MediaFormat format;
+ final String mime;
+ final String selectedMediaType;
// first, build the filename and get the output folder (if possible)
// later, run a very very very large file checking logic
@@ -603,6 +620,7 @@ private void prepareSelectedDownload() {
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
+ selectedMediaType = getString(R.string.last_download_type_audio_key);
mainStorage = mainStorageAudio;
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
switch (format) {
@@ -617,12 +635,14 @@ private void prepareSelectedDownload() {
}
break;
case R.id.video_button:
+ selectedMediaType = getString(R.string.last_download_type_video_key);
mainStorage = mainStorageVideo;
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
mime = format.mimeType;
filename += format.suffix;
break;
case R.id.subtitle_button:
+ selectedMediaType = getString(R.string.last_download_type_subtitle_key);
mainStorage = mainStorageVideo; // subtitle & video files go together
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
mime = format.mimeType;
@@ -664,6 +684,11 @@ private void prepareSelectedDownload() {
// check for existing file with the same name
checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime);
+
+ // remember the last media type downloaded by the user
+ prefs.edit()
+ .putString(getString(R.string.last_used_download_type), selectedMediaType)
+ .apply();
}
private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
@@ -749,7 +774,7 @@ private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
.setTitle(R.string.download_dialog_title)
.setMessage(msgBody)
- .setNegativeButton(android.R.string.cancel, null);
+ .setNegativeButton(R.string.cancel, null);
final StoredFileHelper finalStorage = storage;
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
index aec404825..74216639c 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java
@@ -19,9 +19,15 @@
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
+import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
+import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
+import org.schabi.newpipe.extractor.exceptions.PaidContentException;
+import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
+import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
+import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
@@ -211,12 +217,30 @@ protected boolean onError(final Throwable exception) {
if (exception instanceof ReCaptchaException) {
onReCaptchaException((ReCaptchaException) exception);
return true;
- } else if (exception instanceof ContentNotAvailableException) {
- showError(getString(R.string.content_not_available), false);
- return true;
} else if (ExceptionUtils.isNetworkRelated(exception)) {
showError(getString(R.string.network_error), true);
return true;
+ } else if (exception instanceof AgeRestrictedContentException) {
+ showError(getString(R.string.restricted_video_no_stream), false);
+ return true;
+ } else if (exception instanceof GeographicRestrictionException) {
+ showError(getString(R.string.georestricted_content), false);
+ return true;
+ } else if (exception instanceof PaidContentException) {
+ showError(getString(R.string.paid_content), false);
+ return true;
+ } else if (exception instanceof PrivateContentException) {
+ showError(getString(R.string.private_content), false);
+ return true;
+ } else if (exception instanceof SoundCloudGoPlusContentException) {
+ showError(getString(R.string.soundcloud_go_plus_content), false);
+ return true;
+ } else if (exception instanceof YoutubeMusicPremiumContentException) {
+ showError(getString(R.string.youtube_music_premium_content), false);
+ return true;
+ } else if (exception instanceof ContentNotAvailableException) {
+ showError(getString(R.string.content_not_available), false);
+ return true;
} else if (exception instanceof ContentNotSupportedException) {
showError(getString(R.string.content_not_supported), false);
return true;
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
index 558440f9e..583eecab8 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
@@ -3,7 +3,6 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -19,6 +18,7 @@
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
+import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
@@ -75,7 +75,7 @@ public void onCreate(final Bundle savedInstanceState) {
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
previousYoutubeRestrictedModeEnabled =
- PreferenceManager.getDefaultSharedPreferences(getContext())
+ PreferenceManager.getDefaultSharedPreferences(requireContext())
.getBoolean(youtubeRestrictedModeEnabledKey, false);
}
@@ -106,7 +106,7 @@ public void onResume() {
super.onResume();
boolean youtubeRestrictedModeEnabled =
- PreferenceManager.getDefaultSharedPreferences(getContext())
+ PreferenceManager.getDefaultSharedPreferences(requireContext())
.getBoolean(youtubeRestrictedModeEnabledKey, false);
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
@@ -149,10 +149,8 @@ public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_search:
try {
- NavigationHelper.openSearchFragment(
- getFragmentManager(),
- ServiceHelper.getSelectedServiceId(activity),
- "");
+ NavigationHelper.openSearchFragment(getFM(),
+ ServiceHelper.getSelectedServiceId(activity), "");
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdapter.java
similarity index 95%
rename from app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java
rename to app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdapter.java
index 38f013200..91900df4c 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdaptor.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdapter.java
@@ -10,12 +10,12 @@
import java.util.ArrayList;
import java.util.List;
-public class TabAdaptor extends FragmentPagerAdapter {
+public class TabAdapter extends FragmentPagerAdapter {
private final List mFragmentList = new ArrayList<>();
private final List mFragmentTitleList = new ArrayList<>();
private final FragmentManager fragmentManager;
- public TabAdaptor(final FragmentManager fm) {
+ public TabAdapter(final FragmentManager fm) {
super(fm);
this.fragmentManager = fm;
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index f883ff916..9edf3acb4 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -8,8 +8,8 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.text.Html;
+import androidx.preference.PreferenceManager;
+import androidx.core.text.HtmlCompat;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.util.Linkify;
@@ -53,6 +53,7 @@
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.stream.AudioStream;
@@ -191,7 +192,7 @@ public class VideoDetailFragment extends BaseStateFragment
private AppBarLayout appBarLayout;
private ViewPager viewPager;
- private TabAdaptor pageAdapter;
+ private TabAdapter pageAdapter;
private TabLayout tabLayout;
private FrameLayout relatedStreamsLayout;
@@ -252,7 +253,7 @@ public void onPause() {
if (currentWorker != null) {
currentWorker.dispose();
}
- PreferenceManager.getDefaultSharedPreferences(getContext())
+ PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit()
.putString(getString(R.string.stream_info_selected_tab_key),
pageAdapter.getItemTitle(viewPager.getCurrentItem()))
@@ -329,7 +330,7 @@ public void onActivityResult(final int requestCode, final int resultCode, final
case ReCaptchaActivity.RECAPTCHA_REQUEST:
if (resultCode == Activity.RESULT_OK) {
NavigationHelper
- .openVideoDetailFragment(getFragmentManager(), serviceId, url, name);
+ .openVideoDetailFragment(getFM(), serviceId, url, name);
} else {
Log.e(TAG, "ReCaptcha failed");
}
@@ -410,9 +411,9 @@ public void onClick(final View v) {
openPopupPlayer(false);
break;
case R.id.detail_controls_playlist_append:
- if (getFragmentManager() != null && currentInfo != null) {
+ if (getFM() != null && currentInfo != null) {
PlaylistAppendDialog.fromStreamInfo(currentInfo)
- .show(getFragmentManager(), TAG);
+ .show(getFM(), TAG);
}
break;
case R.id.detail_controls_download:
@@ -451,11 +452,8 @@ public void onClick(final View v) {
private void openChannel(final String subChannelUrl, final String subChannelName) {
try {
- NavigationHelper.openChannelFragment(
- getFragmentManager(),
- currentInfo.getServiceId(),
- subChannelUrl,
- subChannelName);
+ NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
+ subChannelUrl, subChannelName);
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
@@ -468,6 +466,9 @@ public boolean onLongClick(final View v) {
}
switch (v.getId()) {
+ case R.id.detail_controls_playlist_append:
+ NavigationHelper.openBookmarksFragment(getFM());
+ break;
case R.id.detail_controls_background:
openBackgroundPlayer(true);
break;
@@ -486,7 +487,7 @@ public boolean onLongClick(final View v) {
}
break;
case R.id.detail_title_root_layout:
- ShareUtils.copyToClipboard(getContext(), videoTitleTextView.getText().toString());
+ ShareUtils.copyToClipboard(requireContext(), videoTitleTextView.getText().toString());
break;
}
@@ -557,7 +558,7 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) {
appBarLayout = rootView.findViewById(R.id.appbarlayout);
viewPager = rootView.findViewById(R.id.viewpager);
- pageAdapter = new TabAdaptor(getChildFragmentManager());
+ pageAdapter = new TabAdapter(getChildFragmentManager());
viewPager.setAdapter(pageAdapter);
tabLayout = rootView.findViewById(R.id.tablayout);
tabLayout.setupWithViewPager(viewPager);
@@ -568,7 +569,7 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) {
thumbnailBackgroundButton.requestFocus();
- if (AndroidTvUtils.isTv(getContext())) {
+ if (AndroidTvUtils.isTv(requireContext())) {
// remove ripple effects from detail controls
final int transparent = getResources().getColor(R.color.transparent_background_color);
detailControlsAddToPlaylist.setBackgroundColor(transparent);
@@ -591,6 +592,7 @@ protected void initListeners() {
detailControlsBackground.setOnClickListener(this);
detailControlsPopup.setOnClickListener(this);
detailControlsAddToPlaylist.setOnClickListener(this);
+ detailControlsAddToPlaylist.setOnLongClickListener(this);
detailControlsDownload.setOnClickListener(this);
detailControlsDownload.setOnLongClickListener(this);
@@ -695,6 +697,18 @@ public boolean onOptionsItemSelected(final MenuItem item) {
currentInfo.getOriginalUrl());
}
return true;
+ case R.id.menu_item_share_stream:
+ if (currentInfo != null) {
+ final Stream stream;
+ if (currentInfo.getVideoStreams().isEmpty()
+ && currentInfo.getVideoOnlyStreams().isEmpty()) {
+ stream = getDefaultAudioStream();
+ } else {
+ stream = getSelectedVideoStream();
+ }
+ ShareUtils.shareUrl(requireContext(), currentInfo.getName(), stream.getUrl());
+ }
+ return true;
case R.id.menu_item_openInBrowser:
if (currentInfo != null) {
ShareUtils.openUrlInBrowser(requireContext(), currentInfo.getOriginalUrl());
@@ -928,10 +942,10 @@ private boolean shouldShowComments() {
//////////////////////////////////////////////////////////////////////////*/
private void openBackgroundPlayer(final boolean append) {
- AudioStream audioStream = currentInfo.getAudioStreams()
- .get(ListHelper.getDefaultAudioFormat(activity, currentInfo.getAudioStreams()));
+ final AudioStream audioStream = getDefaultAudioStream();
- boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
+ final boolean useExternalAudioPlayer = PreferenceManager
+ .getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 16) {
@@ -1015,6 +1029,20 @@ private VideoStream getSelectedVideoStream() {
return sortedVideoStreams != null ? sortedVideoStreams.get(selectedVideoStreamIndex) : null;
}
+ /**
+ * Get the stream to play when the current stream is an audio-only stream.
+ *
+ * This is the audio-only equivalent of getSelectedVideoStream,
+ * without the ability for the user to select a custom stream quality.
+ *
+ * @return AudioStream instance according to user settings
+ */
+ private AudioStream getDefaultAudioStream() {
+ final List audioStreams = currentInfo.getAudioStreams();
+ final int streamIndex = ListHelper.getDefaultAudioFormat(activity, audioStreams);
+ return audioStreams.get(streamIndex);
+ }
+
private void prepareDescription(final Description description) {
if (description == null || TextUtils.isEmpty(description.getContent())
|| description == Description.EMPTY_DESCRIPTION) {
@@ -1023,24 +1051,17 @@ private void prepareDescription(final Description description) {
if (description.getType() == Description.HTML) {
disposables.add(Single.just(description.getContent())
- .map((@NonNull String descriptionText) -> {
- Spanned parsedDescription;
- if (Build.VERSION.SDK_INT >= 24) {
- parsedDescription = Html.fromHtml(descriptionText, 0);
- } else {
- //noinspection deprecation
- parsedDescription = Html.fromHtml(descriptionText);
- }
- return parsedDescription;
- })
+ .map((@NonNull final String descriptionText) ->
+ HtmlCompat.fromHtml(descriptionText,
+ HtmlCompat.FROM_HTML_MODE_LEGACY))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe((@NonNull Spanned spanned) -> {
+ .subscribe((@NonNull final Spanned spanned) -> {
videoDescriptionView.setText(spanned);
videoDescriptionView.setVisibility(View.VISIBLE);
}));
} else if (description.getType() == Description.MARKDOWN) {
- final Markwon markwon = Markwon.builder(getContext())
+ final Markwon markwon = Markwon.builder(requireContext())
.usePlugin(LinkifyPlugin.create())
.build();
markwon.setMarkdown(videoDescriptionView, description.getContent());
@@ -1066,6 +1087,11 @@ private void setHeightThumbnail() {
private void showContent() {
contentRootLayoutHiding.setVisibility(View.VISIBLE);
+ final boolean showDescriptionOnLoad = PreferenceManager.getDefaultSharedPreferences(activity)
+ .getBoolean(getString(R.string.always_expand_description_key), false);
+ if (showDescriptionOnLoad) {
+ toggleTitleAndDescription();
+ }
}
protected void setInitialData(final int sid, final String u, final String title) {
@@ -1261,33 +1287,35 @@ public void handleResult(@NonNull final StreamInfo info) {
setTitleToUrl(info.getServiceId(), info.getOriginalUrl(), info.getName());
if (!info.getErrors().isEmpty()) {
- showSnackBarError(info.getErrors(),
- UserAction.REQUESTED_STREAM,
- CompatibilityUtil.getNameOfService(info.getServiceId()),
- info.getUrl(),
- 0);
- }
-
- switch (info.getStreamType()) {
- case LIVE_STREAM:
- case AUDIO_LIVE_STREAM:
- detailControlsDownload.setVisibility(View.GONE);
- spinnerToolbar.setVisibility(View.GONE);
- break;
- default:
- if (info.getAudioStreams().isEmpty()) {
- detailControlsBackground.setVisibility(View.GONE);
- }
- if (!info.getVideoStreams().isEmpty() || !info.getVideoOnlyStreams().isEmpty()) {
- break;
+ // Bandcamp fan pages are not yet supported and thus a ContentNotAvailableException is
+ // thrown. This is not an error and thus should not be shown to the user.
+ for (final Throwable throwable : info.getErrors()) {
+ if (throwable instanceof ContentNotSupportedException
+ && "Fan pages are not supported".equals(throwable.getMessage())) {
+ info.getErrors().remove(throwable);
}
+ }
- detailControlsPopup.setVisibility(View.GONE);
- spinnerToolbar.setVisibility(View.GONE);
- thumbnailPlayButton.setImageResource(R.drawable.ic_headset_shadow);
- break;
+ if (!info.getErrors().isEmpty()) {
+ showSnackBarError(info.getErrors(),
+ UserAction.REQUESTED_STREAM,
+ CompatibilityUtil.getNameOfService(info.getServiceId()),
+ info.getUrl(),
+ 0);
+ }
}
+ detailControlsDownload.setVisibility(info.getStreamType() == StreamType.LIVE_STREAM
+ || info.getStreamType() == StreamType.AUDIO_LIVE_STREAM ? View.GONE : View.VISIBLE);
+ detailControlsBackground.setVisibility(info.getAudioStreams().isEmpty()
+ ? View.GONE : View.VISIBLE);
+
+ final boolean noVideoStreams =
+ info.getVideoStreams().isEmpty() && info.getVideoOnlyStreams().isEmpty();
+ detailControlsPopup.setVisibility(noVideoStreams ? View.GONE : View.VISIBLE);
+ thumbnailPlayButton.setImageResource(
+ noVideoStreams ? R.drawable.ic_headset_shadow : R.drawable.ic_play_arrow_shadow);
+
if (autoPlayEnabled) {
openVideoPlayer();
// Only auto play in the first open
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
index 9ce62a0df..14224310e 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
@@ -6,7 +6,6 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
@@ -15,6 +14,7 @@
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
index 86b093e45..e934b11a5 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
@@ -208,6 +208,8 @@ public void handleResult(@NonNull final I result) {
if (result.getRelatedItems().size() > 0) {
infoListAdapter.addInfoItemList(result.getRelatedItems());
showListFooter(hasMoreItems());
+ } else if (hasMoreItems()) {
+ loadMoreItems();
} else {
infoListAdapter.clearStreamItemList();
showEmptyState();
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
index 8db02c764..ce589a419 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
@@ -75,6 +75,8 @@ public class ChannelFragment extends BaseListInfoFragment
private final CompositeDisposable disposables = new CompositeDisposable();
private Disposable subscribeButtonMonitor;
+ private boolean channelContentNotSupported;
+
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
@@ -137,6 +139,9 @@ public void onViewCreated(final View rootView, final Bundle savedInstanceState)
contentNotSupportedTextView = rootView.findViewById(R.id.error_content_not_supported);
kaomojiTextView = rootView.findViewById(R.id.channel_kaomoji);
noVideosTextView = rootView.findViewById(R.id.channel_no_videos);
+ if (channelContentNotSupported) {
+ showContentNotSupported();
+ }
}
@Override
@@ -427,8 +432,8 @@ public void onClick(final View v) {
case R.id.sub_channel_title_view:
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
try {
- NavigationHelper.openChannelFragment(getFragmentManager(),
- currentInfo.getServiceId(), currentInfo.getParentChannelUrl(),
+ NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
+ currentInfo.getParentChannelUrl(),
currentInfo.getParentChannelName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
@@ -476,8 +481,8 @@ public void handleResult(@NonNull final ChannelInfo result) {
if (!TextUtils.isEmpty(currentInfo.getParentChannelName())) {
headerSubChannelTitleView.setText(String.format(
- getString(R.string.channel_created_by),
- currentInfo.getParentChannelName())
+ getString(R.string.channel_created_by),
+ currentInfo.getParentChannelName())
);
headerSubChannelTitleView.setVisibility(View.VISIBLE);
headerSubChannelAvatarView.setVisibility(View.VISIBLE);
@@ -491,6 +496,7 @@ public void handleResult(@NonNull final ChannelInfo result) {
playlistCtrl.setVisibility(View.VISIBLE);
+ channelContentNotSupported = false;
List errors = new ArrayList<>(result.getErrors());
if (!errors.isEmpty()) {
@@ -499,7 +505,12 @@ public void handleResult(@NonNull final ChannelInfo result) {
for (Iterator it = errors.iterator(); it.hasNext();) {
Throwable throwable = it.next();
if (throwable instanceof ContentNotSupportedException) {
- showContentNotSupported();
+ /*
+ channelBinding might not be initialized when handleResult() is called
+ (e.g. after rotating the screen, https://github.com/TeamNewPipe/NewPipe/issues/6696)
+ showContentNotSupported() will be called later
+ */
+ channelContentNotSupported = true;
it.remove();
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
index cc4691c11..9d9074018 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
@@ -7,6 +7,7 @@
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -29,6 +30,8 @@ public class CommentsFragment extends BaseListInfoFragment {
private boolean mIsVisibleToUser = false;
+ private TextView emptyStateDesc;
+
public static CommentsFragment getInstance(final int serviceId, final String url,
final String name) {
CommentsFragment instance = new CommentsFragment();
@@ -36,6 +39,13 @@ public static CommentsFragment getInstance(final int serviceId, final String ur
return instance;
}
+ @Override
+ protected void initViews(final View rootView, final Bundle savedInstanceState) {
+ super.initViews(rootView, savedInstanceState);
+
+ emptyStateDesc = rootView.findViewById(R.id.empty_state_desc);
+ }
+
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@@ -93,7 +103,12 @@ public void showLoading() {
public void handleResult(@NonNull final CommentsInfo result) {
super.handleResult(result);
- AnimationUtils.slideUp(getView(), 120, 150, 0.06f);
+ emptyStateDesc.setText(
+ result.isCommentsDisabled()
+ ? R.string.comments_are_disabled
+ : R.string.no_comments);
+
+ AnimationUtils.slideUp(requireView(), 120, 150, 0.06f);
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index bc81db767..95820ee48 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -11,12 +11,12 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.content.res.AppCompatResources;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
@@ -26,8 +26,10 @@
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
+import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
@@ -44,12 +46,12 @@
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StreamDialogEntry;
-import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import de.hdodenhof.circleimageview.CircleImageView;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
@@ -58,6 +60,7 @@
import io.reactivex.disposables.Disposables;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
public class PlaylistFragment extends BaseListInfoFragment {
private CompositeDisposable disposables;
@@ -74,7 +77,7 @@ public class PlaylistFragment extends BaseListInfoFragment {
private TextView headerTitleView;
private View headerUploaderLayout;
private TextView headerUploaderName;
- private ImageView headerUploaderAvatar;
+ private CircleImageView headerUploaderAvatar;
private TextView headerStreamCount;
private View playlistCtrl;
@@ -287,10 +290,8 @@ public void handleResult(@NonNull final PlaylistInfo result) {
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
headerUploaderLayout.setOnClickListener(v -> {
try {
- NavigationHelper.openChannelFragment(getFragmentManager(),
- result.getServiceId(),
- result.getUploaderUrl(),
- result.getUploaderName());
+ NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
+ result.getUploaderUrl(), result.getUploaderName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
@@ -302,8 +303,22 @@ public void handleResult(@NonNull final PlaylistInfo result) {
playlistCtrl.setVisibility(View.VISIBLE);
- IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar,
- ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
+ final String avatarUrl = result.getUploaderAvatarUrl();
+ if (result.getServiceId() == ServiceList.YouTube.getServiceId()
+ && (YoutubeParsingHelper.isYoutubeMixId(result.getId())
+ || YoutubeParsingHelper.isYoutubeMusicMixId(result.getId()))) {
+ // this is an auto-generated playlist (e.g. Youtube mix), so a radio is shown
+ headerUploaderAvatar.setDisableCircularTransformation(true);
+ headerUploaderAvatar.setBorderColor(
+ getResources().getColor(R.color.transparent_background_color));
+ headerUploaderAvatar.setImageDrawable(AppCompatResources.getDrawable(requireContext(),
+ resolveResourceIdFromAttr(requireContext(), R.attr.ic_radio)));
+
+ } else {
+ IMAGE_LOADER.displayImage(avatarUrl, headerUploaderAvatar,
+ ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
+ }
+
headerStreamCount.setText(Localization
.localizeStreamCount(getContext(), result.getStreamCount()));
@@ -478,7 +493,7 @@ private void updateBookmarkButtons() {
final int titleRes = playlistEntity == null
? R.string.bookmark_playlist : R.string.unbookmark_playlist;
- playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr));
+ playlistBookmarkButton.setIcon(resolveResourceIdFromAttr(activity, iconAttr));
playlistBookmarkButton.setTitle(titleRes);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
index acc8376c2..de31287a8 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
@@ -5,11 +5,11 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.Html;
import android.text.TextUtils;
import android.text.TextWatcher;
+import android.text.style.CharacterStyle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -29,6 +29,7 @@
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.TooltipCompat;
+import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
@@ -43,6 +44,8 @@
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.search.SearchInfo;
+import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory;
+import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@@ -53,6 +56,7 @@
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.CompatibilityUtil;
import org.schabi.newpipe.util.Constants;
+import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
@@ -75,12 +79,12 @@
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
-import static android.text.Html.escapeHtml;
+import androidx.core.text.HtmlCompat;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
-public class SearchFragment extends BaseListFragment
+public class SearchFragment extends BaseListFragment>
implements BackPressable {
/*//////////////////////////////////////////////////////////////////////////
// Search
@@ -156,6 +160,7 @@ public class SearchFragment extends BaseListFragment 0
&& !isLoading.get()) {
hideSuggestionsPanel();
@@ -724,7 +733,7 @@ private void initSuggestionObserver() {
suggestionDisposable = observable
.switchMap(query -> {
final Flowable> flowable = historyRecordManager
- .getRelatedSearches(query, 3, 25);
+ .getRelatedSearches(query, 60, 100);
final Observable> local = flowable.toObservable()
.map(searchHistoryEntries -> {
List result = new ArrayList<>();
@@ -742,6 +751,13 @@ private void initSuggestionObserver() {
final Observable> network = ExtractorHelper
.suggestionsFor(serviceId, query)
+ .onErrorReturn(throwable -> {
+ if (!ExceptionUtils.isNetworkRelated(throwable)) {
+ showSnackBarError(throwable, UserAction.GET_SUGGESTIONS,
+ CompatibilityUtil.getNameOfService(serviceId), searchString, 0);
+ }
+ return new ArrayList<>();
+ })
.toObservable()
.map(strings -> {
List result = new ArrayList<>();
@@ -791,28 +807,30 @@ protected void doInitialLoadLogic() {
// no-op
}
- private void search(final String ss, final String[] cf, final String sf) {
+ private void search(final String theSearchString,
+ final String[] theContentFilter,
+ final String theSortFilter) {
if (DEBUG) {
- Log.d(TAG, "search() called with: query = [" + ss + "]");
+ Log.d(TAG, "search() called with: query = [" + theSearchString + "]");
}
- if (ss.isEmpty()) {
+ if (theSearchString.isEmpty()) {
return;
}
try {
- final StreamingService streamingService = NewPipe.getServiceByUrl(ss);
+ final StreamingService streamingService = NewPipe.getServiceByUrl(theSearchString);
if (streamingService != null) {
showLoading();
disposables.add(Observable
- .fromCallable(() ->
- NavigationHelper.getIntentByLink(activity, streamingService, ss))
+ .fromCallable(() -> NavigationHelper.getIntentByLink(activity,
+ streamingService, theSearchString))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
- getFragmentManager().popBackStackImmediate();
+ getFM().popBackStackImmediate();
activity.startActivity(intent);
}, throwable ->
- showError(getString(R.string.url_not_supported_toast), false)));
+ showError(getString(R.string.unsupported_url), false)));
return;
}
} catch (Exception ignored) {
@@ -820,29 +838,27 @@ private void search(final String ss, final String[] cf, final String sf) {
}
lastSearchedString = this.searchString;
- this.searchString = ss;
+ this.searchString = theSearchString;
infoListAdapter.clearStreamItemList();
hideSuggestionsPanel();
hideKeyboardSearch();
- historyRecordManager.onSearched(serviceId, ss)
+ disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> {
},
error -> showSnackBarError(error, UserAction.SEARCHED,
- CompatibilityUtil.getNameOfService(serviceId), ss, 0)
- );
- suggestionPublisher.onNext(ss);
+ CompatibilityUtil.getNameOfService(serviceId), theSearchString, 0)
+ ));
+ suggestionPublisher.onNext(theSearchString);
startLoading(false);
}
@Override
public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad);
- if (disposables != null) {
- disposables.clear();
- }
+ disposables.clear();
if (searchDisposable != null) {
searchDisposable.dispose();
}
@@ -881,8 +897,7 @@ protected void loadMoreItems() {
@Override
protected boolean hasMoreItems() {
- // TODO: No way to tell if search has more items in the moment
- return true;
+ return Page.isValid(nextPage);
}
@Override
@@ -895,22 +910,25 @@ protected void onItemSelected(final InfoItem selectedItem) {
// Utils
//////////////////////////////////////////////////////////////////////////*/
- private void changeContentFilter(final MenuItem item, final List cf) {
- this.filterItemCheckedId = item.getItemId();
+ private void changeContentFilter(final MenuItem item, final List theContentFilter) {
+ filterItemCheckedId = item.getItemId();
item.setChecked(true);
- this.contentFilter = new String[]{cf.get(0)};
+ contentFilter = new String[]{theContentFilter.get(0)};
if (!TextUtils.isEmpty(searchString)) {
- search(searchString, this.contentFilter, sortFilter);
+ search(searchString, contentFilter, sortFilter);
}
}
- private void setQuery(final int sid, final String ss, final String[] cf, final String sf) {
- this.serviceId = sid;
- this.searchString = searchString;
- this.contentFilter = cf;
- this.sortFilter = sf;
+ private void setQuery(final int theServiceId,
+ final String theSearchString,
+ final String[] theContentFilter,
+ final String theSortFilter) {
+ serviceId = theServiceId;
+ searchString = theSearchString;
+ contentFilter = theContentFilter;
+ sortFilter = theSortFilter;
}
/*//////////////////////////////////////////////////////////////////////////
@@ -924,7 +942,7 @@ public void handleSuggestions(@NonNull final List suggestions) {
suggestionsRecyclerView.smoothScrollToPosition(0);
suggestionsRecyclerView.post(() -> suggestionListAdapter.setItems(suggestions));
- if (errorPanelRoot.getVisibility() == View.VISIBLE) {
+ if (suggestionsPanelVisible && errorPanelRoot.getVisibility() == View.VISIBLE) {
hideLoading();
}
}
@@ -1005,10 +1023,9 @@ private void handleSearchSuggestion() {
: R.string.did_you_mean);
final String highlightedSearchSuggestion =
- "" + escapeHtml(searchSuggestion) + " ";
- correctSuggestion.setText(
- Html.fromHtml(String.format(helperText, highlightedSearchSuggestion)));
-
+ "" + Html.escapeHtml(searchSuggestion) + " ";
+ final String text = String.format(helperText, highlightedSearchSuggestion);
+ correctSuggestion.setText(HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_LEGACY));
correctSuggestion.setOnClickListener(v -> {
correctSuggestion.setVisibility(View.GONE);
@@ -1028,7 +1045,7 @@ private void handleSearchSuggestion() {
}
@Override
- public void handleNextItems(final ListExtractor.InfoItemsPage result) {
+ public void handleNextItems(final ListExtractor.InfoItemsPage> result) {
showListFooter(false);
infoListAdapter.addInfoItemList(result.getItems());
nextPage = result.getNextPage();
@@ -1067,8 +1084,7 @@ protected boolean onError(final Throwable exception) {
// Suggestion item touch helper
//////////////////////////////////////////////////////////////////////////*/
- public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView,
- @NonNull final RecyclerView.ViewHolder viewHolder) {
+ public int getSuggestionMovementFlags(@NonNull final RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
if (position == RecyclerView.NO_POSITION) {
return 0;
@@ -1079,8 +1095,7 @@ public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
}
- public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
- final int i) {
+ public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
final String query = suggestionListAdapter.getItem(position).query;
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java
index e4d221e47..bc622b379 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java
@@ -3,7 +3,6 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -13,6 +12,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
@@ -79,11 +79,11 @@ protected View getListHeader() {
autoplaySwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
final SharedPreferences pref = PreferenceManager
- .getDefaultSharedPreferences(getContext());
+ .getDefaultSharedPreferences(requireContext());
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
autoplaySwitch.setChecked(autoplay);
autoplaySwitch.setOnCheckedChangeListener((compoundButton, b) ->
- PreferenceManager.getDefaultSharedPreferences(getContext()).edit()
+ PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
.putBoolean(getString(R.string.auto_queue_key), b).apply());
return headerRootLayout;
} else {
@@ -202,7 +202,7 @@ protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String s) {
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(requireContext());
boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
if (autoplaySwitch != null) {
autoplaySwitch.setChecked(autoplay);
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java
index 842d9c455..4fc2d9f84 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java
@@ -1,6 +1,8 @@
package org.schabi.newpipe.info_list.holder;
+import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
import android.widget.TextView;
import org.schabi.newpipe.R;
@@ -31,11 +33,15 @@
public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
public final TextView itemTitleView;
+ private final ImageView itemHeartView;
+ private final ImageView itemPinnedView;
public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_comments_item, parent);
itemTitleView = itemView.findViewById(R.id.itemTitleView);
+ itemHeartView = itemView.findViewById(R.id.detail_heart_image_view);
+ itemPinnedView = itemView.findViewById(R.id.detail_pinned_view);
}
@Override
@@ -49,5 +55,9 @@ public void updateFromItem(final InfoItem infoItem,
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
itemTitleView.setText(item.getUploaderName());
+
+ itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
+
+ itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
index 863273a88..bf524b9f5 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
@@ -1,13 +1,18 @@
package org.schabi.newpipe.info_list.holder;
+import android.content.SharedPreferences;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.text.util.Linkify;
+import android.view.View;
import android.view.ViewGroup;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.text.util.LinkifyCompat;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
@@ -22,6 +27,7 @@
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
+import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -31,7 +37,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
private static final int COMMENT_DEFAULT_LINES = 2;
private static final int COMMENT_EXPANDED_LINES = 1000;
private static final Pattern PATTERN = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
+ private final String downloadThumbnailKey;
+ private final int commentHorizontalPadding;
+ private final int commentVerticalPadding;
+ private SharedPreferences preferences = null;
+ private final RelativeLayout itemRoot;
public final CircleImageView itemThumbnailView;
private final TextView itemContentView;
private final TextView itemLikesCountView;
@@ -65,11 +76,20 @@ public String transformUrl(final Matcher match, final String url) {
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
+ itemRoot = itemView.findViewById(R.id.itemRoot);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
itemDislikesCountView = itemView.findViewById(R.id.detail_thumbs_down_count_view);
itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
+
+ downloadThumbnailKey = infoItemBuilder.getContext().
+ getString(R.string.download_thumbnail_key);
+
+ commentHorizontalPadding = (int) infoItemBuilder.getContext()
+ .getResources().getDimension(R.dimen.comments_horizontal_padding);
+ commentVerticalPadding = (int) infoItemBuilder.getContext()
+ .getResources().getDimension(R.dimen.comments_vertical_padding);
}
public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
@@ -85,17 +105,36 @@ public void updateFromItem(final InfoItem infoItem,
}
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
+ preferences = PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext());
+
itemBuilder.getImageLoader()
.displayImage(item.getUploaderAvatarUrl(),
itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
+ if (preferences.getBoolean(downloadThumbnailKey, true)) {
+ itemThumbnailView.setVisibility(View.VISIBLE);
+ itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
+ commentVerticalPadding, commentVerticalPadding);
+ } else {
+ itemThumbnailView.setVisibility(View.GONE);
+ itemRoot.setPadding(commentHorizontalPadding, commentVerticalPadding,
+ commentHorizontalPadding, commentVerticalPadding);
+ }
+
itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
streamUrl = item.getUrl();
itemContentView.setLines(COMMENT_DEFAULT_LINES);
- commentText = item.getCommentText();
+ // commentText = item.getCommentText();
+ try {
+ commentText = item.getCommentText().getContent().replace(" ", "")
+ .replace(" ", " ").replace("", " ")
+ .replace("", " ").replace(" ", " ");
+ } catch (Exception e) {
+ commentText = " ";
+ }
itemContentView.setText(commentText);
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
@@ -106,7 +145,10 @@ public void updateFromItem(final InfoItem infoItem,
}
if (item.getLikeCount() >= 0) {
- itemLikesCountView.setText(String.valueOf(item.getLikeCount()));
+ itemLikesCountView.setText(
+ Localization.shortCount(
+ itemBuilder.getContext(),
+ item.getLikeCount()));
} else {
itemLikesCountView.setText("-");
}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
index 5fa0904de..c89ca6c90 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
@@ -1,10 +1,11 @@
package org.schabi.newpipe.info_list.holder;
-import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.preference.PreferenceManager;
+
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
index 962ccef54..824612fd2 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
@@ -70,7 +70,8 @@ public void updateFromItem(final InfoItem infoItem,
} else {
itemProgressView.setVisibility(View.GONE);
}
- } else if (item.getStreamType() == StreamType.LIVE_STREAM) {
+ } else if (item.getStreamType() == StreamType.LIVE_STREAM
+ || item.getStreamType() == StreamType.AUDIO_LIVE_STREAM) {
itemDurationView.setText(R.string.duration_live);
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
R.color.live_duration_background_color));
diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
index 650953bea..8e3eb9ff0 100644
--- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java
@@ -4,7 +4,6 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
@@ -12,6 +11,7 @@
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt
index d319c9fa3..4415d25e0 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt
@@ -7,8 +7,6 @@ import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
-import java.util.Calendar
-import java.util.Date
import org.schabi.newpipe.MainActivity.DEBUG
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.database.feed.model.FeedEntity
@@ -18,6 +16,8 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.local.subscription.FeedGroupIcon
+import java.util.Calendar
+import java.util.Date
class FeedDatabaseManager(context: Context) {
private val database = NewPipeDatabase.getInstance(context)
@@ -65,10 +65,10 @@ class FeedDatabaseManager(context: Context) {
}
fun outdatedSubscriptionsForGroup(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, outdatedThreshold: Date) =
- feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
+ feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
fun markAsOutdated(subscriptionId: Long) = feedTable
- .setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
+ .setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
fun upsertAll(
subscriptionId: Long,
@@ -116,38 +116,38 @@ class FeedDatabaseManager(context: Context) {
fun subscriptionIdsForGroup(groupId: Long): Flowable> {
return feedGroupTable.getSubscriptionIdsFor(groupId)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
}
fun updateSubscriptionsForGroup(groupId: Long, subscriptionIds: List): Completable {
return Completable.fromCallable { feedGroupTable.updateSubscriptionsForGroup(groupId, subscriptionIds) }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
}
fun createGroup(name: String, icon: FeedGroupIcon): Maybe {
return Maybe.fromCallable { feedGroupTable.insert(FeedGroupEntity(0, name, icon)) }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
}
fun getGroup(groupId: Long): Maybe {
return feedGroupTable.getGroup(groupId)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
}
fun updateGroup(feedGroupEntity: FeedGroupEntity): Completable {
return Completable.fromCallable { feedGroupTable.update(feedGroupEntity) }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
}
fun deleteGroup(groupId: Long): Completable {
return Completable.fromCallable { feedGroupTable.delete(groupId) }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
}
fun updateGroupsOrder(groupIdList: List): Completable {
@@ -155,8 +155,8 @@ class FeedDatabaseManager(context: Context) {
val orderMap = groupIdList.associateBy({ it }, { index++ })
return Completable.fromCallable { feedGroupTable.updateOrder(orderMap) }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
}
fun oldestSubscriptionUpdate(groupId: Long): Flowable> {
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt
index 8018e2cd8..09b1c9783 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt
@@ -32,8 +32,8 @@ import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.preference.PreferenceManager
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import icepick.State
-import java.util.Calendar
import kotlinx.android.synthetic.main.error_retry.error_button_retry
import kotlinx.android.synthetic.main.error_retry.error_message_view
import kotlinx.android.synthetic.main.fragment_feed.empty_state_view
@@ -51,9 +51,11 @@ import org.schabi.newpipe.local.feed.service.FeedLoadService
import org.schabi.newpipe.report.UserAction
import org.schabi.newpipe.util.AnimationUtils.animateView
import org.schabi.newpipe.util.Localization
+import java.util.Calendar
class FeedFragment : BaseListFragment() {
private lateinit var viewModel: FeedViewModel
+ private lateinit var swipeRefreshLayout: SwipeRefreshLayout
@State
@JvmField
var listState: Parcelable? = null
@@ -71,7 +73,7 @@ class FeedFragment : BaseListFragment() {
super.onCreate(savedInstanceState)
groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
- ?: FeedGroupEntity.GROUP_ALL_ID
+ ?: FeedGroupEntity.GROUP_ALL_ID
groupName = arguments?.getString(KEY_GROUP_NAME) ?: ""
}
@@ -81,7 +83,8 @@ class FeedFragment : BaseListFragment() {
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
super.onViewCreated(rootView, savedInstanceState)
-
+ swipeRefreshLayout = requireView().findViewById(R.id.swiperefresh)
+ swipeRefreshLayout.setOnRefreshListener { reloadContent() }
viewModel = ViewModelProviders.of(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
viewModel.stateLiveData.observe(viewLifecycleOwner, Observer { it?.let(::handleResult) })
}
@@ -138,15 +141,15 @@ class FeedFragment : BaseListFragment() {
}
AlertDialog.Builder(requireContext())
- .setMessage(R.string.feed_use_dedicated_fetch_method_help_text)
- .setNeutralButton(enableDisableButtonText) { _, _ ->
- sharedPreferences.edit()
- .putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod)
- .apply()
- }
- .setPositiveButton(resources.getString(R.string.finish), null)
- .create()
- .show()
+ .setMessage(R.string.feed_use_dedicated_fetch_method_help_text)
+ .setNeutralButton(enableDisableButtonText) { _, _ ->
+ sharedPreferences.edit()
+ .putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod)
+ .apply()
+ }
+ .setPositiveButton(resources.getString(R.string.finish), null)
+ .create()
+ .show()
return true
}
@@ -187,6 +190,7 @@ class FeedFragment : BaseListFragment() {
empty_state_view?.let { animateView(it, false, 0) }
animateView(error_panel, false, 0)
+ swipeRefreshLayout.isRefreshing = false
}
override fun showEmptyState() {
@@ -227,7 +231,7 @@ class FeedFragment : BaseListFragment() {
showLoading()
val isIndeterminate = progressState.currentProgress == -1 &&
- progressState.maxProgress == -1
+ progressState.maxProgress == -1
if (!isIndeterminate) {
loading_progress_text.text = "${progressState.currentProgress}/${progressState.maxProgress}"
@@ -238,7 +242,7 @@ class FeedFragment : BaseListFragment() {
}
loading_progress_bar.isIndeterminate = isIndeterminate ||
- (progressState.maxProgress > 0 && progressState.currentProgress == 0)
+ (progressState.maxProgress > 0 && progressState.currentProgress == 0)
loading_progress_bar.progress = progressState.currentProgress
loading_progress_bar.max = progressState.maxProgress
@@ -261,8 +265,10 @@ class FeedFragment : BaseListFragment() {
}
if (loadedState.itemsErrors.isNotEmpty()) {
- showSnackBarError(loadedState.itemsErrors, UserAction.REQUESTED_FEED,
- "none", "Loading feed", R.string.general_error)
+ showSnackBarError(
+ loadedState.itemsErrors, UserAction.REQUESTED_FEED,
+ "none", "Loading feed", R.string.general_error
+ )
}
if (loadedState.items.isEmpty()) {
@@ -305,9 +311,11 @@ class FeedFragment : BaseListFragment() {
override fun hasMoreItems() = false
private fun triggerUpdate() {
- getActivity()?.startService(Intent(requireContext(), FeedLoadService::class.java).apply {
- putExtra(FeedLoadService.EXTRA_GROUP_ID, groupId)
- })
+ getActivity()?.startService(
+ Intent(requireContext(), FeedLoadService::class.java).apply {
+ putExtra(FeedLoadService.EXTRA_GROUP_ID, groupId)
+ }
+ )
listState = null
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedState.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedState.kt
index de3dd3113..00ca76b8e 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedState.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedState.kt
@@ -1,8 +1,8 @@
package org.schabi.newpipe.local.feed
import androidx.annotation.StringRes
-import java.util.Calendar
import org.schabi.newpipe.extractor.stream.StreamInfoItem
+import java.util.Calendar
sealed class FeedState {
data class ProgressState(
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt
index da2b5ffa4..fca09ee09 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt
@@ -9,9 +9,6 @@ import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.Function4
import io.reactivex.schedulers.Schedulers
-import java.util.Calendar
-import java.util.Date
-import java.util.concurrent.TimeUnit
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.service.FeedEventManager
@@ -20,6 +17,9 @@ import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
+import java.util.Calendar
+import java.util.Date
+import java.util.concurrent.TimeUnit
class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
@@ -35,36 +35,38 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
val stateLiveData: LiveData = mutableStateLiveData
private var combineDisposable = Flowable
- .combineLatest(
- FeedEventManager.events(),
- feedDatabaseManager.asStreamItems(groupId),
- feedDatabaseManager.notLoadedCount(groupId),
- feedDatabaseManager.oldestSubscriptionUpdate(groupId),
+ .combineLatest(
+ FeedEventManager.events(),
+ feedDatabaseManager.asStreamItems(groupId),
+ feedDatabaseManager.notLoadedCount(groupId),
+ feedDatabaseManager.oldestSubscriptionUpdate(groupId),
- Function4 { t1: FeedEventManager.Event, t2: List, t3: Long, t4: List ->
- return@Function4 CombineResultHolder(t1, t2, t3, t4.firstOrNull())
- }
- )
- .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe {
- val (event, listFromDB, notLoadedCount, oldestUpdate) = it
+ Function4 { t1: FeedEventManager.Event, t2: List, t3: Long, t4: List ->
+ return@Function4 CombineResultHolder(t1, t2, t3, t4.firstOrNull())
+ }
+ )
+ .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ val (event, listFromDB, notLoadedCount, oldestUpdate) = it
- val oldestUpdateCalendar =
- oldestUpdate?.let { Calendar.getInstance().apply { time = it } }
+ val oldestUpdateCalendar =
+ oldestUpdate?.let { Calendar.getInstance().apply { time = it } }
- mutableStateLiveData.postValue(when (event) {
+ mutableStateLiveData.postValue(
+ when (event) {
is IdleEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount)
is ProgressEvent -> FeedState.ProgressState(event.currentProgress, event.maxProgress, event.progressMessage)
is SuccessResultEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount, event.itemsErrors)
is ErrorResultEvent -> FeedState.ErrorState(event.error)
- })
-
- if (event is ErrorResultEvent || event is SuccessResultEvent) {
- FeedEventManager.reset()
}
+ )
+
+ if (event is ErrorResultEvent || event is SuccessResultEvent) {
+ FeedEventManager.reset()
}
+ }
override fun onCleared() {
super.onCleared()
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt
index b72098345..1759d56a5 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt
@@ -3,8 +3,8 @@ package org.schabi.newpipe.local.feed.service
import androidx.annotation.StringRes
import io.reactivex.Flowable
import io.reactivex.processors.BehaviorProcessor
-import java.util.concurrent.atomic.AtomicBoolean
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
+import java.util.concurrent.atomic.AtomicBoolean
object FeedEventManager {
private var processor: BehaviorProcessor = BehaviorProcessor.create()
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
index 65860096c..cc3d5caa2 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
@@ -27,10 +27,10 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.IBinder
-import android.preference.PreferenceManager
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
+import androidx.preference.PreferenceManager
import io.reactivex.Flowable
import io.reactivex.Notification
import io.reactivex.Single
@@ -40,13 +40,9 @@ import io.reactivex.functions.Consumer
import io.reactivex.functions.Function
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
-import java.io.IOException
-import java.util.Calendar
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.atomic.AtomicBoolean
-import java.util.concurrent.atomic.AtomicInteger
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
+import org.schabi.newpipe.App
import org.schabi.newpipe.MainActivity.DEBUG
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
@@ -62,12 +58,17 @@ import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.util.ExceptionUtils
import org.schabi.newpipe.util.ExtractorHelper
+import java.io.IOException
+import java.util.Calendar
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
class FeedLoadService : Service() {
companion object {
private val TAG = FeedLoadService::class.java.simpleName
private const val NOTIFICATION_ID = 7293450
- private const val ACTION_CANCEL = "org.schabi.newpipe.local.feed.service.FeedLoadService.CANCEL"
+ private const val ACTION_CANCEL = App.PACKAGE_NAME + ".local.feed.service.FeedLoadService.CANCEL"
/**
* How often the notification will be updated.
@@ -108,8 +109,11 @@ class FeedLoadService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (DEBUG) {
- Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "]," +
- " flags = [" + flags + "], startId = [" + startId + "]")
+ Log.d(
+ TAG,
+ "onStartCommand() called with: intent = [" + intent + "]," +
+ " flags = [" + flags + "], startId = [" + startId + "]"
+ )
}
if (intent == null || loadingSubscription != null) {
@@ -122,10 +126,10 @@ class FeedLoadService : Service() {
val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
val useFeedExtractor = defaultSharedPreferences
- .getBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), false)
+ .getBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), false)
val thresholdOutdatedSecondsString = defaultSharedPreferences
- .getString(getString(R.string.feed_update_threshold_key), getString(R.string.feed_update_threshold_default_value))
+ .getString(getString(R.string.feed_update_threshold_key), getString(R.string.feed_update_threshold_default_value))
val thresholdOutdatedSeconds = thresholdOutdatedSecondsString!!.toInt()
startLoading(groupId, useFeedExtractor, thresholdOutdatedSeconds)
@@ -182,63 +186,63 @@ class FeedLoadService : Service() {
}
subscriptions
- .limit(1)
+ .limit(1)
- .doOnNext {
- currentProgress.set(0)
- maxProgress.set(it.size)
- }
- .filter { it.isNotEmpty() }
+ .doOnNext {
+ currentProgress.set(0)
+ maxProgress.set(it.size)
+ }
+ .filter { it.isNotEmpty() }
- .observeOn(AndroidSchedulers.mainThread())
- .doOnNext {
- startForeground(NOTIFICATION_ID, notificationBuilder.build())
- updateNotificationProgress(null)
- broadcastProgress()
- }
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnNext {
+ startForeground(NOTIFICATION_ID, notificationBuilder.build())
+ updateNotificationProgress(null)
+ broadcastProgress()
+ }
- .observeOn(Schedulers.io())
- .flatMap { Flowable.fromIterable(it) }
- .takeWhile { !cancelSignal.get() }
-
- .parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2)
- .runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2)
- .filter { !cancelSignal.get() }
-
- .map { subscriptionEntity ->
- try {
- val listInfo = if (useFeedExtractor) {
- ExtractorHelper
- .getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url)
- .blockingGet()
- } else {
- ExtractorHelper
- .getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true)
- .blockingGet()
- } as ListInfo
-
- return@map Notification.createOnNext(Pair(subscriptionEntity.uid, listInfo))
- } catch (e: Throwable) {
- val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}"
- val wrapper = RequestException(subscriptionEntity.uid, request, e)
- return@map Notification.createOnError>>(wrapper)
- }
+ .observeOn(Schedulers.io())
+ .flatMap { Flowable.fromIterable(it) }
+ .takeWhile { !cancelSignal.get() }
+
+ .parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2)
+ .runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2)
+ .filter { !cancelSignal.get() }
+
+ .map { subscriptionEntity ->
+ try {
+ val listInfo = if (useFeedExtractor) {
+ ExtractorHelper
+ .getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url)
+ .blockingGet()
+ } else {
+ ExtractorHelper
+ .getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true)
+ .blockingGet()
+ } as ListInfo
+
+ return@map Notification.createOnNext(Pair(subscriptionEntity.uid, listInfo))
+ } catch (e: Throwable) {
+ val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}"
+ val wrapper = RequestException(subscriptionEntity.uid, request, e)
+ return@map Notification.createOnError>>(wrapper)
}
- .sequential()
+ }
+ .sequential()
- .observeOn(AndroidSchedulers.mainThread())
- .doOnNext(errorHandlingConsumer)
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnNext(errorHandlingConsumer)
- .observeOn(AndroidSchedulers.mainThread())
- .doOnNext(notificationsConsumer)
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnNext(notificationsConsumer)
- .observeOn(Schedulers.io())
- .buffer(BUFFER_COUNT_BEFORE_INSERT)
- .doOnNext(databaseConsumer)
+ .observeOn(Schedulers.io())
+ .buffer(BUFFER_COUNT_BEFORE_INSERT)
+ .doOnNext(databaseConsumer)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(resultSubscriber)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(resultSubscriber)
}
private fun broadcastProgress() {
@@ -275,7 +279,8 @@ class FeedLoadService : Service() {
notificationUpdater.onNext(getString(R.string.feed_processing_message))
postEvent(ProgressEvent(R.string.feed_processing_message))
- disposables.add(Single
+ disposables.add(
+ Single
.fromCallable {
feedResultsHolder.ready()
@@ -294,7 +299,8 @@ class FeedLoadService : Service() {
return@subscribe
}
stopService()
- })
+ }
+ )
}
}
@@ -365,16 +371,18 @@ class FeedLoadService : Service() {
private var maxProgress = AtomicInteger(-1)
private fun createNotification(): NotificationCompat.Builder {
- val cancelActionIntent = PendingIntent.getBroadcast(this,
- NOTIFICATION_ID, Intent(ACTION_CANCEL), 0)
+ val cancelActionIntent = PendingIntent.getBroadcast(
+ this,
+ NOTIFICATION_ID, Intent(ACTION_CANCEL), 0
+ )
return NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
- .setOngoing(true)
- .setProgress(-1, -1, true)
- .setSmallIcon(R.drawable.ic_newpipe_triangle_white)
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
- .addAction(0, getString(R.string.cancel), cancelActionIntent)
- .setContentTitle(getString(R.string.feed_notification_loading))
+ .setOngoing(true)
+ .setProgress(-1, -1, true)
+ .setSmallIcon(R.drawable.ic_newpipe_triangle_white)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .addAction(0, getString(R.string.cancel), cancelActionIntent)
+ .setContentTitle(getString(R.string.feed_notification_loading))
}
private fun setupNotification() {
@@ -385,10 +393,12 @@ class FeedLoadService : Service() {
flow.limit(1).concatWith(flow.skip(1).throttleLatest(NOTIFICATION_SAMPLING_PERIOD.toLong(), TimeUnit.MILLISECONDS))
}
- disposables.add(notificationUpdater
+ disposables.add(
+ notificationUpdater
.publish(throttleAfterFirstEmission)
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(this::updateNotificationProgress))
+ .subscribe(this::updateNotificationProgress)
+ )
}
private fun updateNotificationProgress(updateDescription: String?) {
diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java
index 96a385ca8..0e0e1e897 100644
--- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java
@@ -20,7 +20,7 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import androidx.annotation.NonNull;
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java
index 61de65b41..b29643ba9 100644
--- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java
@@ -11,7 +11,6 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
-import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@@ -22,7 +21,6 @@
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.text.DateFormat;
-import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
@@ -71,15 +69,11 @@ public void updateFromItem(final LocalItem localItem,
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
- StreamStateEntity state = historyRecordManager
- .loadLocalStreamStateBatch(new ArrayList() {{
- add(localItem);
- }}).blockingGet().get(0);
- if (state != null) {
+ if (item.getProgressTime() > 0) {
itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
- .toSeconds(state.getProgressTime()));
+ .toSeconds(item.getProgressTime()));
} else {
itemProgressView.setVisibility(View.GONE);
}
@@ -105,7 +99,6 @@ public void updateFromItem(final LocalItem localItem,
return true;
});
- itemThumbnailView.setOnTouchListener(getOnTouchListener(item));
itemHandleView.setOnTouchListener(getOnTouchListener(item));
}
@@ -117,18 +110,14 @@ public void updateState(final LocalItem localItem,
}
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
- StreamStateEntity state = historyRecordManager
- .loadLocalStreamStateBatch(new ArrayList() {{
- add(localItem);
- }}).blockingGet().get(0);
- if (state != null && item.getStreamEntity().getDuration() > 0) {
+ if (item.getProgressTime() > 0 && item.getStreamEntity().getDuration() > 0) {
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
- .toSeconds(state.getProgressTime()));
+ .toSeconds(item.getProgressTime()));
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
- .toSeconds(state.getProgressTime()));
+ .toSeconds(item.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java
index dd6abe8ba..b75603b99 100644
--- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java
@@ -11,7 +11,6 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
-import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@@ -22,7 +21,6 @@
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.text.DateFormat;
-import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
/*
@@ -99,15 +97,11 @@ public void updateFromItem(final LocalItem localItem,
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
- StreamStateEntity state = historyRecordManager
- .loadLocalStreamStateBatch(new ArrayList() {{
- add(localItem);
- }}).blockingGet().get(0);
- if (state != null) {
+ if (item.getProgressTime() > 0) {
itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
- .toSeconds(state.getProgressTime()));
+ .toSeconds(item.getProgressTime()));
} else {
itemProgressView.setVisibility(View.GONE);
}
@@ -147,18 +141,14 @@ public void updateState(final LocalItem localItem,
}
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
- StreamStateEntity state = historyRecordManager
- .loadLocalStreamStateBatch(new ArrayList() {{
- add(localItem);
- }}).blockingGet().get(0);
- if (state != null && item.getStreamEntity().getDuration() > 0) {
+ if (item.getProgressTime() > 0 && item.getStreamEntity().getDuration() > 0) {
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
- .toSeconds(state.getProgressTime()));
+ .toSeconds(item.getProgressTime()));
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
- .toSeconds(state.getProgressTime()));
+ .toSeconds(item.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
index 485d3f391..b764226b8 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
@@ -96,7 +96,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment removeWatchedStreams(false))
- .setNeutralButton(
- R.string.remove_watched_popup_yes_and_partially_watched_videos,
- (DialogInterface d, int id) -> removeWatchedStreams(true))
- .setNegativeButton(R.string.cancel,
- (DialogInterface d, int id) -> d.cancel())
- .create()
- .show();
- }
- break;
- default:
- return super.onOptionsItemSelected(item);
+ if (item.getItemId() == R.id.menu_item_remove_watched) {
+ if (!isRemovingWatched) {
+ new AlertDialog.Builder(requireContext())
+ .setMessage(R.string.remove_watched_popup_warning)
+ .setTitle(R.string.remove_watched_popup_title)
+ .setPositiveButton(R.string.yes,
+ (DialogInterface d, int id) -> removeWatchedStreams(false))
+ .setNeutralButton(
+ R.string.remove_watched_popup_yes_and_partially_watched_videos,
+ (DialogInterface d, int id) -> removeWatchedStreams(true))
+ .setNegativeButton(R.string.cancel,
+ (DialogInterface d, int id) -> d.cancel())
+ .create()
+ .show();
+ }
+ } else if (item.getItemId() == R.id.menu_item_rename_playlist) {
+ createRenameDialog();
+ } else {
+ return super.onOptionsItemSelected(item);
}
return true;
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt
index 7fea3b5d8..427252e2c 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt
@@ -11,7 +11,6 @@ import android.content.res.Configuration
import android.os.Bundle
import android.os.Environment
import android.os.Parcelable
-import android.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@@ -20,6 +19,7 @@ import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.ViewModelProviders
import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import com.nononsenseapps.filepicker.Utils
import com.xwray.groupie.Group
@@ -29,12 +29,6 @@ import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.State
import io.reactivex.disposables.CompositeDisposable
-import java.io.File
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-import kotlin.math.floor
-import kotlin.math.max
import kotlinx.android.synthetic.main.dialog_title.view.itemAdditionalDetails
import kotlinx.android.synthetic.main.dialog_title.view.itemTitleView
import kotlinx.android.synthetic.main.fragment_subscription.items_list
@@ -68,6 +62,12 @@ import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.ShareUtils
import org.schabi.newpipe.util.ThemeHelper
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import kotlin.math.floor
+import kotlin.math.max
class SubscriptionFragment : BaseStateFragment() {
private lateinit var viewModel: SubscriptionViewModel
@@ -208,14 +208,19 @@ class SubscriptionFragment : BaseStateFragment() {
if (!exportFile.parentFile.canWrite() || !exportFile.parentFile.canRead()) {
Toast.makeText(activity, R.string.invalid_directory, Toast.LENGTH_SHORT).show()
} else {
- activity.startService(Intent(activity, SubscriptionsExportService::class.java)
- .putExtra(KEY_FILE_PATH, exportFile.absolutePath))
+ activity.startService(
+ Intent(activity, SubscriptionsExportService::class.java)
+ .putExtra(KEY_FILE_PATH, exportFile.absolutePath)
+ )
}
} else if (requestCode == REQUEST_IMPORT_CODE) {
val path = Utils.getFileForUri(data.data!!).absolutePath
- ImportConfirmationDialog.show(this, Intent(activity, SubscriptionsImportService::class.java)
+ ImportConfirmationDialog.show(
+ this,
+ Intent(activity, SubscriptionsImportService::class.java)
.putExtra(KEY_MODE, PREVIOUS_EXPORT_MODE)
- .putExtra(KEY_VALUE, path))
+ .putExtra(KEY_VALUE, path)
+ )
}
}
}
@@ -247,9 +252,9 @@ class SubscriptionFragment : BaseStateFragment() {
feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter)
feedGroupsSortMenuItem = HeaderWithMenuItem(
- getString(R.string.feed_groups_header_title),
- ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort),
- menuItemOnClickListener = ::openReorderDialog
+ getString(R.string.feed_groups_header_title),
+ ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort),
+ menuItemOnClickListener = ::openReorderDialog
)
add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel)))
@@ -260,10 +265,11 @@ class SubscriptionFragment : BaseStateFragment() {
subscriptionsSection.setHideWhenEmpty(true)
importExportItem = FeedImportExportItem(
- { onImportPreviousSelected() },
- { onImportFromServiceSelected(it) },
- { onExportSelected() },
- importExportItemExpandedState ?: false)
+ { onImportPreviousSelected() },
+ { onImportFromServiceSelected(it) },
+ { onExportSelected() },
+ importExportItemExpandedState ?: false
+ )
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
}
@@ -284,8 +290,8 @@ class SubscriptionFragment : BaseStateFragment() {
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
val commands = arrayOf(
- getString(R.string.share),
- getString(R.string.unsubscribe)
+ getString(R.string.share),
+ getString(R.string.unsubscribe)
)
val actions = DialogInterface.OnClickListener { _, i ->
@@ -301,16 +307,18 @@ class SubscriptionFragment : BaseStateFragment() {
bannerView.itemAdditionalDetails.visibility = View.GONE
AlertDialog.Builder(requireContext())
- .setCustomTitle(bannerView)
- .setItems(commands, actions)
- .create()
- .show()
+ .setCustomTitle(bannerView)
+ .setItems(commands, actions)
+ .create()
+ .show()
}
private fun deleteChannel(selectedItem: ChannelInfoItem) {
- disposables.add(subscriptionManager.deleteSubscription(selectedItem.serviceId, selectedItem.url).subscribe {
- Toast.makeText(requireContext(), getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show()
- })
+ disposables.add(
+ subscriptionManager.deleteSubscription(selectedItem.serviceId, selectedItem.url).subscribe {
+ Toast.makeText(requireContext(), getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show()
+ }
+ )
}
override fun doInitialLoadLogic() = Unit
@@ -332,8 +340,10 @@ class SubscriptionFragment : BaseStateFragment() {
}
private val listenerChannelItem = object : OnClickGesture() {
- override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment(fm,
- selectedItem.serviceId, selectedItem.url, selectedItem.name)
+ override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment(
+ fm,
+ selectedItem.serviceId, selectedItem.url, selectedItem.name
+ )
override fun held(selectedItem: ChannelInfoItem) = showLongTapDialog(selectedItem)
}
@@ -420,14 +430,16 @@ class SubscriptionFragment : BaseStateFragment() {
private fun shouldUseGridLayout(): Boolean {
val listMode = PreferenceManager.getDefaultSharedPreferences(requireContext())
- .getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value))
+ .getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value))
return when (listMode) {
getString(R.string.list_view_mode_auto_key) -> {
val configuration = resources.configuration
- (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
- configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
+ (
+ configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
+ configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE)
+ )
}
getString(R.string.list_view_mode_grid_key) -> true
else -> false
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt
index 2740591e6..b36ae110e 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt
@@ -32,7 +32,8 @@ class SubscriptionManager(context: Context) {
filterQuery.isNotEmpty() -> {
return if (showOnlyUngrouped) {
subscriptionTable.getSubscriptionsOnlyUngroupedFiltered(
- currentGroupId, filterQuery)
+ currentGroupId, filterQuery
+ )
} else {
subscriptionTable.getSubscriptionsFiltered(filterQuery)
}
@@ -44,7 +45,8 @@ class SubscriptionManager(context: Context) {
fun upsertAll(infoList: List): List {
val listEntities = subscriptionTable.upsertAll(
- infoList.map { SubscriptionEntity.from(it) })
+ infoList.map { SubscriptionEntity.from(it) }
+ )
database.runInTransaction {
infoList.forEachIndexed { index, info ->
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt
index b7f16c319..37c1211b2 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt
@@ -6,11 +6,11 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.xwray.groupie.Group
import io.reactivex.schedulers.Schedulers
-import java.util.concurrent.TimeUnit
import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.item.ChannelItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
+import java.util.concurrent.TimeUnit
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application)
@@ -22,22 +22,22 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
val feedGroupsLiveData: LiveData> = mutableFeedGroupsLiveData
private var feedGroupItemsDisposable = feedDatabaseManager.groups()
- .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
- .map { it.map(::FeedGroupCardItem) }
- .subscribeOn(Schedulers.io())
- .subscribe(
- { mutableFeedGroupsLiveData.postValue(it) },
- { mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
- )
+ .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
+ .map { it.map(::FeedGroupCardItem) }
+ .subscribeOn(Schedulers.io())
+ .subscribe(
+ { mutableFeedGroupsLiveData.postValue(it) },
+ { mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
+ )
private var stateItemsDisposable = subscriptionManager.subscriptions()
- .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
- .map { it.map { entity -> ChannelItem(entity.toChannelInfoItem(), entity.uid, ChannelItem.ItemVersion.MINI) } }
- .subscribeOn(Schedulers.io())
- .subscribe(
- { mutableStateLiveData.postValue(SubscriptionState.LoadedState(it)) },
- { mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
- )
+ .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
+ .map { it.map { entity -> ChannelItem(entity.toChannelInfoItem(), entity.uid, ChannelItem.ItemVersion.MINI) } }
+ .subscribeOn(Schedulers.io())
+ .subscribe(
+ { mutableStateLiveData.postValue(SubscriptionState.LoadedState(it)) },
+ { mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
+ )
override fun onCleared() {
super.onCleared()
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
index 66387d298..905d12240 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
@@ -23,8 +23,6 @@ import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
-import java.io.Serializable
-import kotlin.collections.contains
import kotlinx.android.synthetic.main.dialog_feed_group_create.*
import kotlinx.android.synthetic.main.toolbar_search_layout.*
import org.schabi.newpipe.R
@@ -42,6 +40,8 @@ import org.schabi.newpipe.local.subscription.item.PickerIconItem
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
import org.schabi.newpipe.util.AndroidTvUtils
import org.schabi.newpipe.util.ThemeHelper
+import java.io.Serializable
+import kotlin.collections.contains
class FeedGroupDialog : DialogFragment(), BackPressable {
private lateinit var viewModel: FeedGroupDialogViewModel
@@ -115,21 +115,30 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- viewModel = ViewModelProvider(this,
- FeedGroupDialogViewModel.Factory(requireContext(),
- groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped)
+ viewModel = ViewModelProvider(
+ this,
+ FeedGroupDialogViewModel.Factory(
+ requireContext(),
+ groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped
+ )
).get(FeedGroupDialogViewModel::class.java)
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
- viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer {
- setupSubscriptionPicker(it.first, it.second)
- })
- viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
- when (it) {
- ProcessingEvent -> disableInput()
- SuccessEvent -> dismiss()
+ viewModel.subscriptionsLiveData.observe(
+ viewLifecycleOwner,
+ Observer {
+ setupSubscriptionPicker(it.first, it.second)
}
- })
+ )
+ viewModel.dialogEventLiveData.observe(
+ viewLifecycleOwner,
+ Observer {
+ when (it) {
+ ProcessingEvent -> disableInput()
+ SuccessEvent -> dismiss()
+ }
+ }
+ )
subscriptionGroupAdapter = GroupAdapter().apply {
add(subscriptionMainSection)
@@ -140,8 +149,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
// Disable animations, too distracting.
itemAnimator = null
adapter = subscriptionGroupAdapter
- layoutManager = GridLayoutManager(requireContext(), subscriptionGroupAdapter.spanCount,
- RecyclerView.VERTICAL, false).apply {
+ layoutManager = GridLayoutManager(
+ requireContext(), subscriptionGroupAdapter.spanCount,
+ RecyclerView.VERTICAL, false
+ ).apply {
spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup
}
}
@@ -354,7 +365,8 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
val selectedCount = this.selectedSubscriptions.size
val selectedCountText = resources.getQuantityString(
R.plurals.feed_group_dialog_selection_count,
- selectedCount, selectedCount)
+ selectedCount, selectedCount
+ )
selected_subscription_count_view.text = selectedCountText
subscriptions_header_info.text = selectedCountText
}
@@ -409,10 +421,12 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
separator.onlyVisibleIn(SubscriptionsPickerScreen, IconPickerScreen)
cancel_button.onlyVisibleIn(InitialScreen, DeleteScreen)
- confirm_button.setText(when {
- currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED -> R.string.create
- else -> android.R.string.ok
- })
+ confirm_button.setText(
+ when {
+ currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED -> R.string.create
+ else -> android.R.string.ok
+ }
+ )
delete_button.visibility = when {
currentScreen != InitialScreen -> View.GONE
@@ -469,8 +483,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
}
private fun hideKeyboardSearch() {
- inputMethodManager.hideSoftInputFromWindow(toolbar_search_edit_text.windowToken,
- InputMethodManager.RESULT_UNCHANGED_SHOWN)
+ inputMethodManager.hideSoftInputFromWindow(
+ toolbar_search_edit_text.windowToken,
+ InputMethodManager.RESULT_UNCHANGED_SHOWN
+ )
toolbar_search_edit_text.clearFocus()
}
@@ -481,8 +497,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
}
private fun hideKeyboard() {
- inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken,
- InputMethodManager.RESULT_UNCHANGED_SHOWN)
+ inputMethodManager.hideSoftInputFromWindow(
+ group_name_input.windowToken,
+ InputMethodManager.RESULT_UNCHANGED_SHOWN
+ )
group_name_input.clearFocus()
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt
index e9a7e4eb7..f03803024 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt
@@ -32,9 +32,9 @@ class FeedGroupDialogViewModel(
private var subscriptionsFlowable = Flowable
.combineLatest(
- filterSubscriptions.startWith(initialQuery),
- toggleShowOnlyUngrouped.startWith(initialShowOnlyUngrouped),
- BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
+ filterSubscriptions.startWith(initialQuery),
+ toggleShowOnlyUngrouped.startWith(initialShowOnlyUngrouped),
+ BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
)
.distinctUntilChanged()
.switchMap { filter ->
@@ -55,8 +55,10 @@ class FeedGroupDialogViewModel(
.subscribe(mutableGroupLiveData::postValue)
private var subscriptionsDisposable = Flowable
- .combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
- BiFunction { t1: List, t2: List -> t1 to t2.toSet() })
+ .combineLatest(
+ subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
+ BiFunction { t1: List, t2: List -> t1 to t2.toSet() }
+ )
.subscribeOn(Schedulers.io())
.subscribe(mutableSubscriptionsLiveData::postValue)
@@ -68,15 +70,19 @@ class FeedGroupDialogViewModel(
}
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set) {
- doAction(feedDatabaseManager.createGroup(name, selectedIcon)
- .flatMapCompletable {
- feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
- })
+ doAction(
+ feedDatabaseManager.createGroup(name, selectedIcon)
+ .flatMapCompletable {
+ feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
+ }
+ )
}
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set, sortOrder: Long) {
- doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
- .andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
+ doAction(
+ feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
+ .andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder)))
+ )
}
fun deleteGroup() {
@@ -120,8 +126,10 @@ class FeedGroupDialogViewModel(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun create(modelClass: Class): T {
- return FeedGroupDialogViewModel(context.applicationContext,
- groupId, initialQuery, initialShowOnlyUngrouped) as T
+ return FeedGroupDialogViewModel(
+ context.applicationContext,
+ groupId, initialQuery, initialShowOnlyUngrouped
+ ) as T
}
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt
index 92c063b4b..0d0c0ae65 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt
@@ -16,7 +16,6 @@ import com.xwray.groupie.TouchCallback
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
-import java.util.Collections
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.confirm_button
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.feed_groups_list
import org.schabi.newpipe.R
@@ -25,6 +24,7 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewMo
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
import org.schabi.newpipe.util.ThemeHelper
+import java.util.Collections
class FeedGroupReorderDialog : DialogFragment() {
private lateinit var viewModel: FeedGroupReorderDialogViewModel
@@ -51,12 +51,15 @@ class FeedGroupReorderDialog : DialogFragment() {
viewModel = ViewModelProviders.of(this).get(FeedGroupReorderDialogViewModel::class.java)
viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups))
- viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
- when (it) {
- ProcessingEvent -> disableInput()
- SuccessEvent -> dismiss()
+ viewModel.dialogEventLiveData.observe(
+ viewLifecycleOwner,
+ Observer {
+ when (it) {
+ ProcessingEvent -> disableInput()
+ SuccessEvent -> dismiss()
+ }
}
- })
+ )
feed_groups_list.layoutManager = LinearLayoutManager(requireContext())
feed_groups_list.adapter = groupAdapter
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialogViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialogViewModel.kt
index ea2cbe98f..5b42c3163 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialogViewModel.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialogViewModel.kt
@@ -21,9 +21,9 @@ class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewMod
private var actionProcessingDisposable: Disposable? = null
private var groupsDisposable = feedDatabaseManager.groups()
- .limit(1)
- .subscribeOn(Schedulers.io())
- .subscribe(mutableGroupsLiveData::postValue)
+ .limit(1)
+ .subscribeOn(Schedulers.io())
+ .subscribe(mutableGroupsLiveData::postValue)
override fun onCleared() {
super.onCleared()
@@ -40,8 +40,8 @@ class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewMod
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
actionProcessingDisposable = completable
- .subscribeOn(Schedulers.io())
- .subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
+ .subscribeOn(Schedulers.io())
+ .subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt
index f33c58f43..8089f6480 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt
@@ -36,8 +36,10 @@ class ChannelItem(
viewHolder.itemAdditionalDetails.text = getDetailLine(viewHolder.root.context)
if (itemVersion == ItemVersion.NORMAL) viewHolder.itemChannelDescriptionView.text = infoItem.description
- ImageLoader.getInstance().displayImage(infoItem.thumbnailUrl, viewHolder.itemThumbnailView,
- ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS)
+ ImageLoader.getInstance().displayImage(
+ infoItem.thumbnailUrl, viewHolder.itemThumbnailView,
+ ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS
+ )
gesturesListener?.run {
viewHolder.containerView.setOnClickListener { selected(infoItem) }
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt
index 717e2410a..e56bb408c 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt
@@ -20,7 +20,7 @@ data class FeedGroupReorderItem(
val dragCallback: ItemTouchHelper
) : Item() {
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper) :
- this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
+ this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
override fun getId(): Long {
return when (groupId) {
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt
index 5478dcac4..5fd70a684 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt
@@ -49,8 +49,10 @@ class FeedImportExportItem(
expandIconListener?.let { viewHolder.import_export_options.removeListener(it) }
expandIconListener = CollapsibleView.StateListener { newState ->
- AnimationUtils.animateRotation(viewHolder.import_export_expand_icon,
- 250, if (newState == CollapsibleView.COLLAPSED) 0 else 180)
+ AnimationUtils.animateRotation(
+ viewHolder.import_export_expand_icon,
+ 250, if (newState == CollapsibleView.COLLAPSED) 0 else 180
+ )
}
viewHolder.import_export_options.currentState = if (isExpanded) CollapsibleView.EXPANDED else CollapsibleView.COLLAPSED
@@ -85,8 +87,10 @@ class FeedImportExportItem(
}
private fun setupImportFromItems(listHolder: ViewGroup) {
- val previousBackupItem = addItemView(listHolder.context.getString(R.string.previous_export),
- ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_backup), listHolder)
+ val previousBackupItem = addItemView(
+ listHolder.context.getString(R.string.previous_export),
+ ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_backup), listHolder
+ )
previousBackupItem.setOnClickListener { onImportPreviousSelected() }
val iconColor = if (ThemeHelper.isLightThemeSelected(listHolder.context)) Color.BLACK else Color.WHITE
@@ -112,8 +116,10 @@ class FeedImportExportItem(
}
private fun setupExportToItems(listHolder: ViewGroup) {
- val previousBackupItem = addItemView(listHolder.context.getString(R.string.file),
- ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder)
+ val previousBackupItem = addItemView(
+ listHolder.context.getString(R.string.file),
+ ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder
+ )
previousBackupItem.setOnClickListener { onExportSelected() }
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt
index 324932256..0f5bdeb94 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt
@@ -37,11 +37,11 @@ class HeaderWithMenuItem(
viewHolder.header_menu_item.setImageResource(itemIcon)
val listener: OnClickListener? =
- onClickListener?.let { OnClickListener { onClickListener.invoke() } }
+ onClickListener?.let { OnClickListener { onClickListener.invoke() } }
viewHolder.root.setOnClickListener(listener)
val menuItemListener: OnClickListener? =
- menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
+ menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
viewHolder.header_menu_item.setOnClickListener(menuItemListener)
updateMenuItemVisibility(viewHolder)
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
index 7d33da71f..9fa8c652b 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt
@@ -21,8 +21,10 @@ data class PickerSubscriptionItem(
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
- ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl,
- viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS)
+ ImageLoader.getInstance().displayImage(
+ subscriptionEntity.avatarUrl,
+ viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
+ )
viewHolder.title_view.text = subscriptionEntity.name
viewHolder.selected_highlight.visibility = if (isSelected) View.VISIBLE else View.GONE
@@ -38,7 +40,9 @@ data class PickerSubscriptionItem(
fun updateSelected(containerView: View, isSelected: Boolean) {
this.isSelected = isSelected
- animateView(containerView.selected_highlight,
- AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
+ animateView(
+ containerView.selected_highlight,
+ AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150
+ )
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java
index 12b64d89d..8a7d7d6ea 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java
@@ -27,6 +27,7 @@
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
+import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.subscription.SubscriptionItem;
@@ -50,7 +51,7 @@ public class SubscriptionsExportService extends BaseImportExportService {
* A {@link LocalBroadcastManager local broadcast} will be made with this action
* when the export is successfully completed.
*/
- public static final String EXPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription"
+ public static final String EXPORT_COMPLETE_ACTION = App.PACKAGE_NAME + ".local.subscription"
+ ".services.SubscriptionsExportService.EXPORT_COMPLETE";
private Subscription subscription;
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
index 06ba55106..5fb278d9c 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
@@ -20,15 +20,18 @@
package org.schabi.newpipe.local.subscription.services;
import android.content.Intent;
+import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.documentfile.provider.DocumentFile;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
+import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.NewPipe;
@@ -66,7 +69,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
* A {@link LocalBroadcastManager local broadcast} will be made with this action
* when the import is successfully completed.
*/
- public static final String IMPORT_COMPLETE_ACTION = "org.schabi.newpipe.local.subscription"
+ public static final String IMPORT_COMPLETE_ACTION = App.PACKAGE_NAME + ".local.subscription"
+ ".services.SubscriptionsImportService.IMPORT_COMPLETE";
/**
@@ -87,6 +90,9 @@ public class SubscriptionsImportService extends BaseImportExportService {
private String channelUrl;
@Nullable
private InputStream inputStream;
+ @Nullable
+ private String inputStreamType;
+ private Uri uri;
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
@@ -110,6 +116,9 @@ public int onStartCommand(final Intent intent, final int flags, final int startI
try {
inputStream = new FileInputStream(new File(filePath));
+
+ final DocumentFile documentFile = DocumentFile.fromSingleUri(this, uri);
+ inputStreamType = documentFile.getType();
} catch (FileNotFoundException e) {
handleError(e);
return START_NOT_STICKY;
@@ -279,7 +288,7 @@ private Flowable> importFromChannelUrl() {
private Flowable> importFromInputStream() {
return Flowable.fromCallable(() -> NewPipe.getService(currentServiceId)
.getSubscriptionExtractor()
- .fromInputStream(inputStream));
+ .fromInputStream(inputStream, inputStreamType));
}
private Flowable> importFromPreviousExport() {
diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
index 57df398b6..9972d4018 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
@@ -32,7 +32,7 @@
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.IBinder;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
@@ -50,6 +50,7 @@
import com.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.BuildConfig;
+import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.event.PlayerEventListener;
@@ -70,19 +71,19 @@
*/
public final class BackgroundPlayer extends Service {
public static final String ACTION_CLOSE
- = "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
+ = App.PACKAGE_NAME + ".player.BackgroundPlayer.CLOSE";
public static final String ACTION_PLAY_PAUSE
- = "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
+ = App.PACKAGE_NAME + ".player.BackgroundPlayer.PLAY_PAUSE";
public static final String ACTION_REPEAT
- = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
+ = App.PACKAGE_NAME + ".player.BackgroundPlayer.REPEAT";
public static final String ACTION_PLAY_NEXT
- = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT";
+ = App.PACKAGE_NAME + ".player.BackgroundPlayer.ACTION_PLAY_NEXT";
public static final String ACTION_PLAY_PREVIOUS
- = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS";
+ = App.PACKAGE_NAME + ".player.BackgroundPlayer.ACTION_PLAY_PREVIOUS";
public static final String ACTION_FAST_REWIND
- = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
+ = App.PACKAGE_NAME + ".player.BackgroundPlayer.ACTION_FAST_REWIND";
public static final String ACTION_FAST_FORWARD
- = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
+ = App.PACKAGE_NAME + ".player.BackgroundPlayer.ACTION_FAST_FORWARD";
public static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
private static final String TAG = "BackgroundPlayer";
diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java
index 9da3c3c86..9af5d6c49 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java
@@ -1,6 +1,7 @@
package org.schabi.newpipe.player;
import android.content.Intent;
+import android.view.Menu;
import android.view.MenuItem;
import org.schabi.newpipe.R;
@@ -70,4 +71,9 @@ public boolean onPlayerOptionSelected(final MenuItem item) {
public Intent getPlayerShutdownIntent() {
return new Intent(ACTION_CLOSE);
}
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ return super.onCreateOptionsMenu(menu);
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
index 61c5d9e68..23e3adc42 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
@@ -27,7 +27,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
@@ -56,8 +56,10 @@
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.DownloaderImpl;
+import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
+import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.helper.AudioReactor;
import org.schabi.newpipe.player.helper.LoadController;
@@ -134,7 +136,7 @@ public abstract class BasePlayer implements
// Playback
//////////////////////////////////////////////////////////////////////////*/
- protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f};
+ protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f, 2.25f, 2.5f, 2.75f, 3.0f};
protected PlayQueue playQueue;
protected PlayQueueAdapter playQueueAdapter;
@@ -157,7 +159,7 @@ public abstract class BasePlayer implements
//////////////////////////////////////////////////////////////////////////*/
protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds
- protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
+ protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 2000; // 2 seconds
protected SimpleExoPlayer simpleExoPlayer;
protected AudioReactor audioReactor;
@@ -447,10 +449,19 @@ public void onLoadingFailed(final String imageUri, final View view,
@Override
public void onLoadingComplete(final String imageUri, final View view,
final Bitmap loadedImage) {
+ final float width = Math.min(
+ context.getResources().getDimension(R.dimen.player_notification_thumbnail_width),
+ loadedImage.getWidth());
+ currentThumbnail = Bitmap.createScaledBitmap(loadedImage,
+ (int) width,
+ (int) (loadedImage.getHeight() / (loadedImage.getWidth() / width)), true);
+
if (DEBUG) {
Log.d(TAG, "Thumbnail - onLoadingComplete() called with: "
+ "imageUri = [" + imageUri + "], view = [" + view + "], "
- + "loadedImage = [" + loadedImage + "]");
+ + "loadedImage = [" + loadedImage + "], "
+ + loadedImage.getWidth() + "x" + loadedImage.getHeight()
+ + ", scaled width = " + width);
}
currentThumbnail = loadedImage;
}
@@ -648,9 +659,22 @@ public void triggerProgressUpdate() {
if (simpleExoPlayer == null) {
return;
}
+ // Use duration of currentItem for non-live streams,
+ // because HLS streams are fragmented
+ // and thus the whole duration is not available to the player
+ // TODO: revert #6307 when introducing proper HLS support
+ final int duration;
+ if (currentItem != null
+ && currentItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM
+ && currentItem.getStreamType() != StreamType.LIVE_STREAM) {
+ // convert seconds to milliseconds
+ duration = (int) (currentItem.getDuration() * 1000);
+ } else {
+ duration = (int) simpleExoPlayer.getDuration();
+ }
onUpdateProgress(
Math.max((int) simpleExoPlayer.getCurrentPosition(), 0),
- (int) simpleExoPlayer.getDuration(),
+ duration,
simpleExoPlayer.getBufferedPercentage()
);
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
index 56744d858..f373a5e5e 100644
--- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
@@ -33,7 +33,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -48,6 +48,7 @@
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
@@ -86,6 +87,7 @@
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
+import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.ThemeHelper;
@@ -222,6 +224,13 @@ public boolean onKeyDown(final int keyCode, final KeyEvent event) {
switch (event.getKeyCode()) {
default:
break;
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ if (playerImpl.isPlaying()) {
+ break;
+ }
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
+ break;
case KeyEvent.KEYCODE_BACK:
if (AndroidTvUtils.isTv(getApplicationContext())
&& playerImpl.isControlsVisible()) {
@@ -517,6 +526,7 @@ public void onPlaybackParameterChanged(final float playbackTempo, final float pl
private class VideoPlayerImpl extends VideoPlayer {
private static final float MAX_GESTURE_LENGTH = 0.75f;
+ private LinearLayout metadata;
private TextView titleTextView;
private TextView channelTextView;
private RelativeLayout volumeRelativeLayout;
@@ -561,6 +571,7 @@ private class VideoPlayerImpl extends VideoPlayer {
@Override
public void initViews(final View view) {
super.initViews(view);
+ this.metadata = view.findViewById(R.id.metadataView);
this.titleTextView = view.findViewById(R.id.titleTextView);
this.channelTextView = view.findViewById(R.id.channelTextView);
this.volumeRelativeLayout = view.findViewById(R.id.volumeRelativeLayout);
@@ -619,6 +630,7 @@ public void initListeners() {
gestureDetector.setIsLongpressEnabled(false);
getRootView().setOnTouchListener(listener);
+ metadata.setOnClickListener(this);
queueButton.setOnClickListener(this);
repeatButton.setOnClickListener(this);
shuffleButton.setOnClickListener(this);
@@ -702,6 +714,12 @@ public void onShuffleClicked() {
updatePlaybackButtons();
}
+ @Override
+ public void onPlay() {
+ super.onPlay();
+ showControlsThenHide();
+ }
+
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@@ -814,6 +832,15 @@ public void onMuteUnmuteButtonClicked() {
setMuteButton(muteButton, playerImpl.isMuted());
}
+ public void onMetadataClicked() {
+ NavigationHelper.openVideoDetail(context,
+ ServiceHelper.getSelectedServiceId(context),
+ playerImpl.getVideoUrl(), playerImpl.getVideoTitle());
+
+ ((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
+ destroy();
+ finish();
+ }
@Override
public void onClick(final View v) {
@@ -850,6 +877,8 @@ public void onClick(final View v) {
return;
} else if (v.getId() == kodiButton.getId()) {
onKodiShare();
+ } else if (v.getId() == metadata.getId()) {
+ onMetadataClicked();
}
if (getCurrentState() != STATE_COMPLETED) {
@@ -1101,7 +1130,7 @@ public void safeHideControls(final long duration, final long delay) {
}
View controlsRoot = getControlsRoot();
- if (controlsRoot.isInTouchMode()) {
+ if (controlsRoot.isInTouchMode() || isPlaying()) {
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
getControlsVisibilityHandler().postDelayed(() ->
animateView(controlsRoot, false, duration, 0,
diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
index 0ccec3067..96b7146ca 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
@@ -34,7 +34,7 @@
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
@@ -65,6 +65,7 @@
import com.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.BuildConfig;
+import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.event.PlayerEventListener;
@@ -89,10 +90,10 @@
* @author mauriciocolli
*/
public final class PopupVideoPlayer extends Service {
- public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
+ public static final String ACTION_CLOSE = App.PACKAGE_NAME + ".player.PopupVideoPlayer.CLOSE";
public static final String ACTION_PLAY_PAUSE
- = "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE";
- public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
+ = App.PACKAGE_NAME + ".player.PopupVideoPlayer.PLAY_PAUSE";
+ public static final String ACTION_REPEAT = App.PACKAGE_NAME + ".player.PopupVideoPlayer.REPEAT";
private static final String TAG = ".PopupVideoPlayer";
private static final boolean DEBUG = BasePlayer.DEBUG;
private static final int NOTIFICATION_ID = 40028922;
@@ -133,6 +134,8 @@ public final class PopupVideoPlayer extends Service {
private VideoPlayerImpl playerImpl;
private boolean isPopupClosing = false;
+ private static final float MAXIMUM_OPACITY_ALLOWED_FOR_R_AND_HIGHER = 0.8f;
+
/*//////////////////////////////////////////////////////////////////////////
// Service-Activity Binder
//////////////////////////////////////////////////////////////////////////*/
@@ -224,7 +227,7 @@ private void initPopup() {
popupWidth = popupRememberSizeAndPos
? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize;
- final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O
+ final int layoutParamType = Build.VERSION.SDK_INT < Build.VERSION_CODES.O
? WindowManager.LayoutParams.TYPE_PHONE
: WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -274,6 +277,13 @@ private void initPopupCloseOverlay() {
layoutParamType,
flags,
PixelFormat.TRANSLUCENT);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ // Setting maximum opacity allowed for touch events to other apps for Android 12 and
+ // higher to prevent non interaction when using other apps with the popup player
+ closeOverlayLayoutParams.alpha = MAXIMUM_OPACITY_ALLOWED_FOR_R_AND_HIGHER;
+ }
+
closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
closeOverlayLayoutParams.softInputMode = WindowManager
.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
index 72becef8f..debc6704e 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
@@ -1,5 +1,6 @@
package org.schabi.newpipe.player;
+import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
@@ -17,9 +18,12 @@
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
+import android.widget.Toast;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.ActivityCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -40,10 +44,14 @@
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@@ -115,6 +123,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
// Activity Lifecycle
////////////////////////////////////////////////////////////////////////////
+ @Override
+ public void openOptionsMenu() {
+ super.openOptionsMenu();
+ }
+
@Override
protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
@@ -138,7 +151,7 @@ protected void onCreate(final Bundle savedInstanceState) {
protected void onResume() {
super.onResume();
if (redraw) {
- recreate();
+ ActivityCompat.recreate(this);
redraw = false;
}
}
@@ -149,6 +162,10 @@ public boolean onCreateOptionsMenu(final Menu m) {
getMenuInflater().inflate(R.menu.menu_play_queue, m);
getMenuInflater().inflate(getPlayerOptionMenuResource(), m);
onMaybeMuteChanged();
+ // to avoid null reference
+ if (player != null) {
+ onPlaybackParameterChanged(player.getPlaybackParameters());
+ }
return true;
}
@@ -181,10 +198,57 @@ public boolean onOptionsItemSelected(final MenuItem item) {
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
);
return true;
+ case R.id.action_set_timer:
+ setTimer(this);
+ return true;
}
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
}
+ private void setTimer(ServicePlayerActivity servicePlayerActivity) {
+ String[] listItems = {"5 Minutes", "10 Minutes", "20 Minutes", "30 Minutes", "45 Minutes", "1 Hour"};
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(servicePlayerActivity);
+ builder.setTitle("Choose item");
+
+ builder.setItems(listItems, (dialog, which) -> {
+ long time = getTimerTime(which);
+ Timer timer = new Timer();
+ timer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ runOnUiThread(() -> player.onPause());
+ }
+ }, time);
+ Date d = new Date();
+ d.setTime(System.currentTimeMillis() + time);
+ Toast.makeText(servicePlayerActivity, "Player will pause after : " + listItems[which] + " at : " + d, Toast.LENGTH_LONG).show();
+ });
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+
+ }
+
+ protected long getTimerTime(int index) {
+ switch (index) {
+ case 0:
+ return 5 * 60 * 1000;
+ case 1:
+ return 10 * 60 * 1000;
+ case 2:
+ return 20 * 60 * 1000;
+ case 3:
+ return 30 * 60 * 1000;
+ case 4:
+ return 45 * 60 * 1000;
+ case 5:
+ return 60 * 60 * 1000;
+ default:
+ return 1000;
+ }
+ }
+
@Override
protected void onDestroy() {
super.onDestroy();
@@ -485,7 +549,8 @@ public void onClick(final View view) {
} else if (view.getId() == shuffleButton.getId()) {
player.onShuffleClicked();
} else if (view.getId() == metadata.getId()) {
- scrollToSelected();
+ onOpenDetail(ServiceHelper.getSelectedServiceId(getApplicationContext()),
+ player.getVideoUrl(), player.getVideoTitle());
} else if (view.getId() == progressLiveSync.getId()) {
player.seekToDefault();
}
@@ -508,6 +573,7 @@ public void onPlaybackParameterChanged(final float playbackTempo, final float pl
final boolean playbackSkipSilence) {
if (player != null) {
player.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
+ onPlaybackParameterChanged(player.getPlaybackParameters());
}
}
@@ -689,7 +755,7 @@ private void onPlayModeChanged(final int repeatMode, final boolean shuffled) {
shuffleButton.setImageAlpha(shuffleAlpha);
}
- private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
+ private void onPlaybackParameterChanged(@Nullable final PlaybackParameters parameters) {
if (parameters != null) {
if (menu != null && player != null) {
final MenuItem item = menu.findItem(R.id.action_playback_speed);
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
index 576d42a00..9eac7e79b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
@@ -32,7 +32,7 @@
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Handler;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -98,8 +98,8 @@ public abstract class VideoPlayer extends BasePlayer
//////////////////////////////////////////////////////////////////////////*/
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
- public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
- public static final int DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds
+ public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 seconds
+ public static final int DPAD_CONTROLS_HIDE_TIME = 5000; // 5 seconds
protected static final int RENDERER_UNAVAILABLE = -1;
@@ -252,7 +252,7 @@ public void initPlayer(final boolean playOnReady) {
simpleExoPlayer.addTextOutput(cues -> subtitleView.onCues(cues));
// Setup audio session with onboard equalizer
- if (Build.VERSION.SDK_INT >= 21) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
trackSelector.setParameters(trackSelector.buildUponParameters()
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
index 369e3236e..a49e998a3 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
@@ -5,35 +5,33 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.Intent;
-import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.audiofx.AudioEffect;
-import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.media.AudioFocusRequestCompat;
+import androidx.media.AudioManagerCompat;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.analytics.AnalyticsListener;
+import com.google.android.exoplayer2.decoder.DecoderCounters;
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, AnalyticsListener {
private static final String TAG = "AudioFocusReactor";
- private static final boolean SHOULD_BUILD_FOCUS_REQUEST =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
-
private static final int DUCK_DURATION = 1500;
private static final float DUCK_AUDIO_TO = .2f;
- private static final int FOCUS_GAIN_TYPE = AudioManager.AUDIOFOCUS_GAIN;
+ private static final int FOCUS_GAIN_TYPE = AudioManagerCompat.AUDIOFOCUS_GAIN;
private static final int STREAM_TYPE = AudioManager.STREAM_MUSIC;
private final SimpleExoPlayer player;
private final Context context;
private final AudioManager audioManager;
- private final AudioFocusRequest request;
+ private final AudioFocusRequestCompat request;
public AudioReactor(@NonNull final Context context,
@NonNull final SimpleExoPlayer player) {
@@ -42,20 +40,17 @@ public AudioReactor(@NonNull final Context context,
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
player.addAnalyticsListener(this);
- if (SHOULD_BUILD_FOCUS_REQUEST) {
- request = new AudioFocusRequest.Builder(FOCUS_GAIN_TYPE)
- .setAcceptsDelayedFocusGain(true)
- .setWillPauseWhenDucked(true)
- .setOnAudioFocusChangeListener(this)
- .build();
- } else {
- request = null;
- }
+ request = new AudioFocusRequestCompat.Builder(FOCUS_GAIN_TYPE)
+ //.setAcceptsDelayedFocusGain(true)
+ .setWillPauseWhenDucked(true)
+ .setOnAudioFocusChangeListener(this)
+ .build();
}
public void dispose() {
abandonAudioFocus();
player.removeAnalyticsListener(this);
+ notifyAudioSessionUpdate(false, player.getAudioSessionId());
}
/*//////////////////////////////////////////////////////////////////////////
@@ -63,19 +58,11 @@ public void dispose() {
//////////////////////////////////////////////////////////////////////////*/
public void requestAudioFocus() {
- if (SHOULD_BUILD_FOCUS_REQUEST) {
- audioManager.requestAudioFocus(request);
- } else {
- audioManager.requestAudioFocus(this, STREAM_TYPE, FOCUS_GAIN_TYPE);
- }
+ AudioManagerCompat.requestAudioFocus(audioManager, request);
}
public void abandonAudioFocus() {
- if (SHOULD_BUILD_FOCUS_REQUEST) {
- audioManager.abandonAudioFocusRequest(request);
- } else {
- audioManager.abandonAudioFocus(this);
- }
+ AudioManagerCompat.abandonAudioFocusRequest(audioManager, request);
}
public int getVolume() {
@@ -87,7 +74,7 @@ public void setVolume(final int volume) {
}
public int getMaxVolume() {
- return audioManager.getStreamMaxVolume(STREAM_TYPE);
+ return AudioManagerCompat.getStreamMaxVolume(audioManager, STREAM_TYPE);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -163,11 +150,16 @@ public void onAnimationEnd(final Animator animation) {
@Override
public void onAudioSessionId(final EventTime eventTime, final int audioSessionId) {
+ notifyAudioSessionUpdate(true, audioSessionId);
+ }
+
+ private void notifyAudioSessionUpdate(final boolean active, final int audioSessionId) {
if (!PlayerHelper.isUsingDSP(context)) {
return;
}
-
- final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
+ final Intent intent = new Intent(active
+ ? AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION
+ : AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId);
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
context.sendBroadcast(intent);
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
index 92ae009f6..d2e509144 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
@@ -31,7 +31,8 @@ private LoadController(final int initialPlaybackBufferMs,
DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs,
- initialPlaybackBufferMs, initialPlaybackBufferMs);
+ initialPlaybackBufferMs,
+ DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS);
internalLoadControl = builder.createDefaultLoadControl();
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
index 0d511d565..b63a9cd87 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
@@ -1,9 +1,11 @@
package org.schabi.newpipe.player.helper;
+import static org.schabi.newpipe.player.BasePlayer.DEBUG;
+import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
@@ -18,9 +20,6 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.SliderStrategy;
-import static org.schabi.newpipe.player.BasePlayer.DEBUG;
-import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
-
public class PlaybackParameterDialog extends DialogFragment {
// Minimum allowable range in ExoPlayer
private static final double MINIMUM_PLAYBACK_VALUE = 0.10f;
@@ -120,7 +119,7 @@ public void onAttach(final Context context) {
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
- assureCorrectAppLanguage(getContext());
+ assureCorrectAppLanguage(requireContext());
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO);
@@ -150,12 +149,11 @@ public void onSaveInstanceState(final Bundle outState) {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
- assureCorrectAppLanguage(getContext());
- final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null);
+ assureCorrectAppLanguage(requireContext());
+ final View view = View.inflate(requireContext(), R.layout.dialog_playback_parameter, null);
setupControlViews(view);
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
- .setTitle(R.string.playback_speed_control)
.setView(view)
.setCancelable(true)
.setNegativeButton(R.string.cancel, (dialogInterface, i) ->
@@ -237,12 +235,12 @@ private void setupHookingControl(@NonNull final View rootView) {
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
if (unhookingCheckbox != null) {
// restore whether pitch and tempo are unhooked or not
- unhookingCheckbox.setChecked(PreferenceManager.getDefaultSharedPreferences(getContext())
+ unhookingCheckbox.setChecked(PreferenceManager.getDefaultSharedPreferences(requireContext())
.getBoolean(getString(R.string.playback_unhook_key), true));
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
// save whether pitch and tempo are unhooked or not
- PreferenceManager.getDefaultSharedPreferences(getContext())
+ PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit()
.putBoolean(getString(R.string.playback_unhook_key), isChecked)
.apply();
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java
index 5fea4761b..447cf9012 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java
@@ -21,11 +21,13 @@ public class PlayerDataSource {
private static final int EXTRACTOR_MINIMUM_RETRY = Integer.MAX_VALUE;
private static final int LIVE_STREAM_EDGE_GAP_MILLIS = 10000;
+ private final Integer continueLoadingCheckIntervalBytes;
private final DataSource.Factory cacheDataSourceFactory;
private final DataSource.Factory cachelessDataSourceFactory;
public PlayerDataSource(@NonNull final Context context, @NonNull final String userAgent,
@NonNull final TransferListener transferListener) {
+ continueLoadingCheckIntervalBytes = PlayerHelper.getProgressiveLoadIntervalBytes(context);
cacheDataSourceFactory = new CacheFactory(context, userAgent, transferListener);
cachelessDataSourceFactory
= new DefaultDataSourceFactory(context, userAgent, transferListener);
@@ -70,6 +72,7 @@ public DashMediaSource.Factory getDashMediaSourceFactory() {
public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory() {
return new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
+ .setContinueLoadingCheckIntervalBytes(continueLoadingCheckIntervalBytes)
.setLoadErrorHandlingPolicy(
new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
index e63e56bf9..a700647b1 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
@@ -3,7 +3,7 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.view.accessibility.CaptioningManager;
import androidx.annotation.IntDef;
@@ -38,6 +38,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -229,7 +230,7 @@ public static long getPreferredCacheSize() {
}
public static long getPreferredFileSize() {
- return 512 * 1024L;
+ return 2 * 1024 * 1024L; // ExoPlayer CacheDataSink.MIN_RECOMMENDED_FRAGMENT_SIZE
}
/**
@@ -324,6 +325,13 @@ public static void setScreenBrightness(@NonNull final Context context,
setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis());
}
+ @NonNull
+ public static Integer getProgressiveLoadIntervalBytes(@NonNull final Context context) {
+ return Integer.parseInt(Objects.requireNonNull(getPreferences(context).getString(
+ context.getString(R.string.progressive_load_interval_key),
+ context.getString(R.string.progressive_load_interval_bytes_default_value))));
+ }
+
////////////////////////////////////////////////////////////////////////////
// Private helpers
////////////////////////////////////////////////////////////////////////////
diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java
index 30a959784..fafb6c87f 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java
@@ -53,7 +53,6 @@ public void buildStreamInfoItem(final PlayQueueItemHolder holder, final PlayQueu
return false;
});
- holder.itemThumbnailView.setOnTouchListener(getOnTouchListener(holder));
holder.itemHandle.setOnTouchListener(getOnTouchListener(holder));
}
diff --git a/app/src/main/java/org/schabi/newpipe/report/AcraReportSenderFactory.java b/app/src/main/java/org/schabi/newpipe/report/AcraReportSenderFactory.java
index f4c1c4ac8..2655ea672 100644
--- a/app/src/main/java/org/schabi/newpipe/report/AcraReportSenderFactory.java
+++ b/app/src/main/java/org/schabi/newpipe/report/AcraReportSenderFactory.java
@@ -4,9 +4,12 @@
import androidx.annotation.NonNull;
+import com.google.auto.service.AutoService;
+
import org.acra.config.CoreConfiguration;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderFactory;
+import org.schabi.newpipe.App;
/*
* Created by Christian Schabesberger on 13.09.16.
@@ -28,6 +31,10 @@
* along with NewPipe. If not, see .
*/
+/**
+ * Used by ACRA in {@link App}.initAcra() as the factory for report senders.
+ */
+@AutoService(ReportSenderFactory.class)
public class AcraReportSenderFactory implements ReportSenderFactory {
@NonNull
public ReportSender create(@NonNull final Context context,
diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
index 8946f866c..63db7782c 100644
--- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
@@ -407,9 +407,8 @@ private String buildMarkdown() {
.append("\n* __Content Language:__ ").append(getContentLanguageString())
.append("\n* __App Language:__ ").append(getAppLanguage())
.append("\n* __Service:__ ").append(errorInfo.getServiceName())
- .append("\n* __Version:__ ").append(BuildConfig.VERSION_NAME)
- .append("\n* __OS:__ ").append(getOsString()).append("\n");
-
+ .append("\n* __Version:__ ").append(BuildConfig.VERSION_NAME)
+ .append("\n* __OS:__ ").append(getOsString()).append("\n");
// Collapse all logs to a single paragraph when there are more than one
// to keep the GitHub issue clean.
diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
index a9531693c..ededf965e 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
@@ -8,6 +8,7 @@
import android.widget.Toast;
import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
import androidx.preference.Preference;
import org.schabi.newpipe.R;
@@ -31,7 +32,7 @@ public boolean onPreferenceChange(final Preference preference, final Object newV
if (!newValue.equals(startThemeKey) && getActivity() != null) {
// If it's not the current theme
- getActivity().recreate();
+ ActivityCompat.recreate(requireActivity());
}
return false;
diff --git a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
index 125931ee1..2687da2bf 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
@@ -2,7 +2,7 @@
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.view.View;
import androidx.annotation.Nullable;
@@ -20,7 +20,7 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
- defaultPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ defaultPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
super.onCreate(savedInstanceState);
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
index 55bbe6c68..a193ec758 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -7,13 +7,16 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.preference.PreferenceManager;
+import android.text.InputType;
import android.util.Log;
+import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
import com.nononsenseapps.filepicker.Utils;
import com.nostra13.universalimageloader.core.ImageLoader;
@@ -21,6 +24,7 @@
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.localization.Localization;
@@ -76,7 +80,23 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
initialSelectedContentCountry = org.schabi.newpipe.util.Localization
.getPreferredContentCountry(requireContext());
initialLanguage = PreferenceManager
- .getDefaultSharedPreferences(getContext()).getString("app_language_key", "en");
+ .getDefaultSharedPreferences(requireContext()).getString("app_language_key", "en");
+
+ final Preference clearCookiePref = findPreference(getString(R.string.clear_cookie_key));
+
+ clearCookiePref.setOnPreferenceClickListener(preference -> {
+ defaultPreferences.edit()
+ .putString(getString(R.string.recaptcha_cookies_key), "").apply();
+ DownloaderImpl.getInstance().setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, "");
+ Toast.makeText(getActivity(), R.string.recaptcha_cookies_cleared,
+ Toast.LENGTH_SHORT).show();
+ clearCookiePref.setVisible(false);
+ return true;
+ });
+
+ if (defaultPreferences.getString(getString(R.string.recaptcha_cookies_key), "").isEmpty()) {
+ clearCookiePref.setVisible(false);
+ }
}
@Override
@@ -105,15 +125,14 @@ public boolean onPreferenceTreeClick(final Preference preference) {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
-
- String homeDir = getActivity().getApplicationInfo().dataDir;
- databasesDir = new File(homeDir + "/databases");
- newpipeDb = new File(homeDir + "/databases/newpipe.db");
- newpipeDbJournal = new File(homeDir + "/databases/newpipe.db-journal");
- newpipeDbShm = new File(homeDir + "/databases/newpipe.db-shm");
- newpipeDbWal = new File(homeDir + "/databases/newpipe.db-wal");
-
- newpipeSettings = new File(homeDir + "/databases/newpipe.settings");
+ final File homeDir = ContextCompat.getDataDir(requireContext());
+ databasesDir = new File(homeDir, "/databases");
+ newpipeDb = new File(homeDir, "/databases/newpipe.db");
+ newpipeDbJournal = new File(homeDir, "/databases/newpipe.db-journal");
+ newpipeDbShm = new File(homeDir, "/databases/newpipe.db-shm");
+ newpipeDbWal = new File(homeDir, "/databases/newpipe.db-wal");
+
+ newpipeSettings = new File(homeDir, "/databases/newpipe.settings");
newpipeSettings.delete();
addPreferencesFromResource(R.xml.content_settings);
@@ -150,7 +169,7 @@ public void onDestroy() {
final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization
.getPreferredContentCountry(requireContext());
final String selectedLanguage = PreferenceManager
- .getDefaultSharedPreferences(getContext()).getString("app_language_key", "en");
+ .getDefaultSharedPreferences(requireContext()).getString("app_language_key", "en");
if (!selectedLocalization.equals(initialSelectedLocalization)
|| !selectedContentCountry.equals(initialSelectedContentCountry)
@@ -178,8 +197,7 @@ public void onActivityResult(final int requestCode, final int resultCode,
&& resultCode == Activity.RESULT_OK && data.getData() != null) {
String path = Utils.getFileForUri(data.getData()).getAbsolutePath();
if (requestCode == REQUEST_EXPORT_PATH) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
- exportDatabase(path + "/NewPipeData-" + sdf.format(new Date()) + ".zip");
+ showExportDialog(requireActivity(), path);
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.override_current_data)
@@ -192,6 +210,33 @@ public void onActivityResult(final int requestCode, final int resultCode,
}
}
+ private void showExportDialog(final Context c, final String path) {
+ final EditText fileNameET = new EditText(c);
+
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
+ fileNameET.setText("NewPipeData-" + sdf.format(new Date()) + ".zip");
+
+ fileNameET.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
+ fileNameET.setHint(R.string.export_data_file_name_hint);
+
+ final AlertDialog dialog = new AlertDialog.Builder(c)
+ .setTitle(R.string.export_data_file_name)
+ .setIcon(R.drawable.ic_import_export_white_24dp)
+ .setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(R.string.export_button, (dialog1, which) -> {
+ final String fileName = fileNameET.getText().toString();
+ if (fileName.matches("[-_. A-Za-z0-9]+\\.zip")) {
+ exportDatabase(path + "/" + fileName);
+ } else {
+ Toast.makeText(getContext(), R.string.no_valid_zip_file_name,
+ Toast.LENGTH_SHORT).show();
+ }
+ })
+ .create();
+ dialog.setView(fileNameET, 50, 0, 50, 0);
+ dialog.show();
+ }
+
private void exportDatabase(final String path) {
try {
//checkpoint before export
@@ -218,7 +263,7 @@ private void saveSharedPreferencesToFile(final File dst) {
ObjectOutputStream output = null;
try {
output = new ObjectOutputStream(new FileOutputStream(dst));
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(requireContext());
output.writeObject(pref.getAll());
} catch (FileNotFoundException e) {
@@ -302,7 +347,7 @@ private void loadSharedPreferences(final File src) {
try {
input = new ObjectInputStream(new FileInputStream(src));
SharedPreferences.Editor prefEdit = PreferenceManager
- .getDefaultSharedPreferences(getContext()).edit();
+ .getDefaultSharedPreferences(requireContext()).edit();
prefEdit.clear();
Map entries = (Map) input.readObject();
for (Map.Entry entry : entries.entrySet()) {
diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java
index 7435880df..cfca77efb 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java
@@ -4,7 +4,7 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.Menu;
diff --git a/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java
index bef9a7b56..86f5ef2d6 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java
@@ -16,11 +16,14 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.PermissionHelper;
-import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.LinkedList;
public class VideoAudioSettingsFragment extends BasePreferenceFragment {
private SharedPreferences.OnSharedPreferenceChangeListener listener;
+ private ListPreference defaultRes,defaultPopupRes,limitMobDataUsage;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@@ -29,6 +32,9 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
updateSeekOptions();
listener = (sharedPreferences, s) -> {
+ defaultRes = (ListPreference) findPreference(getString(R.string.default_resolution_key));
+ defaultPopupRes = (ListPreference) findPreference(getString(R.string.default_popup_resolution_key));
+ limitMobDataUsage = (ListPreference) findPreference(getString(R.string.limit_mobile_data_usage_key));
// on M and above, if user chooses to minimise to popup player on exit
// and the app doesn't have display over other apps permission,
@@ -50,7 +56,72 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
} else if (s.equals(getString(R.string.use_inexact_seek_key))) {
updateSeekOptions();
}
+
+ //check if "show higher resolutions" was changed
+ if(s.equals(getString(R.string.show_higher_resolutions_key))){
+
+ if(checkIfShowHighRes()){
+ showHigherResolutions(true);
+ }
+ else {
+
+ //if the setting was turned off and any of the defaults is set to 1440p or 2160p, change them to 1080p60
+ //(the next highest value)
+ if(defaultRes.getValue().equals("1440p") || defaultRes.getValue().equals("1440p60") ||
+ defaultRes.getValue().equals("2160p") || defaultRes.getValue().equals("2160p60")){
+ defaultRes.setValueIndex(3);
+ }
+ if(defaultPopupRes.getValue().equals("1440p") || defaultPopupRes.getValue().equals("1440p60") ||
+ defaultPopupRes.getValue().equals("2160p") || defaultPopupRes.getValue().equals("2160p60")){
+ defaultPopupRes.setValueIndex(3);
+ }
+ if(limitMobDataUsage.getValue().equals("1440p") || limitMobDataUsage.getValue().equals("1440p60") ||
+ limitMobDataUsage.getValue().equals("2160p") || limitMobDataUsage.getValue().equals("2160p60")){
+ limitMobDataUsage.setValueIndex(3);
+ }
+
+ showHigherResolutions(false);
+
+ }
+ }
+
};
+ if(!checkIfShowHighRes()){
+ showHigherResolutions(false);
+ }
+ }
+
+ private boolean checkIfShowHighRes(){
+ return getPreferenceManager().getSharedPreferences().getBoolean(getString(R.string.show_higher_resolutions_key),false);
+ }
+
+ private void showHigherResolutions(boolean show){
+
+ Resources res = getResources();
+ ArrayList resolutions = new ArrayList(Arrays.asList(res.getStringArray(R.array.resolution_list_description)));
+ ArrayList resolutionValues = new ArrayList(Arrays.asList(res.getStringArray(R.array.resolution_list_values)));
+
+ ArrayList mobileDataResolutions = new ArrayList(Arrays.asList(res.getStringArray(R.array.limit_data_usage_description_list)));
+ ArrayList mobileDataResolutionValues = new ArrayList(Arrays.asList(res.getStringArray(R.array.limit_data_usage_values_list)));
+
+ if(!show) {
+ List higherResolutions = Arrays.asList("1440p", "1440p60", "2160p", "2160p60");
+
+ resolutions.removeAll(higherResolutions);
+ resolutionValues.removeAll(higherResolutions);
+
+ mobileDataResolutions.removeAll(higherResolutions);
+ mobileDataResolutionValues.removeAll(higherResolutions);
+ }
+
+ defaultRes.setEntries(resolutions.toArray(new String[resolutions.size()]));
+ defaultRes.setEntryValues(resolutionValues.toArray(new String[resolutionValues.size()]));
+
+ defaultPopupRes.setEntries(resolutions.toArray(new String[resolutions.size()]));
+ defaultPopupRes.setEntryValues(resolutionValues.toArray(new String[resolutionValues.size()]));
+
+ limitMobDataUsage.setEntries(mobileDataResolutions.toArray(new String[mobileDataResolutions.size()]));
+ limitMobDataUsage.setEntryValues(mobileDataResolutionValues.toArray(new String[mobileDataResolutionValues.size()]));
}
/**
@@ -91,6 +162,11 @@ private void updateSeekOptions() {
getString(R.string.seek_duration_key));
durations.setEntryValues(displayedDurationValues.toArray(new CharSequence[0]));
durations.setEntries(displayedDescriptionValues.toArray(new CharSequence[0]));
+ defaultRes = (ListPreference) findPreference(getString(R.string.default_resolution_key));
+ defaultPopupRes = (ListPreference) findPreference(
+ getString(R.string.default_popup_resolution_key));
+ limitMobDataUsage = (ListPreference) findPreference(
+ getString(R.string.limit_mobile_data_usage_key));
final int selectedDuration = Integer.parseInt(durations.getValue());
if (inexactSeek && selectedDuration / (int) DateUtils.SECOND_IN_MILLIS % 10 == 5) {
final int newDuration = selectedDuration / (int) DateUtils.SECOND_IN_MILLIS + 5;
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
index c76df7047..8f7a995b3 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
@@ -2,7 +2,7 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.widget.Toast;
import org.schabi.newpipe.R;
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt b/app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt
index 528912ceb..0addb26fb 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt
+++ b/app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt
@@ -10,9 +10,11 @@ class ExceptionUtils {
*/
@JvmStatic
fun isInterruptedCaused(throwable: Throwable): Boolean {
- return hasExactCause(throwable,
- InterruptedIOException::class.java,
- InterruptedException::class.java)
+ return hasExactCause(
+ throwable,
+ InterruptedIOException::class.java,
+ InterruptedException::class.java
+ )
}
/**
@@ -20,8 +22,10 @@ class ExceptionUtils {
*/
@JvmStatic
fun isNetworkRelated(throwable: Throwable): Boolean {
- return hasAssignableCause(throwable,
- IOException::class.java)
+ return hasAssignableCause(
+ throwable,
+ IOException::class.java
+ )
}
/**
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index a23bce425..edd4cc16b 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -1,6 +1,6 @@
/*
* Copyright 2017 Mauricio Colli
- * Extractors.java is part of NewPipe
+ * ExtractorHelper.java is part of NewPipe
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
@@ -37,10 +37,16 @@
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfo;
+import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
+import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
+import org.schabi.newpipe.extractor.exceptions.PaidContentException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
+import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
+import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
+import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
import org.schabi.newpipe.extractor.feed.FeedExtractor;
import org.schabi.newpipe.extractor.feed.FeedInfo;
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
@@ -289,6 +295,21 @@ public static void handleGeneralException(final Context context, final int servi
context.startActivity(intent);
} else if (ExceptionUtils.isNetworkRelated(exception)) {
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
+ } else if (exception instanceof AgeRestrictedContentException) {
+ Toast.makeText(context, R.string.restricted_video_no_stream,
+ Toast.LENGTH_LONG).show();
+ } else if (exception instanceof GeographicRestrictionException) {
+ Toast.makeText(context, R.string.georestricted_content, Toast.LENGTH_LONG).show();
+ } else if (exception instanceof PaidContentException) {
+ Toast.makeText(context, R.string.paid_content, Toast.LENGTH_LONG).show();
+ } else if (exception instanceof PrivateContentException) {
+ Toast.makeText(context, R.string.private_content, Toast.LENGTH_LONG).show();
+ } else if (exception instanceof SoundCloudGoPlusContentException) {
+ Toast.makeText(context, R.string.soundcloud_go_plus_content,
+ Toast.LENGTH_LONG).show();
+ } else if (exception instanceof YoutubeMusicPremiumContentException) {
+ Toast.makeText(context, R.string.youtube_music_premium_content,
+ Toast.LENGTH_LONG).show();
} else if (exception instanceof ContentNotAvailableException) {
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
} else if (exception instanceof ContentNotSupportedException) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java
index 3179662ba..e7174f2bd 100644
--- a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java
@@ -2,7 +2,7 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
diff --git a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java
index b676a1a88..a42d4913f 100644
--- a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java
+++ b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java
@@ -44,6 +44,14 @@ public static String getTranslatedKioskName(final String kioskId, final Context
return c.getString(R.string.most_liked);
case "conferences":
return c.getString(R.string.conferences);
+ case "recent":
+ return c.getString(R.string.recent);
+ case "live":
+ return c.getString(R.string.duration_live);
+ case "Featured":
+ return c.getString(R.string.featured);
+ case "Radio":
+ return c.getString(R.string.radio);
default:
return kioskId;
}
@@ -59,9 +67,16 @@ public static int getKioskIcon(final String kioskId, final Context c) {
case "Local":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local);
case "Recently added":
+ case "recent":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent);
case "Most liked":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_thumb_up);
+ case "live":
+ return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_live_tv);
+ case "Featured":
+ return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_stars);
+ case "Radio":
+ return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_radio);
default:
return 0;
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
index 189b6823e..8fe411870 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
@@ -3,7 +3,7 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java
index 7e336f02d..f7301c275 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -5,7 +5,9 @@
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.preference.PreferenceManager;
+import android.icu.text.CompactDecimalFormat;
+import android.os.Build;
+import androidx.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
@@ -184,6 +186,11 @@ public static String localizeWatchingCount(final Context context, final long wat
}
public static String shortCount(final Context context, final long count) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ return CompactDecimalFormat.getInstance(getAppLocale(context),
+ CompactDecimalFormat.CompactStyle.SHORT).format(count);
+ }
+
double value = (double) count;
if (count >= 1000000000) {
return localizeNumber(context, round(value / 1000000000, 1))
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index ccaa79f98..1a83318cb 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -7,7 +7,7 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
@@ -52,6 +52,7 @@
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.player.PopupVideoPlayerActivity;
+import org.schabi.newpipe.player.ServicePlayerActivity;
import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.settings.SettingsActivity;
@@ -124,7 +125,8 @@ public static Intent getPlayerIntent(@NonNull final Context context,
.putExtra(BasePlayer.IS_MUTED, isMuted);
}
- public static void playOnMainPlayer(final Context context, final PlayQueue queue,
+ public static void playOnMainPlayer(final Context context,
+ final PlayQueue queue,
final boolean resumePlayback) {
final Intent playerIntent
= getPlayerIntent(context, MainVideoPlayer.class, queue, resumePlayback);
@@ -132,7 +134,8 @@ public static void playOnMainPlayer(final Context context, final PlayQueue queue
context.startActivity(playerIntent);
}
- public static void playOnPopupPlayer(final Context context, final PlayQueue queue,
+ public static void playOnPopupPlayer(final Context context,
+ final PlayQueue queue,
final boolean resumePlayback) {
if (!PermissionHelper.isPopupEnabled(context)) {
PermissionHelper.showPopupEnablementToast(context);
@@ -144,7 +147,8 @@ public static void playOnPopupPlayer(final Context context, final PlayQueue queu
getPlayerIntent(context, PopupVideoPlayer.class, queue, resumePlayback));
}
- public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue,
+ public static void playOnBackgroundPlayer(final Context context,
+ final PlayQueue queue,
final boolean resumePlayback) {
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
.show();
@@ -481,6 +485,11 @@ public static void openRouterActivity(final Context context, final String url) {
context.startActivity(mIntent);
}
+ public static void openBackgroundPlayer(final Context context) {
+ final Intent intent = new Intent(context, BackgroundPlayerActivity.class);
+ context.startActivity(intent);
+ }
+
public static void openAbout(final Context context) {
Intent intent = new Intent(context, AboutActivity.class);
context.startActivity(intent);
diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java
index e89cbf5db..69bbc05a2 100644
--- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java
@@ -2,7 +2,7 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import com.grack.nanojson.JsonArray;
import com.grack.nanojson.JsonObject;
diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
index 668895222..71480fe15 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
@@ -2,7 +2,7 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
@@ -38,6 +38,8 @@ public static int getIcon(final int serviceId) {
return R.drawable.place_holder_gadse;
case 3:
return R.drawable.place_holder_peertube;
+ case 4:
+ return R.drawable.place_holder_bandcamp;
default:
return R.drawable.place_holder_circle;
}
@@ -48,6 +50,7 @@ public static String getTranslatedFilterString(final String filter, final Contex
case "all":
return c.getString(R.string.all);
case "videos":
+ case "sepia_videos":
case "music_videos":
return c.getString(R.string.videos_string);
case "channels":
diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
index 74ea34fcc..f78450883 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
@@ -21,7 +21,7 @@
import android.content.Context;
import android.content.res.TypedArray;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java b/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java
index bd5ae10e8..4f26de544 100644
--- a/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java
+++ b/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java
@@ -246,7 +246,7 @@ private static void fixFocusHierarchy(final View decor) {
// keyboard META key for moving between clusters). We have to fix this unfortunate accident
// While we are at it, let's deal with touchscreenBlocksFocus too.
- if (Build.VERSION.SDK_INT < 26) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
diff --git a/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java b/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java
index 655b86818..1f2d5de8f 100644
--- a/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java
+++ b/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java
@@ -142,7 +142,7 @@ public boolean dispatchUnhandledMove(final View focused, final int direction) {
}
private boolean tryFocusFinder(final int direction) {
- if (Build.VERSION.SDK_INT >= 28) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// Android 9 implemented bunch of handy changes to focus, that render code below less
// useful, and also broke findNextFocusFromRect in way, that render this hack useless
return false;
diff --git a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java
index 618200f27..3d509eed7 100644
--- a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java
+++ b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java
@@ -54,12 +54,12 @@ public void run() {
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 void run() {
}
} 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) {
diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java
index b110d038b..f088991bd 100644
--- a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java
+++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java
@@ -1,313 +1,313 @@
-package us.shandian.giga.get;
-
-import android.util.Log;
-
-import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.StreamingService;
-import org.schabi.newpipe.extractor.exceptions.ExtractionException;
-import org.schabi.newpipe.extractor.stream.AudioStream;
-import org.schabi.newpipe.extractor.stream.StreamExtractor;
-import org.schabi.newpipe.extractor.stream.SubtitlesStream;
-import org.schabi.newpipe.extractor.stream.VideoStream;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.net.HttpURLConnection;
-import java.nio.channels.ClosedByInterruptException;
-import java.util.List;
-
-import us.shandian.giga.get.DownloadMission.HttpError;
-
-import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE;
-
-public class DownloadMissionRecover extends Thread {
- private static final String TAG = "DownloadMissionRecover";
- static final int mID = -3;
-
- private final DownloadMission mMission;
- private final boolean mNotInitialized;
-
- private final int mErrCode;
-
- private HttpURLConnection mConn;
- private MissionRecoveryInfo mRecovery;
- private StreamExtractor mExtractor;
-
- DownloadMissionRecover(DownloadMission mission, int errCode) {
- mMission = mission;
- mNotInitialized = mission.blocks == null && mission.current == 0;
- mErrCode = errCode;
- }
-
- @Override
- public void run() {
- if (mMission.source == null) {
- mMission.notifyError(mErrCode, null);
- return;
- }
-
- Exception err = null;
- int attempt = 0;
-
- while (attempt++ < mMission.maxRetry) {
- try {
- tryRecover();
- return;
- } catch (InterruptedIOException | ClosedByInterruptException e) {
- return;
- } catch (Exception e) {
- if (!mMission.running || super.isInterrupted()) return;
- err = e;
- }
- }
-
- // give up
- mMission.notifyError(mErrCode, err);
- }
-
- private void tryRecover() throws ExtractionException, IOException, HttpError {
- if (mExtractor == null) {
- try {
- StreamingService svr = NewPipe.getServiceByUrl(mMission.source);
- mExtractor = svr.getStreamExtractor(mMission.source);
- mExtractor.fetchPage();
- } catch (ExtractionException e) {
- mExtractor = null;
- throw e;
- }
- }
-
- // maybe the following check is redundant
- if (!mMission.running || super.isInterrupted()) return;
-
- if (!mNotInitialized) {
- // set the current download url to null in case if the recovery
- // process is canceled. Next time start() method is called the
- // recovery will be executed, saving time
- mMission.urls[mMission.current] = null;
-
- mRecovery = mMission.recoveryInfo[mMission.current];
- resolveStream();
- return;
- }
-
- Log.w(TAG, "mission is not fully initialized, this will take a while");
-
- try {
- for (; mMission.current < mMission.urls.length; mMission.current++) {
- mRecovery = mMission.recoveryInfo[mMission.current];
-
- if (test()) continue;
- if (!mMission.running) return;
-
- resolveStream();
- if (!mMission.running) return;
-
- // before continue, check if the current stream was resolved
- if (mMission.urls[mMission.current] == null) {
- break;
- }
- }
- } finally {
- mMission.current = 0;
- }
-
- mMission.writeThisToFile();
-
- if (!mMission.running || super.isInterrupted()) return;
-
- mMission.running = false;
- mMission.start();
- }
-
- private void resolveStream() throws IOException, ExtractionException, HttpError {
- // FIXME: this getErrorMessage() always returns "video is unavailable"
- /*if (mExtractor.getErrorMessage() != null) {
- mMission.notifyError(mErrCode, new ExtractionException(mExtractor.getErrorMessage()));
- return;
- }*/
-
- String url = null;
-
- switch (mRecovery.kind) {
- case 'a':
- for (AudioStream audio : mExtractor.getAudioStreams()) {
- if (audio.getAverageBitrate() == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) {
- url = audio.getUrl();
- break;
- }
- }
- break;
- case 'v':
- List videoStreams;
- if (mRecovery.desired2)
- videoStreams = mExtractor.getVideoOnlyStreams();
- else
- videoStreams = mExtractor.getVideoStreams();
- for (VideoStream video : videoStreams) {
- if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) {
- url = video.getUrl();
- break;
- }
- }
- break;
- case 's':
- for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.format)) {
- String tag = subtitles.getLanguageTag();
- if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) {
- url = subtitles.getUrl();
- break;
- }
- }
- break;
- default:
- throw new RuntimeException("Unknown stream type");
- }
-
- resolve(url);
- }
-
- private void resolve(String url) throws IOException, HttpError {
- if (mRecovery.validateCondition == null) {
- Log.w(TAG, "validation condition not defined, the resource can be stale");
- }
-
- if (mMission.unknownLength || mRecovery.validateCondition == null) {
- recover(url, false);
- return;
- }
-
- ///////////////////////////////////////////////////////////////////////
- ////// Validate the http resource doing a range request
- /////////////////////
- try {
- mConn = mMission.openConnection(url, true, mMission.length - 10, mMission.length);
- mConn.setRequestProperty("If-Range", mRecovery.validateCondition);
- mMission.establishConnection(mID, mConn);
-
- int code = mConn.getResponseCode();
-
- switch (code) {
- case 200:
- case 413:
- // stale
- recover(url, true);
- return;
- case 206:
- // in case of validation using the Last-Modified date, check the resource length
- long[] contentRange = parseContentRange(mConn.getHeaderField("Content-Range"));
- boolean lengthMismatch = contentRange[2] != -1 && contentRange[2] != mMission.length;
-
- recover(url, lengthMismatch);
- return;
- }
-
- throw new HttpError(code);
- } finally {
- disconnect();
- }
- }
-
- private void recover(String url, boolean stale) {
- Log.i(TAG,
- String.format("recover() name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url)
- );
-
- mMission.urls[mMission.current] = url;
-
- if (url == null) {
- mMission.urls = new String[0];
- mMission.notifyError(ERROR_RESOURCE_GONE, null);
- return;
- }
-
- if (mNotInitialized) return;
-
- if (stale) {
- mMission.resetState(false, false, DownloadMission.ERROR_NOTHING);
- }
-
- mMission.writeThisToFile();
-
- if (!mMission.running || super.isInterrupted()) return;
-
- mMission.running = false;
- mMission.start();
- }
-
- private long[] parseContentRange(String value) {
- long[] range = new long[3];
-
- if (value == null) {
- // this never should happen
- return range;
- }
-
- try {
- value = value.trim();
-
- if (!value.startsWith("bytes")) {
- return range;// unknown range type
- }
-
- int space = value.lastIndexOf(' ') + 1;
- int dash = value.indexOf('-', space) + 1;
- int bar = value.indexOf('/', dash);
-
- // start
- range[0] = Long.parseLong(value.substring(space, dash - 1));
-
- // end
- range[1] = Long.parseLong(value.substring(dash, bar));
-
- // resource length
- value = value.substring(bar + 1);
- if (value.equals("*")) {
- range[2] = -1;// unknown length received from the server but should be valid
- } else {
- range[2] = Long.parseLong(value);
- }
- } catch (Exception e) {
- // nothing to do
- }
-
- return range;
- }
-
- private boolean test() {
- if (mMission.urls[mMission.current] == null) return false;
-
- try {
- mConn = mMission.openConnection(mMission.urls[mMission.current], true, -1, -1);
- mMission.establishConnection(mID, mConn);
-
- if (mConn.getResponseCode() == 200) return true;
- } catch (Exception e) {
- // nothing to do
- } finally {
- disconnect();
- }
-
- return false;
- }
-
- private void disconnect() {
- try {
- try {
- mConn.getInputStream().close();
- } finally {
- mConn.disconnect();
- }
- } catch (Exception e) {
- // nothing to do
- } finally {
- mConn = null;
- }
- }
-
- @Override
- public void interrupt() {
- super.interrupt();
- if (mConn != null) disconnect();
- }
-}
+package us.shandian.giga.get;
+
+import android.util.Log;
+
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.extractor.stream.AudioStream;
+import org.schabi.newpipe.extractor.stream.StreamExtractor;
+import org.schabi.newpipe.extractor.stream.SubtitlesStream;
+import org.schabi.newpipe.extractor.stream.VideoStream;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.HttpURLConnection;
+import java.nio.channels.ClosedByInterruptException;
+import java.util.List;
+
+import us.shandian.giga.get.DownloadMission.HttpError;
+
+import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE;
+
+public class DownloadMissionRecover extends Thread {
+ private static final String TAG = "DownloadMissionRecover";
+ static final int mID = -3;
+
+ private final DownloadMission mMission;
+ private final boolean mNotInitialized;
+
+ private final int mErrCode;
+
+ private HttpURLConnection mConn;
+ private MissionRecoveryInfo mRecovery;
+ private StreamExtractor mExtractor;
+
+ DownloadMissionRecover(DownloadMission mission, int errCode) {
+ mMission = mission;
+ mNotInitialized = mission.blocks == null && mission.current == 0;
+ mErrCode = errCode;
+ }
+
+ @Override
+ public void run() {
+ if (mMission.source == null) {
+ mMission.notifyError(mErrCode, null);
+ return;
+ }
+
+ Exception err = null;
+ int attempt = 0;
+
+ while (attempt++ < mMission.maxRetry) {
+ try {
+ tryRecover();
+ return;
+ } catch (InterruptedIOException | ClosedByInterruptException e) {
+ return;
+ } catch (Exception e) {
+ if (!mMission.running || super.isInterrupted()) return;
+ err = e;
+ }
+ }
+
+ // give up
+ mMission.notifyError(mErrCode, err);
+ }
+
+ private void tryRecover() throws ExtractionException, IOException, HttpError {
+ if (mExtractor == null) {
+ try {
+ StreamingService svr = NewPipe.getServiceByUrl(mMission.source);
+ mExtractor = svr.getStreamExtractor(mMission.source);
+ mExtractor.fetchPage();
+ } catch (ExtractionException e) {
+ mExtractor = null;
+ throw e;
+ }
+ }
+
+ // maybe the following check is redundant
+ if (!mMission.running || super.isInterrupted()) return;
+
+ if (!mNotInitialized) {
+ // set the current download url to null in case if the recovery
+ // process is canceled. Next time start() method is called the
+ // recovery will be executed, saving time
+ mMission.urls[mMission.current] = null;
+
+ mRecovery = mMission.recoveryInfo[mMission.current];
+ resolveStream();
+ return;
+ }
+
+ Log.w(TAG, "mission is not fully initialized, this will take a while");
+
+ try {
+ for (; mMission.current < mMission.urls.length; mMission.current++) {
+ mRecovery = mMission.recoveryInfo[mMission.current];
+
+ if (test()) continue;
+ if (!mMission.running) return;
+
+ resolveStream();
+ if (!mMission.running) return;
+
+ // before continue, check if the current stream was resolved
+ if (mMission.urls[mMission.current] == null) {
+ break;
+ }
+ }
+ } finally {
+ mMission.current = 0;
+ }
+
+ mMission.writeThisToFile();
+
+ if (!mMission.running || super.isInterrupted()) return;
+
+ mMission.running = false;
+ mMission.start();
+ }
+
+ private void resolveStream() throws IOException, ExtractionException, HttpError {
+ // FIXME: this getErrorMessage() always returns "video is unavailable"
+ /*if (mExtractor.getErrorMessage() != null) {
+ mMission.notifyError(mErrCode, new ExtractionException(mExtractor.getErrorMessage()));
+ return;
+ }*/
+
+ String url = null;
+
+ switch (mRecovery.kind) {
+ case 'a':
+ for (AudioStream audio : mExtractor.getAudioStreams()) {
+ if (audio.getAverageBitrate() == mRecovery.desiredBitrate && audio.getFormat() == mRecovery.format) {
+ url = audio.getUrl();
+ break;
+ }
+ }
+ break;
+ case 'v':
+ List videoStreams;
+ if (mRecovery.desired2)
+ videoStreams = mExtractor.getVideoOnlyStreams();
+ else
+ videoStreams = mExtractor.getVideoStreams();
+ for (VideoStream video : videoStreams) {
+ if (video.resolution.equals(mRecovery.desired) && video.getFormat() == mRecovery.format) {
+ url = video.getUrl();
+ break;
+ }
+ }
+ break;
+ case 's':
+ for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.format)) {
+ String tag = subtitles.getLanguageTag();
+ if (tag.equals(mRecovery.desired) && subtitles.isAutoGenerated() == mRecovery.desired2) {
+ url = subtitles.getUrl();
+ break;
+ }
+ }
+ break;
+ default:
+ throw new RuntimeException("Unknown stream type");
+ }
+
+ resolve(url);
+ }
+
+ private void resolve(String url) throws IOException, HttpError {
+ if (mRecovery.validateCondition == null) {
+ Log.w(TAG, "validation condition not defined, the resource can be stale");
+ }
+
+ if (mMission.unknownLength || mRecovery.validateCondition == null) {
+ recover(url, false);
+ return;
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+ ////// Validate the http resource doing a range request
+ /////////////////////
+ try {
+ mConn = mMission.openConnection(url, true, mMission.length - 10, mMission.length);
+ mConn.setRequestProperty("If-Range", mRecovery.validateCondition);
+ mMission.establishConnection(mID, mConn);
+
+ int code = mConn.getResponseCode();
+
+ switch (code) {
+ case 200:
+ case 413:
+ // stale
+ recover(url, true);
+ return;
+ case 206:
+ // in case of validation using the Last-Modified date, check the resource length
+ long[] contentRange = parseContentRange(mConn.getHeaderField("Content-Range"));
+ boolean lengthMismatch = contentRange[2] != -1 && contentRange[2] != mMission.length;
+
+ recover(url, lengthMismatch);
+ return;
+ }
+
+ throw new HttpError(code);
+ } finally {
+ disconnect();
+ }
+ }
+
+ private void recover(String url, boolean stale) {
+ Log.i(TAG,
+ String.format("recover() name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url)
+ );
+
+ mMission.urls[mMission.current] = url;
+
+ if (url == null) {
+ mMission.urls = new String[0];
+ mMission.notifyError(ERROR_RESOURCE_GONE, null);
+ return;
+ }
+
+ if (mNotInitialized) return;
+
+ if (stale) {
+ mMission.resetState(false, false, DownloadMission.ERROR_NOTHING);
+ }
+
+ mMission.writeThisToFile();
+
+ if (!mMission.running || super.isInterrupted()) return;
+
+ mMission.running = false;
+ mMission.start();
+ }
+
+ private long[] parseContentRange(String value) {
+ long[] range = new long[3];
+
+ if (value == null) {
+ // this never should happen
+ return range;
+ }
+
+ try {
+ value = value.trim();
+
+ if (!value.startsWith("bytes")) {
+ return range;// unknown range type
+ }
+
+ int space = value.lastIndexOf(' ') + 1;
+ int dash = value.indexOf('-', space) + 1;
+ int bar = value.indexOf('/', dash);
+
+ // start
+ range[0] = Long.parseLong(value.substring(space, dash - 1));
+
+ // end
+ range[1] = Long.parseLong(value.substring(dash, bar));
+
+ // resource length
+ value = value.substring(bar + 1);
+ if (value.equals("*")) {
+ range[2] = -1;// unknown length received from the server but should be valid
+ } else {
+ range[2] = Long.parseLong(value);
+ }
+ } catch (Exception e) {
+ // nothing to do
+ }
+
+ return range;
+ }
+
+ private boolean test() {
+ if (mMission.urls[mMission.current] == null) return false;
+
+ try {
+ mConn = mMission.openConnection(mMission.urls[mMission.current], true, -1, -1);
+ mMission.establishConnection(mID, mConn);
+
+ if (mConn.getResponseCode() == 200) return true;
+ } catch (Exception e) {
+ // nothing to do
+ } finally {
+ disconnect();
+ }
+
+ return false;
+ }
+
+ private void disconnect() {
+ try {
+ try {
+ mConn.getInputStream().close();
+ } finally {
+ mConn.disconnect();
+ }
+ } catch (Exception e) {
+ // nothing to do
+ } finally {
+ mConn = null;
+ }
+ }
+
+ @Override
+ public void interrupt() {
+ super.interrupt();
+ if (mConn != null) disconnect();
+ }
+}
diff --git a/app/src/main/java/us/shandian/giga/get/FinishedMission.java b/app/src/main/java/us/shandian/giga/get/FinishedMission.java
index 6bc5423b8..29f3c6296 100644
--- a/app/src/main/java/us/shandian/giga/get/FinishedMission.java
+++ b/app/src/main/java/us/shandian/giga/get/FinishedMission.java
@@ -1,18 +1,18 @@
-package us.shandian.giga.get;
-
-import androidx.annotation.NonNull;
-
-public class FinishedMission extends Mission {
-
- public FinishedMission() {
- }
-
- public FinishedMission(@NonNull DownloadMission mission) {
- source = mission.source;
- length = mission.length;
- timestamp = mission.timestamp;
- kind = mission.kind;
- storage = mission.storage;
- }
-
-}
+package us.shandian.giga.get;
+
+import androidx.annotation.NonNull;
+
+public class FinishedMission extends Mission {
+
+ public FinishedMission() {
+ }
+
+ public FinishedMission(@NonNull DownloadMission mission) {
+ source = mission.source;
+ length = mission.length;
+ timestamp = mission.timestamp;
+ kind = mission.kind;
+ storage = mission.storage;
+ }
+
+}
diff --git a/app/src/main/java/us/shandian/giga/get/Mission.java b/app/src/main/java/us/shandian/giga/get/Mission.java
index 8e814a2af..81cddb4ae 100644
--- a/app/src/main/java/us/shandian/giga/get/Mission.java
+++ b/app/src/main/java/us/shandian/giga/get/Mission.java
@@ -1,60 +1,60 @@
-package us.shandian.giga.get;
-
-import androidx.annotation.NonNull;
-
-import java.io.Serializable;
-import java.util.Calendar;
-
-import us.shandian.giga.io.StoredFileHelper;
-
-public abstract class Mission implements Serializable {
- private static final long serialVersionUID = 1L;// last bump: 27 march 2019
-
- /**
- * Source url of the resource
- */
- public String source;
-
- /**
- * Length of the current resource
- */
- public long length;
-
- /**
- * creation timestamp (and maybe unique identifier)
- */
- public long timestamp;
-
- /**
- * pre-defined content type
- */
- public char kind;
-
- /**
- * The downloaded file
- */
- public StoredFileHelper storage;
-
- /**
- * Delete the downloaded file
- *
- * @return {@code true] if and only if the file is successfully deleted, otherwise, {@code false}
- */
- public boolean delete() {
- if (storage != null) return storage.delete();
- return true;
- }
-
- /**
- * Indicate if this mission is deleted whatever is stored
- */
- public transient boolean deleted = false;
-
- @NonNull
- @Override
- public String toString() {
- Calendar calendar = Calendar.getInstance();
- calendar.setTimeInMillis(timestamp);
- return "[" + calendar.getTime().toString() + "] " + (storage.isInvalid() ? storage.getName() : storage.getUri());
- }
-}
+package us.shandian.giga.get;
+
+import androidx.annotation.NonNull;
+
+import java.io.Serializable;
+import java.util.Calendar;
+
+import us.shandian.giga.io.StoredFileHelper;
+
+public abstract class Mission implements Serializable {
+ private static final long serialVersionUID = 1L;// last bump: 27 march 2019
+
+ /**
+ * Source url of the resource
+ */
+ public String source;
+
+ /**
+ * Length of the current resource
+ */
+ public long length;
+
+ /**
+ * creation timestamp (and maybe unique identifier)
+ */
+ public long timestamp;
+
+ /**
+ * pre-defined content type
+ */
+ public char kind;
+
+ /**
+ * The downloaded file
+ */
+ public StoredFileHelper storage;
+
+ /**
+ * Delete the downloaded file
+ *
+ * @return {@code true] if and only if the file is successfully deleted, otherwise, {@code false}
+ */
+ public boolean delete() {
+ if (storage != null) return storage.delete();
+ return true;
+ }
+
+ /**
+ * Indicate if this mission is deleted whatever is stored
+ */
+ public transient boolean deleted = false;
+
+ @NonNull
+ @Override
+ public String toString() {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(timestamp);
+ return "[" + calendar.getTime().toString() + "] " + (storage.isInvalid() ? storage.getName() : storage.getUri());
+ }
+}
diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java
index c1912351a..b8304cd6b 100644
--- a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java
+++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.java
@@ -1,115 +1,115 @@
-package us.shandian.giga.get;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import androidx.annotation.NonNull;
-
-import org.schabi.newpipe.extractor.MediaFormat;
-import org.schabi.newpipe.extractor.stream.AudioStream;
-import org.schabi.newpipe.extractor.stream.Stream;
-import org.schabi.newpipe.extractor.stream.SubtitlesStream;
-import org.schabi.newpipe.extractor.stream.VideoStream;
-
-import java.io.Serializable;
-
-public class MissionRecoveryInfo implements Serializable, Parcelable {
- private static final long serialVersionUID = 0L;
-
- MediaFormat format;
- String desired;
- boolean desired2;
- int desiredBitrate;
- byte kind;
- String validateCondition = null;
-
- public MissionRecoveryInfo(@NonNull Stream stream) {
- if (stream instanceof AudioStream) {
- desiredBitrate = ((AudioStream) stream).getAverageBitrate();
- desired2 = false;
- kind = 'a';
- } else if (stream instanceof VideoStream) {
- desired = ((VideoStream) stream).getResolution();
- desired2 = ((VideoStream) stream).isVideoOnly();
- kind = 'v';
- } else if (stream instanceof SubtitlesStream) {
- desired = ((SubtitlesStream) stream).getLanguageTag();
- desired2 = ((SubtitlesStream) stream).isAutoGenerated();
- kind = 's';
- } else {
- throw new RuntimeException("Unknown stream kind");
- }
-
- format = stream.getFormat();
- if (format == null) throw new NullPointerException("Stream format cannot be null");
- }
-
- @NonNull
- @Override
- public String toString() {
- String info;
- StringBuilder str = new StringBuilder();
- str.append("{type=");
- switch (kind) {
- case 'a':
- str.append("audio");
- info = "bitrate=" + desiredBitrate;
- break;
- case 'v':
- str.append("video");
- info = "quality=" + desired + " videoOnly=" + desired2;
- break;
- case 's':
- str.append("subtitles");
- info = "language=" + desired + " autoGenerated=" + desired2;
- break;
- default:
- info = "";
- str.append("other");
- }
-
- str.append(" format=")
- .append(format.getName())
- .append(' ')
- .append(info)
- .append('}');
-
- return str.toString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeInt(this.format.ordinal());
- parcel.writeString(this.desired);
- parcel.writeInt(this.desired2 ? 0x01 : 0x00);
- parcel.writeInt(this.desiredBitrate);
- parcel.writeByte(this.kind);
- parcel.writeString(this.validateCondition);
- }
-
- private MissionRecoveryInfo(Parcel parcel) {
- this.format = MediaFormat.values()[parcel.readInt()];
- this.desired = parcel.readString();
- this.desired2 = parcel.readInt() != 0x00;
- this.desiredBitrate = parcel.readInt();
- this.kind = parcel.readByte();
- this.validateCondition = parcel.readString();
- }
-
- public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
- @Override
- public MissionRecoveryInfo createFromParcel(Parcel source) {
- return new MissionRecoveryInfo(source);
- }
-
- @Override
- public MissionRecoveryInfo[] newArray(int size) {
- return new MissionRecoveryInfo[size];
- }
- };
-}
+package us.shandian.giga.get;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import org.schabi.newpipe.extractor.MediaFormat;
+import org.schabi.newpipe.extractor.stream.AudioStream;
+import org.schabi.newpipe.extractor.stream.Stream;
+import org.schabi.newpipe.extractor.stream.SubtitlesStream;
+import org.schabi.newpipe.extractor.stream.VideoStream;
+
+import java.io.Serializable;
+
+public class MissionRecoveryInfo implements Serializable, Parcelable {
+ private static final long serialVersionUID = 0L;
+
+ MediaFormat format;
+ String desired;
+ boolean desired2;
+ int desiredBitrate;
+ byte kind;
+ String validateCondition = null;
+
+ public MissionRecoveryInfo(@NonNull Stream stream) {
+ if (stream instanceof AudioStream) {
+ desiredBitrate = ((AudioStream) stream).getAverageBitrate();
+ desired2 = false;
+ kind = 'a';
+ } else if (stream instanceof VideoStream) {
+ desired = ((VideoStream) stream).getResolution();
+ desired2 = ((VideoStream) stream).isVideoOnly();
+ kind = 'v';
+ } else if (stream instanceof SubtitlesStream) {
+ desired = ((SubtitlesStream) stream).getLanguageTag();
+ desired2 = ((SubtitlesStream) stream).isAutoGenerated();
+ kind = 's';
+ } else {
+ throw new RuntimeException("Unknown stream kind");
+ }
+
+ format = stream.getFormat();
+ if (format == null) throw new NullPointerException("Stream format cannot be null");
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ String info;
+ StringBuilder str = new StringBuilder();
+ str.append("{type=");
+ switch (kind) {
+ case 'a':
+ str.append("audio");
+ info = "bitrate=" + desiredBitrate;
+ break;
+ case 'v':
+ str.append("video");
+ info = "quality=" + desired + " videoOnly=" + desired2;
+ break;
+ case 's':
+ str.append("subtitles");
+ info = "language=" + desired + " autoGenerated=" + desired2;
+ break;
+ default:
+ info = "";
+ str.append("other");
+ }
+
+ str.append(" format=")
+ .append(format.getName())
+ .append(' ')
+ .append(info)
+ .append('}');
+
+ return str.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(this.format.ordinal());
+ parcel.writeString(this.desired);
+ parcel.writeInt(this.desired2 ? 0x01 : 0x00);
+ parcel.writeInt(this.desiredBitrate);
+ parcel.writeByte(this.kind);
+ parcel.writeString(this.validateCondition);
+ }
+
+ private MissionRecoveryInfo(Parcel parcel) {
+ this.format = MediaFormat.values()[parcel.readInt()];
+ this.desired = parcel.readString();
+ this.desired2 = parcel.readInt() != 0x00;
+ this.desiredBitrate = parcel.readInt();
+ this.kind = parcel.readByte();
+ this.validateCondition = parcel.readString();
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ @Override
+ public MissionRecoveryInfo createFromParcel(Parcel source) {
+ return new MissionRecoveryInfo(source);
+ }
+
+ @Override
+ public MissionRecoveryInfo[] newArray(int size) {
+ return new MissionRecoveryInfo[size];
+ }
+ };
+}
diff --git a/app/src/main/java/us/shandian/giga/io/ProgressReport.java b/app/src/main/java/us/shandian/giga/io/ProgressReport.java
index 14ae9ded9..e382747f6 100644
--- a/app/src/main/java/us/shandian/giga/io/ProgressReport.java
+++ b/app/src/main/java/us/shandian/giga/io/ProgressReport.java
@@ -1,11 +1,11 @@
-package us.shandian.giga.io;
-
-public interface ProgressReport {
-
- /**
- * Report the size of the new file
- *
- * @param progress the new size
- */
- void report(long progress);
+package us.shandian.giga.io;
+
+public interface ProgressReport {
+
+ /**
+ * Report the size of the new file
+ *
+ * @param progress the new size
+ */
+ void report(long progress);
}
\ No newline at end of file
diff --git a/app/src/main/java/us/shandian/giga/io/StoredFileHelper.java b/app/src/main/java/us/shandian/giga/io/StoredFileHelper.java
index ad3ceec3d..eba9437e1 100644
--- a/app/src/main/java/us/shandian/giga/io/StoredFileHelper.java
+++ b/app/src/main/java/us/shandian/giga/io/StoredFileHelper.java
@@ -315,6 +315,7 @@ public boolean equals(StoredFileHelper storage) {
return false;
if (this.isInvalid() || storage.isInvalid()) {
+ if (this.srcName == null || storage.srcName == null || this.srcType == null || storage.srcType == null) return false;
return this.srcName.equalsIgnoreCase(storage.srcName) && this.srcType.equalsIgnoreCase(storage.srcType);
}
diff --git a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java
index 04958c495..dc46ced5d 100644
--- a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java
+++ b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java
@@ -1,44 +1,44 @@
-package us.shandian.giga.postprocessing;
-
-import androidx.annotation.NonNull;
-
-import org.schabi.newpipe.streams.OggFromWebMWriter;
-import org.schabi.newpipe.streams.io.SharpStream;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-class OggFromWebmDemuxer extends Postprocessing {
-
- OggFromWebmDemuxer() {
- super(true, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER);
- }
-
- @Override
- boolean test(SharpStream... sources) throws IOException {
- ByteBuffer buffer = ByteBuffer.allocate(4);
- sources[0].read(buffer.array());
-
- // youtube uses WebM as container, but the file extension (format suffix) is "*.opus"
- // check if the file is a webm/mkv file before proceed
-
- switch (buffer.getInt()) {
- case 0x1a45dfa3:
- return true;// webm/mkv
- case 0x4F676753:
- return false;// ogg
- }
-
- throw new UnsupportedOperationException("file not recognized, failed to demux the audio stream");
- }
-
- @Override
- int process(SharpStream out, @NonNull SharpStream... sources) throws IOException {
- OggFromWebMWriter demuxer = new OggFromWebMWriter(sources[0], out);
- demuxer.parseSource();
- demuxer.selectTrack(0);
- demuxer.build();
-
- return OK_RESULT;
- }
-}
+package us.shandian.giga.postprocessing;
+
+import androidx.annotation.NonNull;
+
+import org.schabi.newpipe.streams.OggFromWebMWriter;
+import org.schabi.newpipe.streams.io.SharpStream;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+class OggFromWebmDemuxer extends Postprocessing {
+
+ OggFromWebmDemuxer() {
+ super(true, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER);
+ }
+
+ @Override
+ boolean test(SharpStream... sources) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(4);
+ sources[0].read(buffer.array());
+
+ // youtube uses WebM as container, but the file extension (format suffix) is "*.opus"
+ // check if the file is a webm/mkv file before proceed
+
+ switch (buffer.getInt()) {
+ case 0x1a45dfa3:
+ return true;// webm/mkv
+ case 0x4F676753:
+ return false;// ogg
+ }
+
+ throw new UnsupportedOperationException("file not recognized, failed to demux the audio stream");
+ }
+
+ @Override
+ int process(SharpStream out, @NonNull SharpStream... sources) throws IOException {
+ OggFromWebMWriter demuxer = new OggFromWebMWriter(sources[0], out);
+ demuxer.parseSource();
+ demuxer.selectTrack(0);
+ demuxer.build();
+
+ return OK_RESULT;
+ }
+}
diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
old mode 100755
new mode 100644
index 3da0e75b8..828f1adaf
--- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
+++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
@@ -24,7 +24,7 @@
import android.os.IBinder;
import android.os.Message;
import android.os.Parcelable;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.util.Log;
import android.util.SparseArray;
import android.widget.Toast;
@@ -160,7 +160,7 @@ public void onCreate() {
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mNetworkStateListenerL = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
@@ -240,7 +240,7 @@ public void onDestroy() {
manageLock(false);
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
mConnectivityManager.unregisterNetworkCallback(mNetworkStateListenerL);
else
unregisterReceiver(mNetworkStateListener);
@@ -466,7 +466,7 @@ public void notifyFinishedDownload(String name) {
if (downloadDoneCount < 1) {
downloadDoneList.append(name);
- if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
downloadDoneNotification.setContentTitle(getString(R.string.app_name));
} else {
downloadDoneNotification.setContentTitle(null);
@@ -505,7 +505,7 @@ public void notifyFailedDownload(DownloadMission mission) {
.setContentIntent(mOpenDownloadList);
}
- if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
downloadFailedNotification.setContentTitle(getString(R.string.app_name));
downloadFailedNotification.setStyle(new NotificationCompat.BigTextStyle()
.bigText(getString(R.string.download_failed).concat(": ").concat(mission.storage.getName())));
diff --git a/app/src/main/java/us/shandian/giga/ui/common/Deleter.java b/app/src/main/java/us/shandian/giga/ui/common/Deleter.java
index a0828c23d..b005d0bf9 100644
--- a/app/src/main/java/us/shandian/giga/ui/common/Deleter.java
+++ b/app/src/main/java/us/shandian/giga/ui/common/Deleter.java
@@ -1,138 +1,138 @@
-package us.shandian.giga.ui.common;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.os.Handler;
-import android.view.View;
-
-import com.google.android.material.snackbar.Snackbar;
-
-import org.schabi.newpipe.R;
-
-import java.util.ArrayList;
-
-import us.shandian.giga.get.FinishedMission;
-import us.shandian.giga.get.Mission;
-import us.shandian.giga.service.DownloadManager;
-import us.shandian.giga.service.DownloadManager.MissionIterator;
-import us.shandian.giga.ui.adapter.MissionAdapter;
-
-public class Deleter {
- private static final int TIMEOUT = 5000;// ms
- private static final int DELAY = 350;// ms
- private static final int DELAY_RESUME = 400;// ms
-
- private Snackbar snackbar;
- private ArrayList items;
- private boolean running = true;
-
- private Context mContext;
- private MissionAdapter mAdapter;
- private DownloadManager mDownloadManager;
- private MissionIterator mIterator;
- private Handler mHandler;
- private View mView;
-
- private final Runnable rShow;
- private final Runnable rNext;
- private final Runnable rCommit;
-
- public Deleter(View v, Context c, MissionAdapter a, DownloadManager d, MissionIterator i, Handler h) {
- mView = v;
- mContext = c;
- mAdapter = a;
- mDownloadManager = d;
- mIterator = i;
- mHandler = h;
-
- // use variables to know the reference of the lambdas
- rShow = this::show;
- rNext = this::next;
- rCommit = this::commit;
-
- items = new ArrayList<>(2);
- }
-
- public void append(Mission item) {
- mIterator.hide(item);
- items.add(0, item);
-
- show();
- }
-
- private void forget() {
- mIterator.unHide(items.remove(0));
- mAdapter.applyChanges();
-
- show();
- }
-
- private void show() {
- if (items.size() < 1) return;
-
- pause();
- running = true;
-
- mHandler.postDelayed(rNext, DELAY);
- }
-
- private void next() {
- if (items.size() < 1) return;
-
- String msg = mContext.getString(R.string.file_deleted).concat(":\n").concat(items.get(0).storage.getName());
-
- snackbar = Snackbar.make(mView, msg, Snackbar.LENGTH_INDEFINITE);
- snackbar.setAction(R.string.undo, s -> forget());
- snackbar.setActionTextColor(Color.YELLOW);
- snackbar.show();
-
- mHandler.postDelayed(rCommit, TIMEOUT);
- }
-
- private void commit() {
- if (items.size() < 1) return;
-
- while (items.size() > 0) {
- Mission mission = items.remove(0);
- if (mission.deleted) continue;
-
- mIterator.unHide(mission);
- mDownloadManager.deleteMission(mission);
-
- if (mission instanceof FinishedMission) {
- mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mission.storage.getUri()));
- }
- break;
- }
-
- if (items.size() < 1) {
- pause();
- return;
- }
-
- show();
- }
-
- public void pause() {
- running = false;
- mHandler.removeCallbacks(rNext);
- mHandler.removeCallbacks(rShow);
- mHandler.removeCallbacks(rCommit);
- if (snackbar != null) snackbar.dismiss();
- }
-
- public void resume() {
- if (running) return;
- mHandler.postDelayed(rShow, DELAY_RESUME);
- }
-
- public void dispose() {
- if (items.size() < 1) return;
-
- pause();
-
- for (Mission mission : items) mDownloadManager.deleteMission(mission);
- items = null;
- }
-}
+package us.shandian.giga.ui.common;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Handler;
+import android.view.View;
+
+import com.google.android.material.snackbar.Snackbar;
+
+import org.schabi.newpipe.R;
+
+import java.util.ArrayList;
+
+import us.shandian.giga.get.FinishedMission;
+import us.shandian.giga.get.Mission;
+import us.shandian.giga.service.DownloadManager;
+import us.shandian.giga.service.DownloadManager.MissionIterator;
+import us.shandian.giga.ui.adapter.MissionAdapter;
+
+public class Deleter {
+ private static final int TIMEOUT = 5000;// ms
+ private static final int DELAY = 350;// ms
+ private static final int DELAY_RESUME = 400;// ms
+
+ private Snackbar snackbar;
+ private ArrayList items;
+ private boolean running = true;
+
+ private Context mContext;
+ private MissionAdapter mAdapter;
+ private DownloadManager mDownloadManager;
+ private MissionIterator mIterator;
+ private Handler mHandler;
+ private View mView;
+
+ private final Runnable rShow;
+ private final Runnable rNext;
+ private final Runnable rCommit;
+
+ public Deleter(View v, Context c, MissionAdapter a, DownloadManager d, MissionIterator i, Handler h) {
+ mView = v;
+ mContext = c;
+ mAdapter = a;
+ mDownloadManager = d;
+ mIterator = i;
+ mHandler = h;
+
+ // use variables to know the reference of the lambdas
+ rShow = this::show;
+ rNext = this::next;
+ rCommit = this::commit;
+
+ items = new ArrayList<>(2);
+ }
+
+ public void append(Mission item) {
+ mIterator.hide(item);
+ items.add(0, item);
+
+ show();
+ }
+
+ private void forget() {
+ mIterator.unHide(items.remove(0));
+ mAdapter.applyChanges();
+
+ show();
+ }
+
+ private void show() {
+ if (items.size() < 1) return;
+
+ pause();
+ running = true;
+
+ mHandler.postDelayed(rNext, DELAY);
+ }
+
+ private void next() {
+ if (items.size() < 1) return;
+
+ String msg = mContext.getString(R.string.file_deleted).concat(":\n").concat(items.get(0).storage.getName());
+
+ snackbar = Snackbar.make(mView, msg, Snackbar.LENGTH_INDEFINITE);
+ snackbar.setAction(R.string.undo, s -> forget());
+ snackbar.setActionTextColor(Color.YELLOW);
+ snackbar.show();
+
+ mHandler.postDelayed(rCommit, TIMEOUT);
+ }
+
+ private void commit() {
+ if (items.size() < 1) return;
+
+ while (items.size() > 0) {
+ Mission mission = items.remove(0);
+ if (mission.deleted) continue;
+
+ mIterator.unHide(mission);
+ mDownloadManager.deleteMission(mission);
+
+ if (mission instanceof FinishedMission) {
+ mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mission.storage.getUri()));
+ }
+ break;
+ }
+
+ if (items.size() < 1) {
+ pause();
+ return;
+ }
+
+ show();
+ }
+
+ public void pause() {
+ running = false;
+ mHandler.removeCallbacks(rNext);
+ mHandler.removeCallbacks(rShow);
+ mHandler.removeCallbacks(rCommit);
+ if (snackbar != null) snackbar.dismiss();
+ }
+
+ public void resume() {
+ if (running) return;
+ mHandler.postDelayed(rShow, DELAY_RESUME);
+ }
+
+ public void dispose() {
+ if (items.size() < 1) return;
+
+ pause();
+
+ for (Mission mission : items) mDownloadManager.deleteMission(mission);
+ items = null;
+ }
+}
diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java
index 09f4d0c79..52e858903 100644
--- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java
+++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java
@@ -11,7 +11,7 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
-import android.preference.PreferenceManager;
+import androidx.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java
index 551e80a3e..29f790e84 100644
--- a/app/src/main/java/us/shandian/giga/util/Utility.java
+++ b/app/src/main/java/us/shandian/giga/util/Utility.java
@@ -28,8 +28,10 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
+import java.util.Random;
import us.shandian.giga.io.StoredFileHelper;
+import us.shandian.giga.get.DownloadMission;
public class Utility {
@@ -278,6 +280,28 @@ public static long getContentLength(HttpURLConnection connection) {
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);
}
diff --git a/app/src/main/res/drawable-hdpi/ic_newpipe_update.png b/app/src/main/res/drawable-hdpi/ic_newpipe_update.png
old mode 100755
new mode 100644
diff --git a/app/src/main/res/drawable-mdpi/ic_newpipe_update.png b/app/src/main/res/drawable-mdpi/ic_newpipe_update.png
old mode 100755
new mode 100644
diff --git a/app/src/main/res/drawable-night/ic_heart.xml b/app/src/main/res/drawable-night/ic_heart.xml
new file mode 100644
index 000000000..6128a3d0d
--- /dev/null
+++ b/app/src/main/res/drawable-night/ic_heart.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable-nodpi/not_available_monkey.png b/app/src/main/res/drawable-nodpi/not_available_monkey.png
deleted file mode 100644
index ef0068bed..000000000
Binary files a/app/src/main/res/drawable-nodpi/not_available_monkey.png and /dev/null differ
diff --git a/app/src/main/res/drawable-nodpi/place_holder_bandcamp.png b/app/src/main/res/drawable-nodpi/place_holder_bandcamp.png
new file mode 100644
index 000000000..848e109c2
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/place_holder_bandcamp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_newpipe_update.png b/app/src/main/res/drawable-xhdpi/ic_newpipe_update.png
old mode 100755
new mode 100644
diff --git a/app/src/main/res/drawable-xxhdpi/ic_newpipe_update.png b/app/src/main/res/drawable-xxhdpi/ic_newpipe_update.png
old mode 100755
new mode 100644
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_newpipe_update.png b/app/src/main/res/drawable-xxxhdpi/ic_newpipe_update.png
old mode 100755
new mode 100644
diff --git a/app/src/main/res/drawable/ic_heart.xml b/app/src/main/res/drawable/ic_heart.xml
new file mode 100644
index 000000000..86d1f0527
--- /dev/null
+++ b/app/src/main/res/drawable/ic_heart.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_live_tv_black_24dp.xml b/app/src/main/res/drawable/ic_live_tv_black_24dp.xml
new file mode 100644
index 000000000..1f7957c4a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_live_tv_black_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_live_tv_white_24dp.xml b/app/src/main/res/drawable/ic_live_tv_white_24dp.xml
new file mode 100644
index 000000000..303858f9d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_live_tv_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_next_white_24dp.xml b/app/src/main/res/drawable/ic_next_white_24dp.xml
new file mode 100644
index 000000000..603880c2b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_next_white_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_pin_black_24dp.xml b/app/src/main/res/drawable/ic_pin_black_24dp.xml
new file mode 100644
index 000000000..161eed233
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pin_black_24dp.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_pin_white_24dp.xml b/app/src/main/res/drawable/ic_pin_white_24dp.xml
new file mode 100644
index 000000000..6be4f9acc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pin_white_24dp.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_previous_white_24dp.xml b/app/src/main/res/drawable/ic_previous_white_24dp.xml
new file mode 100644
index 000000000..14279ecb2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_previous_white_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_timer_black_24dp.xml b/app/src/main/res/drawable/ic_timer_black_24dp.xml
new file mode 100644
index 000000000..09d53a6e3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_timer_black_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_timer_white_24dp.xml b/app/src/main/res/drawable/ic_timer_white_24dp.xml
new file mode 100644
index 000000000..48e71ba7d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_timer_white_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/not_available_monkey.xml b/app/src/main/res/drawable/not_available_monkey.xml
new file mode 100644
index 000000000..b15a381c5
--- /dev/null
+++ b/app/src/main/res/drawable/not_available_monkey.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/activity_player_queue_control.xml b/app/src/main/res/layout-land/activity_player_queue_control.xml
index 84a29e0c8..a7872a83a 100644
--- a/app/src/main/res/layout-land/activity_player_queue_control.xml
+++ b/app/src/main/res/layout-land/activity_player_queue_control.xml
@@ -185,7 +185,7 @@
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
-
@@ -238,7 +238,7 @@
app:srcCompat="@drawable/ic_shuffle_white_24dp"
tools:ignore="ContentDescription"/>
-
diff --git a/app/src/main/res/layout-large-land/activity_main_player.xml b/app/src/main/res/layout-large-land/activity_main_player.xml
index 16dcff639..a52f7896c 100644
--- a/app/src/main/res/layout-large-land/activity_main_player.xml
+++ b/app/src/main/res/layout-large-land/activity_main_player.xml
@@ -159,6 +159,8 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/qualityTextView"
+ android:clickable="true"
+ android:focusable="true"
android:gravity="top"
android:orientation="vertical"
android:paddingLeft="8dp"
@@ -169,15 +171,16 @@
android:id="@+id/titleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:clickable="false"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
+ android:focusable="true"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="15sp"
android:textStyle="bold"
- android:clickable="true"
tools:ignore="RtlHardcoded"
tools:text="The Video Title LONG very LONG"/>
@@ -185,14 +188,15 @@
android:id="@+id/channelTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:clickable="false"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
+ android:focusable="true"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="12sp"
- android:clickable="true"
tools:text="The Video Artist LONG very LONG very Long"/>
diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml
index cb2b9ccfe..74f10556d 100644
--- a/app/src/main/res/layout-large-land/fragment_video_detail.xml
+++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml
@@ -48,7 +48,7 @@
android:id="@+id/detail_thumbnail_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@android:color/transparent"
+ android:background="?windowBackground"
android:contentDescription="@string/detail_thumbnail_view_description"
android:scaleType="fitCenter"
tools:ignore="RtlHardcoded"
diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml
index 4bae123ee..a86a76dec 100644
--- a/app/src/main/res/layout/activity_main_player.xml
+++ b/app/src/main/res/layout/activity_main_player.xml
@@ -157,6 +157,8 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/qualityTextView"
+ android:clickable="true"
+ android:focusable="true"
android:gravity="top"
android:orientation="vertical"
android:paddingLeft="8dp"
@@ -167,6 +169,7 @@
android:id="@+id/titleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:clickable="false"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
@@ -175,7 +178,6 @@
android:textColor="@android:color/white"
android:textSize="15sp"
android:textStyle="bold"
- android:clickable="true"
android:focusable="true"
tools:ignore="RtlHardcoded"
tools:text="The Video Title LONG very LONG"/>
@@ -184,6 +186,7 @@
android:id="@+id/channelTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:clickable="false"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
@@ -191,7 +194,6 @@
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="12sp"
- android:clickable="true"
android:focusable="true"
tools:text="The Video Artist LONG very LONG very Long"/>
diff --git a/app/src/main/res/layout/activity_player_queue_control.xml b/app/src/main/res/layout/activity_player_queue_control.xml
index c5b8e5743..35053c9c8 100644
--- a/app/src/main/res/layout/activity_player_queue_control.xml
+++ b/app/src/main/res/layout/activity_player_queue_control.xml
@@ -1,7 +1,7 @@
+ app:title="@string/app_name" />
@@ -30,42 +30,55 @@
android:id="@+id/play_queue"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:layout_above="@id/metadata"
android:layout_below="@id/appbar"
- android:layout_above="@id/center"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager"
- tools:listitem="@layout/play_queue_item"/>
+ tools:listitem="@layout/play_queue_item" />
-
+
+
+ android:layout_above="@id/progress_bar"
+ android:background="?attr/selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:orientation="vertical"
+ android:padding="8dp"
+ tools:ignore="RtlHardcoded,RtlSymmetry">
-
-
-
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:scrollHorizontally="true"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textSize="14sp"
+ tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta." />
+ tools:text="Duis posuere arcu condimentum lobortis mattis." />
-
-
-
+ android:layout_above="@+id/playback_controls">
+ tools:text="1:06:29" />
+ tools:secondaryProgress="50" />
+ tools:text="1:23:49" />
+ android:visibility="gone" />
+ tools:ignore="ContentDescription" />
-
+ app:srcCompat="@drawable/ic_previous_white_24dp"
+ tools:ignore="ContentDescription" />
+ android:src="@drawable/exo_controls_rewind"
+ android:tint="?attr/colorAccent" />
+ tools:ignore="ContentDescription" />
+ tools:visibility="visible" />
+ android:src="@drawable/exo_controls_fastforward"
+ android:tint="?attr/colorAccent" />
-
+ app:srcCompat="@drawable/ic_next_white_24dp"
+ tools:ignore="ContentDescription" />
+ tools:ignore="ContentDescription" />
diff --git a/app/src/main/res/layout/dialog_feed_group_create.xml b/app/src/main/res/layout/dialog_feed_group_create.xml
index 17893fecc..e88e42b12 100644
--- a/app/src/main/res/layout/dialog_feed_group_create.xml
+++ b/app/src/main/res/layout/dialog_feed_group_create.xml
@@ -207,7 +207,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@+id/confirm_button"
- android:text="@android:string/cancel" />
+ android:text="@string/cancel" />
+ android:paddingStart="6dp"
+ android:paddingTop="4dp"
+ android:paddingEnd="6dp">
@@ -346,32 +352,37 @@
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@+id/stepSizeSelector"
- android:layout_margin="@dimen/video_item_search_padding"
+ android:layout_marginStart="12dp"
+ android:layout_marginTop="6dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="6dp"
android:background="?attr/separator_color"/>
-
+ android:layout_height="match_parent"
+ android:layout_below="@id/separatorCheckbox"
+ android:orientation="vertical">
-
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_choose_tabs.xml b/app/src/main/res/layout/fragment_choose_tabs.xml
index 20fbc0bf8..60c281984 100644
--- a/app/src/main/res/layout/fragment_choose_tabs.xml
+++ b/app/src/main/res/layout/fragment_choose_tabs.xml
@@ -1,32 +1,49 @@
-
+
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_comments.xml b/app/src/main/res/layout/fragment_comments.xml
index 7159dd5b9..b8b27248a 100644
--- a/app/src/main/res/layout/fragment_comments.xml
+++ b/app/src/main/res/layout/fragment_comments.xml
@@ -42,6 +42,7 @@
tools:ignore="HardcodedText,UnusedAttribute"/>
-
+ android:layout_below="@+id/refresh_root_view">
+
+
+
+
+ app:tabGravity="fill"
+ app:tabMinWidth="60dp"/>
diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml
index 474c89d32..842913921 100644
--- a/app/src/main/res/layout/fragment_video_detail.xml
+++ b/app/src/main/res/layout/fragment_video_detail.xml
@@ -42,7 +42,8 @@
android:id="@+id/detail_thumbnail_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@android:color/transparent"
+ android:minHeight="200dp"
+ android:background="?windowBackground"
android:contentDescription="@string/detail_thumbnail_view_description"
android:scaleType="fitCenter"
tools:ignore="RtlHardcoded"
diff --git a/app/src/main/res/layout/list_channel_item.xml b/app/src/main/res/layout/list_channel_item.xml
index 547054cc0..86c3c34cc 100644
--- a/app/src/main/res/layout/list_channel_item.xml
+++ b/app/src/main/res/layout/list_channel_item.xml
@@ -1,32 +1,19 @@
-
-
-
-
+
+
+
diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml
index 1cbafdb6b..e06bcd57b 100644
--- a/app/src/main/res/layout/list_comments_item.xml
+++ b/app/src/main/res/layout/list_comments_item.xml
@@ -8,7 +8,7 @@
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
- android:padding="@dimen/video_item_search_padding">
+ android:padding="@dimen/comments_vertical_padding">
+
+
+
+
@@ -106,7 +129,7 @@
android:layout_height="wrap_content"
android:layout_below="@id/itemCommentContentView"
android:layout_marginLeft="12dp"
- android:layout_toRightOf="@id/detail_thumbs_up_count_view"
+ android:layout_toRightOf="@id/detail_heart_image_view"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
diff --git a/app/src/main/res/layout/list_radio_icon_item.xml b/app/src/main/res/layout/list_radio_icon_item.xml
index 947747c16..4c6d0d881 100644
--- a/app/src/main/res/layout/list_radio_icon_item.xml
+++ b/app/src/main/res/layout/list_radio_icon_item.xml
@@ -15,5 +15,5 @@
android:paddingStart="?attr/listPreferredItemPaddingLeft"
android:background="?attr/checked_selector"
android:textColor="?attr/textColorAlertDialogListItem"
- tools:drawableLeft="?attr/ic_play"
+ tools:drawableLeft="?attr/ic_play_arrow"
tools:text="Lorem ipsum dolor sit amet" />
diff --git a/app/src/main/res/layout/list_stream_item.xml b/app/src/main/res/layout/list_stream_item.xml
index d2000381d..1ed04b7e4 100644
--- a/app/src/main/res/layout/list_stream_item.xml
+++ b/app/src/main/res/layout/list_stream_item.xml
@@ -1,10 +1,10 @@
-
+ android:progressDrawable="?progress_horizontal_drawable"
+ app:layout_constraintEnd_toEndOf="@+id/itemThumbnailView"
+ app:layout_constraintStart_toStartOf="@+id/itemThumbnailView"
+ app:layout_constraintTop_toBottomOf="@+id/itemThumbnailView" />
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/local_playlist_header.xml b/app/src/main/res/layout/local_playlist_header.xml
index 04fe32ab0..c0f873ea1 100644
--- a/app/src/main/res/layout/local_playlist_header.xml
+++ b/app/src/main/res/layout/local_playlist_header.xml
@@ -21,14 +21,11 @@
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:marqueeRepeatLimit="marquee_forever"
- android:scrollHorizontally="true"
- android:singleLine="true"
+ android:ellipsize="end"
+ android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/playlist_detail_title_text_size"
- tools:text="Mix musics #23 title Lorem ipsum dolor sit amet, consectetur..."/>
+ tools:text="Mix musics #23 title Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/play_queue_item.xml b/app/src/main/res/layout/play_queue_item.xml
index a8f69ed34..aa64cd2e3 100644
--- a/app/src/main/res/layout/play_queue_item.xml
+++ b/app/src/main/res/layout/play_queue_item.xml
@@ -12,7 +12,7 @@
android:paddingTop="6dp"
android:paddingBottom="6dp">
-
-
+
+
diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml
new file mode 100644
index 000000000..05920099a
--- /dev/null
+++ b/app/src/main/res/menu/main_menu.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_about.xml b/app/src/main/res/menu/menu_about.xml
new file mode 100644
index 000000000..673cef94b
--- /dev/null
+++ b/app/src/main/res/menu/menu_about.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_local_playlist.xml b/app/src/main/res/menu/menu_local_playlist.xml
index a955e9539..41791b791 100644
--- a/app/src/main/res/menu/menu_local_playlist.xml
+++ b/app/src/main/res/menu/menu_local_playlist.xml
@@ -2,8 +2,12 @@
+
+ app:showAsAction="never" />
diff --git a/app/src/main/res/menu/menu_play_queue.xml b/app/src/main/res/menu/menu_play_queue.xml
index cab2e8df5..12b641778 100644
--- a/app/src/main/res/menu/menu_play_queue.xml
+++ b/app/src/main/res/menu/menu_play_queue.xml
@@ -39,4 +39,11 @@
android:orderInCategory="3"
android:title="@string/switch_to_main"
app:showAsAction="never"/>
+
+
diff --git a/app/src/main/res/menu/menu_playlist.xml b/app/src/main/res/menu/menu_playlist.xml
index 9a60caf54..e4c20a5ea 100644
--- a/app/src/main/res/menu/menu_playlist.xml
+++ b/app/src/main/res/menu/menu_playlist.xml
@@ -7,7 +7,7 @@
android:id="@+id/menu_item_share"
android:icon="?attr/ic_share"
android:title="@string/share"
- app:showAsAction="ifRoom"/>
+ app:showAsAction="ifRoom" />
+ tools:visible="true" />
+ app:showAsAction="never" />
+ app:showAsAction="never" />
\ No newline at end of file
diff --git a/app/src/main/res/menu/video_detail_menu.xml b/app/src/main/res/menu/video_detail_menu.xml
index 609b5a08c..6fd00b13f 100644
--- a/app/src/main/res/menu/video_detail_menu.xml
+++ b/app/src/main/res/menu/video_detail_menu.xml
@@ -25,4 +25,10 @@
android:orderInCategory="2"
android:title="@string/open_in_browser"
app:showAsAction="never"/>
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 80b730f36..0dcdc814c 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,4 +2,5 @@
+
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 2d007a043..8e70afd10 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -46,7 +46,7 @@
عرض خيار التشغيل بواسطة كودي
السمة
تم النشر بتاريخ %1$s
- رابط URL غير معتمد
+ رابط URL غير معتمد
استخدام مشغل صوت خارجي
استخدام مشغل Ùيديو خارجي
(إختبارية) إجراء التنزيلات من خلال استخدام بروكسي Tor لزيادة الخصوصية ( تشغيل الÙيديو المباشر غير مدعوم Øتى الأن ).
diff --git a/app/src/main/res/values-b+ast/strings.xml b/app/src/main/res/values-b+ast/strings.xml
index 213c6e884..5d7de2856 100644
--- a/app/src/main/res/values-b+ast/strings.xml
+++ b/app/src/main/res/values-b+ast/strings.xml
@@ -276,7 +276,7 @@
Tempu
Tonu
Refugar
- La URL nun se sofita
+ La URL nun se sofita
Reproduciendo en segundu planu
Reproduciendo nel mou ventanu
Atroxa llocalmente les consultes de gueta
diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml
index 40712041e..fa7dbd6d6 100644
--- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml
+++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml
@@ -29,7 +29,7 @@
黑色
下载
下一个
- ä¸æ”¯æŒçš„ URL
+ ä¸æ”¯æŒçš„ URL
外观
其他
全部
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index fbd7a99c0..c8f026ab9 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -85,7 +85,7 @@
\"ÐаÑтупнае\" и \"Прапанаванае\" відÑа
\"ЗаціÑніце, каб дадаць\"
Паказаць падказку пры націÑканні \"У акне\" ці \"У фоне\" на Ñтаронцы звеÑтак аб відÑа
- URL не падтрымліваецца
+ URL не падтрымліваецца
Краіна кантÑнту па змаўчанні
СÑрвіÑ
Мова кантÑнту па змаўчанні
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index 400c8d280..0d41f3186 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -68,7 +68,7 @@
Следващ клип
Показвай „Ñледващ“ и „подобни“
Показвай Ñъвет „задръж за добавÑне“
- Ðепознат URL
+ Ðепознат URL
Език на Ñъдържанието по подразбиране
Плейър
Поведение
diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml
index 0299457c3..264b767c2 100644
--- a/app/src/main/res/values-bn-rBD/strings.xml
+++ b/app/src/main/res/values-bn-rBD/strings.xml
@@ -49,7 +49,7 @@
ডাউনলোড
পরবরà§à¦¤à§€ à¦à¦¿à¦¡à¦¿à¦“
পরবরà§à¦¤à§€ à¦à¦¬à¦‚ অনà§à¦°à§‚প à¦à¦¿à¦¡à¦¿à¦“গà§à¦²à¦¿ দেখাও
- URL সমরà§à¦¥à¦¿à¦¤ নয়
+ URL সমরà§à¦¥à¦¿à¦¤ নয়
কনà§à¦Ÿà§‡à¦¨à§à¦Ÿ à¦à¦° জনà§à¦¯ পছনà§à¦¦à¦¸à¦‡ à¦à¦¾à¦·à¦¾
à¦à¦¿à¦¡à¦¿à¦“ à¦à¦¬à¦‚ অডিও
পপআপ
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index d77c6a63a..abb65418d 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -281,7 +281,7 @@
Emmagatzema les cerques localment
Crea un historial de vÃdeos visualitzats
Reprèn automà ticament
- Aquest URL no és compatible
+ Aquest URL no és compatible
Informe d\'error
Més tard
Filtra
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index f18da7ba0..b0d16cc44 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -35,7 +35,7 @@
Stáhnout
DalÅ¡Ã
Zobrazovat \'dalÅ¡Ã\' a \'podobná\' videa
- URL nenà podporováno
+ URL nenà podporováno
Preferovaný jazyk obsahu
Video a zvuk
Vzhled
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 91ca1040f..3cc1cef30 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -92,7 +92,7 @@
Vis \'Næste\' og \'Lignende\' videoer
Vis \"Hold for at tilføje\"-tip
Vis et tip når der trykkes på baggrunds- eller pop op-knappen på siden med videodetaljer
- Denne webadresse er ikke understøttet
+ Denne webadresse er ikke understøttet
Standardland for indhold
Tjeneste
Standardsprog for indhold
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 20c4e3286..0d4c15e71 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -27,7 +27,7 @@
Herunterladen
Nächste
„Nächste“ und „Ähnliche“ Videos anzeigen
- Nicht unterstützte URL
+ Nicht unterstützte URL
Video & Audio
Bevorzugte Sprache des Inhalts
Video-Vorschaubild
@@ -468,6 +468,7 @@
Ausschalten, um Kommentare auszublenden
Automatische Wiedergabe
Keine Kommentare
+ Kommentare sind deaktiviert
Kommentare konnten nicht geladen werden
Schließen
Wiedergabe fortsetzen
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index ed5a6b299..ca14bcd31 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -35,7 +35,7 @@
Λήψη
Επόμενο
Εμφάνιση \"Επόμενου\" και \"Σχετικών\" βίντεο
- Δεν υποστηÏίζεται η διεÏθυνση URL
+ Δεν υποστηÏίζεται η διεÏθυνση URL
Î ÏοεπιλεγμÎνη γλώσσα πεÏιεχομÎνου
Βίντεο & Ήχος
Εμφάνιση
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index f219ee2e8..44158131f 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -25,7 +25,7 @@
Luma
ElÅuti
Vica filmeto
- Ligilo ne subtenita
+ Ligilo ne subtenita
Preferata enhavlingvo
Filmeto kaj sono
Apero
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index cbff74f70..8beea383e 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -27,7 +27,7 @@
Formato de audio predeterminado
Descargar
Siguiente
- No se admite el URL
+ No se admite el URL
Usar reproductor de vÃdeo externo
Usar reproductor de audio externo
Tema
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index a32d26af2..383c16e89 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -84,7 +84,7 @@
Kuva \'järgmine\' ja \'sarnased\' videod
Kuva vihjet \"lisamiseks hoia\"
Kuva vihje, kui videoandmete lehel vajutatakse tausta või hüpikakna nupule
- Mitte toetatud URL
+ Mitte toetatud URL
Sisu vaikimisi riik
Teenus
Sisu vaikimisi keel
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index db02eaedc..2419cecd1 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -25,7 +25,7 @@
Deskargatu
Hurrengoa
Erakutsi \'hurrengo\' eta \'antzeko\' bideoak
- URLak ez du euskarririk
+ URLak ez du euskarririk
Edukiaren hizkuntz lehenetsia
Bideoa eta Audioa
Erreproduzitu
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 8f40e68ff..4ebc7ebfb 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -38,7 +38,7 @@
بارگیری
بعدی
نماش ویدیوهای «بعدی» و «مشابه»
- نشانی پشتیبانی نشده
+ نشانی پشتیبانی نشده
زبان Ù…Øتوای ترجیØÛŒ
ویدیو و صدا
ظاهر
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index d325f7ee4..d2822b5e9 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -67,7 +67,7 @@
Lataus
Seuraava
Näytä seuraavia ja samankaltaisia videoita
- URL ei tuettu
+ URL ei tuettu
Oletus-sisällon kieli
Soitin
Käyttäytyminen
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 0d14f1390..390c5fe30 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -27,7 +27,7 @@
Télécharger
Suivant
Afficher les vidéos « Suivantes » et « Similaires »
- URL non pris en charge
+ URL non pris en charge
Vidéo et audio
Autre
Miniature d’aperçu vidéo
@@ -614,4 +614,5 @@
Afficher la date originelle sur les items
Mode restreint de YouTube
Afficher les abonnements sans groupes uniquement
+ Ce contenu n\'est pas disponible dans votre pays.
\ No newline at end of file
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 8450d60c8..9c6ac9a8d 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -90,7 +90,7 @@
Mostrar vÃdeos «seguintes» e «semellantes»
Mostrar a suxestión «Manteña presionado para engadir á cola»
Mostrar unha suxestión ao premer o botón de segundo plano ou o de popup na páxina de detalles do vÃdeo
- Este URL non está soportado
+ Este URL non está soportado
PaÃs predeterminado para o contido
Servizo
Reprodutor
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml
index 0ac642b19..18a3447ab 100644
--- a/app/src/main/res/values-he/strings.xml
+++ b/app/src/main/res/values-he/strings.xml
@@ -54,7 +54,7 @@
הורדה
הב×
להציג ×¡×¨×˜×•× ×™× ×“×•×ž×™× ×•×‘××™× ×‘×ª×•×¨
- כתובת ×œ× × ×ª×ž×›×ª
+ כתובת ×œ× × ×ª×ž×›×ª
שפת התוכן המועדפת
×¡×¨×˜×•× ×™× ×•×©×ž×¢
חלון צף
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index ad1f3f5fc..90a27d31e 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -17,19 +17,19 @@
सदसà¥à¤¯à¤¤à¤¾ ली
चैनल सदसà¥à¤¯à¤¤à¤¾ रदà¥à¤¦ करी गयी
सदसà¥à¤¯à¤¤à¤¾
- पीछे
+ बैकगà¥à¤°à¤¾à¤‰à¤‚ड
पॉपअप
ऑटोपà¥â€à¤²à¥‡ करें
ऑडियो
हलका
- काली
+ काला
देखे हà¥à¤ वीडियो की सूची
डाउनलोड
अगला
वीडियो और ऑडियो
इतिहास और कैश à¤à¤‚डार
बैकगà¥à¤°à¤¾à¤‰à¤‚ड में चल रहा है
- कोई दरà¥à¤¶à¤• नहीं
+ कोई दृशà¥à¤¯ नहीं
कोई वीडियो नहीं है
नà¥à¤¯à¥‚पाइप के बारे में जाने
सेटिंग
@@ -44,7 +44,7 @@
योगदान करें
इतिहास
खोजा जा चूका है
- विडियो जो देख लिठगठहै
+ वीडियो जो देख लिठगठहै
इतिहास बंद है
इतिहास
इतिहास खाली है
@@ -69,47 +69,47 @@
ऑडियो डाउनलोड फ़ोलà¥à¤¡à¤°
डाउनलोड की गई वीडियो फ़ाइलें यहां संगà¥à¤°à¤¹à¥€à¤¤ हैं
ऑडियो फ़ाइलों के लिठडाउनलोड फ़ोलà¥à¤¡à¤° चà¥à¤¨à¥‡à¤‚
- अनà¥à¤¯ अपà¥à¤ª के दà¥à¤µà¤¾à¤°à¤¾ NewPipe के आहà¥à¤µà¤¾à¤¨ पर वीडियो तà¥à¤°à¤‚त चले
+ अनà¥à¤¯ अपà¥à¤ª के दà¥à¤µà¤¾à¤°à¤¾ नयूपाइप के आहà¥à¤µà¤¾à¤¨ पर वीडियो तà¥à¤°à¤‚त चले
वीडियो का डिफ़ॉलà¥à¤Ÿ रिज़ॉलà¥à¤¯à¥‚शन
- विडियो पॉपअप का डिफ़ॉलà¥à¤Ÿ रिज़ॉलà¥à¤¯à¥‚शन
+ वीडियो पॉपअप का डिफ़ॉलà¥à¤Ÿ रिज़ॉलà¥à¤¯à¥‚शन
उचà¥à¤š रिज़ॉलà¥à¤¯à¥‚शन दिखाà¤à¤‚
- केवल कà¥à¤› ही यंतà¥à¤° है जो 2K/4K विडियो चला सकते हैं
- Kodi से चलाये
- Kore à¤à¤ªà¥à¤ª नहीं मिली, इसे इनसà¥à¤Ÿà¥‰à¤² करें\?
- \"Kodi से चलाये\" वाला विकलà¥à¤ª दिखाà¤
- कोडी मीडिया सेंटर के माधà¥à¤¯à¤® से वीडियो चलाने के लिठà¤à¤• विकलà¥à¤ª पà¥à¤°à¤¦à¤°à¥à¤¶à¤¿à¤¤ करें
- डिफ़ॉलà¥à¤Ÿ ऑडियो का फॉरà¥à¤®à¥‡à¤Ÿ
- डिफ़ॉलà¥à¤Ÿ विडियो का फॉरà¥à¤®à¥‡à¤Ÿ
- à¤à¤ªà¥à¤ª का नया रूप
- काला
- विडियो पॉपअप की आकर और उसकी सà¥à¤¥à¤¤à¤¿ को याद रखे
- विडियो पॉपअप के पहले वाली आकर और उसकी सà¥à¤¥à¤¿à¤¤à¤¿ को याद रखे
+ केवल कà¥à¤› ही यंतà¥à¤° है जो 2K/4K वीडियो चला सकते हैं
+ कोडी मे चलाà¤
+ गैर- मौजूदा Kore à¤à¤ª सà¥à¤¥à¤¾à¤ªà¤¿à¤¤ करें\?
+ \"कोडी मे चलाà¤à¤‚\" वाला विकलà¥à¤ª दिखाà¤à¤
+ कोडी मीडिया सेंटर के माधà¥à¤¯à¤® से वीडियो चलाने के लिठविकलà¥à¤ª पà¥à¤°à¤¦à¤°à¥à¤¶à¤¿à¤¤ करें
+ पà¥à¤°à¤¥à¤® सà¥à¤¥à¤¾à¤ªà¤¿à¤¤ धà¥à¤µà¤¨à¤¿ फॉरà¥à¤®à¥‡à¤Ÿ
+ पà¥à¤°à¤¥à¤® सà¥à¤¥à¤¾à¤ªà¤¿à¤¤ वीडियो फॉरà¥à¤®à¥‡à¤Ÿ
+ à¤à¤ª थीम
+ गहरा
+ वीडियो पॉपअप की आकर और उसकी सà¥à¤¥à¤¤à¤¿ को याद रखें
+ वीडियो पॉपअप के पहले वाली आकर और उसकी सà¥à¤¥à¤¿à¤¤à¤¿ को याद रखें
पà¥à¤²à¥‡à¤¯à¤° इशारा नियंतà¥à¤°à¤£
- विडियो पà¥à¤²à¥‡à¤¯à¤° की बà¥à¤°à¤¾à¤‡à¤Ÿà¤¨à¥‡à¤¸ और धà¥à¤µà¤¨à¥€ को नियंतà¥à¤°à¤£ के लिठफ़ोन में इशारो का पà¥à¤°à¤¯à¥‹à¤— करे
- खोज के सà¥à¤à¤¾à¤µ देखे
- जब कà¥à¤› ढूंड रहे हो तो सà¥à¤à¤¾à¤µ दिखाये
- खोज के इतिहास को देखे
- खोज के query को फ़ोन की मेमोरी में ही रखे
- देखे हà¥à¤ विडियो की सूची रखे
- फोकस मिलने पर विडियो पà¥à¤¨à¤ƒ आगे चले
- रूकावट आने पर à¤à¥€ विडियो को जारी रखें (जैसे - फ़ोन कॉल आये)
- \'अगला\' और \'पहले समान\' वीडियो दिखाà¤
- \"जोड़ने के लिठपकड़ें रहे\" दिखाà¤
- जब बैकगà¥à¤°à¤¾à¤‰à¤‚ड और पॉपअप बटन विडियो के विवरण पनà¥à¤¨à¥‡ में दबाई जाठतो tip को दिखाà¤
- ये वाला URL इसमें नहीं चलेगा
+ वीडियो पà¥à¤²à¥‡à¤¯à¤° की बà¥à¤°à¤¾à¤‡à¤Ÿà¤¨à¥‡à¤¸ और धà¥à¤µà¤¨à¥€ को नियंतà¥à¤°à¤£ के लिठफ़ोन में इशारो का पà¥à¤°à¤¯à¥‹à¤— करें
+ खोज के सà¥à¤à¤¾à¤µ देखें
+ जब कà¥à¤› ढूंड रहे हों तो सà¥à¤à¤¾à¤µ दिखायें
+ खोज के इतिहास को देखें
+ खोज के query को फ़ोन की मेमोरी में ही रखें
+ देखे हà¥à¤ वीडियो की सूची रखें
+ फोकस मिलने पर वीडियो पà¥à¤¨à¤ƒ आगे चलें
+ रूकावटें (जैसे - फ़ोन कॉल) खतà¥à¤® होने के बाद विडियो पà¥à¤²à¥‡ करें
+ \'अगला\' और \'पहले समान\' वीडियो दिखाà¤à¤‚
+ \"कतार में जोड़ने के लिठसà¥à¤ªà¤°à¥à¤¶ बनाये रखें\" दिखाà¤à¤‚
+ जब बैकगà¥à¤°à¤¾à¤‰à¤‚ड और पॉपअप बटन वीडियो के विवरण पनà¥à¤¨à¥‡ में दबाई जाठतो सलाह दिखाà¤à¤‚
+ असमरà¥à¤¥à¤¿à¤¤ URL
डिफ़ॉलà¥à¤Ÿ विषय की à¤à¤¾à¤·à¤¾
पà¥à¤²à¥‡à¤¯à¤°
- चाल चलन
+ वà¥à¤¯à¤µà¤¹à¤¾à¤°
पॉपअप
दिखावट
और दà¥à¤¸à¤°à¥‡
- विडियो पॉपअप के अंदाज में चल रहा
+ वीडियो पॉपअप के अंदाज में चल रहा
बैकगà¥à¤°à¤¾à¤‰à¤‚ड पà¥à¤²à¥‡à¤¯à¤° की कतार पर
पॉपअप पà¥à¤²à¥‡à¤¯à¤° की कतार पर
- चलाये
+ चलायें
विषयवसà¥à¤¤à¥
- उमà¥à¤° पà¥à¤°à¤¤à¤¿à¤¬à¤‚धित विषय वसà¥à¤¤à¥
- उमà¥à¤° पà¥à¤°à¤¤à¤¿à¤¬à¤‚दित विडियो है .इस पà¥à¤°à¤•à¤¾à¤° की विषयवसà¥à¤¤à¥ को अनà¥à¤®à¤¤à¤¿ देने के लिठसेटिंग से संà¤à¤µ है |
+ आयॠपà¥à¤°à¤¤à¤¿à¤¬à¤‚धित सामगà¥à¤°à¥€ दिखाà¤à¤‚
+ उमà¥à¤° पà¥à¤°à¤¤à¤¿à¤¬à¤‚दित वीडियो है .इस पà¥à¤°à¤•à¤¾à¤° की विषयवसà¥à¤¤à¥ को अनà¥à¤®à¤¤à¤¿ देने के लिठसेटिंग से संà¤à¤µ है |
लाइव
डाउनलोड
डाउनलोड
@@ -119,57 +119,56 @@
पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ
सहमत हूà¤
बाद में
- बंद करे
+ बंद करें
छलनी
तरोताजा
साफ़
- आकार को छोटा-बड़ा करे
- बेहतर विडियो की कà¥à¤µà¤¾à¤²à¤¿à¤Ÿà¥€
- वापस जाà¤
- सारे पà¥à¤²à¥‡ करे
- NewPipe की अधिसूचना
- नà¥à¤¯à¥‚पाइप के बैकगà¥à¤°à¤¾à¤‰à¤‚ड में चल रहे विडियो और पॉपअप विडियो के लिठअधिसूचना
+ आकार को छोटा-बड़ा करें
+ बेहतर वीडियो की कà¥à¤µà¤¾à¤²à¤¿à¤Ÿà¥€
+ वापस जाà¤à¤‚
+ सारे पà¥à¤²à¥‡ करें
+ नयूपाइप की अधिसूचना
+ नà¥à¤¯à¥‚पाइप के बैकगà¥à¤°à¤¾à¤‰à¤‚ड में चल रहे वीडियो और पॉपअप वीडियो के लिठअधिसूचना
[नहीं जानते]
तà¥à¤°à¥à¤Ÿà¥€
नेटवरà¥à¤• में तà¥à¤°à¥à¤Ÿà¥€
- सारे thumbnail(फोटो जो फ़ोन की मेमोरी में है ) à¤à¤°à¥‡ नहीं जा सकते
- विडियो के URL signature को decrypt नहीं कर सकते
- इस website का निरंकà¥à¤·à¤£ नहीं कर सकते
+ सारे थंमनेल (फोटो जो फ़ोन की मेमोरी में है ) à¤à¤°à¥‡ नहीं जा सकते
+ वीडियो के URL signature को decrypt नहीं कर सकते
website का पूरी तरह से निरंकà¥à¤·à¤£ नहीं हो सकता
विषय वसà¥à¤¤à¥ उपलबà¥à¤§ नहीं है
डाउनलोड मेनू को सà¥à¤¥à¤¾à¤ªà¤¿à¤¤ नहीं कर सकते
"सीधे पà¥à¤°à¤¸à¤¾à¤°à¤£ के लिये फिलहाल समरà¥à¤¥à¤¨ नहीं है "
- कोई à¤à¥€ विडियो नहीं मिल रहा
- फोटो को load नहीं कर सकते
- APP/UI टूट गया
- इस विडियो को चलाने में असफल हà¥à¤
- कà¤à¥€ ठीक न होने वाले विडियो पà¥à¤²à¥‡à¤¯à¤° की तà¥à¤°à¥à¤Ÿà¥€ आ रही है
- विडियो पà¥à¤²à¥‡à¤¯à¤° तà¥à¤°à¥à¤Ÿà¥€ से ठीक हो रहा है
+ कोई à¤à¥€ वीडियो नहीं मिल रहा
+ फोटो को लोड नहीं कर सकते
+ APP/UI कà¥à¤°à¥ˆà¤¶ हो गया
+ इस वीडियो को चलाने में असफल हà¥à¤
+ कà¤à¥€ ठीक न होने वाले वीडियो पà¥à¤²à¥‡à¤¯à¤° की तà¥à¤°à¥à¤Ÿà¥€ आ रही है
+ वीडियो पà¥à¤²à¥‡à¤¯à¤° तà¥à¤°à¥à¤Ÿà¥€ से ठीक हो रहा है
खेद है की, à¤à¤¸à¤¾ होना नहीं चाहिठथा.
तà¥à¤°à¥à¤Ÿà¥€ की रिपोरà¥à¤Ÿ को ईमेल से à¤à¥‡à¤œà¥‡
- माफ़ करे , कà¥à¤› तà¥à¤°à¥à¤Ÿà¤¿à¤¯à¤¾à¤ हो रही है
+ माफ़ करे , कà¥à¤› तà¥à¤°à¥à¤Ÿà¤¿à¤¯à¤¾à¤ हो रही हैं
रिपोरà¥à¤Ÿ
जानकारी:
कà¥à¤¯à¤¾ हà¥à¤†:
कà¥à¤¯à¤¾:\\nमांग:\\nविषयवसà¥à¤¤à¥ की à¤à¤¾à¤·à¤¾:\\nसेवा:\\nजीà¤à¤®à¤Ÿà¥€ समय:\\nपैकेज:\\nसंसà¥à¤•à¤°à¤£:\\nOS संसà¥à¤•à¤°à¤£:
आपकी टिपà¥à¤ªà¤£à¥€:
विवरण:
- विडियो के thumbnail के पूरà¥à¤µ दरà¥à¤¶à¤¨
+ वीडियो के थंमनेल के पूरà¥à¤µ दरà¥à¤¶à¤¨
वीडियो चलाये, समय :
- अपलोडर के thumbnail वाले फोटो
+ अपलोडर के थंमनेल वाले फोटो
पसंद
नापसंद
Tor का पà¥à¤°à¤¯à¥‹à¤— करे
- ( पà¥à¤°à¤¯à¥‹à¤—ातà¥à¤®à¤• ) वà¥à¤¯à¤•à¥à¤¤à¤¿à¤—त सà¥à¤°à¤•à¥à¤·à¤¾ को बढाने के लिठTor के जरिये टà¥à¤°à¥ˆà¤«à¤¿à¤• को डाउनलोड करने के लिठजोर दे ( विडियो को सà¥à¤Ÿà¥à¤°à¥€à¤® अà¤à¥€ नहीं कर सकता ).
- तà¥à¤°à¥à¤Ÿà¥€ के रिपोरà¥à¤Ÿ करे
+ ( पà¥à¤°à¤¯à¥‹à¤—ातà¥à¤®à¤• ) वà¥à¤¯à¤•à¥à¤¤à¤¿à¤—त सà¥à¤°à¤•à¥à¤·à¤¾ को बढाने के लिठTor के जरिये टà¥à¤°à¥ˆà¤«à¤¿à¤• को डाउनलोड करने के लिठजोर दें ( वीडियो को सà¥à¤Ÿà¥à¤°à¥€à¤® अà¤à¥€ नहीं कर सकता ).
+ तà¥à¤°à¥à¤Ÿà¥€ के रिपोरà¥à¤Ÿ करें
उपयोगकरà¥à¤¤à¤¾ की रिपोरà¥à¤Ÿ
कोई परिणाम नहीं मिला
- यंहा कà¥à¤› नहीं है
+ यहां कà¥à¤› नहीं है
"डाउनलोड वाली डायरेकà¥à¤Ÿà¤°à¥€ को बना नहीं सकते \'%1$s\'"
डाउनलोड की निरà¥à¤¦à¥‡à¤¶à¤¿à¤•à¤¾ बना दी गई है \'%1$s\'
- विडियो
+ वीडियो
ऑडियो
- फिर से कोशिश करे
+ फिर से कोशिश करें
सà¥à¤Ÿà¥‹à¤°à¥‡à¤œ में पà¥à¤°à¤µà¥‡à¤¶ के लिठअनà¥à¤®à¤¤à¤¿ नहीं मिली
हज़ार
करोड़
@@ -177,11 +176,11 @@
कोई à¤à¥€ सदसà¥à¤¯ नहीं है
- %s सदसà¥à¤¯
- - %s सदसà¥à¤¯à¥‹
+ - %s सदसà¥à¤¯
- - %s दरà¥à¤¶à¤•
- - %s दरà¥à¤¶à¤•à¥‡
+ - %s दृषà¥à¤Ÿ
+ - %s दृषà¥à¤Ÿ
- %s वीडियो
@@ -201,10 +200,10 @@
फाइल पहले से ही बनी हà¥à¤ˆ है
URL सही नहीं है या फिर हो सकता है इनà¥à¤Ÿà¤°à¤¨à¥‡à¤Ÿ उपलबà¥à¤§ नहीं है
नà¥à¤¯à¥‚पाइप डाउनलोड हो रहा है
- विवरण देखने के लिठदबाये
+ विवरण देखने के लिठदबायें
कृपया इंतज़ार करे…
कà¥à¤²à¤¿à¤ªà¤¬à¥‹à¤°à¥à¤¡ पर कॉपी हो गया है
- कृपया बाद में सेटिंगà¥à¤¸ में डाउनलोड सà¥à¤¥à¤¾à¤¨ चà¥à¤¨à¥‡
+ कृपया बाद में सेटिंगà¥à¤¸ में डाउनलोड सà¥à¤¥à¤¾à¤¨ चà¥à¤¨à¥‡à¤‚
पॉपअप के तरीके में खोलने के लिठअनà¥à¤®à¤¤à¤¿ की जरà¥à¤°à¤¤ है
reCAPTCHA चà¥à¤¨à¥Œà¤¤à¥€
reCAPTCHA चà¥à¤¨à¥Œà¤¤à¥€ का अनà¥à¤°à¥‹à¤§ किया
@@ -214,29 +213,29 @@
रिपà¥à¤²à¥‡à¤¸à¤®à¥‡à¤‚ट करैकà¥à¤Ÿà¤°
वरà¥à¤£ और अंक
विशेष वरà¥à¤£
- %2$s के दà¥à¤µà¤¾à¤°à¤¾ © %1$s जो %3$s के अधीन आते है
+ %2$s के दà¥à¤µà¤¾à¤°à¤¾ © %1$s जो %3$s के अधीन आते हैं
लाइसेंस load नहीं हो रहा
सहयोगकरà¥à¤¤à¤¾
à¤à¤‚डà¥à¤°à¤¾à¤‡à¤¡ के लिठहलà¥à¤•à¤¾ और मà¥à¤«à¥à¤¤ सà¥à¤Ÿà¥à¤°à¥€à¤®à¤¿à¤‚ग à¤à¤ªà¥à¤²à¤¿à¤•à¥‡à¤¶à¤¨|
अगर आपके पास कोई सà¥à¤à¤¾à¤µ हो जैसे -अनà¥à¤µà¤¾à¤¦ , डिजाईन में बदलाव ,code को साफ़ रखना , या फिर code में जायदा बदलाव लाना हो तो - साहयता के लिठआपका सà¥à¤µà¤¾à¤—त है . जितना जà¥à¤¯à¤¾à¤¦à¤¾ होगा उतना बेहतर होगा !
- कà¥à¤¯à¤¾ आप इसको खोज इतिहास के मिटाना चाहते है ?
+ कà¥à¤¯à¤¾ आप इसको खोज इतिहास के मिटाना चाहते हैं ?
मà¥à¤–à¥à¤¯ पेज की विषयवसà¥à¤¤à¥
खाली पनà¥à¤¨à¤¾
- kiosk पनà¥à¤¨à¤¾
+ कियोसà¥à¤• पनà¥à¤¨à¤¾
सदसà¥à¤¯à¤¤à¤¾ वाला पनà¥à¤¨à¤¾
फीड वाला पेज
चैनल वाला पनà¥à¤¨à¤¾
- चैनल को चà¥à¤¨à¥‡
- अà¤à¥€ तक किसी à¤à¥€ चैनल के सदसà¥à¤¯ नहीं है
- kiosk को चà¥à¤¨à¥‡
- kiosk
+ चैनल को चà¥à¤¨à¥‡à¤‚
+ अà¤à¥€ तक किसी à¤à¥€ चैनल के सदसà¥à¤¯ नहीं हैं
+ कियोसà¥à¤• को चà¥à¤¨à¥‡à¤‚
+ कियोसà¥à¤•
टॉप 50
- नया और गरम
- पृषà¥à¤ à¤à¥‚मि पà¥à¤²à¥‡à¤¯à¤°
+ नया और पà¥à¤°à¤šà¤²à¤¿à¤¤
+ बैकगà¥à¤°à¤¾à¤‰à¤‚ड पà¥à¤²à¥‡à¤¯à¤°
पॉपअप पà¥à¤²à¥‡à¤¯à¤°
- निकाले
+ निकालें
विवरण
- जोड़ने के लिठपकड़ें रहे
+ जोड़ने के लिठपकड़ें रहें
पृषà¥à¤Ÿà¤à¥‚मि की कतार में लगाà¤
नठपॉपअप कि कतार में लगाà¤
यंहा से चलाना शà¥à¤°à¥‚ करे
@@ -270,8 +269,8 @@
ख़ारिज करें
नाम बदलें
दान करें
- NewPipe सà¥à¤µà¤¯à¤‚सेवकों दà¥à¤µà¤¾à¤°à¤¾ विकसित किया जाता है जो आपको अचà¥à¤›à¤¾ अनà¥à¤à¤µ देने के लिठअपना खाली समय वà¥à¤¯à¤¤à¥€à¤¤ करते हैं। सà¥à¤µà¤¯à¤‚सेवको को मदद à¤à¥‡à¤œà¥‡, ताकि वह NewPipe को और अचà¥à¤›à¤¾ बना सके।
- वापस दे
+ नयूपाइप सà¥à¤µà¤¯à¤‚सेवकों दà¥à¤µà¤¾à¤°à¤¾ विकसित किया जाता है जो आपको अचà¥à¤›à¤¾ अनà¥à¤à¤µ देने के लिठअपना खाली समय वà¥à¤¯à¤¤à¥€à¤¤ करते हैं। सà¥à¤µà¤¯à¤‚सेवको को मदद à¤à¥‡à¤œà¥‡, ताकि वह नयूपाइप को और अचà¥à¤›à¤¾ बना सके।
+ वापस दें
वेबसाइट
अधिक जानकारी और खबरों के लिठनà¥à¤¯à¥‚पाइप की वेबसाइट पर जाà¤à¤‚।
कà¥à¤¯à¤¾ आप इस आइटम को वॉच इतिहास से हटाना चाहते हैं?
@@ -291,19 +290,19 @@
पॉपअप पà¥à¤²à¥‡à¤¯à¤°
हमेशा पूछें
जानकारी पà¥à¤°à¤¾à¤ªà¥à¤¤ की जा रही है…
- अनà¥à¤°à¥‹à¤§à¤¿à¤¤ सामगà¥à¤°à¥€ लोड कर रहे है
+ अनà¥à¤°à¥‹à¤§à¤¿à¤¤ सामगà¥à¤°à¥€ लोड कर रहे हैं
नई पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ
हटाà¤à¤‚
नाम बदलें
नाम
पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ में जोड़ें
- पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ थंबनेल के रूप में सेट करें
+ पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ थंमनेल के रूप में सेट करें
पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ बà¥à¤•à¤®à¤¾à¤°à¥à¤• करें
बà¥à¤•à¤®à¤¾à¤°à¥à¤• हटायें
पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ को हटाना चाहते हैं\?
सूची बना दी गई
पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ में जोड़ा गया
- पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ का थंबनेल बदल दिया गया है।
+ पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ का थंमनेल बदल दिया गया है।
सूची हटाने में असफल।
कोई अनà¥à¤¶à¥€à¤°à¥à¤·à¤• नहीं है
फिट
@@ -314,20 +313,20 @@
LeakCanary सकà¥à¤·à¤® करें
हीप डंप करने के दौरान मेमोरी लीक मॉनिटरिंग à¤à¤ª को अनà¥à¤¤à¥à¤¤à¤°à¤¦à¤¾à¤¯à¥€ बना सकता है
Out-of-Lifecycle तà¥à¤°à¥à¤Ÿà¤¿à¤¯à¥‹à¤‚ की रिपोरà¥à¤Ÿ करें
- छायापà¥à¤°à¤¾à¤°à¥à¤ª लोड करें
- तेजी से अचूक तलाश का पà¥à¤°à¤¯à¥‹à¤— करें
- अचूक खोज पà¥à¤²à¥‡à¤¯à¤° को कम परिशà¥à¤¦à¥à¤§à¤¤à¤¾ के साथ तेजी से पदों की तलाश करने की अनà¥à¤®à¤¤à¤¿ देता है
- थंबनेल लोड करने, डेटा और मेमोरी उपयोग को रोकने के लिठबंद करें। इन-मेमोरी और ऑन-डिसà¥à¤• छवि कैश दोनों को बदलता है|
- छवि कैश मिटा दिया
- कैश मेटाडेटा वाइप करें
- सà¤à¥€ कैश किठगठवेबपृषà¥à¤ डेटा हटाà¤à¤‚
- मेटाडाटा कैश मिटा दिया गया
- अगली सà¥à¤Ÿà¥à¤°à¥€à¤® को सà¥à¤µà¤šà¤¾à¤²à¤¿à¤¤ रूप से जोड़ें
+ थंमनेल लोड करें
+ तेज और अचूक तलाश का पà¥à¤°à¤¯à¥‹à¤— करें
+ अचूक खोज से पà¥à¤²à¥‡à¤¯à¤° में कम सटीकता से लेकिन तेजी से वीडियो पोजीशनà¥à¤¸ की तलाश कर सकता हैं। 5, 15 या 25 सेकंड की तलाश में यह काम नहीं करता
+ थंमनेल लोड करने, डेटा और मेमोरी उपयोग को रोकने के लिठबंद करें। इन-मेमोरी और ऑन-डिसà¥à¤• छवि कैश दोनों को बदलता है|
+ छवि कैश मिटाया गया
+ कैश मेटाडेटा मिटाà¤à¤‚
+ कैश किठगठसà¤à¥€ वेबपेज का डेटा हटाà¤à¤‚
+ मेटाडाटा कैश मिटाया गया
+ अगली सà¥à¤Ÿà¥à¤°à¥€à¤® को ऑटोमैटिकली जोड़ें
गैर-दोहराने वाली कतार में अंतिम सà¥à¤Ÿà¥à¤°à¥€à¤® चलाते समय संबंधित सà¥à¤Ÿà¥à¤°à¥€à¤® को सà¥à¤µà¤¤à¤ƒ संलगà¥à¤¨ करें
फाइल
- चेनलà¥à¤¸
+ चैनल
सूची
- कà¥à¤¸
+ टà¥à¤°à¥ˆà¤•
उपà¤à¥‹à¤—ता
देखे हà¥à¤ वीडियो की सूची साफ करें
चलाये गठसà¥à¤Ÿà¥à¤°à¥€à¤® के इतिहास और पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨à¥‹à¤‚ को साफ करता है
@@ -335,40 +334,44 @@
देखे हà¥à¤ का इतिहास साफ कर दिया गया।
ढूंढने के इतिहास को साफ करें
ढूंढे गठशबà¥à¤¦à¥‹ का इतिहास साफ करता है
- पूरे खोज इतिहास को मिटा दे \?
+ पूरे खोज इतिहास को मिटा दें \?
ढूंढने के इतिहास को साफ कर दिया।
à¤à¤¸à¥€ कोई à¤à¥€ फ़ोलà¥à¤¡à¤° मौजूद नहीं है
अमानà¥à¤¯ फाइल/विषय - वसà¥à¤¤à¥ का सà¥à¤°à¥‹à¤¤
फ़ाइल मौजूद नहीं है या उसे पढ़ने या लिखने की परà¥à¤¯à¤¾à¤ªà¥à¤¤ अनà¥à¤®à¤¤à¤¿ नही़ं है
फ़ाइल का नाम खाली नहीं हो सकता
à¤à¤• à¤à¥‚ल हà¥à¤ˆ: %1$s
- डाउनलोड करने के लिठकोई सà¥à¤Ÿà¥à¤°à¥€à¤® उपलबà¥à¤§ नही है
+ डाउनलोड करने के लिठकोई सà¥à¤Ÿà¥à¤°à¥€à¤® उपलबà¥à¤§ नहीं है
à¤à¤• चीज़ साफ कर दी गई।
- इस फ़ाइल को चलाने के लिठकोई à¤à¤ª सà¥à¤¥à¤¾à¤ªà¤¿à¤¤ नही है
+ इस फ़ाइल को चलाने के लिठकोई à¤à¤ª सà¥à¤¥à¤¾à¤ªà¤¿à¤¤ नहीं है
नà¥à¤¯à¥‚पाइप की गोपनीयता नीति
नà¥à¤¯à¥‚पाइप परियोजना आपकी गोपनीयता को बहोत गंà¤à¥€à¤° रूप से लेता है। इसलिà¤, à¤à¤ª आपकी अनà¥à¤®à¤¤à¤¿ के बिना कोई डेटा जमा नही करता।
-\nनà¥à¤¯à¥‚पाइप की गोपनीयता नीति विसà¥à¤¤à¤¾à¤° से समज़ाती है कि कोनसा डेटा à¤à¥‡à¤œà¤¾ या संगà¥à¤°à¤¹ किया जाता है जब आप कà¥à¤°à¥‡à¤¶ विवरण à¤à¥‡à¤œà¤¤à¥‡ है।
- गोपनीयता नीति पà¥à¥‡
+\nनà¥à¤¯à¥‚पाइप की गोपनीयता नीति विसà¥à¤¤à¤¾à¤° से समज़ाती है कि कोनसा डेटा à¤à¥‡à¤œà¤¾ या संगà¥à¤°à¤¹ किया जाता है जब आप कà¥à¤°à¥‡à¤¶ विवरण à¤à¥‡à¤œà¤¤à¥‡ हैं।
+ गोपनीयता नीति पà¥à¥‡à¤‚
कà¥à¤¯à¤¾ आप सेटिंगà¥à¤¸ à¤à¥€ आयात करना चाहते है?
- पसंदीदा \'खोलने\' कि पà¥à¤°à¤•à¥à¤°à¤¿à¤¯à¤¾
+ पसंदीदा \'खोलने\' की पà¥à¤°à¤•à¥à¤°à¤¿à¤¯à¤¾
सामगà¥à¤°à¥€ खोलते समय डिफ़ॉलà¥à¤Ÿ कारवाही — %s
अनà¥à¤¶à¥€à¤°à¥à¤·à¤•
- पà¥à¤²à¥‡à¤¯à¤° अनà¥à¤¶à¥€à¤°à¥à¤·à¤• के शबà¥à¤¦à¥‹à¤‚ का परिमाण और पृषà¥à¤ à¤à¥‚मि शैलियो को बदले। लागू करने के लिठà¤à¤ª को पà¥à¤¨à¤ƒ पà¥à¤°à¤¾à¤°à¤®à¥à¤ करना जरूरी है।
+ पà¥à¤²à¥‡à¤¯à¤° अनà¥à¤¶à¥€à¤°à¥à¤·à¤• के शबà¥à¤¦à¥‹à¤‚ का परिमाण और बैकगà¥à¤°à¤¾à¤‰à¤‚ड शैलियो को बदलें। लागू करने के लिठà¤à¤ª को पà¥à¤¨à¤ƒ पà¥à¤°à¤¾à¤°à¤®à¥à¤ करना जरूरी है।
आयात/निरà¥à¤¯à¤¾à¤¤
आयात
- से आयात करे
- पर निरà¥à¤¯à¤¾à¤¤ करे
+ से आयात करें
+ पर निरà¥à¤¯à¤¾à¤¤ करें
आयात किया जा रहा है…
निरà¥à¤¯à¤¾à¤¤ किया जा रहा है…
- फाइल आयात करे
+ फाइल आयात करें
पहले वाला निरà¥à¤¯à¤¾à¤¤
सबà¥à¤¸à¤•à¥à¤°à¤¿à¤ªà¥à¤¶à¤¨ आयात नही कर सके
सबà¥à¤¸à¤•à¥à¤°à¤¿à¤ªà¥à¤¶à¤¨ निरà¥à¤¯à¤¾à¤¤ नही कर सके
- निरà¥à¤¯à¤¾à¤¤ की गई फाइल को डाउनलोड करके यूटà¥à¤¯à¥‚ब सबà¥à¤¸à¤•à¥à¤°à¤¿à¤ªà¥à¤¶à¤¨ को आयात करे:
+ गूगल टेकआउट से यूटà¥à¤¯à¥‚ब सदसà¥à¤¯à¤¤à¤¾ आयात करें:
\n
-\n1. इस URL पर जाà¤: %1$s
-\n2. जब कहा जाà¤, लॉगिन करे
-\n3. à¤à¤• डाउनलोड शà¥à¤°à¥‚ होना चाहिठ(यही निरà¥à¤¯à¤¾à¤¤ की गई फाइल है)
+\n1. इस यूआरà¤à¤² पर जाà¤à¤‚: %1$s
+\n2. पूछे जाने पर लॉग इन करें
+\n3. \"सà¤à¥€ डाटा शामिल करें\" पर कà¥à¤²à¤¿à¤• करें, फिर \"सà¤à¥€ को अचयनित करें\" पर, फिर केवल \"सदसà¥à¤¯à¤¤à¤¾à¤à¤\" चà¥à¤¨à¥‡à¤‚ और \" ठीक है\" पर कà¥à¤²à¤¿à¤• करें
+\n4. \"अगला चरण\" पर कà¥à¤²à¤¿à¤• करें और फिर \"निरà¥à¤¯à¤¾à¤¤ बनाà¤à¤‚\"
+\n5. पà¥à¤°à¤•à¤Ÿ होने के बाद \"डाउनलोड\" बटन पर कà¥à¤²à¤¿à¤• करें
+\n6. नीचे आयात फ़ाइल पर कà¥à¤²à¤¿à¤• करें और डाउनलोड की गई .zip फ़ाइल चà¥à¤¨à¥‡à¤‚
+\n7. [यदि .zip आयात विफल हो जाता है] .csv फ़ाइल निकालें (आमतौर पर \"यूटà¥à¤¯à¥‚ब और यूटà¥à¤¯à¥‚ब मà¥à¤¯à¥à¤œà¤¼à¤¿à¤•/सबà¥à¤¸à¤•à¥à¤°à¤¿à¤ªà¤¶à¤¨/subscriptions.csv\" के अंतरà¥à¤—त), नीचे आयात फ़ाइल पर कà¥à¤²à¤¿à¤• करें और निकाली गई सीà¤à¤¸à¤µà¥€ फ़ाइल चà¥à¤¨à¥‡à¤‚
आपका आई डी, soundcloud.com/(आपका आई डी)
धà¥à¤¯à¤¾à¤¨ रखे, यह तरीका नेटवरà¥à¤• साधनो के लिठमंहगा हो सकता है।
\n
@@ -377,42 +380,42 @@
गति
ऊंचाई
अनलिंक (बिगाड़ सकता है)
- खामोशी के समय तेज़ी से आगे बà¥à¥‡
+ खामोशी के समय तेज़ी से आगे बà¥à¥‡à¤‚
कदम
रिसेट
- सà¥à¤µà¥€à¤•à¤¾à¤°à¥‡
- असà¥à¤µà¥€à¤•à¤¾à¤° करे
+ सà¥à¤µà¥€à¤•à¤¾à¤°à¥‡à¤‚
+ असà¥à¤µà¥€à¤•à¤¾à¤° करें
असीमित
- मोबाइल डेटा उपयोग करते समय रेसॉलà¥à¤¯à¥à¤¸à¥‡à¤¨ को सिमित करे
+ मोबाइल डेटा उपयोग करते समय रिजॉलà¥à¤¯à¥à¤¶à¤¨ को सीमित करें
à¤à¤ª बदलते समय उसे मिनिमाइज करे
मà¥à¤–à¥à¤¯ वीडियो चालक से दूसरी à¤à¤ª पर जाने पर — %s
- कोई नही
- पृषà¥à¤ à¤à¥‚मि पà¥à¤²à¥‡à¤¯à¤° में बदले
- पॉप अप पà¥à¤²à¥‡à¤¯à¤° में बदले
- नà¥à¤¯à¥‚पाइप à¤à¤• काॅपीलेफ़à¥à¤Ÿ फ़à¥à¤°à¥€ साॅफ़à¥à¤Ÿà¤µà¥‡à¤° है: इसे आप अपनी इचà¥à¤›à¤¾ के अनà¥à¤¸à¤¾à¤° इसà¥à¤¤à¥‡à¤®à¤¾à¤², जाà¤à¤š, बाà¤à¤Ÿ तथा और बेहतर बना सकते है। खास तौर पर आप इसे फ़à¥à¤°à¥€ साॅफ़à¥à¤Ÿà¤µà¥‡à¤° फ़ाउंडेशन के दà¥à¤µà¤¾à¤°à¤¾ जारी जीà¤à¤¨à¤¯à¥‚ जनरल पबà¥à¤²à¤¿à¤• लाइसेंस के तीसरे या उसके बाद आने वाले कोई à¤à¥€ वरà¥à¤£à¤¨ के शरà¥à¤¤à¥‹à¤‚ के मà¥à¤¤à¤¾à¤¬à¤¿à¤• फिर से बाà¤à¤Ÿ या बदल सकते हैं।
+ कोई नहीं
+ बैकगà¥à¤°à¤¾à¤‰à¤‚ड पà¥à¤²à¥‡à¤¯à¤° में बदलें
+ पॉप अप पà¥à¤²à¥‡à¤¯à¤° में बदलें
+ नà¥à¤¯à¥‚पाइप à¤à¤• काॅपीलेफ़à¥à¤Ÿ फ़à¥à¤°à¥€ साॅफ़à¥à¤Ÿà¤µà¥‡à¤° है: इसे आप अपनी इचà¥à¤›à¤¾ के अनà¥à¤¸à¤¾à¤° इसà¥à¤¤à¥‡à¤®à¤¾à¤², जाà¤à¤š, बाà¤à¤Ÿ तथा और बेहतर बना सकते हैं। खास तौर पर आप इसे फ़à¥à¤°à¥€ साॅफ़à¥à¤Ÿà¤µà¥‡à¤° फ़ाउंडेशन के दà¥à¤µà¤¾à¤°à¤¾ जारी जीà¤à¤¨à¤¯à¥‚ जनरल पबà¥à¤²à¤¿à¤• लाइसेंस के तीसरे या उसके बाद आने वाले कोई à¤à¥€ वरà¥à¤£à¤¨ के शरà¥à¤¤à¥‹à¤‚ के मà¥à¤¤à¤¾à¤¬à¤¿à¤• फिर से बाà¤à¤Ÿ या बदल सकते हैं।
सदसà¥à¤¯à¤¤à¤¾ वापस ले ली
नया टॅब
टॅब चà¥à¤¨à¥‡
वॉलà¥à¤¯à¥‚म नियंतà¥à¤°à¤£
कतार
- पà¥à¤²à¥‡à¤¯à¤° का वॉलà¥à¤¯à¥‚म बà¥à¤¨à¥‡ के लिठइशारो का इसà¥à¤¤à¥‡à¤®à¤¾à¤² करे
+ पà¥à¤²à¥‡à¤¯à¤° का वॉलà¥à¤¯à¥‚म बà¥à¤¨à¥‡ के लिठइशारो का इसà¥à¤¤à¥‡à¤®à¤¾à¤² करें
चमक बदलने का इशारा
- पà¥à¤²à¥‡à¤¯à¤° की चमक बदलने के लिठइशारो का इसà¥à¤¤à¥‡à¤®à¤¾à¤² करे
+ पà¥à¤²à¥‡à¤¯à¤° की चमक बदलने के लिठइशारो का इसà¥à¤¤à¥‡à¤®à¤¾à¤² करें
अपडेट
फाइल मिटा दी गयी
अपà¥à¤ª अपडेट अधिसूचना
- नठNewPipe अपडेट की अधिसूचना
+ नठनयूपाइप अपडेट की अधिसूचना
à¤à¤•à¥à¤¸à¤Ÿà¤°à¥à¤¨à¤² सà¥à¤Ÿà¥‹à¤°à¥‡à¤œ अनà¥à¤ªà¤²à¤¬à¥à¤§
- SD कारà¥à¤¡ पर डाउनलोड करना संà¤à¤µ नहीं।डाउनलोड सà¥à¤¥à¤¾à¤¨ पà¥à¤¨à¤ƒ चà¥à¤¨à¥‡\?
+ SD कारà¥à¤¡ पर डाउनलोड करना संà¤à¤µ नहीं।डाउनलोड सà¥à¤¥à¤¾à¤¨ पà¥à¤¨à¤ƒ चà¥à¤¨à¥‡à¤‚\?
सामानà¥à¤¯ चà¥à¤¨à¤¾à¤µ पर लौटे
- कà¥à¤¯à¤¾ आप सामानà¥à¤¯ चà¥à¤¨à¤¾à¤µ पर लौटना चाहते है\?
+ कà¥à¤¯à¤¾ आप सामानà¥à¤¯ चà¥à¤¨à¤¾à¤µ पर लौटना चाहते हैं\?
अà¤à¤¿à¤¦à¤¾à¤¤à¤¾ संखà¥à¤¯à¤¾ अनà¥à¤ªà¤²à¤¬à¥à¤§
मà¥à¤–à¥à¤¯ पृषà¥à¤ पर कौन से टैब दिखाठजाते हैं
चयन
अपडेट
सूची
- दृशà¥à¤¯ बदले
- NewPipe अपडेट उपलबà¥à¤§!
+ दृशà¥à¤¯ बदलें
+ नयूपाइप अपडेट उपलबà¥à¤§!
डाउनलोड करने के लिठटैप करें
समापà¥à¤¤
अपूरà¥à¤£
@@ -423,7 +426,7 @@
डाउनलोड विफल रहा
डाउनलोड समापà¥à¤¤
%s डाउनलोड समापà¥à¤¤
- अनोखा नाम बनाये
+ अनोखा नाम बनायें
ऊपर लिखना
इस नाम का à¤à¤• डाउनलोड चालू है
कोड
@@ -439,20 +442,20 @@
रà¥à¤•à¥‹
अधिकतम पà¥à¤¨à¤°à¥à¤ªà¥à¤°à¤¯à¤¾à¤¸
डाउनलोड रदà¥à¤¦ करने से पहले पà¥à¤°à¤¯à¤¾à¤¸à¥‹à¤‚ की अधिकतम संखà¥à¤¯à¤¾
- मीटरà¥à¤¡ नेटवरà¥à¤• पर रोके
+ मीटरà¥à¤¡ नेटवरà¥à¤• पर रोकें
मोबाइल डाटा का इसà¥à¤¤à¥‡à¤®à¤¾à¤² करते समय उपयोगी है, परनà¥à¤¤à¥ कà¥à¤› डौनà¥à¤²à¥‹à¤¡à¤¸ रोके नहीं जा सकते है
घटनायें
समà¥à¤®à¥‡à¤²à¤¨à¥‹à¤‚
- टिपà¥à¤ªà¤£à¤¿à¤¯à¤¾à¤ दिखाà¤
+ टिपà¥à¤ªà¤£à¤¿à¤¯à¤¾à¤ दिखाà¤à¤‚
टिपà¥à¤ªà¤£à¤¿à¤¯à¤¾à¤ न देखने के लिठबंद करे
ऑटोपà¥à¤²à¥‡
कोई टिपणà¥à¤£à¥€ नहीं
टिपà¥à¤ªà¤£à¤¿à¤¯à¤¾à¤ लोड नहीं कर सका
बंद करे
फिर से शà¥à¤°à¥‚ करे
- आखरी पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨ पर लौटे
+ आखरी पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨ पर लौटें
सूचियों में सà¥à¤¥à¤¾à¤¨
- पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨ निशान सूचियों में दिखाà¤
+ पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨ निशान सूचियों में दिखाà¤à¤‚
डाटा मिटायें
पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨ मिटा दिठगà¤|
फाइल की जगह बदली गयी या फिर फाइल मिटा दी गयी
@@ -460,7 +463,7 @@
इस नाम की à¤à¤• डाउनलोड की गई फ़ाइल पहले से मौजूद है
फाइल के ऊपर नहीं लिख सकते
इस नाम का à¤à¤• डाउनलोड बाकी है
- फ़ाइल पर कारà¥à¤¯ करते समय NewPipe बंद किया गया
+ फ़ाइल पर कारà¥à¤¯ करते समय नयूपाइप बंद किया गया
डिवाइस पर जगह समापà¥à¤¤
पà¥à¤°à¤—ति खो गई, कà¥à¤¯à¥‹à¤‚कि फ़ाइल मिटा दी गई थी
कनेकà¥à¤¶à¤¨ का समय समापà¥à¤¤
@@ -469,12 +472,21 @@
à¤à¤• ही समय में à¤à¤• डाउनलोड चलेगा
डाउनलोड पà¥à¤°à¤¾à¤°à¤‚ठकरें
डाउनलोड रोकें
- डोलोड कहाठकरने के लिठपूछे
+ डाउनलोड कहाठकरने के लिठपूछें
आपको हर डाउनलोड का सà¥à¤¥à¤¾à¤¨ पूछा जाà¤à¤—ा
SAF का उपयोग करें
- पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨à¥‹à¤‚ को मिटाये
- सारे पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨à¥‹à¤‚ को मिटाये
- सारे पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨à¥‹à¤‚ को मिटाये\?
+ देखे गठवीडियो हटायें\?
+ देखे गठको हटा दें
+ à¤à¤• इंसà¥à¤Ÿà¥‡à¤‚स चà¥à¤¨à¥‡à¤‚
+ हाल ही के
+ रेडियो
+ विशेष रà¥à¤ª से पà¥à¤°à¤¦à¤°à¥à¤¶à¤¿à¤¤
+ à¤à¤²à¥à¤¬à¤®
+ वीडियो
+ यह वीडियो उमà¥à¤° पà¥à¤°à¤¤à¤¿à¤¬à¤‚धित है।
+ पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨à¥‹à¤‚ को मिटायें
+ सारे पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨à¥‹à¤‚ को मिटायें
+ सारे पà¥à¤²à¥‡à¤¬à¥ˆà¤• सà¥à¤¥à¤¾à¤¨à¥‹à¤‚ को मिटायें\?
पà¥à¤°à¤à¤¾à¤µà¥€ होने के लिठडाउनलोड फ़ोलà¥à¤¡à¤° बदलें
फà¥à¤°à¥‡à¤—मेंट या à¤à¤•à¥à¤Ÿà¤¿à¤µà¤¿à¤Ÿà¥€ लाइफसाइकिल के बाद Rx सनà¥à¤¦à¥‡à¤¶ ना पहà¥à¤à¤šà¤¾à¤¯à¤¾ जा सके तोह ज़रूर कोशिश करे
SoundCloud पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² निरà¥à¤¯à¤¾à¤¤ करने के लिठआईडी या यà¥à¤†à¤°à¤à¤² दीजिये:
@@ -483,7 +495,36 @@
\n३ लोग इन करे
\n४ आप जिस पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² यà¥à¤†à¤°à¤à¤² पे à¤à¥‡à¤œà¥‡ जाते है उसे कॉपी करे|
यूरोप के जनरल डाटा पà¥à¤°à¥‹à¤Ÿà¥‡à¤•à¥à¤¶à¤¨ रेगà¥à¤²à¥‡à¤¶à¤¨ (जी डी पी आर) का पालन करने के लिà¤, हम आपका धà¥à¤¯à¤¾à¤¨ नà¥à¤¯à¥‚पाइप की नयी पà¥à¤°à¤¾à¤‡à¤µà¥‡à¤¸à¥€ पालिसी पी डालना चाहते है।इसे बारीकी से पà¥à¤¿à¤à¥¤
-\nआपको अगर हमें किसी मà¥à¤¸à¥€à¤¬à¤¤ का सनà¥à¤¦à¥‡à¤¶ à¤à¥‡à¤œà¤¨à¤¾ हो तो इसे सà¥à¤µà¥€à¤•à¤¾à¤° करे।
+\nआपको अगर हमें किसी मà¥à¤¸à¥€à¤¬à¤¤ का सनà¥à¤¦à¥‡à¤¶ à¤à¥‡à¤œà¤¨à¤¾ हो तो इसे सà¥à¤µà¥€à¤•à¤¾à¤° करें।
+
+ - %d चयनित
+ - %d चयनित हà¥à¤
+
+
+ - %d दिन
+ - %d दिनो
+
+
+ - %d घंटा
+ - %d घंटे
+
+
+ - %d मिनट
+ - %d मिनटà¥à¤¸
+
+
+ - %d सेकेंड
+ - %d सेकंडà¥à¤¸
+
+ पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ पृषà¥à¤
+ पà¥à¤²à¥‡à¤²à¤¿à¤¸à¥à¤Ÿ का चयन करें
+ मेमरी लीक दरà¥à¤¶à¤¾à¤à¤
+ अनà¥à¤®à¥à¤¯à¥‚ट
+ मà¥à¤¯à¥‚ट
+ सबसे जà¥à¤¯à¤¾à¤¦à¤¾ पसंद
+ हाल ही में जोड़ा
+ सिसà¥à¤Ÿà¤® डिफ़ॉलà¥à¤Ÿ
+ à¤à¤ª की à¤à¤¾à¤·à¤¾
सामानà¥à¤¯ टैबà¥à¤¸ का इसà¥à¤¤à¥‡à¤®à¤¾à¤², सहेजे टैबà¥à¤¸ को पà¥à¤¨à¥‡ में रूकावट
जब इस à¤à¤ª के लिठअपडेट उपलबà¥à¤§ हो, अधिसूचना दिखाई जाये
सूचि देखने वाला ढंग
@@ -497,10 +538,10 @@
\nसà¥à¤šà¤¨à¤¾ - कà¥à¤› उपकरणों पर ये नहीं चलता
सेवा चà¥à¤¨à¥‡, वरà¥à¤¤à¤®à¤¾à¤¨ चà¥à¤¨à¤¾à¤µ :
सामानà¥à¤¯ कीओसà¥à¤•
- कोई नहीं देख रहा है
+ कोई दरà¥à¤¶à¤• नहीं
- - %s आदमी देख रहा है
- - %s आदमी देख रहे है
+ - %s दरà¥à¤¶à¤• है
+ - %s दरà¥à¤¶à¤• हैं
कोई नहीं सà¥à¤¨ रहा है
@@ -508,5 +549,42 @@
- %s शà¥à¤°à¥‹à¤¤à¤¾
à¤à¤ª के पà¥à¤¨à¤ƒ आरंठहोने के बाद à¤à¤¾à¤·à¤¾ बदल जाà¤à¤—ी।
- लॉक सà¥à¤•à¥à¤°à¥€à¤¨ पर वीडियो की छोटी तसà¥à¤µà¥€à¤° दिखाà¤
+ लॉक सà¥à¤•à¥à¤°à¥€à¤¨ पर वीडियो की थंमनेल
+ बैकगà¥à¤°à¤¾à¤‰à¤‚ड पà¥à¤²à¥‡à¤¯à¤° का उपयोग करते समय लॉक सà¥à¤•à¥à¤°à¥€à¤¨ पर वीडियो थंमनेल पà¥à¤°à¤¦à¤°à¥à¤¶à¤¿à¤¤ करता है
+ फ़ीड
+ समूह का नाम नहीं
+ सबà¥à¤¸à¤•à¥à¤°à¤¿à¤ªà¥à¤¶à¤¨à¤¸ चà¥à¤¨à¥‡à¤‚
+ कà¥à¤¯à¤¾ आप इस समूह को हटाना चाहते हैं\?
+ नया
+ फ़ीड संसाधित हो रही है …
+ उपलबà¥à¤§ होने पर समरà¥à¤ªà¤¿à¤¤ फ़ीड से पà¥à¤°à¤¾à¤ªà¥à¤¤ करें
+ कà¥à¤› सेवाओं में उपलबà¥à¤§, यह आमतौर पर बहà¥à¤¤ तेज होता है लेकिन सीमित मातà¥à¤°à¤¾ में आइटम और अकà¥à¤¸à¤° अधूरी जानकारी (जैसे कोई अवधि नहीं, आइटम पà¥à¤°à¤•à¤¾à¤°, कोई लाइव सà¥à¤¥à¤¿à¤¤à¤¿ नहीं) लौटा सकता है
+ तेज मोड सकà¥à¤·à¤® करें
+ तेज मोड अकà¥à¤·à¤® करें
+ कà¥à¤¯à¤¾ आपको लगता है कि फीड लोडिंग बहà¥à¤¤ धीमी है\? यदि à¤à¤¸à¤¾ है, तो तेज़ लोडिंग को सकà¥à¤·à¤® करने का पà¥à¤°à¤¯à¤¾à¤¸ करें (आप इसे सेटिंगà¥à¤¸ में या नीचे दिठगठबटन को दबाकर बदल सकते हैं)।
+ फीड लोड हो रही है…
+ फ़ीड अपडेट चरणसीमा
+ अपडेट अंतराल जब सबà¥à¤¸à¤•à¥à¤°à¤¿à¤ªà¥à¤¶à¤¨ फ़ीड दोबारा अपडेट किये जा सकें — %s
+ हमेशा अपडेट करें
+ पà¥à¤²à¥‡à¤¬à¥ˆà¤• लोड अंतराल आकार
+ पà¥à¤°à¤—तिशील सामगà¥à¤°à¥€ पर लोड अंतराल आकार बदलें (वरà¥à¤¤à¤®à¤¾à¤¨ में %s)। à¤à¤• कम मान उनकी आरंà¤à¤¿à¤• लोडिंग को गति दे सकता है
+ हमेशा विवरण का विसà¥à¤¤à¤¾à¤° करें
+ यूटà¥à¤¯à¥‚ब का \"पà¥à¤°à¤¤à¤¿à¤¬à¤‚धित मोड\" चालू करें
+ URL की पहचान नहीं हो सकी। दूसरे à¤à¤ª से खोलें\?
+ निरà¥à¤®à¤¾à¤¤à¤¾ दà¥à¤µà¤¾à¤°à¤¾ दिया दिल
+ तलाश अवधि फासà¥à¤Ÿ-फ़ॉरवरà¥à¤¡ /- रिवाइंड करे
+ पियरटà¥à¤¯à¥‚ब इंसटैंस
+ इंसटैंस पहले से मौजूद है
+ इंसटैंस मानà¥à¤¯ नहीं किया जा सका
+ इंसटैंस यूआरà¤à¤² दरà¥à¤œ करें
+ इंसटैंस जोड़ें
+ %s पर अपनी पसंद के इंसटैंस ढूà¤à¤¢à¤¼à¥‡à¤‚
+ अपने पसंदीदा पीयर टà¥à¤¯à¥‚ब इंसटैंस चà¥à¤¨à¥‡à¤‚
+ केवल HTTPS यूआरà¤à¤² ही समरà¥à¤¥à¤¿à¤¤ हैं
+ यह à¤à¤• साउंडकà¥à¤²à¤¾à¤‰à¤¡ गो+ टà¥à¤°à¥ˆà¤• है, कम से कम आपके देश में, इस कारण इसे नà¥à¤¯à¥‚पाइप दà¥à¤µà¤¾à¤°à¤¾ सà¥à¤Ÿà¥à¤°à¥€à¤® या डाउनलोड नहीं किया जा सकता है।
+ यह सामगà¥à¤°à¥€ केवल उन उपयोगकरà¥à¤¤à¤¾à¤“ं के लिठउपलबà¥à¤§ है, जिनà¥à¤¹à¥‹à¤‚ने à¤à¥à¤—तान किया हो, इस कारण इसे नà¥à¤¯à¥‚पाइप दà¥à¤µà¤¾à¤°à¤¾ सà¥à¤Ÿà¥à¤°à¥€à¤® या डाउनलोड नहीं किया जा सकता है।
+ यह वीडियो सिरà¥à¤« यूटà¥à¤¯à¥‚ब मà¥à¤¯à¥‚जिक के पà¥à¤°à¥€à¤®à¤¿à¤¯à¤® सदसà¥à¤¯à¥‹à¤‚ के लिठही उपलबà¥à¤§ है, इसलिठइसे नà¥à¤¯à¥‚पाइप दà¥à¤µà¤¾à¤°à¤¾ सà¥à¤Ÿà¥à¤°à¥€à¤® या डाउनलोड नहीं किया जा सकता है।
+ यह सामगà¥à¤°à¥€ पà¥à¤°à¤¾à¤‡à¤µà¥‡à¤Ÿ है, इसलिठइसे नà¥à¤¯à¥‚पाइप दà¥à¤µà¤¾à¤°à¤¾ सà¥à¤Ÿà¥à¤°à¥€à¤® या डाउनलोड नहीं किया जा सकता है।
+ आइटम हटाने के लिठसà¥à¤µà¤¾à¤‡à¤ª करें
+ पिन की हà¥à¤ˆ टिपà¥à¤ªà¤£à¥€
\ No newline at end of file
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 4512e265b..2cfb65def 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -68,7 +68,7 @@
Preuzmi
Sljedeće
Prikaži \'Sljedeće\' i \'SliÄne\' videozapise
- URL nije podržan
+ URL nije podržan
Zadani jezik sadržaja
Video i zvuk
SkoÄni prozor
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 1dcfb9413..ec0a99b6a 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -26,7 +26,7 @@
Alapértelmezett hang formátum
Letöltés
Következő
- Nem támogatott webcÃm
+ Nem támogatott webcÃm
Külső videólejátszó használata
Külső hanglejátszó használata
Válaszd ki a hangfájlok letöltési helyét
diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml
index 150046340..25eb5c65e 100644
--- a/app/src/main/res/values-ia/strings.xml
+++ b/app/src/main/res/values-ia/strings.xml
@@ -66,7 +66,7 @@
Sequente
Reproduction automatic
Monstrar le videos sequente e simile
- URL non supportate
+ URL non supportate
Pais predefinite del contentos
Servicio
Lingua predefinite del contento
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index cc976ecff..1f77505c3 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -34,7 +34,7 @@
Unduh
Berikutnya
Tampilkan video \'Berikutnya\' dan \'Serupa\'
- URL tidak didukung
+ URL tidak didukung
Bahasa konten
Video & Audio
Tampilan
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 0b2b3b44b..5f020e57e 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -27,7 +27,7 @@
Scarica
Prossimo
Mostra video \"Prossimo\" e \"Simili\"
- URL non supportato
+ URL non supportato
Lingua Predefinita per Contenuti
Video e Audio
Copertina di anteprima video
diff --git a/app/src/main/res/values-iw b/app/src/main/res/values-iw
deleted file mode 120000
index 08adc17cc..000000000
--- a/app/src/main/res/values-iw
+++ /dev/null
@@ -1 +0,0 @@
-values-he/
\ No newline at end of file
diff --git a/app/src/main/res/values-iw b/app/src/main/res/values-iw
new file mode 100644
index 000000000..08adc17cc
--- /dev/null
+++ b/app/src/main/res/values-iw
@@ -0,0 +1 @@
+values-he/
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index f66930a0c..7a150f639 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -26,7 +26,7 @@
ä¿å˜
次
「次ã®å‹•ç”»ã€ã¨ã€Œé–¢é€£å‹•ç”»ã€ã‚’表示
- URLã¯ä½¿ç”¨ã§ãã¾ã›ã‚“
+ URLã¯ä½¿ç”¨ã§ãã¾ã›ã‚“
優先言語
å‹•ç”»ã¨éŸ³å£°
%1$s ビュー
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 78550ca5e..e7b13c1c6 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -27,7 +27,7 @@
다운로드
다ìŒ
다ìŒ/ìœ ì‚¬í•œ 비디오 표시
- 지ì›í•˜ì§€ 않는 URL입니다
+ 지ì›í•˜ì§€ 않는 URL입니다
기본 컨í…ì¸ ì–¸ì–´
비디오 & 오디오
비디오 미리보기 ì¸ë„¤ì¼
diff --git a/app/src/main/res/values-ku/strings.xml b/app/src/main/res/values-ku/strings.xml
index 47c46cc49..7fee584fc 100644
--- a/app/src/main/res/values-ku/strings.xml
+++ b/app/src/main/res/values-ku/strings.xml
@@ -81,7 +81,7 @@
داگرتن
دواتر
پیشاندانی ’دواتر’ و ڤیدیۆ ’هاوشێوەکان’
- بەستەرەکە پشتگیری نەکراوە
+ بەستەرەکە پشتگیری نەکراوە
وڵاتی بنەڕەتی
خزمەتگوزاری
کارپێکەر
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
index b152fb1ae..60f7b0411 100644
--- a/app/src/main/res/values-land/dimens.xml
+++ b/app/src/main/res/values-land/dimens.xml
@@ -40,6 +40,7 @@
90dp
45dp
+ 8dp
8dp
4dp
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index fb05da09c..562fce75c 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -56,7 +56,7 @@
Atsisiųsti
Kitas vaizdo įrašas
Rodyti kitus panašius vaizdo įrašus
- URL nepalaikoma
+ URL nepalaikoma
Numatytoji tūrinio kalba
Vaizdas ir garsas
IÅ¡Å¡okantis langas
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index 660148f01..95614bd56 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -85,7 +85,7 @@
Прикажи „Ñледни“ и „Ñлични“ видеа
Прикажи „задржи за прикачување“
Покажи Ñовет при притиÑкање на позадината или кога Ñкок копчето е притиÑнато на видео \"Детали:\"
- Ðеподдржана URL врÑка
+ Ðеподдржана URL врÑка
Земја на прикажани видеа
УÑлуга
Стандарден јазик на Ñодржина
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index bc27edaa4..443182030 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -366,7 +366,7 @@
à´¸àµà´¥à´¿à´° à´•à´¨àµà´±àµ†à´¨àµà´±àµâ€Œ à´à´¾à´·
സേവനം
à´¸àµà´¥à´¿à´° à´•à´¨àµà´±àµ†à´¨àµà´±àµ രാജàµà´¯à´‚
- à´…à´¨àµà´¯àµ‹à´œàµà´¯à´®à´²àµà´²à´¾à´¤àµà´¤ URL
+ à´…à´¨àµà´¯àµ‹à´œàµà´¯à´®à´²àµà´²à´¾à´¤àµà´¤ URL
പോപàµà´ªà´ªàµà´ªàµ/ബാകàµà´•àµà´—àµà´°àµ—à´£àµà´Ÿàµ ബടàµà´Ÿàµº അമർതàµà´¤àµà´®àµà´ªàµ‹àµ¾ \"വിശദാംശങàµà´™àµ¾\" à´Žà´¨àµà´¨ ടിപൠകാണികàµà´•àµà´‚
\"ഹോൾഡൠടൠഅപàµà´ªàµ†àµ»à´¡àµ\" à´Žà´¨àµà´¨ ടിപൠകാണികàµà´•àµà´•
\'à´…à´Ÿàµà´¤àµà´¤\' , \'സമാനമായ\' വീഡിയോകൾ കാണികàµà´•àµà´•
diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml
index 1944877f8..c9c24174f 100644
--- a/app/src/main/res/values-ms/strings.xml
+++ b/app/src/main/res/values-ms/strings.xml
@@ -92,7 +92,7 @@
Paparkan video \'Seterusnya\' dan \'Berkaitan\'
Tunjukkan tip \"Pegang untuk menambahkan\"
Tunjukkan tip apabila butang latar belakang atau popup ditekan pada halaman butiran video
- URL tidak disokong
+ URL tidak disokong
Negara kandungan utama
Perkhidmatan
Bahasa kandungan utama
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index 6adb1c9f8..f8efff3f9 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -35,7 +35,7 @@
Last ned
Neste
Vis \"Neste\" og \"Lignende\" -videoer
- Nettadressen støttes ikke
+ Nettadressen støttes ikke
Foretrukket innholdsspråk
Video og lyd
Utseende
diff --git a/app/src/main/res/values-ne/strings.xml b/app/src/main/res/values-ne/strings.xml
index aac2a27ae..5cfba6eff 100644
--- a/app/src/main/res/values-ne/strings.xml
+++ b/app/src/main/res/values-ne/strings.xml
@@ -94,7 +94,7 @@
\'अरà¥à¤•à¥‹\' र \'समान\' à¤à¤¿à¤¡à¤¿à¤¯à¥‹ देखाउन
\"होलà¥à¤¡ संलगà¥à¤¨ गरà¥à¤¨\" टिप देखाउन
पृषà¥à¤ à¤à¥‚मि वा à¤à¤¿à¤¡à¤¿à¤¯à¥‹ मा पपअप बटन थिचà¥à¤¦à¤¾ टिप देखाउन \"विवरण:\"
- असमरà¥à¤¥à¤¿à¤¤ URL
+ असमरà¥à¤¥à¤¿à¤¤ URL
पूरà¥à¤µà¤¨à¤¿à¤°à¥à¤§à¤¾à¤°à¤¿à¤¤ सामगà¥à¤°à¥€ देश
सेवा
पूरà¥à¤µà¤¨à¤¿à¤°à¥à¤§à¤¾à¤°à¤¿à¤¤ सामगà¥à¤°à¥€ à¤à¤¾à¤·à¤¾
diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml
index a80cec9cc..813831a6b 100644
--- a/app/src/main/res/values-nl-rBE/strings.xml
+++ b/app/src/main/res/values-nl-rBE/strings.xml
@@ -85,7 +85,7 @@
Toont ‘Volgende’ en ‘Vergelijkbare’ video’s
Toont tip ‘Ingedrukt houden voor toe te voegen’
Toont tip wanneer dat den achtergrond- of pop-upknop wordt ingedrukt op de videogegevenspagina
- URL wordt niet ondersteund
+ URL wordt niet ondersteund
Standaardinhoudsland
Dienst
Standaardtaal voor inhoud
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index ca4539a60..195b6d068 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -26,7 +26,7 @@
Standaardaudioformaat
Downloaden
Volgende
- URL wordt niet ondersteund
+ URL wordt niet ondersteund
‘volgende’ en ‘vergelijkbare’ video’s weergeven
Standaardtaal voor inhoud
Externe videospeler gebruiken
diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml
index 6a84b99a4..7517fcbb7 100644
--- a/app/src/main/res/values-pa/strings.xml
+++ b/app/src/main/res/values-pa/strings.xml
@@ -1,113 +1,119 @@
- ਸ਼à©à¨°à©‚ ਕਰਨ ਲਈ ਸਰਚ ਦਬਾਓ
- %1$s VIEWS
- %1$s ਨੂੰ ਪਬਲਿਸ਼ ਕੀਤੀ ਗਈ
- ਸਟà©à¨°à©€à¨® ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ। ਤà©à¨¸à©€à¨‚ VLC à¨à¨°à¨¨à¨¾ ਚਾਹੋਗੇ \?
- ਸਟà©à¨°à©€à¨® ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ ਤà©à¨¸à©€à¨‚ VLC ਇੰਸਟਾਲ ਕਰ ਸਕਦੇ ਹੋ.
- ਇੰਸਟਾਲ
+ ਵੱਡਦਰਸ਼ੀ ਕੱਚ \'ਤੇ ਟੈਪ ਕਰਕੇ ਸਰਚ ਕਰਨਾ ਸ਼à©à¨°à©‚ ਕਰੋ।
+ %1$s ਵਿਊਜ਼
+ %1$s ਨੂੰ ਜਾਰੀ ਕੀਤੀ ਗਈ
+ ਕੋਈ ਸਟà©à¨°à©€à¨® ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ । ਤà©à¨¸à©€à¨‚ ਵੀà¨à©±à¨²à¨¸à©€ ਇੰਸਟਾਲ ਕਰਨਾ ਚਾਹੋਗੇ \?
+ ਸਟà©à¨°à©€à¨® ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ (ਤà©à¨¸à©€à¨‚ ਵੀà¨à©±à¨²à¨¸à©€ ਇੰਸਟਾਲ ਕਰਕੇ ਇਸਨੂੰ ਚਲਾ ਸਕਦੇ ਹੋ)
+ ਇੰਸਟਾਲ ਕਰੋ
ਰੱਦ ਕਰੋ
- ਬà©à¨°à¨¾à¨Šà©›à¨° ਵਿੱਚ ਖੋਲੋ
- ਪੌਪ-ਅਪ ਵਿਚ ਖੋਲੋ
+ ਬà©à¨°à¨¾à¨Šà©›à¨° ਵਿੱਚ ਖੋਲà©à¨¹à©‹
+ ਪੌਪ-ਅਪ ਵਿੱਚ ਖੋਲੋ
ਸਾਂà¨à¨¾ ਕਰੋ
ਡਾਊਨਲੋਡ
- ਡਾਊਨਲੋਡ ਸਟà©à¨°à©€à¨® ਫਾਈਲ
+ ਸਟà©à¨°à©€à¨® ਫ਼ਾਈਲ ਡਾਊਨਲੋਡ ਕਰੋ
ਖੋਜੋ
ਸੈਟਿੰਗਾਂ
ਕੀ ਤà©à¨¹à¨¾à¨¡à¨¾ ਮਤਲਬ: %1$s\?
ਸਾਂà¨à¨¾ ਕਰੋ
ਬà©à¨°à¨¾à¨Šà©›à¨° ਚà©à¨£à©‹
- ਰੋਟੇਸ਼ਨ
- ਹੋਰ ਵੀਡੀਓ ਪਲੇਅਰ ਦੀ ਵਰਤੋਂ ਕਰੋ
- ਕà©à¨ ਰੇਸੋਲਿਯà©à¨¶à¨¨ ਤੇ ਆਵਾਜ਼ ਨੂੰ ਹਟਾਉਂਦਾ ਹੈ
- ਬਾਹਰੀ ਆਡੀਓ ਪਲੇਅਰ ਦੀ ਵਰਤੋਂ ਕਰੋ
- NewPipe ਪੌਪ-ਅਪ ਮੋਡ
- ਸਬਸਕà©à¨°à¨¾à¨ˆà¨¬
- ਸਬਸਕà©à¨°à¨¾à¨ˆà¨¬ ਕੀਤਾ ਹੈ
- ਚੈਨਲ ਅਨ-ਸਬਸਕà©à¨°à¨¾à¨ˆà¨¬
- ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨ ਨੂੰ ਬਦਲਣ ਵਿਚ ਅਸਮਰੱਥ
- ਜਾਣਕਾਰੀ ਵੇਖੋ
+ ਘà©à¨®à¨¾à¨‰à¨£
+ ਬਾਹਰੀ ਵੀਡੀਓ ਪਲੇਅਰ ਵਰਤੋ
+ ਕà©à¨ ਰੈਜ਼ੋਲਿਊਸ਼ਨਾਂ\'ਤੇ ਆਵਾਜ਼ ਨੂੰ ਹਟਾ ਦਿੰਦਾ ਹੈ
+ ਬਾਹਰੀ ਆਡੀਓ ਪਲੇਅਰ ਵਰਤੋ
+ ਪੌਪ-ਅਪ ਮੋਡ
+ ਸਬਸਕà©à¨°à¨¾à¨ˆà¨¬ ਕਰੋ
+ ਸਬਸਕà©à¨°à¨¾à¨ˆà¨¬ ਹੈ
+ ਅਨ-ਸਬਸਕà©à¨°à¨¾à¨ˆà¨¬ ਕਰੋ
+ ਚੈਨਲ ਅਨ-ਸਬਸਕà©à¨°à¨¾à¨ˆà¨¬ ਹੋ ਗਿਆ
+ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨ ਬਦਲਣ ਵਿੱਚ ਨਾਕਾਮੀ
+ ਜਾਣਕਾਰੀ ਵਿਖਾਓ
ਮà©à©±à¨–
- ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨ ਅੱਪਡੇਟ ਕਰਨ ਵਿਚ ਅਸਮਰੱਥ
- ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨
+ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨ ਅੱਪਡੇਟ ਕਰਨ ਵਿਚ ਨਾਕਾਮੀ
+ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨à¨¾à¨‚
ਬà©à©±à¨•à¨®à¨¾à¨°à¨• ਪਲੇਲਿਸਟਾਂ
- ਨਵਾਂ ਕੀ ਹੈ
- ਬੈਕਗਰਾਊਂਡ
- ਪੋਪ-ਅਪ
+ ਨਵੀਂ ਟੈਬ
+ ਟੈਬ ਚà©à¨£à©‹
+ ਨਵਾਂ ਕੀ ਹੈ
+ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਆਡੀਓ
+ ਪੌਪ-ਅਪ
ਸ਼ਾਮਿਲ ਕਰੋ
- ਵੀਡੀਓ ਦਾ ਡਾਊਨਲੋਡ ਮਾਰਗ
- ਡਾਉਨਲੋਡ ਕੀਤੇ ਵੀਡੀਓ ਨੂੰ ਸਟੋਰ ਕਰਨ ਦਾ ਮਾਰਗ
- ਵੀਡੀਓ ਲਈ ਡਾਊਨਲੋਡ ਮਾਰਗ à¨à¨°à©‹
- ਆਡੀਓ ਦਾ ਡਾਊਨਲੋਡ ਫੋਲਡਰ
- ਡਾਊਨਲੋਡ ਕੀਤੇ ਆਡੀਓ ਇੱਥੇ ਸਟੋਰ ਹਨ
- ਆਡੀਓ ਫਾਈਲਾਂ ਲਈ ਡਾਊਨਲੋਡ ਮਾਰਗ à¨à¨°à©‹
- ਆਟੋ ਪਲੇ
- ਜਦੋਂ ਕਿਸੇ ਹੋਰ à¨à¨ª ਜਰੀਠNewPipe ਨੂੰ ਖੋਲਿਆ ਜਾਂਦਾ ਹੈ ਤਾਂ ਇਹ ਵੀਡੀਓ ਚਲਾਉਂਦਾ ਹੈ
- Default Resolution
- Default ਪੌਪ-ਅਪ Resolution
- ਉੱਚ ਰੇਸੋਲਿਯà©à¨¶à¨¨à¨¾à¨‚ ਵੀ ਦਿਖਾਓ
- ਸਿਰਫ ਕà©à©±à¨ ਉਪਕਰਣ ਹੀ 2K/4K ਵੀਡੀਓ ਵਿਖਾਉਣ ਵਿਚ ਸਮਰਥਨ ਹà©à©°à¨¦à©‡ ਹਨ
- Kodi ਨਾਲ ਚਲਾਓ
- Kore à¨à¨ª ਮੌਜੂਦ ਨਹੀਂ ਹੈ। ਕੀ ਤà©à¨¸à©€à¨‚ ਇਸਨੂੰ ਇੰਸਟਾਲ ਕਰਨਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ \?
- \"Kodi ਨਾਲ ਚਲਾਓ\" ਵਿਕਲਪ ਦਿਖਾਓ
- Kodi ਮੀਡੀਆ ਸੈਂਟਰ ਰਾਹੀਂ ਵੀਡੀਓ ਚਲਾਉਣ ਲਈ ਇੱਕ ਵਿਕਲਪ ਵਿਖਾਓ
+ ਵੀਡੀਓ ਲਈ ਡਾਊਨਲੋਡ ਫ਼ੋਲਡਰ
+ ਡਾਉਨਲੋਡ ਕੀਤੇ ਵੀਡੀਓ ਇਸ ਥਾਂ ਤੇ ਸਟੋਰ ਹੋਣਗੇ
+ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਲਈ ਡਾਊਨਲੋਡ ਫ਼ੋਲਡਰ ਚà©à¨£à©‹
+ ਆਡੀਓ ਲਈ ਡਾਊਨਲੋਡ ਫ਼ੋਲਡਰ
+ ਡਾਊਨਲੋਡ ਕੀਤੀਆਂ ਆਡੀਓ ਇੱਥੇ ਜਮà©à¨¹à¨¾à¨‚ ਹà©à©°à¨¦à©€à¨†à¨‚ ਹਨ
+ ਆਡੀਓ ਫ਼ਾਈਲਾਂ ਲਈ ਡਾਊਨਲੋਡ ਫ਼ੋਲਡਰ ਚà©à¨£à©‹
+ ਆਟੋ-ਪਲੇਅ
+ ਜਦੋਂ ਕਿਸੇ ਹੋਰ à¨à¨ª ਜਰੀਠਨਿਊਪਾਈਪ ਨੂੰ ਖੋਲਿਆ ਜਾਂਦਾ ਹੈ ਤਾਂ ਇਹ ਵੀਡੀਓ ਚਲਾਉਂਦਾ ਹੈ
+ ਡਿਫ਼ਾਲਟ ਰੈਜ਼ੋਲਿਊਸ਼ਨ
+ ਪੌਪ-ਅਪ ਲਈ ਡਿਫ਼ਾਲਟ ਰੈਜ਼ੋਲਿਊਸ਼ਨ
+ ਉੱਚ ਰੈਜ਼ੋਲਿਊਸ਼ਨਾਂ ਵੀ ਵਿਖਾਓ
+ ਸਿਰਫ਼ ਕà©à¨ ਹੀ ਡਿਵਾਈਸ 2K/4K ਵੀਡੀਓ ਨੂੰ ਚਲਾ ਸਕਦੇ ਹਨ
+ Kodi ਵਿੱਚ ਚਲਾਓ
+ Kore à¨à¨ª ਇੰਸਟਾਲ ਨਹੀਂ ਹੈ। ਕੀ ਤà©à¨¸à©€à¨‚ ਇਸਨੂੰ ਇੰਸਟਾਲ ਕਰਨਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ \?
+ \"Kodi ਵਿੱਚ ਚਲਾਓ\" ਵਿਕਲਪ ਵਿਖਾਓ
+ Kodi ਮੀਡੀਆ ਸੈਂਟਰ ਰਾਹੀਂ ਵੀਡੀਓ ਚਲਾਉਣ ਲਈ ਵਿਕਲਪ ਵਿਖਾਓ
+ ਲਾਕ ਸਕਰੀਨ ਤੇ ਵੀਡੀਓ ਥੰਮਨੇਲ
+ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਪਲੇਅਰ ਵਰਤਦੇ ਸਮੇਂ ਲਾਕ ਸਕਰੀਨ ਤੇ ਵੀਡੀਓ ਥੰਮਨੇਲ ਵਿਖਾਉਂਦਾ ਹੈ
ਆਡੀਓ
- Default ਆਡੀਓ ਫਾਰਮੈਟ
- "Default ਵੀਡੀਓ ਫਾਰਮੈਟ"
+ ਡਿਫ਼ਾਲਟ ਆਡੀਓ ਫਾਰਮੈਟ
+ ਡਿਫ਼ਾਲਟ ਵੀਡੀਓ ਫਾਰਮੈਟ"
ਥੀਮ
ਸਫੈਦ
ਗੂੜਾ
- ਕਾਲਾ
+ ਕਾਲà©à¨¹à¨¾
ਪੌਪ-ਅਪ ਦਾ ਆਕਾਰ ਅਤੇ ਸਥਿਤੀ ਯਾਦ ਰੱਖੋ
ਪੌਪ-ਅਪ ਦਾ ਆਖਰੀ ਅਕਾਰ ਅਤੇ ਸਥਿਤੀ ਯਾਦ ਰੱਖੋ
- ਤੇਜ਼ ਪਰ inexact seek ਵਰਤੋ
- Inexact seek ਵੀਡੀਓ ਨੂੰ ਤੇਜ਼ ਪਰ ਅਣ-ਸਟੀਕ ਢੰਗ ਨਾਲ ਅੱਗੇ-ਪਿੱਛੇ ਲਿਜਾਂਦਾ ਹੈ
- ਥੰਬਨੇਲ ਲੋਡ ਕਰੋ
- ਥੰਬਨੇਲ ਲੋਡ, ਡਾਟਾ ਦੀ ਬਚਤ ਅਤੇ ਮੈਮੋਰੀ ਦੀ ਵਰਤੋਂ ਨੂੰ ਰੋਕਣ ਲਈ ਇਸਨੂੰ ਬੰਦ ਕਰੋ। ਇਸ ਵਿਚ ਤਬਦੀਲੀ ਕਰਨ ਨਾਲ ਇਨ-ਮੈਮੋਰੀ ਅਤੇ ਆਨ-ਡਿਸਕ ਚਿੱਤਰ cache ਦੋਵੇਂ ਮਿਟ ਜਾਣਗੇ।
- ਚਿੱਤਰ cache ਮਿਟਾ ਦਿੱਤੀ ਗਈ ਹੈ
- Cached metadata ਮਿਟਾਓ
- ਸਾਰੇ cached ਵੈੱਬ-ਪੇਜਾਂ ਦਾ ਡਾਟਾ ਮਿਟਾਓ
- Metadata cache ਮਿਟਾ ਦਿੱਤੀ ਗਈ ਹੈ
+ ਤੇਜ਼ ਪਰ ਅਸਪੱਸ਼ਟ ਸੀਕ ਵਰਤੋ
+ ਅਸਪੱਸ਼ਟ ਸੀਕ ਵੀਡੀਓ ਨੂੰ ਤੇਜ਼ ਪਰ ਅਣ-ਸਟੀਕ ਢੰਗ ਨਾਲ ਅੱਗੇ-ਪਿੱਛੇ ਲਿਜਾਂਦਾ ਹੈ। ਇਸ ਨਾਲ ਅੱਗੇ-ਪਿੱਛੇ 5,15 ਜਾਂ 25 ਸੈਕੰਡ ਜਾਣਾ ਕੰਮ ਨਹੀਂ ਕਰੇਗਾ।
+ ਅੱਗੇ ਲੰਘਾਉਣ/ਪਿੱਛੇ ਕਰਨ ਦੀ ਸਮਾਂ ਮਿਆਦ
+ ਥੰਮਨੇਲ ਲੋਡ ਕਰੋ
+ ਥੰਮਨੇਲ ਲੋਡ, ਡਾਟਾ ਦੀ ਬਚਤ ਅਤੇ ਮੈਮੋਰੀ ਦੀ ਵਰਤੋਂ ਨੂੰ ਰੋਕਣ ਲਈ ਇਸਨੂੰ ਬੰਦ ਕਰੋ। ਇਸ ਵਿਚ ਤਬਦੀਲੀ ਕਰਨ ਨਾਲ ਇਨ-ਮੈਮੋਰੀ ਅਤੇ ਆਨ-ਡਿਸਕ ਚਿੱਤਰ ਕੈਸ਼ ਦੋਵੇਂ ਮਿਟ ਜਾਣਗੇ।
+ ਚਿੱਤਰ ਕੈਸ਼ ਮਿਟਾ ਦਿੱਤੀ ਗਈ ਹੈ
+ ਕੈਸ਼ ਕੀਤਾ ਮੈਟਾ-ਡਾਟਾ ਮਿਟਾਓ
+ ਸਾਰੇ ਕੈਸ਼ ਕੀਤੇ ਵੈੱਬ-ਪੇਜਾਂ ਦਾ ਡਾਟਾ ਮਿਟਾਓ
+ ਮੈਟਾ-ਡਾਟਾ ਕੈਸ਼ ਮਿਟਾ ਦਿੱਤੀ ਗਈ ਹੈ
ਅਗਲੀ ਸਟà©à¨°à©€à¨® ਨੂੰ ਆਟੋ-ਕਤਾਰਬੱਧ ਕਰੋ
- ਇੱਕ ਨਾ-ਦà©à¨¹à¨°à¨¾à¨‰à¨£ ਵਾਲੀ ਕਤਾਰ ਵਿੱਚ ਆਖਰੀ ਸਟà©à¨°à©€à¨® ਨੂੰ ਚਲਾਉਣ ਵੇਲੇ ਆਪਣੇ-ਆਪ ਸ਼ਾਮਿਲ ਕਰੋ
- ਵੀਡੀਓ ਪਲੇਯਰ gesture ਕੰਟਰੋਲ
- ਸਕà©à¨°à©€à¨¨ ਲਾਈਟ ਅਤੇ ਆਵਾਜ਼ ਨੂੰ ਕੰਟਰੋਲ ਕਰਨ ਲਈ gestures ਦੀ ਵਰਤੋਂ ਕਰੋ
+ ਇੱਕ ਮà©à©±à¨•à¨£ ਵਾਲੀ ਪਰ ਨਾ-ਦà©à¨¹à¨°à¨¾à¨‰à¨£ ਵਾਲੀ ਕਤਾਰ ਨੂੰ, ਸੰਬੰਧਤ ਸਟà©à¨°à©€à¨® ਜੋੜਦਿਆਂ, ਚਲਾਉਂਦੇ ਜਾਓ
+ ਵੀਡੀਓ ਪਲੇਅਰ ਸੰਕੇਤ ਕੰਟਰੋਲ
+ ਸਕà©à¨°à©€à¨¨ ਦੀ ਚਮਕ ਅਤੇ ਆਵਾਜ਼ ਨੂੰ ਕੰਟਰੋਲ ਕਰਨ ਲਈ ਸੰਕੇਤਾਂ ਦੀ ਵਰਤੋਂ ਕਰੋ
ਖੋਜ ਸà©à¨à¨¾à¨…
- ਖੋਜ ਕਰਨ ਵੇਲੇ ਸà©à¨à¨¾à¨… ਦਿਖਾਓ
+ ਖੋਜ ਕਰਨ ਵੇਲੇ ਸà©à¨à¨¾à¨… ਵਿਖਾਓ
ਖੋਜ ਸੂਚੀ
- ਖੋਜ ਸੂਚੀ ਨੂੰ locally ਸਟੋਰ ਕਰੋ
+ ਖੋਜ ਇਤਲਾਹਾਂ ਨੂੰ ਸਥਾਨਕ ਤੌਰ ਤੇ ਸਟੋਰ ਕਰੋ
ਪਹਿਲਾਂ ਤੋਂ ਵੇਖੀਆਂ ਹੋਈਆਂ ਚੀਜ਼ਾਂ ਦੀ ਸੂਚੀ
- ਦੇਖੇ ਗਠਵਿਡੀਓਜ਼ ਦੀ ਸੂਚੀ ਰੱਖੋ
- Focus gain ਹੋਣ ਤੇ ਦੋਬਾਰਾ ਚਲਾਓ
- ਰà©à¨•à¨¾à¨µà¨Ÿà¨¾à¨‚ (ਜਿਵੇਂ ਕਿ ਫੋਨ ਕਾਲਾਂ,ਮੈਸੇਜ) ਤੋਂ ਬਾਅਦ ਪਲੇਅ-ਬੈਕ ਜਾਰੀ ਰੱਖੋ
+ ਵੇਖੇ ਗਠਵਿਡੀਓਜ਼ ਦੀ ਸੂਚੀ ਰੱਖੋ
+ ਫੋਕਸ ਮਿਲਣ ਤੇ ਚਲਾਉਣਾ ਮà©à©œ-ਸ਼à©à¨°à©‚ ਕਰੋ
+ ਵਿਘਨਾਂ (ਜਿਵੇਂ ਕਿ ਫ਼ੋਨ-ਕਾਲਾਂ, ਮੈਸੇਜ) ਤੋਂ ਬਾਅਦ ਚਲਾਉਣਾ ਜਾਰੀ ਰੱਖੋ
ਡਾਊਨਲੋਡ
ਅੱਗੇ
- \'ਅੱਗੇ\' ਅਤੇ \'ਸਮਾਨਅੰਤਰ\' ਵੀਡੀਓ ਦਿਖਾਓ
- \"Hold to append\" ਸà©à¨à¨¾à¨… ਦਿਖਾਓ
- ਵੀਡੀਓ ਵੇਰਵੇ ਪੰਨੇ ਤੇ ਬੈਕਗà©à¨°à¨¾à¨‰à¨‚ਡ ਜਾਂ ਪੌਪ-ਅਪ ਬਟਨ ਦਬਾਉਣ ਤੇ ਸੰਕੇਤ ਦਿਖਾਓ
- ਅਣ-ਸਹਾਇਕ URL
- ਮੂਲ ਦੇਸ਼ Content
+ \'ਅੱਗੇ\' ਅਤੇ \'ਸਮਾਨਅੰਤਰ\' ਵੀਡੀਓ ਵਿਖਾਓ
+ \"ਜੋੜਨ ਲਈ ਬਟਨ ਦਬਾ ਕੇ ਰੱਖੋ\" ਵਿਖਾਓ
+ ਵੀਡੀਓ ਦੇ ਵੇਰਵੇ ਪੰਨੇ \'ਤੇ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਜਾਂ ਪੌਪ-ਅਪ ਬਟਨ ਨੱਪਣ \'ਤੇ ਸੰਕੇਤ ਵਿਖਾਓ
+ ਅਣ-ਸਹਾਇਕ URL
+ ਮੂਲ ਦੇਸ਼ ਸਮੱਗਰੀ
ਸੇਵਾ
- ਪਲੇਯਰ
+ ਪਲੇਅਰ
ਵਿਵਹਾਰ
ਵੀਡੀਓ ਅਤੇ ਆਡੀਓ
- ਹਿਸਟਰੀ ਅਤੇ cache
+ ਅਤੀਤ ਅਤੇ ਕੈਸ਼ੇ
ਪੌਪ-ਅਪ
ਦਿੱਖ
ਹੋਰ
ਡੀ-ਬੱਗ
- ਬੈਕਗà©à¨°à¨¾à¨‰à¨‚ਡ ਵਿੱਚ ਚੱਲ ਰਿਹਾ ਹੈ
+ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਵਿੱਚ ਚੱਲ ਰਿਹਾ ਹੈ
ਪੌਪ-ਅਪ ਮੋਡ ਵਿੱਚ ਚੱਲ ਰਿਹਾ ਹੈ
- ਬੈਕਗà©à¨°à¨¾à¨‰à¨‚ਡ ਪਲੇਅਰ ਵਿੱਚ ਕਤਾਰਬੱਧ
- ਪੌਪ-ਅਪ ਪਲੇਯਰ ਵਿੱਚ ਕਤਾਰਬੱਧ
- ਪਲੇ
- Content
- ਉਮਰ ਪà©à¨°à¨¤à©€à¨¬à©°à¨§à¨¿à¨¤ Content
- ਉਮਰ ਪà©à¨°à¨¤à©€à¨¬à©°à¨§à¨¿à¨¤ ਵੀਡੀਓ ਦਿਖਾਓ ਸੈਟਿੰਗਸ ਤੋਂ ਅਜਿਹੀ ਸਮੱਗਰੀ ਦੀ ਆਗਿਆ ਦੇਣੀ ਸੰà¨à¨µ ਹੈ.
+ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਪਲੇਅਰ ਵਿੱਚ ਕਤਾਰਬੱਧ
+ ਪੌਪ-ਅਪ ਪਲੇਅਰ ਵਿੱਚ ਕਤਾਰਬੱਧ
+ ਪਲੇਅ
+ ਸਮੱਗਰੀ
+ ਉਮਰ-ਮà©à¨¤à¨¾à¨¬à¨•-ਪਾਬੰਦੀਸ਼à©à¨¦à¨¾ ਸਮੱਗਰੀ ਵਿਖਾਓ
+ ਉਮਰ ਪà©à¨°à¨¤à©€à¨¬à©°à¨§à¨¿à¨¤ ਵੀਡੀਓ ਵਿਖਾਓ ਸੈਟਿੰਗਸ ਤੋਂ ਅਜਿਹੀ ਸਮੱਗਰੀ ਦੀ ਆਗਿਆ ਦੇਣੀ ਸੰà¨à¨µ ਹੈ.
ਲਾਈਵ
ਡਾਊਨਲੋਡਸ
ਡਾਊਨਲੋਡਸ
- Error ਰਿਪੋਰਟ
+ ਤਰà©à©±à¨Ÿà©€ ਰਿਪੋਰਟ
ਸਾਰੇ
ਚੈਨਲ
ਪਲੇ ਸੂਚੀ
@@ -118,82 +124,83 @@
ਤਾਜ਼ਾ ਕਰੋ
ਮਿਟਾਓ
ਮà©à©œ ਆਕਾਰ
- ਵਧੀਆ Resolution
- ਵਾਪਿਸ
+ ਉੱਤਮ ਰੈਜ਼ੋਲਿਊਸ਼ਨ
+ ਅਣਕੀਤਾ ਕਰੋ
ਸਾਰੇ ਚਲਾਓ
- ਹਮੇਸ਼ਾ
+ ਹਮੇਸ਼ਾਂ
ਸਿਰਫ਼ ਇਸ ਬਾਰ
ਫਾਈਲ
- NewPipe ਨੋਟੀਫਿਕੇਸ਼ਨ
- NewPipe ਬੈਕਗà©à¨°à¨¾à¨‰à¨‚ਡ ਅਤੇ ਪੌਪ-ਅਪ ਪਲੇਅਰਾਂ ਲਈ ਸੂਚਨਾਵਾਂ
+ ਨਿਊਪਾਈਪ ਨੋਟੀਫਿਕੇਸ਼ਨ
+ ਨਿਊਪਾਈਪ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਅਤੇ ਪੌਪ-ਅਪ ਪਲੇਅਰਾਂ ਲਈ ਸੂਚਨਾਵਾਂ
ਅਣਜਾਣ
- Orientation ਬਦਲੋ
- ਬੈਕਗਰਾਊਂਡ ਵਿੱਚ ਬਦਲੋ
+ ਓਰੀà¨à¨‚ਟੇਸ਼ਨ ਬਦਲੋ
+ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਵਿੱਚ ਬਦਲੋ
ਪੌਪ-ਅਪ ਵਿੱਚ ਬਦਲੋ
ਮà©à©±à¨– ਵਿੱਚ ਬਦਲੋ
- Database ਆਯਾਤ ਕਰੋ
- Database ਨਿਰਯਾਤ ਕਰੋ
- ਤà©à¨¹à¨¾à¨¡à©€ ਮੌਜੂਦਾ ਹਿਸਟਰੀ ਅਤੇ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨à¨¸ ਨੂੰ Override ਕਰਦਾ ਹੈ
- ਹਿਸਟਰੀ, ਸà©à¨¬à¨¸à¨•à©à¨°à¨¿à¨ªà¨¶à¨¨à¨¸ ਅਤੇ ਪਲੇ-ਸੂਚੀ ਨਿਰਯਾਤ ਕਰੋ
- Watch ਹਿਸਟਰੀ ਮਿਟਾਓ
- ਚਲਾਈਆਂ ਗਈਆਂ ਸਟà©à¨°à©€à¨®à¨¾à¨‚ ਦੀ ਸੂਚੀ ਮਿਟਾਉਂਦਾ ਹੈ
- ਕੀ ਸਾਰੀ watch ਹਿਸਟਰੀ ਮਿਟਾ ਦਿੱਤੀ ਜਾਵੇ \?
+ ਟਾਈਮਰ ਸੈੱਟ ਕਰੋ
+ ਡਾਟਾਬੇਸ ਆਯਾਤ ਕਰੋ
+ ਡਾਟਾਬੇਸ ਨਿਰਯਾਤ ਕਰੋ
+ ਤà©à¨¹à¨¾à¨¡à©‡ ਮੌਜੂਦਾ ਇਤਿਹਾਸ, ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨à¨¸, ਪਲੇ-ਸੂਚੀ ਅਤੇ (ਆਪਨਸ਼ਨਲੀ) ਸੈਟਿੰਗਾਂ ਨੂੰ ਨਵਿਆਂ ਨਾਲ਼ ਬਦਲ ਦਿੰਦਾ ਹੈ
+ ਇਤਿਹਾਸ, ਸà©à¨¬à¨¸à¨•à©à¨°à¨¿à¨ªà¨¶à¨¨à¨¸, ਪਲੇ-ਸੂਚੀ ਅਤੇ ਸੈਟਿੰਗਾਂ ਨਿਰਯਾਤ ਕਰੋ
+ ਵੇਖੇ ਗਠਵੀਡੀਓਜ਼ ਦੀ ਸੂਚੀ ਮਿਟਾਓ
+ ਚਲਾਈਆਂ ਗਈਆਂ ਸਟà©à¨°à©€à¨®à¨¾à¨‚ ਦੇ ਇਤਿਹਾਸ ਅਤੇ ਪਲੇ-ਸਥਿਤੀਆਂ ਨੂੰ ਮਿਟਾਉਂਦਾ ਹੈ
+ ਕੀ ਵੇਖੇ ਗਠਵੀਡੀਓਜ਼ ਦਾ ਇਤਿਹਾਸ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇ\?
ਖੋਜ ਸੂਚੀ ਮਿਟਾਓ
ਖੋਜ ਸ਼ਬਦਾਂ ਦੀ ਸੂਚੀ ਮਿਟਾਉਂਦਾ ਹੈ
ਕੀ ਸਾਰੀ ਖੋਜ ਸੂਚੀ ਮਿਟਾ ਦਿਤੀ ਜਾਵੇ \?
- ਖੋਜ ਸੂਚੀ ਮਿਟਾ ਦਿਤੀ ਗਈ ਹੈ.
- ERROR
- ਨੈੱਟਵਰਕ ERROR
- ਸਾਰੇ ਥੰਬਨੇਲ ਲੋਡ ਨਹੀਂ ਹੋ ਸਕੇ
+ ਖੋਜ ਸੂਚੀ ਮਿਟਾ ਦਿਤੀ ਗਈ ਹੈ।
+ ਤਰà©à©±à¨Ÿà©€
+ ਨੈੱਟਵਰਕ ਗੜਬੜ
+ ਸਾਰੇ ਥੰਮਨੇਲ ਲੋਡ ਨਹੀਂ ਹੋ ਸਕੇ
ਵੀਡੀਓ URL ਦਸਤਖਤ ਡੀਕà©à¨°à¨¿à¨ªà¨Ÿ ਨਹੀਂ ਹੋ ਸਕਿਆ
- ਵੈਬਸਾਈਟ parse ਨਹੀਂ ਹੋ ਸਕੀ
- ਵੈਬਸਾਈਟ ਪੂਰੀ ਤਰਾਂ Parse ਨਹੀਂ ਹੋ ਸਕੀ
- Content ਉਪਲਬਧ ਨਹੀਂ ਹੈ
+ ਵੈਬਸਾਈਟ ਪਾਰਸ ਨਹੀਂ ਹੋ ਸਕੀ
+ ਵੈਬਸਾਈਟ ਪੂਰੀ ਤਰਾਂ ਪਾਰਸ ਨਹੀਂ ਹੋ ਸਕੀ
+ ਸਮੱਗਰੀ ਉਪਲਬਧ ਨਹੀਂ ਹੈ
ਡਾਊਨਲੋਡ ਮੀਨੂੰ ਸੈਟ-ਅਪ ਨਹੀਂ ਹੋ ਸਕਿਆ
- ਲਾਈਵ ਸਟà©à¨°à©€à¨® ਅਜੇ supported ਨਹੀਂ ਹਨ
+ ਲਾਈਵ ਸਟà©à¨°à©€à¨® ਅਜੇ ਸਪੋਰਟਡ ਨਹੀਂ ਹਨ
ਕੋਈ ਸਟà©à¨°à©€à¨® ਪà©à¨°à¨¾à¨ªà¨¤ ਨਹੀਂ ਹੋ ਸਕੀ
ਚਿੱਤਰ ਲੋਡ ਨਹੀਂ ਹੋ ਸਕਿਆ
- à¨à¨ª/UI crashed
+ à¨à¨ª/UI ਕਰੈਸ਼ ਹੋ ਗਈ
ਇਸ ਸਟà©à¨°à©€à¨® ਨੂੰ ਚਲਾਇਆ ਨਹੀਂ ਜਾ ਸਕਿਆ
- ਅਣਚਾਹਾ ਪਲੇਅਰ ERROR ਆਇਆ ਹੈ
- ਪਲੇਅਰ ERROR ਤੋਂ Recover ਹੋ ਰਿਹਾ ਹੈ
- External ਪਲੇਅਰ ਇਸ ਕਿਸਮ ਦੇ ਲਿੰਕਾਂ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੇ
+ ਅਣਚਾਹੀ ਪਲੇਅਰ ਤਰà©à©±à¨Ÿà©€ ਆਈ ਹੈ
+ ਪਲੇਅਰ ਤਰà©à©±à¨Ÿà©€ ਤੋਂ ਮà©à©œ-ਸà©à¨°à¨œà©€à¨¤ ਹੋ ਰਿਹਾ ਹੈ
+ ਬਾਹਰੀ ਪਲੇਅਰ ਇਸ ਕਿਸਮ ਦੇ ਲਿੰਕਾਂ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੇ
ਅਵੈਧ URL
ਕੋਈ ਵੀ ਵੀਡੀਓ ਸਟà©à¨°à©€à¨® ਨਹੀਂ ਮਿਲੀ
ਕੋਈ ਵੀ ਆਡੀਓ ਸਟà©à¨°à©€à¨® ਨਹੀਂ ਮਿਲੀ
- ਅਜਿਹਾ ਕੋਈ ਫੋਲਡਰ ਨਹੀਂ
- ਅਜਿਹਾ ਕੋਈ ਫਾਈਲ/Content ਸਰੋਤ ਨਹੀਂ ਹੈ
+ ਅਜਿਹਾ ਕੋਈ ਫ਼ੋਲਡਰ ਨਹੀਂ
+ ਅਜਿਹਾ ਕੋਈ ਫਾਈਲ/ਸਮੱਗਰੀ ਸਰੋਤ ਨਹੀਂ ਹੈ
ਫਾਈਲ ਮੌਜੂਦ ਨਹੀਂ ਹੈ ਜਾਂ ਇਸ ਨੂੰ ਪੜà©à¨¹à¨¨ ਜਾਂ ਲਿਖਣ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ
ਫਾਈਲ ਨਾਮ ਖਾਲੀ ਨਹੀਂ ਹੋ ਸਕਦਾ
- ਇੱਕ ERROR ਆਇਆ ਹੈ: %1$s
+ ਇੱਕ ਤਰà©à©±à¨Ÿà©€ ਆਈ ਹੈ: %1$s
ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਕੋਈ ਸਟà©à¨°à©€à¨® ਉਪਲਬਧ ਨਹੀਂ ਹੈ
- ਮà©à¨†à¨« ਕਰਨਾ, ਅਜਿਹਾ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ ਸੀ.
- ਈ-ਮੇਲ ਦà©à¨†à¨°à¨¾ ERROR ਦੀ ਰਿਪੋਰਟ ਕਰੋ
- ਮà©à¨†à¨« ਕਰਨਾ, ਕà©à¨ ERROR ਹੋਠਹਨ.
+ ਮà©à¨†à¨« ਕਰਨਾ, ਅਜਿਹਾ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ ਸੀ।
+ ਈ-ਮੇਲ ਦà©à¨†à¨°à¨¾ ਇਸ ਤਰà©à©±à¨Ÿà©€ ਦੀ ਇਤਲਾਹ ਦਿਓ
+ ਅਫ਼ਸੋਸ ਹੈ, ਕà©à¨ ਤਰà©à©±à¨Ÿà©€à¨†à¨‚ ਸਾਹਮਣੇ ਆਈਆਂ ਹਨ।
ਰਿਪੋਰਟ
ਜਾਣਕਾਰੀ:
ਕੀ ਹੋਇਆ ਹੈ:
- ਕਿਸ ਬਾਰੇ:\\nRequest:\\nContent à¨à¨¾à¨¶à¨¾:\\nService:\\nGMT ਸਮਾਂ:\\nPackage:\\nVersion:\\nOS version:
+ ਕੀ:\\nRequest:\\nContent à¨à¨¾à¨¸à¨¼à¨¾/ਬੋਲੀ:\\nContent Country:\\nApp à¨à¨¾à¨¶à¨¾/ਬੋਲੀ:\\nService:\\nGMT ਸਮਾਂ:\\nPackage:\\nVersion:\\nOS version:
ਤà©à¨¹à¨¾à¨¡à©€ ਟਿੱਪਣੀ (ਅੰਗਰੇਜ਼ੀ ਵਿਚ):
ਵੇਰਵੇ:
- ਵੀਡੀਓ preview thumbnail
- ਵੀਡੀਓ preview thumbnail
- ਅਪਲੋਡਰ ਦਾ ਅਵਤਾਰ thumbnail
- ਪਸੰਦ
- ਨਾਪਸੰਦ
+ ਵੀਡੀਓ ਪਰੀਵਿਊ ਥੰਮਨੇਲ
+ ਵੀਡੀਓ ਚਲਾਓ, ਮਿਆਦ:
+ ਅਪਲੋਡਰ ਦਾ ਅਵਤਾਰ ਥੰਮਨੇਲ
+ ਪਸੰਦਾਂ
+ ਨਾਪਸੰਦਾਂ
TOR ਦੀ ਵਰਤੋਂ ਕਰੋ
- (ਪà©à¨°à¨¯à©‹à¨— ਅਧੀਨ) ਗੋਪਨੀਯਤਾ ਲਈ TOR ਦà©à¨†à¨°à¨¾ ਟਰੈਫਿਕ ਨੂੰ ਜਬਰੀ Download ਹੋਣ ਲਈ ਮਜਬੂਰ ਕਰੋ (ਸਟà©à¨°à©€à¨®à¨¿à©°à¨— ਵੀਡੀਓ ਅਜੇ supported ਨਹੀਂ ਹਨ).
- ERROR ਰਿਪੋਰਟ ਕਰੋ
- ਯੂਸਰ ਰਿਪੋਰਟ
+ (ਪà©à¨°à¨¯à©‹à¨— ਅਧੀਨ) ਗੋਪਨੀਯਤਾ ਲਈ TOR ਦà©à¨†à¨°à¨¾ ਟਰੈਫਿਕ ਨੂੰ ਜਬਰੀ ਡਾਊਨਲੋਡ ਹੋਣ ਲਈ ਮਜਬੂਰ ਕਰੋ (ਸਟà©à¨°à©€à¨®à¨¿à©°à¨— ਵੀਡੀਓ ਅਜੇ supported ਨਹੀਂ ਹਨ)।
+ ERROR ਦੀ ਇਤਲਾਹ ਦਿਓ
+ ਵਰਤੋਂਕਾਰ ਦੀ ਰਿਪੋਰਟ
ਕੋਈ ਨਤੀਜੇ ਨਹੀਂ
- ਇਥੇ ਦਾ ਸà©à©°à¨¨à¨¾à¨ªà¨¨ ਦੂਰ ਕਰਨ ਲਈ ਕੋਈ ਚੈਨਲ ਸਬਸਕà©à¨°à¨¾à¨‡à¨¬ ਕਰੋ
- ਕਤਾਰਬੱਧ ਕਰਨ ਲਈ ਖਿੱਚੋ
+ ਇਥੇ ਦਾ ਖਾਲੀਪਣ ਦੂਰ ਕਰਨ ਲਈ ਕà©à¨ ਸਰਚ ਕਰੋ ਜਾਂ ਕੋਈ ਚੈਨਲ ਸਬਸਕà©à¨°à¨¾à¨‡à¨¬ ਕਰੋ
+ ਮà©à©œ-ਕà©à¨°à¨®à¨¬à©±à¨§ ਕਰਨ ਲਈ ਡਰੈਗ ਕਰੋ
ਡਾਊਨਲੋਡ ਡਾਇਰੈਕਟਰੀ ਨਹੀਂ ਬਣਾਈ ਜਾ ਸਕਦੀ \'%1$s\'
ਡਾਊਨਲੋਡ ਡਾਇਰੈਕਟਰੀ ਬਣਾਈ ਗਈ \'%1$s\'
ਵੀਡੀਓ
ਆਡੀਓ
ਦੋਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ
- ਸਟੋਰੇਜ਼ Access ਨੂੰ ਅਸਵੀਕਾਰ ਕੀਤਾ ਗਿਆ
+ ਪਹਿਲਾਂ ਸਟੋਰੇਜ ਨੂੰ ਪੜà©à¨¹à¨¨ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ
ਹਜ਼ਾਰ
ਮਿਲੀਅਨ
ਬਿਲੀਅਨ
@@ -202,10 +209,10 @@
- %s ਸਬਸਕà©à¨°à¨¾à¨‡à¨¬à¨°
- %s ਸਬਸਕà©à¨°à¨¾à¨‡à¨¬à¨°à¨¸
- ਕੋਈ views ਨਹੀਂ
+ ਕੋਈ ਵਿਊਜ਼ ਨਹੀਂ
- - %s view
- - %s views
+ - %s ਵਿਊ
+ - %s ਵਿਊਜ਼
ਕੋਈ ਵੀਡੀਓ ਨਹੀਂ
@@ -214,7 +221,7 @@
ਸ਼à©à¨°à©‚ ਕਰੋ
ਰੋਕੋ
- ਪਲੇ
+ ਪਲੇਅ
ਬਣਾਓ
ਮਿਟਾਓ
ਇੱਕ ਮਿਟਾਓ
@@ -225,49 +232,49 @@
ਨਵਾਂ ਮਿਸ਼ਨ
ਠੀਕ ਹੈ
ਫਾਈਲ ਦਾ ਨਾਮ
- threads
- ERROR
- Unsupported ਸਰਵਰ
+ ਥਰੈੱਡ
+ ਤਰà©à©±à¨Ÿà©€
+ ਨਾ- ਸਹਾਇਕ ਸਰਵਰ
ਫਾਈਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ
ਖਰਾਬ URL ਜਾਂ ਇੰਟਰਨੈਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ
- NewPipe ਡਾਊਨਲੋਡ ਹੋ ਰਿਹਾ ਹੈ
+ ਨਿਊਪਾਈਪ ਡਾਊਨਲੋਡ ਹੋ ਰਹੀ ਹੈ
ਵੇਰਵਿਆਂ ਲਈ ਖੋਲੋ
ਕà©à¨°à¨¿à¨ªà¨¾ ਕਰਕੇ ਉਡੀਕ ਕਰੋ…
ਕਲਿਪ-ਬੋਰਡ ਵਿੱਚ ਕਾਪੀ ਹੋ ਗਿਆ ਹੈ
- ਬਾਅਦ ਵਿੱਚ ਸੈਟਿੰਗਾਂ ਵਿਚੋਂ ਇੱਕ ਡਾਉਨਲੋਡ ਫੋਲਡਰ ਨੂੰ ਚà©à¨£à©‹
+ ਬਾਅਦ ਵਿੱਚ ਸੈਟਿੰਗਾਂ ਵਿਚੋਂ ਇੱਕ ਡਾਉਨਲੋਡ ਫ਼ੋਲਡਰ ਨੂੰ ਚà©à¨£à©‹
ਪੌਪ-ਅਪ ਮੋਡ ਵਿੱਚ ਖੋਲà©à¨¹à¨£ ਵਾਸਤੇ ਇਸ ਇਜਾਜ਼ਤ ਦੀ ਲੋੜ ਹੈ
- 1 ਆਈਟਮ ਮਿਟਾਈ ਗਈ.
+ 1 ਆਈਟਮ ਮਿਟਾਈ ਗਈ।
ReCaptcha ਚà©à¨£à©Œà¨¤à©€
ReCaptcha ਚà©à¨£à©Œà¨¤à©€ ਲਈ ਬੇਨਤੀ
ਡਾਊਨਲੋਡ
ਫਾਈਲ ਨਾਮ ਵਿੱਚ ਪà©à¨°à¨µà¨¾à¨¨à¨¿à¨¤ ਅੱਖਰ
- ਗਲਤ ਅੱਖਰ ਇਸ Value ਨਾਲ ਤਬਦੀਲ ਕੀਤੇ ਜਾਣਗੇ
- Replacement ਅੱਖਰ
+ ਗਲਤ ਅੱਖਰ ਇਸ ਚਿੰਨ-ਅੱਖਰ ਨਾਲ ਤਬਦੀਲ ਕੀਤੇ ਜਾਣਗੇ
+ ਵਟਾਂਦਰਾ ਚਿੰਨ
ਅੱਖਰ ਅਤੇ ਅੰਕ
ਬਹà©à¨¤à©‡ ਖ਼ਾਸ ਅੱਖਰ
ਇਸ ਫਾਈਲ ਨੂੰ ਚਲਾਉਣ ਲਈ ਕੋਈ à¨à¨ª ਇੰਸਟਾਲ ਨਹੀਂ ਹੈ
- NewPipe ਬਾਰੇ
+ ਨਿਊਪਾਈਪ ਬਾਰੇ
ਸੈਟਿੰਗਾਂ
à¨à¨ª ਬਾਰੇ
- ਥਰਡ-ਪਾਰਟੀ ਲਾਇਸੈਂਸ
+ ਥਰਡ-ਪਾਰਟੀ ਲਾਈਸੈਂਸ
© %1$s ਵਲੋਂ %2$s, %3$s ਅਧੀਨ
- ਲਾਇਸੈਂਸ ਲੋਡ ਨਹੀਂ ਹੋ ਸਕਿਆ
+ ਲਾਈਸੈਂਸ ਲੋਡ ਨਹੀਂ ਹੋ ਸਕਿਆ
ਵੈਬਸਾਈਟ ਖੋਲà©à¨¹à©‹
à¨à¨ª ਬਾਰੇ
ਯੋਗਦਾਨ ਪਾਉਣ ਵਾਲੇ
- ਲਾਇਸੈਂਸ
- à¨à¨‚ਡਰਾਇਡ ਤੇ ਮà©à¨«à¨¤ ਲਾਈਟਵੇਟ ਸਟà©à¨°à©€à¨®à¨¿à©°à¨—.
+ ਲਾਈਸੈਂਸ
+ à¨à¨‚ਡਰਾਇਡ ਤੇ ਮà©à¨«à¨¼à¨¤ ਹਲਕੀ-ਫà©à¨²à¨•à©€ ਸਟà©à¨°à©€à¨®à¨¿à©°à¨—।
ਯੋਗਦਾਨ ਪਾਓ
à¨à¨¾à¨µà©‡à¨‚ ਤà©à¨¹à¨¾à¨¡à©‡ ਕੋਲ ਵਿਚਾਰ ਹਨ; ਅਨà©à¨µà¨¾à¨¦, ਡਿਜ਼ਾਈਨ ਬਦਲਾਵ, ਕੋਡ ਦੀ ਸਫਾਈ, ਜਾਂ ਅਸਲ à¨à¨¾à¨°à©€ ਕੋਡ ਬਦਲਾਵ — ਹਰ ਮਦਦ ਦਾ ਸਦਾ ਸਵਾਗਤ ਹੈ. ਜਿੰਨਾ ਇਸ ਨੂੰ ਜ਼ਿਆਦਾ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਉੱਨਾ ਹੀ ਇਹ ਬਿਹਤਰ ਹà©à©°à¨¦à¨¾ ਹੈ!
GitHub ਤੇ ਵੇਖੋ
ਦਾਨ ਕਰੋ
- NewPipe ਵਲੰਟੀਅਰਾਂ ਦà©à¨†à¨°à¨¾ ਵਿਕਸਤ ਕੀਤੀ ਗਈ ਹੈ ਜੋ ਤà©à¨¹à¨¾à¨¡à©‡ ਲਈ ਬਿਹਤਰ ਅਨà©à¨à¨µ ਲਿਆਉਣ ਲਈ ਸਮਾਂ ਬਿਤਾਉਂਦੇ ਹਨ. ਇੱਕ ਕੱਪ ਕਾਫੀ ਦਾ ਆਨੰਦ ਲੈਂਦੇ ਹੋਠਡਿਵੈਲਪਰਾਂ ਨੂੰ NewPipe ਨੂੰ ਹੋਰ ਵਧੀਆ ਬਣਾਉਣ ਵਿੱਚ ਸਹਾਇਤਾ ਲਈ ਵਾਪਸ ਦਿਓ.
+ ਨਿਊਪਾਈਪ ਵਲੰਟੀਅਰਾਂ ਦà©à¨†à¨°à¨¾ ਵਿਕਸਤ ਕੀਤੀ ਗਈ ਹੈ ਜੋ ਤà©à¨¹à¨¾à¨¡à©‡ ਲਈ ਬਿਹਤਰ ਵਰਤੋਂਕਾਰ ਤਜਰਬਾ ਲਿਆਉਣ ਲਈ ਸਮਾਂ ਬਿਤਾਉਂਦੇ ਹਨ। ਡਿਵੈਲਪਰਾਂ ਵਾਸਤੇ ਇੱਕ ਕੱਪ ਕਾਫ਼ੀ ਖ਼ਰੀਦ ਦਿਓ ਤਾਂ ਕਿ ਇਸਦਾ ਆਨੰਦ ਲੈਂਦੇ ਹੋਠਉਹ ਨਿਊ-ਪਾਈਪ ਨੂੰ ਹੋਰ ਵਧੀਆ ਬਣਾ ਸਕਣ।
ਵਾਪਸ ਦਿਓ
ਵੈਬਸਾਈਟ
- ਵਧੇਰੇ ਜਾਣਕਾਰੀ ਅਤੇ ਖ਼ਬਰਾਂ ਲਈ NewPipe ਵੈਬਸਾਈਟ ਵੇਖੋ.
- NewPipe\'s ਲਾਇਸੈਂਸ
- NewPipe ਇੱਕ ਕਾੱਪੀਲਿਫਟ ਮà©à©žà¨¤ ਸਾੱਫਟਵੇਅਰ ਹੈ: ਤà©à¨¸à©€à¨‚ ਇਸ ਦੀ ਵਰਤੋਂ, ਅਧਿà¨à¨¨ ਅਤੇ ਇਸ ਨੂੰ ਸਾਂà¨à¨¾ ਕਰ ਸਕਦੇ ਹੋ ਅਤੇ ਇਸ ਵਿੱਚ ਆਪਣੀ ਮਰਜ਼ੀ ਅਨà©à¨¸à¨¾à¨° ਸà©à¨§à¨¾à¨° ਸਕਦੇ ਹੋ. ਖਾਸ ਤੌਰ \'ਤੇ ਤà©à¨¸à©€à¨‚ ਇਸ ਨੂੰ GNU ਜਨਰਲ ਪਬਲਿਕ ਲਾਇਸੈਂਸ ਦੀਆਂ ਸ਼ਰਤਾਂ ਦੇ ਅਧੀਨ ਵੰਡ ਸਕਦੇ ਹੋ / ਜਾਂ ਸੰਸ਼ੋਧਿਤ ਕਰ ਸਕਦੇ ਹੋ ਜਿਵੇਂ ਕਿ ਮà©à¨«à¨¤ ਸਾੱਫਟਵੇਅਰ ਫਾਊਂਡੇਸ਼ਨ ਦà©à¨†à¨°à¨¾ ਪà©à¨°à¨•à¨¾à¨¸à¨¼à¨¤ ਕੀਤਾ ਗਿਆ ਹੈ, ਜਾਂ ਤਾਂ ਲਾਇਸੈਂਸ ਦਾ ਵਰਜ਼ਨ 3, ਜਾਂ (ਤà©à¨¹à¨¾à¨¡à©‡ ਵਿਕਲਪ\' ਤੇ) ਬਾਅਦ ਦਾ ਕੋਈ ਸੰਸਕਰਣ.
- ਲਾਇਸੈਂਸ ਪੜà©à¨¹à©‹
+ ਵਧੇਰੇ ਜਾਣਕਾਰੀ ਅਤੇ ਖ਼ਬਰਾਂ ਲਈ ਨਿਊਪਾਈਪ ਵੈਬਸਾਈਟ ਵੇਖੋ।
+ ਨਿਊਪਾਈਪ ਦਾ ਲਾਈਸੈਂਸ
+ ਨਿਊਪਾਈਪ ਇੱਕ ਕਾੱਪੀਲਿਫਟ ਮà©à©žà¨¤ ਸਾੱਫਟਵੇਅਰ ਹੈ: ਤà©à¨¸à©€à¨‚ ਇਸ ਦੀ ਵਰਤੋਂ, ਅਧਿà¨à¨¨ ਅਤੇ ਇਸ ਨੂੰ ਸਾਂà¨à¨¾ ਕਰ ਸਕਦੇ ਹੋ ਅਤੇ ਇਸ ਵਿੱਚ ਆਪਣੀ ਮਰਜ਼ੀ ਅਨà©à¨¸à¨¾à¨° ਸà©à¨§à¨¾à¨° ਸਕਦੇ ਹੋ. ਖਾਸ ਤੌਰ \'ਤੇ ਤà©à¨¸à©€à¨‚ ਇਸ ਨੂੰ GNU ਜਨਰਲ ਪਬਲਿਕ ਲਾਈਸੈਂਸ ਦੀਆਂ ਸ਼ਰਤਾਂ ਦੇ ਅਧੀਨ ਵੰਡ ਸਕਦੇ ਹੋ / ਜਾਂ ਸੰਸ਼ੋਧਿਤ ਕਰ ਸਕਦੇ ਹੋ ਜਿਵੇਂ ਕਿ ਮà©à¨«à¨¤ ਸਾੱਫਟਵੇਅਰ ਫਾਊਂਡੇਸ਼ਨ ਦà©à¨†à¨°à¨¾ ਪà©à¨°à¨•à¨¾à¨¸à¨¼à¨¤ ਕੀਤਾ ਗਿਆ ਹੈ, ਜਾਂ ਤਾਂ ਲਾਈਸੈਂਸ ਦਾ ਵਰਜ਼ਨ 3, ਜਾਂ (ਤà©à¨¹à¨¾à¨¡à©‡ ਵਿਕਲਪ\' ਤੇ) ਬਾਅਦ ਦਾ ਕੋਈ ਸੰਸਕਰਣ।
+ ਲਾਈਸੈਂਸ ਪੜà©à¨¹à©‹
ਹਿਸਟਰੀ
ਖੋਜਿਆ ਗਿਆ
ਵੇਖਿਆ ਗਿਆ
@@ -282,65 +289,75 @@
ਆਖਰੀ ਚਲਾਈ ਗਈ
ਸਠਤੋਂ ਜਿਆਦਾ ਚਲਾਈ ਗਈ
ਮà©à©±à¨– ਪੰਨੇ ਦੀ ਸਮੱਗਰੀ
- ਖਾਲੀ ਪੇਜ
- Kiosk ਪੇਜ
- ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨ ਪੇਜ
- ਫੀਡ ਪੇਜ
- ਚੈਨਲ ਪੇਜ
+ ਖਾਲੀ ਪੰਨਾ
+ ਕਿਓਸਕ ਪੰਨਾ
+ ਡਿਫ਼ਾਲਟ ਕਿਓਸਕ
+ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨ ਪੰਨਾ
+ ਫੀਡ ਪੰਨਾ
+ ਚੈਨਲ ਪੰਨਾ
ਚੈਨਲ ਚà©à¨£à©‹
ਅਜੇ ਤੱਕ ਕੋਈ ਚੈਨਲ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨ ਨਹੀਂ
- ਇੱਕ Kiosk ਚà©à¨£à©‹
+ ਪਲੇ-ਸੂਚੀ ਚà©à¨£à©‹
+ ਹਾਲੇ ਕੋਈ ਵੀ ਪਲੇ-ਸੂਚੀ ਬà©à©±à¨•à¨®à¨¾à¨°à¨• ਨਹੀਂ ਕੀਤੀ ਹੋਈ
+ ਇੱਕ ਕਿਓਸਕ ਚà©à¨£à©‹
à¨à¨•à¨¸à¨ªà©‹à¨°à¨Ÿ ਕੀਤਾ ਗਿਆ
ਇੰਪੋਰਟ ਕੀਤਾ ਗਿਆ
ਕੋਈ ਵੈਧ ZIP ਫਾਈਲ ਨਹੀਂ ਹੈ
- ਚੇਤਾਵਨੀ: ਸਾਰੀਆਂ ਫਾਈਲਾਂ ਇੰਪੋਰਟ ਨਹੀਂ ਕੀਤੀਆਂ ਜਾ ਸਕੀਆਂ.
- ਇਹ ਤà©à¨¹à¨¾à¨¡à©‡ ਮੌਜੂਦਾ ਸੈਟ-ਅਪ ਨੂੰ Override ਕਰ ਦੇਵੇਗਾ.
- kiosk
+ ਚੇਤਾਵਨੀ: ਸਾਰੀਆਂ ਫਾਈਲਾਂ ਇੰਪੋਰਟ ਨਹੀਂ ਕੀਤੀਆਂ ਜਾ ਸਕੀਆਂ।
+ ਇਹ ਤà©à¨¹à¨¾à¨¡à©‡ ਮੌਜੂਦਾ ਸੈਟ-ਅਪ ਨੂੰ ਓਵਰ-ਰਾਈਡ ਕਰ ਦੇਵੇਗਾ।
+ à¨à¨¾à¨¸à¨¼à¨¾, à¨à¨ª ਨੂੰ ਦੋਬਾਰਾ ਚਲਾਉਣ \'ਤੇ ਬਦਲੇਗੀ।
+ ਕਿਓਸਕ
ਰà©à¨à¨¾à¨¨ ਵਿੱਚ
- ਟੌਪ 50
- ਨਵਾਂ ਅਤੇ ਗਰਮਾ-ਗਰਮ
- ਬੈਕਗà©à¨°à¨¾à¨‰à¨‚ਡ ਪਲੇਅਰ
+ ਸਿਖਰਲੇ 50
+ ਨਵਾਂ ਅਤੇ ਤਾਜ਼ਾ-ਤਰੀਨ
+ ਲੋਕਲ
+ ਹਾਲ ਹੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਹੋਇਆ
+ ਸਠਤੋਂ ਵੱਧ ਪਸੰਦ ਕੀਤਾ ਹੋਇਆ
+ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਪਲੇਅਰ
ਪੌਪ-ਅਪ ਪਲੇਅਰ
ਹਟਾਓ
ਵੇਰਵੇ
ਆਡੀਓ ਸੈਟਿੰਗਾਂ
ਕਤਾਰਬੱਧ ਕਰਨ ਵਾਸਤੇ ਦਬਾ ਕੇ ਰੱਖੋ
- ਬੈਕਗà©à¨°à¨†à¨Šà¨‚ਡ ਵਿੱਚ ਕਤਾਰਬੱਧ ਕਰੋ
+ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਵਿੱਚ ਕਤਾਰਬੱਧ ਕਰੋ
ਨਵੇਂ ਪੌਪ-ਅਪ ਵਿੱਚ ਕਤਾਰਬੱਧ ਕਰੋ
- ਇਥੇ ਚਲਾਉ
- "ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਵਿੱਚ ਚਲਾਉ"
+ ਇੱਥੇ ਚਲਾਓ
+ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਵਿੱਚ ਚਲਾਓ
ਨਵੇਂ ਪੌਪ-ਅਪ ਵਿੱਚ ਚਲਾਓ
- Drawer ਖੋਲੋ
- Drawer ਬੰਦ ਕਰੋ
+ ਡਰਾਅਰ ਖੋਲੋ
+ ਡਰਾਅਰ ਬੰਦ ਕਰੋ
ਜਲਦੀ ਹੀ ਇੱਥੇ ਕà©à©±à¨ ਦਿਖਾਈ ਦੇਵੇਗਾ ;D
- ਤਰਜੀਹੀ \'OPEN\' à¨à¨•à¨¶à¨¨
- Content ਖੋਲà©à¨¹à¨£ ਵੇਲੇ Default ਕਾਰਵਾਈ — %s
+ ਤਰਜੀਹੀ \'ਖੋਲà©à¨¹à©‹\' à¨à¨•à¨¶à¨¨
+ ਸਮੱਗਰੀ ਖੋਲà©à¨¹à¨£ ਵੇਲੇ ਡਿਫ਼ਾਲਟ ਕਾਰਵਾਈ — %s
ਵੀਡੀਓ ਪਲੇਅਰ
ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਪਲੇਅਰ
ਪੌਪ-ਅਪ ਪਲੇਅਰ
- ਹਮੇਸ਼ਾ ਪà©à©±à¨›à©‹
+ ਹਮੇਸ਼ਾਂ ਪà©à©±à¨›à©‹
ਜਾਣਕਾਰੀ ਪà©à¨°à¨¾à¨ªà¨¤ ਕਰ ਰਹੇ ਹਾਂ…
- ਬੇਨਤੀ ਕੀਤਾ Content ਲੋਡ ਕੀਤੀ ਜਾ ਰਿਹਾ ਹੈ
+ ਬੇਨਤੀ ਕੀਤੀ ਸਮੱਗਰੀ ਲੋਡ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ
ਨਵੀਂ ਪਲੇ-ਲਿਸਟ
ਮਿਟਾਓ
ਨਾਮ ਬਦਲੋ
ਨਾਮ
ਪਲੇ-ਲਿਸਟ ਵਿੱਚ ਸ਼ਾਮਿਲ ਕਰੋ
- ਪਲੇ-ਲਿਸਟ thumbnail ਦੇ ਤੌਰ ਤੇ ਸੈੱਟ ਕਰੋ
+ ਅਵਾਜ਼ ਬੰਦ ਕਰੋ
+ ਅਵਾਜ਼ ਚਾਲੂ ਕਰੋ
+ ਬਤੌਰ ਪਲੇ-ਸੂਚੀ ਥੰਮਨੇਲ ਸੈੱਟ ਕਰੋ
ਬà©à©±à¨•à¨®à¨¾à¨°à¨• ਪਲੇ-ਲਿਸਟ
ਬà©à©±à¨•à¨®à¨¾à¨°à¨• ਹਟਾਓ
ਇਸ ਪਲੇ-ਲਿਸਟ ਨੂੰ ਮਿਟਾਉਣਾ ਹੈ \?
ਪਲੇ-ਲਿਸਟ ਬਣਾਈ ਗਈ
ਪਲੇ-ਲਿਸਟ ਕੀਤਾ ਗਿਆ
- ਪਲੇ-ਲਿਸਟ thumbnail ਬਦਲਿਆ ਗਿਆ.
- ਪਲੇ-ਲਿਸਟ ਨੂੰ ਮਿਟਾ ਨਹੀਂ ਸਕੇ.
- No captions
+ ਪਲੇ-ਲਿਸਟ ਥੰਮਨੇਲ ਬਦਲਿਆ ਗਿਆ।
+ ਪਲੇ-ਲਿਸਟ ਨੂੰ ਮਿਟਾ ਨਹੀਂ ਸਕੇ।
+ ਆਪਣੇ-ਆਪ ਬਣੀ (ਕੋਈ ਅਪਲੋਡਰ ਨਹੀਂ ਲੱà¨à¨¿à¨†)
+ ਕੋਈ ਕੈਪਸ਼ਨਾਂ ਨਹੀਂ
ਫਿੱਟ
à¨à¨°à©‹
ਜ਼ੂਮ
- Auto-Generated
- captions
- ਪਲੇਅਰ caption, text ਸਕੇਲ ਅਤੇ ਬੈਕਗà©à¨°à¨¾à¨‰à¨‚ਡ ਸਟਾਈਲ ਨੂੰ ਸੋਧੋ. ਪà©à¨°à¨à¨¾à¨µ ਨੂੰ ਲਾਗੂ ਕਰਨ ਲਈ à¨à¨ª ਨੂੰ ਮà©à©œ ਚਾਲੂ ਕਰਨ ਦੀ ਜ਼ਰੂਰਤ ਹੈ.
+ ਸਵੈ-ਉਤਪੰਨ
+ ਕੈਪਸ਼ਨਾਂ
+ ਪਲੇਅਰ ਕੈਪਸ਼ਨ , ਟੈਕਸਟ ਸਕੇਲ ਅਤੇ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਸਟਾਈਲ ਨੂੰ ਸੋਧੋ. ਪà©à¨°à¨à¨¾à¨µ ਨੂੰ ਲਾਗੂ ਕਰਨ ਲਈ à¨à¨ª ਨੂੰ ਮà©à©œ ਚਾਲੂ ਕਰਨ ਦੀ ਜ਼ਰੂਰਤ ਹੈ।
LeakCanary ਚਲਾਓ
ਮੈਮੋਰੀ ਲੀਕ monitoring, à¨à¨ª ਨੂੰ Unresponsive ਬਣਾ ਸਕਦੀ ਹੈ ਜਦੋਂ ਹੀਪ dumping ਹà©à©°à¨¦à©€ ਹੈ
Out-of-lifecycle ERROR ਰਿਪੋਰਟ ਕਰੋ
@@ -355,141 +372,234 @@
ਪਿੱਛਲਾ à¨à¨•à¨¸à¨ªà©‹à¨°à¨Ÿ
ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨à¨¾à¨‚ ਇੰਪੋਰਟ ਨਹੀਂ ਹੋ ਸਕੀਆਂ
ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨à¨¾à¨‚ à¨à¨•à¨¸à¨ªà©‹à¨°à¨Ÿ ਨਹੀਂ ਹੋ ਸਕੀਆਂ
- ਯੂ ਟਿਊਬ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨à¨¾à¨‚ ਇੰਪੋਰਟ ਕਰਨ ਲਈ à¨à¨•à¨¸à¨ªà©‹à¨°à¨Ÿ ਫਾਈਲ ਡਾਊਨਲੋਡ ਕਰੋ:
-\n
-\n1. ਇਸ URL ਤੇ ਜਾਓ: %1$s
-\n2. ਆਪਣੇ ਖਾਤੇ ਚ ਲੌਗ-ਇਨ ਕਰੋ
-\n3. ਇੱਕ ਡਾਉਨਲੋਡ ਸ਼à©à¨°à©‚ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ (ਇਹੀ à¨à¨•à¨¸à¨ªà©‹à¨°à¨Ÿ ਫਾਈਲ ਹੈ)
+ ਗੂਗਲ ਟੇਕਅਊਟ ਤੋਂ ਯੂਟਿਊਬ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨à¨¾à¨‚ ਇੰਪੋਰਟ ਕਰਨ ਲਈ à¨à¨•à¨¸à¨ªà©‹à¨°à¨Ÿ ਫਾਈਲ ਡਾਊਨਲੋਡ ਕਰੋ:
+\n
+\n1. ਇਸ URL ਤੇ ਜਾਓ: %1$s
+\n2. ਮੰਗਣ ਤੇ ਆਪਣੇ ਖਾਤੇ \'ਚ ਲਾਗ-ਇਨ ਕਰੋ
+\n3. ਕਲਿੱਕ ਕਰੋ \" All data incuded\" ਤੇ, ਫੇਰ \"Deselect all\" ਤੇ ਫੇਰ ਸਿਰਫ \"subscriprion\" ਚà©à¨£à©‹ ਅਤੇ \"OK\" ਕਰੋ
+\n4. \"Next step\" ਤੇ ਕਲਿੱਕ ਕਰੋ ਤੇ ਫੇਰ \"create export\" ਤੇ
+\n5. ਡਾਊਨਲੋਡ ਬਟਨ ਦਿਖਾਈ ਦੇਣ ਤੇ ਇਸ ਤੇ ਕਲਿੱਕ ਕਰੋ।ਇੱਕ ਡਾਉਨਲੋਡ ਸ਼à©à¨°à©‚ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ (ਇਹੀ à¨à¨•à¨¸à¨ªà©‹à¨°à¨Ÿ ਫਾਈਲ ਹੈ)
+\n6. ਥੱਲੇ ਇੰਪੋਰਟ ਫਾਈਲ ਤੇ ਕਲਿੱਕ ਕਰੋ ਤੇ ਡਾਊਨਲੋਡ ਕੀਤੀ .zip ਫਾਈਲ ਚà©à¨£à©‹
+\n7. [ਜੇ .zip ਤੋਂ à¨à¨•à¨¸à¨ªà©‹à¨°à¨Ÿ ਫੇਲ ਹੋ ਜਾਂਦੀ ਹੈ] ਤਾਂ .csv ਫਾਈਲ à¨à¨•à¨¸à¨Ÿà¨°à©ˆà¨•à¨Ÿ ਕਰੋ (ਆਮ ਤੌਰ ਤੇ \"YouTube and YouTube Music/subscriptions/subscriptions.csv\"), ਥੱਲੇ ਦਿੱਤੇ ਇੰਪੋਰਟ ਫਾਈਲ ਤੇ ਕਲਿੱਕ ਕਰਕੇ à¨à¨•à¨¸à¨Ÿà¨°à©ˆà¨•à¨Ÿ ਕੀਤੀ csv ਫਾਈਲ ਚà©à¨£à©‹
URL ਜਾਂ ਆਪਣੀ ID ਟਾਈਪ ਕਰਕੇ ਸਾਉੰਡ ਕਲਾਉਡ ਪà©à¨°à©‹à¨«à¨¾à¨ˆà¨² ਇੰਪੋਰਟ ਕਰੋ:
\n
\n1. ਇੱਕ ਵੈਬ-ਬà©à¨°à¨¾à¨Šà©›à¨° ਵਿੱਚ \"ਡੈਸਕਟਾਪ ਮੋਡ\" ਨੂੰ ਚਾਲੂ ਕਰੋ (ਸਾਈਟ ਮੋਬਾਈਲ ਉਪਕਰਣਾਂ ਲਈ ਉਪਲਬਧ ਨਹੀਂ ਹੈ)
\n2. ਇਸ URL ਤੇ ਜਾਓ: %1$s
\n3. ਆਪਣੇ ਖਾਤੇ ਚ ਲੌਗ-ਇਨ ਕਰੋ
\n4. ਨਿਰਦੇਸ਼ਤ ਕੀਤੇ ਗਠਪà©à¨°à©‹à¨«à¨¾à¨ˆà¨² URL ਨੂੰ ਕਾਪੀ ਕਰੋ.
- yourID, Soundcloud.com/yourid
- ਯਾਦ ਰੱਖੋ ਕਿ ਇਹ ਕਾਰਜ ਡਾਟਾ consuming ਹੋ ਸਕਦਾ ਹੈ.
+ ਤà©à¨¹à¨¾à¨¡à©€ ਆਈਡੀ, soundcloud.com/ਤà©à¨¹à¨¾à¨¡à©€ ਆਈਡੀ
+ ਯਾਦ ਰੱਖੋ ਕਿ ਇਸ ਕਾਰਜ ਨਾਲ ਡਾਟਾ ਖਪਤ ਹੋ ਸਕਦਾ ਹੈ।
\n
\nਕੀ ਤà©à¨¸à©€à¨‚ ਜਾਰੀ ਰੱਖਣਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ\?
ਪਲੇਅਬੈਕ ਸਪੀਡ ਕੰਟਰੋਲ
- tempo
- pitch
- ਅਨਲਿੰਕ (distortion ਪੈਦਾ ਹੋ ਸਕਦੀ ਹੈ)
+ ਤਾਲ
+ ਪਿੱਚ
+ ਅਲਹਿਦਾ ਕਰੋ (ਵਿਗਾੜ ਪੈ ਸਕਦਾ ਹੈ)
ਕੀ ਤà©à¨¸à©€à¨‚ ਸੈਟਿੰਗਾਂ ਨੂੰ ਵੀ ਇੰਪੋਰਟ ਕਰਨਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ\?
- NewPipe\'s ਗੋਪਨੀਯਤਾ ਨੀਤੀ
- NewPipe ਪà©à¨°à©‹à¨œà©ˆà¨•à¨Ÿ ਤà©à¨¹à¨¾à¨¡à©€ ਗੋਪਨੀਯਤਾ ਨੂੰ ਬਹà©à¨¤ ਗੰà¨à©€à¨°à¨¤à¨¾ ਨਾਲ ਲੈਂਦਾ ਹੈ. ਇਸ ਲਈ à¨à¨ª ਤà©à¨¹à¨¾à¨¡à©€ ਸਹਿਮਤੀ ਤੋਂ ਬਿਨਾਂ ਕੋਈ ਵੀ ਡਾਟਾ ਇੱਕਠਾ ਨਹੀਂ ਕਰਦਾ.
-\nNewPipe ਦੀ ਗੋਪਨੀਯਤਾ ਨੀਤੀ ਵਿਸਥਾਰ ਵਿੱਚ ਦੱਸਦੀ ਹੈ ਕਿ ਜਦੋਂ ਤà©à¨¸à©€à¨‚ ਕਰੈਸ਼ ਰਿਪੋਰਟ à¨à©‡à¨œà¨¦à©‡ ਹੋ ਤਾਂ ਕਿਹੜਾ ਡੇਟਾ à¨à©‡à¨œà¨¿à¨† ਜਾਂ ਸਟੋਰ ਕੀਤਾ ਜਾਂਦਾ ਹੈ.
+ ਨਿਊਪਾਈਪ ਦੀ ਗੋਪਨੀਯਤਾ ਨੀਤੀ
+ ਨਿਊਪਾਈਪ ਪà©à¨°à©‹à¨œà©ˆà¨•à¨Ÿ ਤà©à¨¹à¨¾à¨¡à©€ ਗੋਪਨੀਯਤਾ ਨੂੰ ਬਹà©à¨¤ ਗੰà¨à©€à¨°à¨¤à¨¾ ਨਾਲ ਲੈਂਦਾ ਹੈ। ਇਸ ਲਈ à¨à¨ª ਤà©à¨¹à¨¾à¨¡à©€ ਸਹਿਮਤੀ ਤੋਂ ਬਿਨਾਂ ਕੋਈ ਵੀ ਡਾਟਾ ਇੱਕਠਾ ਨਹੀਂ ਕਰਦਾ।
+\nਨਿਊਪਾਈਪ ਦੀ ਗੋਪਨੀਯਤਾ ਨੀਤੀ ਵਿਸਥਾਰ ਵਿੱਚ ਦੱਸਦੀ ਹੈ ਕਿ ਜਦੋਂ ਤà©à¨¸à©€à¨‚ ਕਰੈਸ਼ ਰਿਪੋਰਟ à¨à©‡à¨œà¨¦à©‡ ਹੋ ਤਾਂ ਕਿਹੜਾ ਡਾਟਾ à¨à©‡à¨œà¨¿à¨† ਜਾਂ ਸਟੋਰ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।
ਗੋਪਨੀਯਤਾ ਨੀਤੀ ਪੜà©à¨¹à©‹
- ਯੂਰਪੀਅਨ ਜਨਰਲ ਡੇਟਾ ਪà©à¨°à©‹à¨Ÿà©ˆà¨•à¨¸à¨¼à¨¨ ਰੈਗੂਲੇਸ਼ਨ (ਜੀਡੀਪੀਆਰ) ਦੀ ਪਾਲਣਾ ਕਰਨ ਲਈ, ਅਸੀਂ ਤà©à¨¹à¨¾à¨¡à¨¾ ਧਿਆਨ NewPipe ਦੀ ਗੋਪਨੀਯਤਾ ਨੀਤੀ ਵੱਲ ਖਿੱਚਦੇ ਹਾਂ. ਕਿਰਪਾ ਕਰਕੇ ਇਸਨੂੰ ਧਿਆਨ ਨਾਲ ਪੜà©à¨¹à©‹.
-\nਸਾਨੂੰ BUG ਰਿਪੋਰਟ à¨à©‡à¨œà¨£ ਲਈ ਤà©à¨¹à¨¾à¨¨à©‚à©° ਇਸ ਨੂੰ ਸਵੀਕਾਰ ਕਰਨਾ ਪਵੇਗਾ.
+ ਯੂਰਪੀਅਨ ਜਨਰਲ ਡਾਟਾ ਪà©à¨°à©‹à¨Ÿà©ˆà¨•à¨¸à¨¼à¨¨ ਰੈਗੂਲੇਸ਼ਨ (ਜੀਡੀਪੀਆਰ) ਦੀ ਪਾਲਣਾ ਕਰਨ ਲਈ, ਅਸੀਂ ਤà©à¨¹à¨¾à¨¡à¨¾ ਧਿਆਨ ਨਿਊਪਾਈਪ ਦੀ ਗੋਪਨੀਯਤਾ ਨੀਤੀ ਵੱਲ ਖਿੱਚਦੇ ਹਾਂ. ਕਿਰਪਾ ਕਰਕੇ ਇਸਨੂੰ ਧਿਆਨ ਨਾਲ ਪੜà©à¨¹à©‹à¥¤
+\nਸਾਨੂੰ BUG ਰਿਪੋਰਟ à¨à©‡à¨œà¨£ ਲਈ ਤà©à¨¹à¨¾à¨¨à©‚à©° ਇਸ ਨੂੰ ਸਵੀਕਾਰ ਕਰਨਾ ਪਵੇਗਾ।
ਸਵੀਕਾਰ ਕਰੋ
ਅਸਵੀਕਾਰ
ਕੋਈ ਸੀਮਾ ਨਹੀਂ
- ਮੋਬਾਈਲ ਡਾਟਾ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ Resolution ਨੂੰ ਸੀਮਿਤ ਕਰੋ
- à¨à¨ª switch ਕਰਨ ਤੇ minimize ਕਰੋ
+ ਮੋਬਾਈਲ ਡਾਟਾ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਨੂੰ ਸੀਮਿਤ ਕਰੋ
+ à¨à¨ª ਸਵਿੱਚ ਕਰਨ ਤੇ ਮਿਨੀਮਾਈਜ਼ ਕਰੋ
ਮà©à©±à¨– ਵੀਡੀਓ ਪਲੇਅਰ ਤੋਂ ਦੂਜੇ à¨à¨ª \'ਤੇ ਜਾਣ ਵੇਲੇ à¨à¨•à¨¶à¨¨
ਕੋਈ ਨਹੀਂ
- ਬੈਕਗà©à¨°à¨¾à¨‰à¨‚ਡ ਪਲੇਅਰ ਵਿੱਚ Minimize ਕਰੋ
- ਪੌਪ-ਅਪ ਪਲੇਅਰ ਵਿੱਚ minimize ਕਰੋ
+ ਬੈਕਗà©à¨°à¨¾à¨Šà¨‚ਡ ਪਲੇਅਰ ਵਿੱਚ ਮਿਨੀਮਾਈਜ਼ ਕਰੋ
+ ਪੌਪ-ਅਪ ਪਲੇਅਰ ਵਿੱਚ ਮਿਨੀਮਾਈਜ਼ ਕਰੋ
ਚà©à©±à¨ª ਦੌਰਾਨ ਤੇਜ਼ੀ ਨਾਲ ਅੱਗੇ ਕਰੋ
- ਸਟੇਪ
+ ਕਦਮ
ਰੀਸੈੱਟ
- ਚੈਨਲਾਂ
+ ਚੈਨਲ
ਪਲੇ ਸੂਚੀਆਂ
ਟਰੈਕਸ
ਯੂਜ਼ਰਸ
- ਅਨ-ਸਬਸਕà©à¨°à¨¾à¨ˆà¨¬
- ਨਵਾਂ ਟੈਬ
- ਟੈਬ ਚà©à¨£à©‹
- ਆਵਾਜ਼ gesture ਕੰਟਰੋਲ
- ਆਵਾਜ਼ ਕੰਟਰੋਲ ਕਰਨ ਲਈ gestures ਦੀ ਵਰਤੋਂ ਕਰੋ
- ਸਕà©à¨°à©€à¨¨ ਲਾਈਟ gesture ਕੰਟਰੋਲ
- ਵੀਡੀਓ ਸਕà©à¨°à©€à¨¨ ਲਾਈਟ ਕੰਟਰੋਲ ਕਰਨ ਲਈ gestures ਦੀ ਵਰਤੋਂ ਕਰੋ
- ਮੂਲ à¨à¨¾à¨¶à¨¾ Content
+ ਆਵਾਜ਼ ਸੰਕੇਤ ਕੰਟਰੋਲ
+ ਆਵਾਜ਼ ਕੰਟਰੋਲ ਕਰਨ ਲਈ ਸੰਕੇਤ ਦੀ ਵਰਤੋਂ ਕਰੋ
+ ਸਕà©à¨°à©€à¨¨ ਲਾਈਟ ਸੰਕੇਤ ਕੰਟਰੋਲ
+ ਵੀਡੀਓ ਸਕà©à¨°à©€à¨¨ ਦੀ ਚਮਕ ਕੰਟਰੋਲ ਕਰਨ ਲਈ ਸੰਕੇਤ ਦੀ ਵਰਤੋਂ ਕਰੋ
+ ਮੂਲ à¨à¨¾à¨¶à¨¾ ਸਮੱਗਰੀ
ਅਪਡੇਟਾਂ
ਫਾਈਲ ਮਿਟਾ ਦਿੱਤੀ ਗਈ ਹੈ
- à¨à¨ª ਅੱਪਡੇਟ ਨੋਟੀਫਿਕੇਸ਼ਨ
- ਨਵੇਂ NewPipe ਸੰਸਕਰਣ ਲਈ ਸੂਚਨਾਵਾਂ
+ à¨à¨ª ਅੱਪਡੇਟ ਨੋਟੀਫਿਕੇਸ਼ਨ
+ ਨਿਊ-ਪਾਈਪ ਦੀ ਅਪਡੇਟ ਉਪਲਬੱਧ ਹੈ!
+ ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਦਬਾਓ
+ ਨਵੇਂ ਨਿਊ-ਪਾਈਪ ਸੰਸਕਰਣ ਲਈ ਸੂਚਨਾਵਾਂ
ਬਾਹਰੀ ਸਟੋਰੇਜ ਉਪਲਬਧ ਨਹੀਂ ਹੈ
- ਬਾਹਰੀ SD ਕਾਰਡ ਤੇ ਡਾਊਨਲੋਡ ਕਰਨਾ ਸੰà¨à¨µ ਨਹੀਂ ਹੈ. ਕੀ ਡਾਊਨਲੋਡ ਫੋਲਡਰ ਦੀ ਸਥਿਤੀ ਨੂੰ ਰੀਸੈਟ ਕੀਤਾ ਜਾਵੇ \?
- Default ਟੈਬ ਦੀ ਵਰਤੋਂ ਕਰਦਿਆਂ, save ਕੀਤੇ ਟੈਬਾਂ ਨੂੰ ਪੜà©à¨¹à¨¨ ਵੇਲੇ ERROR
- Default ਮà©à©œ-ਪà©à¨°à¨¾à¨ªà¨¤ ਕਰੋ
- ਕੀ ਤà©à¨¸à©€à¨‚ Default ਲਾਗੂ ਕਰਨਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ \?
+ ਬਾਹਰੀ SD ਕਾਰਡ ਤੇ ਡਾਊਨਲੋਡ ਕਰਨਾ ਸੰà¨à¨µ ਨਹੀਂ ਹੈ. ਕੀ ਡਾਊਨਲੋਡ ਫ਼ੋਲਡਰ ਦੀ ਸਥਿਤੀ ਨੂੰ ਰੀਸੈਟ ਕੀਤਾ ਜਾਵੇ \?
+ ਸਾਂà¨à©€à¨†à¨‚ ਟੈਬਾਂ ਨਹੀਂ ਪੜà©à¨¹ ਹੋਈਆਂ, ਇਸ ਲਈ ਡਿਫ਼ਾਲਟ ਟੈਬਾਂ ਦੀ ਵਰਤੋਂ ਹੋ ਰਹੀ ਹੈ
+ ਡਿਫ਼ਾਲਟ ਮà©à©œ-ਸਥਾਪਿਤ ਕਰੋ
+ ਕੀ ਤà©à¨¸à©€à¨‚ ਡਿਫ਼ਾਲਟ ਮà©à©œ-ਸਥਾਪਤ ਕਰਨਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ\?
+ ਦੂਜੀਆਂ à¨à¨ªà¨¾à¨‚ ਦੇ ਉੱਤੇ ਵਿਖਾਉਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ
ਸਬਸਕà©à¨°à¨¾à¨‡à¨¬à¨°à¨¾à¨‚ ਦੀ ਗਿਣਤੀ ਉਪਲਬਧ ਨਹੀਂ ਹੈ
- ਮà©à©±à¨– ਪੰਨੇ ਤੇ ਕਿਹੜੇ ਟੈਬ ਦਿਖਾਠਜਾਣਗੇ
+ ਮà©à©±à¨– ਪੰਨੇ ਤੇ ਕਿਹੜੇ ਟੈਬ ਵਿਖਾਠਜਾਣਗੇ
+ ਆਈਟਮਾਂ ਨੂੰ ਹਟਾਉਣ ਲਈ ਸਵਾਈਪ ਕਰੋ
ਚੋਣ
ਅਪਡੇਟਾਂ
- ਜਦੋਂ ਨਵਾਂ ਸੰਸਕਰਣ ਉਪਲਬਧ ਹà©à©°à¨¦à¨¾ ਹੈ ਤਾਂ à¨à¨ª ਅਪਡੇਟ ਨੂੰ ਪà©à©±à¨›à¨£ ਲਈ ਇੱਕ ਨੋਟੀਫਿਕੇਸ਼ਨ ਦਿਖਾਓ
- ਲਿਸਟ view ਮੋਡ
+ ਜਦੋਂ ਨਵਾਂ ਸੰਸਕਰਣ ਉਪਲਬਧ ਹà©à©°à¨¦à¨¾ ਹੈ ਤਾਂ à¨à¨ª ਅਪਡੇਟ ਨੂੰ ਪà©à©±à¨›à¨£ ਲਈ ਇੱਕ ਨੋਟੀਫਿਕੇਸ਼ਨ ਵਿਖਾਓ
+ ਲਿਸਟ ਵਿਊ ਮੋਡ
ਲਿਸਟ
ਗਰਿੱਡ
ਆਟੋ
- ਸਵਿੱਚ view
- NewPipe ਅੱਪਡੇਟ ਉਪਲਬੱਧ!
- ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਦਬਾਓ
+ ਸਵਿੱਚ ਵਿਊ
ਮà©à¨•à©°à¨®à¨² ਹੋਇਆ
ਬਕਾਇਆ
ਰà©à¨•à¨¿à¨†
ਕਤਾਰਬੱਧ
Post-processing
+ ਮà©à©œ-ਪà©à¨°à¨¾à¨ªà¨¤à©€
ਕਤਾਰ
- ਸਿਸਟਮ ਦà©à¨†à¨°à¨¾ ਕਾਰਵਾਈ ਤੋਂ ਇਨਕਾਰ ਕੀਤਾ ਗਿਆ
+ ਸਿਸਟਮ ਦà©à¨†à¨°à¨¾ permission Deny ਕੀਤੀ ਗਈ
ਡਾਊਨਲੋਡ ਫੇਲà©à¨¹
ਡਾਊਨਲੋਡ ਮà©à¨•à©°à¨®à¨²
%s ਡਾਊਨਲੋਡ ਮà©à¨•à©°à¨®à¨²
- ਵਿਲੱਖਣ ਨਾਮ Generate ਕਰੋ
- overwrite
+ ਵਿਲੱਖਣ ਨਾਮ ਬਣਾਓ
+ ਓਵਰਰਾਈਟ
ਇਸ ਨਾਮ ਦੇ ਨਾਲ ਇੱਕ ਡਾਊਨਲੋਡ ਪਹਿਲਾਂ ਤੋਂ ਜਾਰੀ ਹੈ
- ERROR ਵਿਖਾਓ
+ ਤਰà©à©±à¨Ÿà©€ ਵਿਖਾਓ
ਕੋਡ
- Destination ਫੋਲਡਰ ਬਣਾਇਆ ਨਹੀਂ ਜਾ ਸਕਦਾ
+ ਟਿਕਾਣਾ ਫ਼ੋਲਡਰ ਬਣਾਇਆ ਨਹੀਂ ਜਾ ਸਕਦਾ
ਫਾਈਲ ਨਹੀਂ ਬਣਾਈ ਜਾ ਸਕਦੀ
ਸਿਸਟਮ ਦà©à¨†à¨°à¨¾ permission Deny ਕੀਤੀ ਗਈ
- ਸà©à¨°à©±à¨–ਿਅਤ ਕà©à¨¨à©ˆà¨•à¨¸à¨¼à¨¨ ਫੇਲà©à¨¹
+ ਸà©à¨°à©±à¨–ਿਅਤ ਕà©à¨¨à©ˆà¨•à¨¸à¨¼à¨¨ ਸਥਾਪਤ ਨਹੀਂ ਹੋ ਸਕਿਆ
ਸਰਵਰ ਨਹੀਂ ਲੱਠਸਕਿਆ
ਸਰਵਰ ਨਾਲ ਜà©à©œ ਨਹੀਂ ਸਕਦਾ
ਸਰਵਰ ਨੇ ਡਾਟਾ ਨਹੀਂ à¨à©‡à¨œà¨¿à¨†
ਸਰਵਰ ਮਲਟੀ-Threaded ਡਾਊਨਲੋਡਸ ਨੂੰ ਸਵੀਕਾਰ ਨਹੀਂ ਕਰਦਾ, ਇਸ ਨਾਲ ਦà©à¨¬à¨¾à¨°à¨¾ ਕੋਸ਼ਿਸ਼ ਕਰੋ @string/msg_threads = 1
ਨਹੀਂ ਲà¨à¨¿à¨†
- Post-processing ਫੇਲà©à¨¹
- ਰà©à©±à¨•à©‹
+ Post-processing ਫੇਲà©à¨¹ ਹੋਈ
+ ਰà©à¨•à©‹
ਵੱਧ ਤੋਂ ਵੱਧ ਕੋਸ਼ਿਸ਼ਾਂ
ਡਾਉਨਲੋਡ ਰੱਦ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਵੱਧ ਤੋਂ ਵੱਧ ਕੋਸ਼ਿਸ਼ਾਂ
- Metered ਨੈਟਵਰਕਸ ਤੇ ਰà©à¨•à¨¾à¨µà¨Ÿ
- ਮੋਬਾਈਲ ਡਾਟਾ ਤੇ switch ਕਰਨ ਵੇਲੇ ਲਾà¨à¨¦à¨¾à¨‡à¨• ਹੈ, ਹਾਲਾਂਕਿ ਕà©à¨ ਡਾਉਨਲੋਡਾਂ ਨੂੰ suspend ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ
- ਇਵੇੰਟਸ
+ ਮੀਟਰ ਕੀਤੇ ਨੈਟਵਰਕਸ ਤੇ ਰà©à¨•à¨¾à¨µà¨Ÿ
+ ਮੋਬਾਈਲ ਡਾਟਾ ਤੇ ਸਵਿੱਚ ਕਰਨ ਵੇਲੇ ਲਾà¨à¨¦à¨¾à¨‡à¨• ਹੈ, ਹਾਲਾਂਕਿ ਕà©à¨ ਡਾਉਨਲੋਡਾਂ ਨੂੰ ਮà©à¨…ੱਤਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ
+ ਇਵੇਂਟਸ
ਕਾਨਫਰੰਸਾਂ
- ਟਿੱਪਣੀਆਂ ਦਿਖਾਓ
- ਟਿੱਪਣੀਆਂ ਵਿਖਾਉਣਾ ਬੰਦ ਕਰਨ ਲਈ ਇਸਨੂੰ ਬੰਦ ਕਰੋ
- ਆਟੋ-ਪਲੇ
+ ਟਿੱਪਣੀਆਂ ਵਿਖਾਓ
+ ਟਿੱਪਣੀਆਂ ਲà©à¨•à¨¾à¨‰à¨£ ਲਈ ਇਸਨੂੰ ਬੰਦ ਕਰੋ
+ ਆਟੋ-ਪਲੇਅ
ਕੋਈ ਟਿੱਪਣੀ ਨਹੀਂ ਕੀਤੀ ਗਈ
+ ਟਿੱਪਣੀਆਂ ਬੰਦ ਕੀਤੀਆਂ ਹੋਈਆਂ ਹਨ
ਟਿੱਪਣੀਆਂ ਲੋਡ ਨਹੀਂ ਹੋ ਸਕੀਆਂ
ਬੰਦ ਕਰੋ
ਪਲੇਅਬੈਕ ਦੋਬਾਰਾ ਸ਼à©à¨°à©‚ ਕਰੋ
ਪਿਛਲੀ ਪਲੇਅਬੈਕ ਸਥਿਤੀ ਤੋਂ ਮà©à©œ ਚਲਾਓ
ਸੂਚੀਆਂ ਦੀ ਸਥਿਤੀ
- ਸੂਚੀਆਂ ਵਿੱਚ ਪਲੇਅਬੈਕ ਸਥਿਤੀ ਸੂਚਕ ਦਿਖਾਓ
+ ਸੂਚੀਆਂ ਵਿੱਚ ਪਲੇਅਬੈਕ ਸਥਿਤੀ ਸੂਚਕ ਵਿਖਾਓ
ਡਾਟਾ ਮਿਟਾਓ
- Watch ਹਿਸਟਰੀ ਮਿਟਾ ਦਿੱਤੀ ਗਈ ਹੈ.
- ਪਲੇਬੈਕ ਸਥਿਤੀ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਗਿਆ ਹੈ.
- ਫਾਈਲ ਮੂਵ ਕੀਤੀ ਜਾਂ ਮਿਟਾਈ ਗਈ ਹੈ
+ ਵੇਖੀਆਂ ਸਟà©à¨°à©€à¨®à¨¾à¨‚ ਦੀ ਇਤਿਹਾਸ ਸੂਚੀ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਗਿਆ
+ ਪਲੇਬੈਕ ਸਥਿਤੀਆਂ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਗਿਆ ਹੈ
+ ਫਾਈਲ ਤਬਦੀਲ ਕੀਤੀ ਜਾਂ ਮਿਟਾਈ ਗਈ
ਇਸ ਨਾਮ ਵਾਲੀ ਇੱਕ ਫਾਈਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ
ਇਸ ਨਾਮ ਨਾਲ ਡਾਉਨਲੋਡ ਕੀਤੀ ਫਾਈਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ
ਫਾਈਲ Overwrite ਨਹੀਂ ਹੋ ਸਕਦੀ
ਇਸ ਨਾਮ ਦੇ ਨਾਲ ਇੱਥੇ ਇੱਕ ਬਕਾਇਆ ਡਾਊਨਲੋਡ ਹੈ
- ਫਾਈਲ ਤੇ ਕੰਮ ਕਰਦੇ ਸਮੇਂ NewPipe ਬੰਦ ਕੀਤੀ ਗਈ ਸੀ
+ ਫਾਈਲ ਤੇ ਕੰਮ ਕਰਦੇ ਸਮੇਂ ਨਿਊਪਾਈਪ ਬੰਦ ਕੀਤੀ ਗਈ ਸੀ
ਡਿਵਾਈਸ ਤੇ ਕੋਈ ਜਗà©à¨¹à¨¾ ਨਹੀਂ ਬਚੀ ਹੈ
Progress lost, ਕਿਉਂਕਿ ਫਾਈਲ ਮਿਟ ਗਈ ਸੀ
- ਕà©à¨¨à©ˆà¨•à¨¸à¨¼à¨¨ timeout
- are you sure\?
+ ਕà©à¨¨à©ˆà¨•à¨¸à¨¼à¨¨ ਟਾਈਮ- ਆਉਟ
+ ਡਾਊਨਲੋਡ ਇਤਿਹਾਸ ਸਾਫ਼ ਕਰੋ
+ ਡਾਊਨਲੋਡ ਕੀਤੀਆਂ ਫ਼ਾਈਲਾਂ ਮਿਟਾਓ
+ ਕੀ ਤà©à¨¸à©€à¨‚ ਆਪਣਾ ਡਾਊਨਲੋਡ ਇਤਿਹਾਸ ਸਾਫ਼ ਕਰਨਾ ਜਾਂ ਡਾਊਨਲੋਡ ਕੀਤੀਆਂ ਸਾਰੀਆਂ ਫ਼ਾਈਲਾਂ ਮਿਟਾਉਣਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ\?
ਡਾਊਨਲੋਡ ਸੀਮਾ ਕਤਾਰ ਵਿੱਚ
- one download will run at the same time
- ਡਾਊਨਲੋਡ ਸ਼à©à¨°à©‚ ਕਰੋ
+ ਇੱਕ ਸਮੇਂ ਇੱਕੋ ਡਾਊਨਲੋਡ ਚੱਲੇਗਾ, ਬਾਕੀ ਕਤਾਰਬੱਧ ਹੋਣਗੇ
+ ਡਾਊਨਲੋਡਸ ਸ਼à©à¨°à©‚ ਕਰੋ
ਡਾਊਨਲੋਡਸ ਰੋਕੋ
ਪà©à©±à¨›à©‹ ਕਿੱਥੇ ਡਾਊਨਲੋਡ ਕਰਨਾ ਹੈ
- ਤà©à¨¹à¨¾à¨¨à©‚à©° ਪà©à©±à¨›à¨¿à¨† ਜਾਵੇਗਾ ਕਿ ਹਰ ਡਾਉਨਲੋਡ ਨੂੰ ਕਿੱਥੇ save ਕਰਨਾ ਹੈ
- ਤà©à¨¹à¨¾à¨¨à©‚à©° ਪà©à©±à¨›à¨¿à¨† ਜਾਵੇਗਾ ਕਿ ਹਰ ਡਾਉਨਲੋਡ ਨੂੰ ਕਿੱਥੇ save ਕਰਨਾ ਹੈ.
+ ਤà©à¨¹à¨¾à¨¨à©‚à©° ਹਰ ਵਾਰ ਪà©à©±à¨›à¨¿à¨† ਜਾਵੇਗਾ ਕਿ ਡਾਊਨਲੋਡ ਨੂੰ ਕਿੱਥੇ ਸਾਂà¨à¨£à¨¾ ਹੈ
+ ਤà©à¨¹à¨¾à¨¨à©‚à©° ਪà©à©±à¨›à¨¿à¨† ਜਾਵੇਗਾ ਕਿ ਡਾਊਨਲੋਡ ਨੂੰ ਕਿੱਥੇ ਸਾਂà¨à¨£à¨¾ ਹੈ।
\nਜੇ ਤà©à¨¸à©€à¨‚ ਬਾਹਰੀ SD ਕਾਰਡ ਤੇ ਡਾਊਨਲੋਡ ਕਰਨਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ ਤਾਂ ਇਸ ਨੂੰ ਚਾਲੂ ਕਰੋ
- SAF ਦੀ ਵਰਤੋਂ ਕਰੋ
- ਸਟੋਰੇਜ਼ à¨à¨•à¨¸à©ˆà¨¸ ਫਰੇਮਵਰਕ ਬਾਹਰੀ SD ਕਾਰਡ ਵਿੱਚ ਡਾਊਨਲੋਡ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ.
+ ਸਿਸਟਮ ਡਾਊਨਲੋਡ ਫ਼ੋਲਡਰ ਚੋਣਕਾਰ SAF ਦੀ ਵਰਤੋਂ ਕਰੋ
+ ਸਟੋਰੇਜ਼ à¨à¨•à¨¸à©ˆà¨¸ ਫਰੇਮਵਰਕ ਬਾਹਰੀ SD ਕਾਰਡ ਵਿੱਚ ਡਾਊਨਲੋਡ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ
\nਨੋਟ: ਕà©à¨ ਉਪਕਰਣ ਅਨà©à¨•à©‚ਲ ਨਹੀਂ ਹਨ
-
\ No newline at end of file
+ ਇਹ ਸਮੱਗਰੀ ਹਾਲੇ ਨਿਊ-ਪਾਈਪ \'ਤੇ ਕੰਮ ਨਹੀਂ ਕਰਦੀ।
+\n
+\nਉਮੀਦ ਹੈ ਆਉਣ ਵਾਲ਼ੇ ਕਿਸੇ ਵਰਜਨ ਵਿੱਚ ਇਹ ਕੰਮ ਕਰੇਗੀ।
+ ਪਲੇ-ਸੂਚੀ ਪੰਨਾ
+ ਜਦੋਂ ਉਪਲਬਧ ਹੋਵੇ ਤਾਂ ਖ਼ਾਸ ਫ਼ੀਡ ਤੋਂ ਮੰਗਵਾਓ
+ ਕà©à¨ ਸੇਵਾਵਾਂ ਵਿੱਚ ਹੀ ਉਪਲੱਬਧ ਇਹ ਤਰੀਕਾ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨ ਫੀਡ ਵਿੱਚ ਤੇਜ ਜਾਣਕਾਰੀ ਮà©à¨¹à©±à¨ˆà¨† ਕਰਵਾਉਂਦਾ ਹੈ, ਪਰ ਕà©à¨ ਗਿਣਤੀ ਦੀਆਂ ਹੀ ਆਈਟਮ ਪà©à¨°à¨¦à¨¾à¨¨ ਕਰ ਸਕਦਾ ਹੈ ਉਹ ਵੀ ਅਕਸਰ ਬਿਨਾਂ ਪੂਰੀ ਜਾਣਕਾਰੀ( ਜਿਵੇਂ ਅਵਧੀ, ਸਟਰੀਮ ਦੀ ਕਿਸਮ, ਕੋਈ ਸਟਰੀਮ ਲਾਈਵ ਹੈ, ਨਹੀਂ ਦੱਸਦਾ)
+ ਤੇਜ਼ ਮੋਡ ਬੰਦ ਕਰੋ
+ ਤੇਜ਼ ਮੋਡ ਚਾਲੂ ਕਰੋ
+ à¨à¨•à¨¸à©‹à¨ªà¨²à©‡à¨…ਰ ਦੀਆਂ ਬੰਦਿਸ਼ਾਂ ਕਰਕੇ ਲੱà¨à¨£ ਮਿਆਦ %d ਸਕਿੰਟ ਸੈੱਟ ਕੀਤੀ ਗਈ ਸੀ
+ ਹਮੇਸ਼ਾਂ ਅਪਡੇਟ ਕਰੋ
+ ਫ਼ੀਡ ਅਪਡੇਟ ਦੀ ਹੱਦ
+ ਅੰਤਰਾਲ ਜਿਸ ਬਾਅਦ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¶à¨¨ ਫੀਡਾਂ ਦà©à¨¬à¨¾à¨°à¨¾ ਅੱਪਡੇਟ ਕੀਤੀਆਂ ਜਾ ਸਕਣ — %s
+ ਫ਼ੀਡ
+ ਸਿਰਫ਼ ਉਹ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¸à¨¼à¨¨à¨¾à¨‚ ਵਿਖਾਓ ਜੋ ਕਿਸੇ ਗਰà©à©±à¨ª ਵਿੱਚ ਨਹੀਂ ਪਾਈਆਂ ਹੋਈਆਂ
+ ਨਵਾਂ
+ ਕੀ ਤà©à¨¸à©€à¨‚ ਇਸ ਗਰà©à©±à¨ª ਨੂੰ ਮਿਟਾਉਣਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ\?
+ ਗਰà©à©±à¨ª ਨਾਮ ਖ਼ਾਲੀ ਕਰੋ
+
+ - %d ਚà©à¨£à©€
+ - %d ਚà©à¨£à©€à¨†à¨‚
+
+ ਕੋਈ ਸਬਸਕà©à¨°à¨¿à¨ªà¨¸à¨¼à¨¨ ਨਹੀਂ ਚà©à¨£à©€ ਹੋਈ
+ ਸà©à¨¬à¨¸à¨•à©à¨°à¨¿à¨ªà¨¸à¨¼à¨¨à¨¾à¨‚ ਚà©à¨£à©‹
+ ਫ਼ੀਡ \'ਤੇ ਅਮਲ ਹੋ ਰਿਹੈ…
+ ਫ਼ੀਡ ਲੋਡ ਹੋ ਰਹੀ ਹੈ…
+ ਲੋਡ ਨਹੀਂ ਹੋਇਆ: %d
+ ਫ਼ੀਡ ਆਖ਼ਰੀ ਵਾਰ %s ਨੂੰ ਅਪਡੇਟ ਹੋਈ ਸੀ
+ ਚੈਨਲ ਗਰà©à©±à¨ª
+
+ - %d ਸਕਿੰਟ
+ - %d ਸਕਿੰਟ
+
+
+ - %d ਮਿੰਟ
+ - %d ਮਿੰਟ
+
+
+ - %d ਘੰਟਾ
+ - %d ਘੰਟੇ
+
+
+ - %d ਦਿਨ
+ - %d ਦਿਨ
+
+ ਹਾਂ, ਅਤੇ ਅੱਧ-ਪਚੱਧੀਆਂ ਵੇਖੀਆਂ ਹੋਈਆਂ ਵੀ
+ ਵੀਡੀਓਜ਼ ਜੋ ਪਲੇ-ਸੂਚੀ ਵਿੱਚ ਜੋੜੇ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਅਤੇ ਬਾਅਦ ਵਿੱਚ ਵੇਖੀਆਂ ਜਾ ਚà©à©±à¨•à©€à¨†à¨‚ ਹਨ, ਉਹ ਹਟਾ ਦਿੱਤੀਆਂ ਜਾਣਗੀਆਂ।
+\nਕੀ ਵਾਕਿਆ ਹੀ ਤà©à¨¸à©€à¨‚ ਇਹਨਾਂ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ\? ਇਸ ਕਾਰਵਾਈ ਨੂੰ ਵਾਪਸ ਨਹੀਂ ਮੋੜਿਆ ਜਾ ਸਕਣਾ!
+ ਇਹ ਡਾਊਨਲੋਡ ਮà©à©œ-ਪà©à¨°à¨¾à¨ªà¨¤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ
+ ਵੇਖੀਆਂ ਹੋਈਆਂ ਵੀਡੀਓਜ਼ ਹਟਾਉਣੀਆਂ ਹਨ\?
+ ਵੇਖੀਆਂ ਹੋਈਆਂ ਹਟਾਓ
+ ਸਿਸਟਮ ਡਿਫ਼ਾਲਟ
+ à¨à¨ª à¨à¨¾à¨¸à¨¼à¨¾
+ ਕੋਈ ਸਥਿਤੀ ਚà©à¨£à©‹
+ ਸਥਿਤੀ ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਹੈ
+ ਸਿਰਫ਼ HTTP URLs ਹੀ ਮੰਨਣਯੋਗ ਹਨ
+ ਸਥਿਤੀ ਦੀ ਜਾਇਜ਼ਗੀ ਤਸਦੀਕ ਨਹੀਂ ਹੋ ਸਕੀ
+ ਸਥਿਤੀ URL ਦਾਖ਼ਲ ਕਰੋ
+ ਸਥਿਤੀਆਂ ਜੋੜੋ
+ ਤà©à¨¹à¨¾à¨¡à©€à¨†à¨‚ ਪਸੰਦੀਦਾ ਸਥਿਤੀਆਂ %s \'ਤੇ ਲੱà¨à©‹
+ ਆਪਣੀ ਪਸੰਦੀਦਾ ਪੀਅਰਟਿਊਬ ਸਥਿਤੀਆਂ ਚà©à¨£à©‹
+ ਪੀਅਰਟਿਊਬ ਸਥਿਤੀਆਂ
+ ਸਾਰੀਆਂ ਪਲੇ-ਸਥਿਤੀਆਂ ਮਿਟਾਉਣੀਆਂ ਹਨ\?
+ ਸਾਰੀਆਂ ਪਲੇ-ਸਥਿਤੀਆਂ ਮਿਟਾਉਂਦਾ ਹੈ
+ ਪਲੇ-ਸਥਿਤੀਆਂ ਮਿਟਾਓ
+ ਹਾਲੀਆ
+ ਉਚੇਚੀ ਪੇਸ਼ਕਸ਼
+ ਰੇਡੀਓ
+ à¨à¨²à¨¬à¨®
+ ਕਲਾਕਾਰ
+ ਗੀਤ
+ ਵੀਡੀਓਜ਼
+ ਇਹ ਵੀਡੀਓ ਉਮਰ-ਪਾਬੰਦੀਸ਼à©à¨¦à¨¾ ਹੈ।
+\n
+\nਜੇ ਤà©à¨¸à©€à¨‚ ਇਸਨੂੰ ਵੇਖਣਾ ਚਾਹà©à©°à¨¦à©‡ ਹੋ ਤਾਂ ਸੈਟਿੰਗਾਂ ਵਿੱਚੋਂ \"%1$s\" ਚਾਲੂ ਕਰੋ।
+ ਇਹ ਵੀਡੀਓ ਉਮਰ-ਹੱਦ ਮà©à¨¤à¨¾à¨¬à¨• ਪਾਬੰਦੀਸ਼à©à¨¦à¨¾ ਹੈ।
+\nਯੂਟਿਊਬ ਦੀਆਂ ਉਮਰ-ਹੱਦ ਪਾਬੰਦੀਸ਼à©à¨¦à¨¾ ਵੀਡੀਓਜ਼ ਬਾਰੇ ਨੀਤੀਆਂ ਦੇ ਕਾਰਨ ਨਿਊ-ਪਾਈਪ ਇਸਦੀਆਂ ਵੀਡੀਓ ਸਟà©à¨°à©€à¨® ਤੱਕ ਨਹੀਂ ਪਹà©à©°à¨š ਸਕਦੀ ਅਤੇ ਇਸੇ ਕਰਕੇ ਇਸਨੂੰ ਚਲਾ ਵੀ ਨਹੀਂ ਸਕਦੀ।
+ ਯੂਟਿਊਬ ਦਾ ਪਾਬੰਦੀਸ਼à©à¨¦à¨¾ ਮੋਡ ਚਾਲੂ ਕਰੋ
+ ਇਹ ਸਮੱਗਰੀ ਸਿਰਫ਼ ਉਹਨਾਂ ਵਰਤੋਂਕਾਰਾਂ ਲਈ ਉਪਲਬਧ ਹੈ ਜਿੰਨà©à¨¹à¨¾à¨‚ ਨੇ ਇਸਦੇ ਲਈ ਕੀਮਤ ਦਿੱਤੀ ਹੈ, ਇਸ ਕਰਕੇ ਨਿਊ-ਪਾਈਪ ਦà©à¨†à¨°à¨¾ ਚਲਾਈ ਜਾਂ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।
+ ਇਹ ਵੀਡੀਓ ਸਿਰਫ਼ ਯੂਟਿਊਬ ਮਿਊਜ਼ਿਕ ਦੇ ਪà©à¨°à©€à¨®à©€à¨…ਮ ਮੈਂਬਰਾਂ ਲਈ ਉਪਲਬਧ ਹੈ, ਇਸ ਕਰਕੇ ਨਿਊ-ਪਾਈਪ ਦà©à¨†à¨°à¨¾ ਚਲਾਈ ਜਾਂ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।
+ ਇਹ ਸਮੱਗਰੀ ਨਿੱਜੀ (ਪà©à¨°à¨¾à¨ˆà¨µà©‡à¨Ÿ) ਹੈ, ਇਸ ਕਰਕੇ ਨਿਊ-ਪਾਈਪ ਦà©à¨†à¨°à¨¾ ਚਲਾਈ ਜਾਂ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।
+ ਇਹ ਸਮੱਗਰੀ ਤà©à¨¹à¨¾à¨¡à©‡ ਮà©à¨²à¨• ਵਿੱਚ ਉਪਲਬਧ ਨਹੀਂ।
+ %s ਲਈ ਨਤੀਜੇ ਵਿਖਾਠਜਾ ਰਹੇ ਹਨ
+ ਹਮੇਸ਼ਾਂ ਡਿਸਕਰਿਪਸ਼ਨ ਦਾ ਵਿਸਤਾਰ ਕਰੋ
+ ਦਿਲੋਂ ਪਸੰਦ ਕੀਤਾ
+ ਪਲੇਅਬੈਕ ਲੋਡ ਦਾ ਅੰਤਰਾਲ ਆਕਾਰ
+ ਪà©à¨°à¨—ਤੀਸ਼ੀਲ ਸਮੱਗਰੀ ਉੱਤੇ ਲੋਡ ਅੰਤਰਾਲ ਆਕਾਰ ਬਦਲੋ (ਮੌਜੂਦਾ %s ਤੇ)। ਛੋਟੀ ਸੰਖਿਆ ਉਸਨੂੰ ਜਲਦੀ ਸ਼à©à¨°à©‚ ਕਰਨ ਵਿੱਚ ਮੱਦਦ ਦੇ ਸਕਦੀ ਹੈ
+
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index deef7afbd..d7ef37a81 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -38,7 +38,7 @@
Pobrane
Dalej
Pokaż \"następne\" i \"podobne\" filmy
- Nieobsługiwany adres URL
+ Nieobsługiwany adres URL
Domyślny język treści
Wideo i audio
WyglÄ…d
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index a0a62ca91..e5c0a25e3 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -69,7 +69,7 @@
Permita acesso ao armazenamento primeiramente
Tema
Publicado em %1$s
- Link não suportado
+ Link não suportado
Ao vivo
Downloads
Downloads
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index a52946dbd..fed41b35d 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -29,7 +29,7 @@
Transferir
Seguinte
Mostrar vÃdeos \'Seguintes\' e \'Semelhantes\'
- URL não suportado
+ URL não suportado
Idioma do conteúdo predefinido
VÃdeo e áudio
Miniatura de pré-visualização de vÃdeo
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 2d08addb9..dfe732d5f 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -36,7 +36,7 @@
Descărcați
Următorul
Arată videoclipurile care urmează și similare
- URL nesuportat
+ URL nesuportat
Limba dorită a conținutului
Video & Audio
Aspect
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 913475d8e..69524aade 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -26,7 +26,7 @@
Формат аудио по умолчанию
Скачать
Следующее
- URL не поддерживаетÑÑ
+ URL не поддерживаетÑÑ
\"Следующее\" и похожие видео
Язык контента по умолчанию
Видео и аудио
@@ -475,6 +475,7 @@
Отключите, чтобы Ñкрыть комментарии
ÐвтовоÑпроизведение
Ðет комментариев
+ Комментарии отключены
Ðе удалоÑÑŒ загрузить комментарии
Позиции воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ñ‹
Файл перемещён или удалён
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 3352c81f8..3d1e234a3 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -35,7 +35,7 @@
Prevziať
Ďalšie
Ukázať \'Ďalšie\' a \'Podobné\' videá
- URL nie je podporovaná
+ URL nie je podporovaná
Preferovaný jazyk obsahu
Video & zvuk
Vzhľad
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index c1cc8d963..189cf86fe 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -28,7 +28,7 @@
Prejem
Naslednji video
Pokaži naslednji video in podobne posnetke
- Zapis naslova URL ni podprt.
+ Zapis naslova URL ni podprt.
Privzeti jezik vsebine
Video in Zvok
SliÄica predogleda videa
diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml
index 278e27371..ca9af4d00 100644
--- a/app/src/main/res/values-sq/strings.xml
+++ b/app/src/main/res/values-sq/strings.xml
@@ -546,7 +546,7 @@
Zgjidhni instancat tuaja të preferuara të PeerTube
Instancat PeerTube
Shteti i parazgjedhur i përmbajtjes
- URL e pambështetur
+ URL e pambështetur
Trego ndihmën kur shtypet sfondi ose butoni i popup në \"Detajet:\" e videos
Trego ndihmën \"Mbaje shtypur për ta shtuar në listë\"
Trego \'Tjetra\' dhe videot \'E ngjashme\'
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 1cd4d2baa..af0002879 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -26,7 +26,7 @@
Подразумевани формат звука
Преузми
Следећи видео
- УРЛ није подржан
+ УРЛ није подржан
Прикажи Ñледећи и Ñлични видео
Подразумевани језик Ñадржаја
Видео и аудио
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 23be5276b..588868814 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -52,7 +52,7 @@
Ladda ned
Nästa
Visa \'Nästa\' och \'Liknande\' videor
- Webbadressen stöds inte
+ Webbadressen stöds inte
Standard innehållsspråk
Video & Ljud
Popup-ruta
diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml
index 89bc53111..248013d65 100644
--- a/app/src/main/res/values-sw600dp/dimens.xml
+++ b/app/src/main/res/values-sw600dp/dimens.xml
@@ -19,6 +19,7 @@
18sp
18sp
+ 10dp
10dp
14sp
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index 52ef33abe..257b39b75 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -119,7 +119,7 @@
"தொலைபேசி அழைபà¯à®ªà¯ போனà¯à®± கà¯à®±à¯à®•à¯à®•à¯€à®Ÿà¯à®•à®³à¯à®•à¯à®•à¯ பிறக௠தொடரவà¯à®®à¯ "
\'அடà¯à®¤à¯à®¤à¯\' மறà¯à®±à¯à®®à¯ \'ஒபà¯à®ªà®¾à®©\' காணொளிகலை காணà¯à®ªà®¿
தேடபà¯à®ªà®Ÿà¯à®Ÿ வாகà¯à®•à®¿à®¯à®¤à¯à®¤à¯ˆ அமைவிடதà¯à®¤à®¿à®²à¯ சேமிகà¯à®•à®µà¯à®®à¯
- ஆதரிகà¯à®•à®ªà¯à®ªà®Ÿà®¾à®¤ URL
+ ஆதரிகà¯à®•à®ªà¯à®ªà®Ÿà®¾à®¤ URL
இயலà¯à®ªà¯à®¨à®¿à®²à¯ˆ தகவலà¯à®•à®³à®¿à®©à¯ நாடà¯
பிழைதிரà¯à®¤à¯à®¤à¯
உளà¯à®³à®Ÿà®•à¯à®•à®®à¯
diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml
index 1f2fd4ce1..1f344bf23 100644
--- a/app/src/main/res/values-te/strings.xml
+++ b/app/src/main/res/values-te/strings.xml
@@ -46,7 +46,7 @@
తదà±à°ªà°°à°¿ వీడియో
తదà±à°ªà°°à°¿ వీడియో మరియౠఇలాంటి వీడియో
à°šà°¿à°Ÿà±à°•à°¾à°¨à± à°…à°¨à±à°¬à°‚ధించడానికి హోలà±à°¡à±à°¨à± చూపà±
- Url మదà±à°¦à°¤à± లేదà±
+ Url మదà±à°¦à°¤à± లేదà±
డిఫాలà±à°Ÿà± à°à°¾à°·
à°ªà±à°²à±‡à°¯à°°à±
à°ªà±à°°à°µà°°à±à°¤à°¨
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index 7910a29c7..20999eb9a 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -91,7 +91,7 @@
à¹à¸ªà¸”งวิดีโภ\'ถัดไป\' à¹à¸¥à¸° \'ที่คล้ายà¸à¸±à¸™\'
à¹à¸ªà¸”งเคล็ดลับ \"à¹à¸•à¸°à¸„้างเพื่à¸à¹€à¸žà¸´à¹ˆà¸¡\"
à¹à¸ªà¸”งเคล็ดลับเมื่à¸à¸à¸”ปุ่มพื้นหลังหรืà¸à¸›à¹Šà¸à¸›à¸à¸±à¸žà¹ƒà¸™à¸«à¸™à¹‰à¸²à¸£à¸²à¸¢à¸¥à¸°à¹€à¸à¸µà¸¢à¸”วิดีโà¸
- URL ที่ไม่สนับสนุน
+ URL ที่ไม่สนับสนุน
ประเทศเริ่มต้นขà¸à¸‡à¹€à¸™à¸·à¹‰à¸à¸«à¸²
บริà¸à¸²à¸£
ภาษาขà¸à¸‡à¹€à¸™à¸·à¹‰à¸à¸«à¸²à¹€à¸£à¸´à¹ˆà¸¡à¸•à¹‰à¸™
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index bc4808f59..fcc0a591a 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -37,7 +37,7 @@
Ä°ndir
Sonraki
\'Sonraki\' ve \'Benzer\' videoları göster
- Desteklenmeyen URL
+ Desteklenmeyen URL
Öntanımlı içerik dili
Ses
Video ve ses
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 20e9dbde1..57d432e5c 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -37,7 +37,7 @@
Завантажити
ÐаÑтупне
Показувати \"ÐаÑтупне\" Ñ– \"Схожі\" відео
- URL не підтримуєтьÑÑ
+ URL не підтримуєтьÑÑ
Переважна мова контенту
Відео та Ðудіо
Зовнішній виглÑд
diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml
index 5609902e3..9963592d9 100644
--- a/app/src/main/res/values-ur/strings.xml
+++ b/app/src/main/res/values-ur/strings.xml
@@ -85,7 +85,7 @@
\'اگلی\' اور \'ملتی جلتی\' ویڈیوز دکھائیں
’’شامل کرنے Ú©Û’ لئے پکڑیں‘‘ Ø§Ø´Ø§Ø±Û Ø¯Ú©Ú¾Ø§Ø¦ÛŒÚº
ویڈیو تÙصیلات Ú©Û’ صÙØÛ’ پر جب پس منظر یا پاپ اپ بٹن دبائے جانے پر ٹپ دکھائیں
- غیر مواÙÙ‚ URL
+ غیر مواÙÙ‚ URL
مشمولات کا Ø·Û’ Ø´Ø¯Û Ù…Ù„Ú©
خدمت
پلیئر
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 1795dd607..e76093fc9 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -53,7 +53,7 @@
Tải vá»
Tiếp theo
Hiển thị video \"Tiếp theo\" và \"Tương tự\"
- URL không được hỗ trợ
+ URL không được hỗ trợ
Hiển thị
Khác
Phát ở chế Ä‘á»™ ná»n
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 8b87e9b10..f77fbb532 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -29,7 +29,7 @@
黑色
下载
下一个
- ä¸æ”¯æŒçš„ URL
+ ä¸æ”¯æŒçš„ URL
外观
其他
全部
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index c06867012..d9893b8a2 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -28,7 +28,7 @@
下載
下一部影片
顯示下一部åŠç›¸é—œçš„影片
- ä¸æ”¯æ´æ¤ç¶²å€
+ ä¸æ”¯æ´æ¤ç¶²å€
é è¨å…§å®¹èªžè¨€
影片åŠè²éŸ³
外觀
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index b71d64a1f..9c0e48665 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -32,7 +32,7 @@
下載
下一個
顯示「下一部ã€èˆ‡ã€Œç›¸é—œã€çš„影片
- ä¸æ”¯æ´æ¤ç¶²å€
+ ä¸æ”¯æ´æ¤ç¶²å€
é è¨å…§å®¹èªžè¨€
影片和音訊
外觀
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index cbf538fb5..7f6740554 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -82,6 +82,8 @@
+
+
diff --git a/app/src/main/res/values/colors_services.xml b/app/src/main/res/values/colors_services.xml
index a09878fae..881f46589 100644
--- a/app/src/main/res/values/colors_services.xml
+++ b/app/src/main/res/values/colors_services.xml
@@ -6,10 +6,10 @@
#000000
#e53935
- #CD322E
- #992722
+ #992722
+ #7a1717
#FFFFFF
- #CD322E
+ #992722
#f57c00
@@ -17,10 +17,10 @@
#000000
#f57c00
- #f57c00
- #995700
+ #a35300
+ #7d4000
#FFFFFF
- #f57c00
+ #a35300
#ff6f00
@@ -28,20 +28,29 @@
#000000
#ff6f00
- #ff6f00
- #c43e00
+ #a34700
+ #942f00
#FFFFFF
- #ff6f00
+ #a34700
-
+
#9e9e9e
#616161
#000000
#9e9e9e
- #9e9e9e
- #616161
+ #878787
+ #4f4f4f
#FFFFFF
- #9e9e9e
+ #878787
+
+
+ #17a0c4
+ #000000
+ #17a0c4
+
+ #1383a1
+ #FFFFFF
+ #1383a1
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index d3feb0ea8..afd123c56 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,8 +1,8 @@
-
- 120dp
- 16dp
+
+ 100dp
+ 8dp
48dp
12dp
130dp
@@ -40,9 +40,12 @@
4dp
8dp
12dp
+ 16dp
+ 12dp
180dp
150dp
+ 200dp
32dp
42dp
@@ -63,13 +66,18 @@
12sp
32dp
+ 18sp
+ 18sp
16dp
18sp
18sp
+ 18sp
70dp
35dp
+ 5dp
5dp
+ 5dp
50dp
16dp
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index 946d586de..05b107fa8 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -50,6 +50,25 @@
- 30000
+ progressive_load_interval
+ 16384
+
+ - 1 KiB
+ - 4 KiB
+ - 16 KiB
+ - 64 KiB
+ - 256 KiB
+ - 1 MiB - ExoPlayer Default
+
+
+ - 1024
+ - 4096
+ - 16384
+ - 65536
+ - 262144
+ - 1048576
+
+
minimize_on_exit_key
@string/minimize_on_exit_none_key
minimize_on_exit_none_key
@@ -75,6 +94,10 @@
- @string/best_resolution_key
+ - 2160p60
+ - 2160p
+ - 1440p60
+ - 1440p
- 1080p60
- 1080p
- 720p60
@@ -86,6 +109,10 @@
- @string/best_resolution
+ - 2160p60
+ - 2160p
+ - 1440p60
+ - 1440p
- 1080p60
- 1080p
- 720p60
@@ -213,6 +240,8 @@
import_data
export_data
+ clear_cookie
+
download_thumbnail_key
cache_wipe_key
@@ -220,6 +249,11 @@
clear_playback_states
clear_search_history
+ @string/last_download_type_video_key
+ last_dl_type_video
+ last_dl_type_audio
+ last_dl_type_subtitle
+
downloads_storage_ask
storage_use_saf
@@ -1069,7 +1103,7 @@
- Italiano
- 日本語
- ꦧꦱꦗꦮ
- - Tamaziɣt Taqbaylit
+ - Taqbaylit
- í•œêµì–´
- کوردی
- Lietuvių kalba
@@ -1109,6 +1143,10 @@
- @string/limit_data_usage_none_key
+ - 2160p60
+ - 2160p
+ - 1440p60
+ - 1440p
- 1080p60
- 1080p
- 720p60
@@ -1137,6 +1175,6 @@
- @string/list
- @string/grid
-
+ always_expand_description
recaptcha_cookies_key
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ac4cc6c44..573e548c6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -11,6 +11,7 @@
Open in browser
Open in popup mode
Share
+ Share stream link
Download
Download stream file
Search
@@ -72,6 +73,8 @@
Use fast inexact seek
Inexact seek allows the player to seek to positions faster with reduced precision. Seeking for 5, 15 or 25 seconds doesn\'t work with this.
Fast-forward/-rewind seek duration
+ Playback load interval size
+ Change the load interval size (currently at %s). A lower value may speed up initial video loading. Changes require a player restart.
Load thumbnails
Show comments
Turn off to hide comments
@@ -107,7 +110,8 @@
Show \'Next\' and \'Similar\' videos
Show \"Hold to append\" tip
Show tip when pressing the background or the popup button in video \"Details:\"
- Unsupported URL
+ Unsupported URL
+ NewPipe could not recognize the provided url. Open it with another method?
Default content country
Service
Default content language
@@ -140,6 +144,7 @@
Show age restricted video. Future changes are possible from the settings.
YouTube restricted mode
This video is age restricted.\n\nIf you want to view it, enable \"Age restricted content\" in the settings.
+ This video is age-restricted.\nDue to new YouTube policies with age-restricted videos, NewPipe cannot access any of its video streams and thus is unable to play it.
Live
Downloads
Downloads
@@ -181,10 +186,14 @@
Switch to Background
Switch to Popup
Switch to Main
+ Set Timer
Import database
Export database
+ Clear reCAPTCHA cookies
+ reCAPTCHA cookies have been cleared
Overrides your current history and subscriptions
Export history, subscriptions and playlists
+ Clear cookies that NewPipe stores when you solve a reCAPTCHA
Clear watch history
Deletes the history of played streams and the playback positions
Delete entire watch history?
@@ -302,6 +311,7 @@
- %s videos
No comments
+ Comments are disabled
Start
Pause
@@ -361,18 +371,18 @@
Libre lightweight streaming on Android.
Contribute
Whether you have ideas of; translation, design changes, code cleaning, or real heavy code changes—help is always welcome. The more is done the better it gets!
- https://github.com/XiangRongLin/NewPipe-preuinified
+ https://github.com/ShareASmile/FoxPipe
View on GitHub
Donate
NewPipe is developed by volunteers spending their free time bringing you the best user experience. Give back to help developers make NewPipe even better while they enjoy a cup of coffee.
- https://newpipe.schabi.org/donate
+ https://newpipe.net/donate
Give back
Website
Visit the NewPipe Website for more info and news.
- https://newpipe.schabi.org/
+ https://newpipe.net/
NewPipe\'s Privacy Policy
The NewPipe project takes your privacy very seriously. Therefore, the app does not collect any data without your consent.\nNewPipe\'s privacy policy explains in detail what data is sent and stored when you send a crash report.
- https://newpipe.schabi.org/legal/privacy/
+ https://newpipe.net/legal/privacy/
Read privacy policy
NewPipe\'s License
NewPipe is copyleft libre software: You can use, study share and improve it at will. Specifically you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@@ -394,6 +404,7 @@
Content of main page
What tabs are shown on the main page
+ Swipe items to remove them
Selection
Blank Page
Kiosk Page
@@ -408,7 +419,11 @@
Select a kiosk
Exported
Imported
+ Export
+ Export file name
+ Please enter file name for export.
No valid ZIP file
+ You entered an invalid name for zip file.
Warning: Could not import all files.
This will override your current setup.
Do you want to also import settings?
@@ -496,7 +511,7 @@
Previous export
Could not import subscriptions
Could not export subscriptions
- Import YouTube subscriptions by downloading the export file:\n\n1. Go to this URL: %1$s\n2. Log in when asked\n3. A download should start (that\'s the export file)
+ Import YouTube subscriptions from Google takeout:\n\n1. Go to this URL: %1$s\n2. Log in when asked\n3. Click on \"All data included\", then on \"Deselect all\", then select only \"subscriptions\" and click \"OK\"\n4. Click on \"Next step\" and then on \"Create export\"\n5. Click on the \"Download\" button after it appears\n6. Click on IMPORT FILE below and select the downloaded zip file\n7. [If the zip import fails] Extract the .csv file (usually under \"YouTube and YouTube Music/subscriptions/subscriptions.csv\"), click on IMPORT FILE below and select the extracted csv file
Import a SoundCloud profile by typing either the URL or your ID:\n\n1. Enable \"desktop mode\" in a web-browser (the site is not available for mobile devices)\n2. Go to this URL: %1$s\n3. Log in when asked\n4. Copy the profile URL you were redirected to.
yourID, soundcloud.com/yourid
Keep in mind this operation can be network expensive.\n\nDo you want to continue?
@@ -518,6 +533,10 @@
@string/limit_data_usage_none_key
- @string/limit_data_usage_none_description
+ - 2160p60
+ - 2160p
+ - 1440p60
+ - 1440p
- 1080p60
- 1080p
- 720p60
@@ -658,4 +677,15 @@
Created by %s
By %s
Playlist page
-
+ Recent
+ This content is not available in your country.
+ This is a SoundCloud Go+ track, at least in your country, so it cannot be streamed or downloaded by NewPipe.
+ This content is private, so it cannot be streamed or downloaded by NewPipe.
+ This video is available only to YouTube Music Premium members, so it cannot be streamed or downloaded by NewPipe.
+ This content is only available to users who have paid, so it cannot be streamed or downloaded by NewPipe.
+ Featured
+ Radio
+ Always expand description
+ Pinned comment
+ Hearted by creator
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 9018a2d2a..ade307ca9 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -47,6 +47,7 @@
- @drawable/ic_home_black_24dp
- @drawable/ic_add_circle_outline_black_24dp
- @drawable/ic_tv_black_24dp
+ - @drawable/ic_live_tv_black_24dp
- @drawable/ic_bookmark_black_24dp
- @drawable/ic_playlist_add_black_24dp
- @drawable/ic_playlist_add_check_black_24dp
@@ -60,6 +61,7 @@
- @drawable/ic_apps_black_24dp
- @drawable/ic_delete_black_24dp
- @drawable/ic_cloud_download_black_24dp
+ - @drawable/ic_timer_black_24dp
- @drawable/ic_done_black_24dp
- @drawable/ic_refresh_black_24dp
- @drawable/ic_computer_black_24dp
@@ -153,6 +155,7 @@
- @drawable/ic_home_white_24dp
- @drawable/ic_add_circle_outline_white_24dp
- @drawable/ic_tv_white_24dp
+ - @drawable/ic_live_tv_white_24dp
- @drawable/ic_bookmark_white_24dp
- @drawable/ic_playlist_add_white_24dp
- @drawable/ic_playlist_add_check_white_24dp
@@ -167,6 +170,7 @@
- @drawable/ic_delete_white_24dp
- @drawable/ic_pause_white_24dp
- @drawable/ic_cloud_download_white_24dp
+ - @drawable/ic_timer_white_24dp
- @drawable/ic_done_white_24dp
- @drawable/ic_refresh_white_24dp
- @drawable/ic_computer_white_24dp
diff --git a/app/src/main/res/values/styles_services.xml b/app/src/main/res/values/styles_services.xml
index 013690b44..d52a44572 100644
--- a/app/src/main/res/values/styles_services.xml
+++ b/app/src/main/res/values/styles_services.xml
@@ -51,23 +51,42 @@
- @color/dark_peertube_accent_color
-
-
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/appearance_settings.xml b/app/src/main/res/xml/appearance_settings.xml
index 31be267af..ac97fa674 100644
--- a/app/src/main/res/xml/appearance_settings.xml
+++ b/app/src/main/res/xml/appearance_settings.xml
@@ -13,7 +13,7 @@
android:summary="%s"
android:title="@string/theme_title"/>
-
+
diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml
index bf9c3d115..9efc5eff7 100644
--- a/app/src/main/res/xml/content_settings.xml
+++ b/app/src/main/res/xml/content_settings.xml
@@ -45,39 +45,39 @@
android:title="@string/peertube_instance_url_title"
android:summary="@string/peertube_instance_url_summary"/>
-
-
-
-
-
-
+
+
@@ -109,7 +115,7 @@
android:title="@string/feed_update_threshold_title"
android:summary="@string/feed_update_threshold_summary"/>
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/update_settings.xml b/app/src/main/res/xml/update_settings.xml
index b527bba9c..b2634f69c 100644
--- a/app/src/main/res/xml/update_settings.xml
+++ b/app/src/main/res/xml/update_settings.xml
@@ -5,7 +5,7 @@
android:key="general_preferences"
android:title="@string/settings_category_updates_title">
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/app/src/test/java/org/schabi/newpipe/ReCaptchaActivityTest.kt b/app/src/test/java/org/schabi/newpipe/ReCaptchaActivityTest.kt
new file mode 100644
index 000000000..b9f6887f9
--- /dev/null
+++ b/app/src/test/java/org/schabi/newpipe/ReCaptchaActivityTest.kt
@@ -0,0 +1,27 @@
+package org.schabi.newpipe
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.schabi.newpipe.ReCaptchaActivity.YT_URL
+
+class ReCaptchaActivityTest {
+ private fun assertSanitized(expected: String, actual: String?) {
+ assertEquals(expected, ReCaptchaActivity.sanitizeRecaptchaUrl(actual))
+ }
+
+ @Test fun `null, empty or blank url is sanitized correctly`() {
+ assertSanitized(YT_URL, null)
+ assertSanitized(YT_URL, "")
+ assertSanitized(YT_URL, " \n \t ")
+ }
+
+ @Test fun `YouTube url containing pbj=1 is sanitized correctly`() {
+ val sanitizedUrl = "https://m.youtube.com/results?search_query=test"
+ assertSanitized(sanitizedUrl, "https://m.youtube.com/results?search_query=test")
+ assertSanitized(sanitizedUrl, "https://m.youtube.com/results?search_query=test&pbj=1&pbj=1")
+ assertSanitized(sanitizedUrl, "https://m.youtube.com/results?pbj=1&search_query=test")
+ assertSanitized("pbj://pbj.pbj.pbj/pbj", "pbj://pbj.pbj.pbj/pbj?pbj=1")
+ assertSanitized("http://www.host.com/b?p1=7&p2=9", "http://www.host.com/b?p1=7&pbj=1&p2=9")
+ assertSanitized("http://www.host.com/a?pbj=0", "http://www.host.com/a?pbj=0")
+ }
+}
diff --git a/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt b/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt
index a0db83ba9..e6d4247ca 100644
--- a/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt
@@ -13,8 +13,10 @@ class FeedGroupIconTest {
val added = usedIds.add(currentIcon.id)
assertTrue("Repeated ids (current item: ${currentIcon.name} - ${currentIcon.id})", added)
- assertEquals("Gap between ids detected (current item: ${currentIcon.name} - ${currentIcon.id} → should be: $shouldBeId)",
- shouldBeId, currentIcon.id)
+ assertEquals(
+ "Gap between ids detected (current item: ${currentIcon.name} - ${currentIcon.id} → should be: $shouldBeId)",
+ shouldBeId, currentIcon.id
+ )
}
}
diff --git a/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt b/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt
index 55dc9f469..2d897e379 100644
--- a/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt
@@ -1,14 +1,14 @@
package org.schabi.newpipe.util
-import java.io.IOException
-import java.io.InterruptedIOException
-import java.net.SocketException
-import javax.net.ssl.SSLException
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.schabi.newpipe.util.ExceptionUtils.Companion.hasAssignableCause
import org.schabi.newpipe.util.ExceptionUtils.Companion.hasExactCause
+import java.io.IOException
+import java.io.InterruptedIOException
+import java.net.SocketException
+import javax.net.ssl.SSLException
class ExceptionUtilsTest {
@Test fun `assignable causes`() {
diff --git a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java
index 0baa2a167..59b6637ca 100644
--- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java
+++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java
@@ -11,38 +11,41 @@
import static org.junit.Assert.assertEquals;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
public class ListHelperTest {
private static final String BEST_RESOLUTION_KEY = "best_resolution";
private static final List AUDIO_STREAMS_TEST_LIST = Arrays.asList(
- new AudioStream("", MediaFormat.M4A, /**/ 128),
- new AudioStream("", MediaFormat.WEBMA, /**/ 192),
- new AudioStream("", MediaFormat.MP3, /**/ 64),
- new AudioStream("", MediaFormat.WEBMA, /**/ 192),
- new AudioStream("", MediaFormat.M4A, /**/ 128),
- new AudioStream("", MediaFormat.MP3, /**/ 128),
- new AudioStream("", MediaFormat.WEBMA, /**/ 64),
- new AudioStream("", MediaFormat.M4A, /**/ 320),
- new AudioStream("", MediaFormat.MP3, /**/ 192),
- new AudioStream("", MediaFormat.WEBMA, /**/ 320));
+ generateAudioStream("m4a-128-1", MediaFormat.M4A, 128),
+ generateAudioStream("webma-192", MediaFormat.WEBMA, 192),
+ generateAudioStream("mp3-64", MediaFormat.MP3, 64),
+ generateAudioStream("webma-192", MediaFormat.WEBMA, 192),
+ generateAudioStream("m4a-128-2", MediaFormat.M4A, 128),
+ generateAudioStream("mp3-128", MediaFormat.MP3, 128),
+ generateAudioStream("webma-64", MediaFormat.WEBMA, 64),
+ generateAudioStream("m4a-320", MediaFormat.M4A, 320),
+ generateAudioStream("mp3-192", MediaFormat.MP3, 192),
+ generateAudioStream("webma-320", MediaFormat.WEBMA, 320));
private static final List VIDEO_STREAMS_TEST_LIST = Arrays.asList(
- new VideoStream("", MediaFormat.MPEG_4, /**/ "720p"),
- new VideoStream("", MediaFormat.v3GPP, /**/ "240p"),
- new VideoStream("", MediaFormat.WEBM, /**/ "480p"),
- new VideoStream("", MediaFormat.v3GPP, /**/ "144p"),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "360p"),
- new VideoStream("", MediaFormat.WEBM, /**/ "360p"));
+ generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false),
+ generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false),
+ generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false),
+ generateVideoStream("v3gpp-144", MediaFormat.v3GPP, "144p", false),
+ generateVideoStream("mpeg_4-360", MediaFormat.MPEG_4, "360p", false),
+ generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false));
private static final List VIDEO_ONLY_STREAMS_TEST_LIST = Arrays.asList(
- new VideoStream("", MediaFormat.MPEG_4, /**/ "720p", true),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "720p", true),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "2160p", true),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "1440p60", true),
- new VideoStream("", MediaFormat.WEBM, /**/ "720p60", true),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "2160p60", true),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "720p60", true),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "1080p", true),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "1080p60", true));
+ generateVideoStream("mpeg_4-720-1", MediaFormat.MPEG_4, "720p", true),
+ generateVideoStream("mpeg_4-720-2", MediaFormat.MPEG_4, "720p", true),
+ generateVideoStream("mpeg_4-2160", MediaFormat.MPEG_4, "2160p", true),
+ generateVideoStream("mpeg_4-1440_60", MediaFormat.MPEG_4, "1440p60", true),
+ generateVideoStream("webm-720_60", MediaFormat.WEBM, "720p60", true),
+ generateVideoStream("mpeg_4-2160_60", MediaFormat.MPEG_4, "2160p60", true),
+ generateVideoStream("mpeg_4-720_60", MediaFormat.MPEG_4, "720p60", true),
+ generateVideoStream("mpeg_4-1080", MediaFormat.MPEG_4, "1080p", true),
+ generateVideoStream("mpeg_4-1080_60", MediaFormat.MPEG_4, "1080p60", true));
@Test
public void getSortedStreamVideosListTest() {
@@ -59,7 +62,8 @@ public void getSortedStreamVideosListTest() {
assertEquals(result.size(), expected.size());
for (int i = 0; i < result.size(); i++) {
- assertEquals(result.get(i).resolution, expected.get(i));
+ assertEquals(result.get(i).getResolution(), expected.get(i));
+ assertEquals(expected.get(i), result.get(i).getResolution());
}
////////////////////
@@ -72,14 +76,14 @@ public void getSortedStreamVideosListTest() {
"720p", "480p", "360p", "240p", "144p");
assertEquals(result.size(), expected.size());
for (int i = 0; i < result.size(); i++) {
- assertEquals(result.get(i).resolution, expected.get(i));
+ assertEquals(result.get(i).getResolution(), expected.get(i));
}
}
@Test
public void getSortedStreamVideosExceptHighResolutionsTest() {
////////////////////////////////////
- // Don't show Higher resolutions //
+ // Don't show Higher getResolution()s //
//////////////////////////////////
List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4,
@@ -95,59 +99,59 @@ public void getSortedStreamVideosExceptHighResolutionsTest() {
@Test
public void getDefaultResolutionTest() {
List testList = Arrays.asList(
- new VideoStream("", MediaFormat.MPEG_4, /**/ "720p"),
- new VideoStream("", MediaFormat.v3GPP, /**/ "240p"),
- new VideoStream("", MediaFormat.WEBM, /**/ "480p"),
- new VideoStream("", MediaFormat.WEBM, /**/ "240p"),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "240p"),
- new VideoStream("", MediaFormat.WEBM, /**/ "144p"),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "360p"),
- new VideoStream("", MediaFormat.WEBM, /**/ "360p"));
+ generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false),
+ generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false),
+ generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false),
+ generateVideoStream("webm-240", MediaFormat.WEBM, "240p", false),
+ generateVideoStream("mpeg_4-240", MediaFormat.MPEG_4, "240p", false),
+ generateVideoStream("webm-144", MediaFormat.WEBM, "144p", false),
+ generateVideoStream("mpeg_4-360", MediaFormat.MPEG_4, "360p", false),
+ generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false));
VideoStream result = testList.get(ListHelper.getDefaultResolutionIndex(
"720p", BEST_RESOLUTION_KEY, MediaFormat.MPEG_4, testList));
- assertEquals("720p", result.resolution);
+ assertEquals("720p", result.getResolution());
assertEquals(MediaFormat.MPEG_4, result.getFormat());
- // Have resolution and the format
+ // Have getResolution() and the format
result = testList.get(ListHelper.getDefaultResolutionIndex(
"480p", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList));
- assertEquals("480p", result.resolution);
+ assertEquals("480p", result.getResolution());
assertEquals(MediaFormat.WEBM, result.getFormat());
- // Have resolution but not the format
+ // Have getResolution() but not the format
result = testList.get(ListHelper.getDefaultResolutionIndex(
"480p", BEST_RESOLUTION_KEY, MediaFormat.MPEG_4, testList));
- assertEquals("480p", result.resolution);
+ assertEquals("480p", result.getResolution());
assertEquals(MediaFormat.WEBM, result.getFormat());
- // Have resolution and the format
+ // Have getResolution() and the format
result = testList.get(ListHelper.getDefaultResolutionIndex(
"240p", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList));
- assertEquals("240p", result.resolution);
+ assertEquals("240p", result.getResolution());
assertEquals(MediaFormat.WEBM, result.getFormat());
- // The best resolution
+ // The best getResolution()
result = testList.get(ListHelper.getDefaultResolutionIndex(
BEST_RESOLUTION_KEY, BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList));
- assertEquals("720p", result.resolution);
+ assertEquals("720p", result.getResolution());
assertEquals(MediaFormat.MPEG_4, result.getFormat());
// Doesn't have the 60fps variant and format
result = testList.get(ListHelper.getDefaultResolutionIndex(
"720p60", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList));
- assertEquals("720p", result.resolution);
+ assertEquals("720p", result.getResolution());
assertEquals(MediaFormat.MPEG_4, result.getFormat());
// Doesn't have the 60fps variant
result = testList.get(ListHelper.getDefaultResolutionIndex(
"480p60", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList));
- assertEquals("480p", result.resolution);
+ assertEquals("480p", result.getResolution());
assertEquals(MediaFormat.WEBM, result.getFormat());
- // Doesn't have the resolution, will return the best one
+ // Doesn't have the getResolution(), will return the best one
result = testList.get(ListHelper.getDefaultResolutionIndex(
"2160p60", BEST_RESOLUTION_KEY, MediaFormat.WEBM, testList));
- assertEquals("720p", result.resolution);
+ assertEquals("720p", result.getResolution());
assertEquals(MediaFormat.MPEG_4, result.getFormat());
}
@@ -155,17 +159,17 @@ public void getDefaultResolutionTest() {
public void getHighestQualityAudioFormatTest() {
AudioStream stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getHighestQualityAudioIndex(
MediaFormat.M4A, AUDIO_STREAMS_TEST_LIST));
- assertEquals(320, stream.average_bitrate);
+ assertEquals(320, stream.getAverageBitrate());
assertEquals(MediaFormat.M4A, stream.getFormat());
stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getHighestQualityAudioIndex(
MediaFormat.WEBMA, AUDIO_STREAMS_TEST_LIST));
- assertEquals(320, stream.average_bitrate);
+ assertEquals(320, stream.getAverageBitrate());
assertEquals(MediaFormat.WEBMA, stream.getFormat());
stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getHighestQualityAudioIndex(
MediaFormat.MP3, AUDIO_STREAMS_TEST_LIST));
- assertEquals(192, stream.average_bitrate);
+ assertEquals(192, stream.getAverageBitrate());
assertEquals(MediaFormat.MP3, stream.getFormat());
}
@@ -177,13 +181,13 @@ public void getHighestQualityAudioFormatPreferredAbsent() {
////////////////////////////////////////
List testList = Arrays.asList(
- new AudioStream("", MediaFormat.M4A, /**/ 128),
- new AudioStream("", MediaFormat.WEBMA, /**/ 192));
+ generateAudioStream("m4a-128", MediaFormat.M4A, 128),
+ generateAudioStream("webma-192", MediaFormat.WEBMA, 192));
// List doesn't contains this format
// It should fallback to the highest bitrate audio no matter what format it is
AudioStream stream = testList.get(ListHelper.getHighestQualityAudioIndex(
MediaFormat.MP3, testList));
- assertEquals(192, stream.average_bitrate);
+ assertEquals(192, stream.getAverageBitrate());
assertEquals(MediaFormat.WEBMA, stream.getFormat());
////////////////////////////////////////////////////////
@@ -191,24 +195,24 @@ public void getHighestQualityAudioFormatPreferredAbsent() {
//////////////////////////////////////////////////////
testList = new ArrayList<>(Arrays.asList(
- new AudioStream("", MediaFormat.WEBMA, /**/ 192),
- new AudioStream("", MediaFormat.M4A, /**/ 192),
- new AudioStream("", MediaFormat.WEBMA, /**/ 192),
- new AudioStream("", MediaFormat.M4A, /**/ 192),
- new AudioStream("", MediaFormat.WEBMA, /**/ 192),
- new AudioStream("", MediaFormat.M4A, /**/ 192),
- new AudioStream("", MediaFormat.WEBMA, /**/ 192)));
+ generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192),
+ generateAudioStream("m4a-192-1", MediaFormat.M4A, 192),
+ generateAudioStream("webma-192-2", MediaFormat.WEBMA, 192),
+ generateAudioStream("m4a-192-2", MediaFormat.M4A, 192),
+ generateAudioStream("webma-192-3", MediaFormat.WEBMA, 192),
+ generateAudioStream("m4a-192-3", MediaFormat.M4A, 192),
+ generateAudioStream("webma-192-4", MediaFormat.WEBMA, 192)));
// List doesn't contains this format, it should fallback to the highest bitrate audio and
// the highest quality format.
stream = testList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.MP3, testList));
- assertEquals(192, stream.average_bitrate);
+ assertEquals(192, stream.getAverageBitrate());
assertEquals(MediaFormat.M4A, stream.getFormat());
// Adding a new format and bitrate. Adding another stream will have no impact since
// it's not a prefered format.
- testList.add(new AudioStream("", MediaFormat.WEBMA, /**/ 192));
+ testList.add(generateAudioStream("webma-192-5", MediaFormat.WEBMA, /**/ 192));
stream = testList.get(ListHelper.getHighestQualityAudioIndex(MediaFormat.MP3, testList));
- assertEquals(192, stream.average_bitrate);
+ assertEquals(192, stream.getAverageBitrate());
assertEquals(MediaFormat.M4A, stream.getFormat());
}
@@ -222,17 +226,17 @@ public void getHighestQualityAudioNull() {
public void getLowestQualityAudioFormatTest() {
AudioStream stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getMostCompactAudioIndex(
MediaFormat.M4A, AUDIO_STREAMS_TEST_LIST));
- assertEquals(128, stream.average_bitrate);
+ assertEquals(128, stream.getAverageBitrate());
assertEquals(MediaFormat.M4A, stream.getFormat());
stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getMostCompactAudioIndex(
MediaFormat.WEBMA, AUDIO_STREAMS_TEST_LIST));
- assertEquals(64, stream.average_bitrate);
+ assertEquals(64, stream.getAverageBitrate());
assertEquals(MediaFormat.WEBMA, stream.getFormat());
stream = AUDIO_STREAMS_TEST_LIST.get(ListHelper.getMostCompactAudioIndex(
MediaFormat.MP3, AUDIO_STREAMS_TEST_LIST));
- assertEquals(64, stream.average_bitrate);
+ assertEquals(64, stream.getAverageBitrate());
assertEquals(MediaFormat.MP3, stream.getFormat());
}
@@ -244,19 +248,19 @@ public void getLowestQualityAudioFormatPreferredAbsent() {
////////////////////////////////////////
List testList = new ArrayList<>(Arrays.asList(
- new AudioStream("", MediaFormat.M4A, /**/ 128),
- new AudioStream("", MediaFormat.WEBMA, /**/ 192)));
+ generateAudioStream("m4a-128", MediaFormat.M4A, 128),
+ generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192)));
// List doesn't contains this format
// It should fallback to the most compact audio no matter what format it is.
AudioStream stream = testList.get(ListHelper.getMostCompactAudioIndex(
MediaFormat.MP3, testList));
- assertEquals(128, stream.average_bitrate);
+ assertEquals(128, stream.getAverageBitrate());
assertEquals(MediaFormat.M4A, stream.getFormat());
// WEBMA is more compact than M4A
- testList.add(new AudioStream("", MediaFormat.WEBMA, /**/ 128));
+ testList.add(generateAudioStream("webma-192-2", MediaFormat.WEBMA, 128));
stream = testList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.MP3, testList));
- assertEquals(128, stream.average_bitrate);
+ assertEquals(128, stream.getAverageBitrate());
assertEquals(MediaFormat.WEBMA, stream.getFormat());
////////////////////////////////////////////////////////
@@ -264,21 +268,21 @@ public void getLowestQualityAudioFormatPreferredAbsent() {
//////////////////////////////////////////////////////
testList = new ArrayList<>(Arrays.asList(
- new AudioStream("", MediaFormat.WEBMA, /**/ 192),
- new AudioStream("", MediaFormat.M4A, /**/ 192),
- new AudioStream("", MediaFormat.WEBMA, /**/ 256),
- new AudioStream("", MediaFormat.M4A, /**/ 192),
- new AudioStream("", MediaFormat.WEBMA, /**/ 192),
- new AudioStream("", MediaFormat.M4A, /**/ 192)));
+ generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192),
+ generateAudioStream("m4a-192-1", MediaFormat.M4A, 192),
+ generateAudioStream("webma-256", MediaFormat.WEBMA, 256),
+ generateAudioStream("m4a-192-2", MediaFormat.M4A, 192),
+ generateAudioStream("webma-192-2", MediaFormat.WEBMA, 192),
+ generateAudioStream("m4a-192-3", MediaFormat.M4A, 192)));
// List doesn't contain this format
// It should fallback to the most compact audio no matter what format it is.
stream = testList.get(ListHelper.getMostCompactAudioIndex(MediaFormat.MP3, testList));
- assertEquals(192, stream.average_bitrate);
+ assertEquals(192, stream.getAverageBitrate());
assertEquals(MediaFormat.WEBMA, stream.getFormat());
// Should be same as above
stream = testList.get(ListHelper.getMostCompactAudioIndex(null, testList));
- assertEquals(192, stream.average_bitrate);
+ assertEquals(192, stream.getAverageBitrate());
assertEquals(MediaFormat.WEBMA, stream.getFormat());
}
@@ -291,14 +295,14 @@ public void getLowestQualityAudioNull() {
@Test
public void getVideoDefaultStreamIndexCombinations() {
List testList = Arrays.asList(
- new VideoStream("", MediaFormat.MPEG_4, /**/ "1080p"),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "720p60"),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "720p"),
- new VideoStream("", MediaFormat.WEBM, /**/ "480p"),
- new VideoStream("", MediaFormat.MPEG_4, /**/ "360p"),
- new VideoStream("", MediaFormat.WEBM, /**/ "360p"),
- new VideoStream("", MediaFormat.v3GPP, /**/ "240p60"),
- new VideoStream("", MediaFormat.WEBM, /**/ "144p"));
+ generateVideoStream("mpeg_4-1080", MediaFormat.MPEG_4, "1080p", false),
+ generateVideoStream("mpeg_4-720_60", MediaFormat.MPEG_4, "720p60", false),
+ generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false),
+ generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false),
+ generateVideoStream("mpeg_4-360", MediaFormat.MPEG_4, "360p", false),
+ generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false),
+ generateVideoStream("v3gpp-240_60", MediaFormat.v3GPP, "240p60", false),
+ generateVideoStream("webm-144", MediaFormat.WEBM, "144p", false));
// exact matches
assertEquals(1, ListHelper.getVideoStreamIndex("720p60", MediaFormat.MPEG_4, testList));
@@ -320,7 +324,7 @@ public void getVideoDefaultStreamIndexCombinations() {
assertEquals(0, ListHelper.getVideoStreamIndex("1080p60", null, testList));
assertEquals(6, ListHelper.getVideoStreamIndex("240p", null, testList));
- // match closest lower resolution
+ // match closest lower getResolution()
assertEquals(7, ListHelper.getVideoStreamIndex("200p", MediaFormat.WEBM, testList));
assertEquals(7, ListHelper.getVideoStreamIndex("200p60", MediaFormat.WEBM, testList));
assertEquals(7, ListHelper.getVideoStreamIndex("200p", MediaFormat.MPEG_4, testList));
@@ -331,4 +335,30 @@ public void getVideoDefaultStreamIndexCombinations() {
// Can't find a match
assertEquals(-1, ListHelper.getVideoStreamIndex("100p", null, testList));
}
+
+ @NonNull
+ private static AudioStream generateAudioStream(@NonNull final String id,
+ @Nullable final MediaFormat mediaFormat,
+ final int averageBitrate) {
+ return new AudioStream.Builder()
+ .setId(id)
+ .setContent("", true)
+ .setMediaFormat(mediaFormat)
+ .setAverageBitrate(averageBitrate)
+ .build();
+ }
+
+ @NonNull
+ private static VideoStream generateVideoStream(@NonNull final String id,
+ @Nullable final MediaFormat mediaFormat,
+ @NonNull final String resolution,
+ final boolean isVideoOnly) {
+ return new VideoStream.Builder()
+ .setId(id)
+ .setContent("", true)
+ .setIsVideoOnly(isVideoOnly)
+ .setResolution(resolution)
+ .setMediaFormat(mediaFormat)
+ .build();
+ }
}
diff --git a/app/src/test/java/org/schabi/newpipe/util/urlfinder/UrlFinderTest.kt b/app/src/test/java/org/schabi/newpipe/util/urlfinder/UrlFinderTest.kt
index 99f26e472..ac8e4c8da 100644
--- a/app/src/test/java/org/schabi/newpipe/util/urlfinder/UrlFinderTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/util/urlfinder/UrlFinderTest.kt
@@ -9,7 +9,8 @@ import org.junit.Test
class UrlFinderTest {
@Test fun `first url from long text`() {
val expected = "https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_"
- val result = UrlFinder.firstUrlFromInput("""
+ val result = UrlFinder.firstUrlFromInput(
+ """
|Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|Eu tincidunt tortor aliquam nulla. URL: https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_ Sed dictum consequat dui.
|Pharetra diam sit amet nisl suscipit adipiscing bibendum est.
@@ -18,13 +19,15 @@ class UrlFinderTest {
|Dapibus ultrices in iaculis nunc sed augue lacus viverra. Nisl purus in mollis nunc.
|Viverra nibh cras pulvinar mattis. ####!@!@!@!#### Not this one: https://www.youtube.com/playlist?list=SHOULD_NOT Nunc sed blandit libero volutpat.
|Nisl tincidunt eget nullam non nisi est sit amet. Purus in massa tempor nec feugiat nisl pretium fusce id.
- |Vulputate eu scelerisque felis imperdiet proin fermentum leo vel.""".trimMargin())
+ |Vulputate eu scelerisque felis imperdiet proin fermentum leo vel.""".trimMargin()
+ )
assertEquals(expected, result)
}
@Test fun `no url from long text`() {
- val result = UrlFinder.firstUrlFromInput("""
+ val result = UrlFinder.firstUrlFromInput(
+ """
|Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|Eu tincidunt tortor aliquam nulla. Sed dictum consequat dui. Pharetra diam sit amet nisl suscipit adipiscing bibendum est.
|Volutpat sed cras ornare arcu dui vivamus. Nulla posuere sollicitudin aliquam ultrices sagittis.
@@ -32,7 +35,8 @@ class UrlFinderTest {
|Dapibus ultrices in iaculis nunc sed augue lacus viverra. Nisl purus in mollis nunc.
|Viverra nibh cras pulvinar mattis. Not this one: sed blandit libero volutpat.
|Nisl tincidunt eget nullam non nisi est sit amet. Purus in massa tempor nec feugiat nisl pretium fusce id.
- |Vulputate eu scelerisque felis imperdiet proin fermentum leo vel.""".trimMargin())
+ |Vulputate eu scelerisque felis imperdiet proin fermentum leo vel.""".trimMargin()
+ )
assertEquals(null, result)
}
@@ -44,14 +48,20 @@ class UrlFinderTest {
}
@Test fun `normal urls`() {
- assertEquals("https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_",
- UrlFinder.firstUrlFromInput("https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_"))
+ assertEquals(
+ "https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_",
+ UrlFinder.firstUrlFromInput("https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_")
+ )
- assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ",
- UrlFinder.firstUrlFromInput("https://www.youtube.com/watch?v=dQw4w9WgXcQ"))
+ assertEquals(
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ UrlFinder.firstUrlFromInput("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ )
- assertEquals("http://www.youtube.com/watch?v=dQw4w9WgXcQ",
- UrlFinder.firstUrlFromInput("http://www.youtube.com/watch?v=dQw4w9WgXcQ"))
+ assertEquals(
+ "http://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ UrlFinder.firstUrlFromInput("http://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ )
assertEquals("https://www.google.com", UrlFinder.firstUrlFromInput("https://www.google.com"))
assertEquals("http://www.google.com/test/", UrlFinder.firstUrlFromInput("http://www.google.com/test/"))
@@ -79,21 +89,33 @@ class UrlFinderTest {
}
@Test fun `random prefixes and suffixes`() {
- assertEquals("https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_",
- UrlFinder.firstUrlFromInput("$#!@#@!#https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_ @@@@@@@@@@@"))
-
- assertEquals("https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_",
- UrlFinder.firstUrlFromInput("(___\"https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_\")))_"))
-
- assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ",
- UrlFinder.firstUrlFromInput(" https://www.youtube.com/watch?v=dQw4w9WgXcQ "))
-
- assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ",
- UrlFinder.firstUrlFromInput(" ------_---__-https://www.youtube.com/watch?v=dQw4w9WgXcQ !!!!!!"))
-
- assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ",
- UrlFinder.firstUrlFromInput("****https://www.youtube.com/watch?v=dQw4w9WgXcQ _"))
- assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ",
- UrlFinder.firstUrlFromInput("https://www.youtube.com/watch?v=dQw4w9WgXcQ\"Not PartOfTheUrl"))
+ assertEquals(
+ "https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_",
+ UrlFinder.firstUrlFromInput("$#!@#@!#https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_ @@@@@@@@@@@")
+ )
+
+ assertEquals(
+ "https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_",
+ UrlFinder.firstUrlFromInput("(___\"https://www.youtube.com/playlist?list=PLabcdefghij-ABCDEFGHIJ1234567890_\")))_")
+ )
+
+ assertEquals(
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ UrlFinder.firstUrlFromInput(" https://www.youtube.com/watch?v=dQw4w9WgXcQ ")
+ )
+
+ assertEquals(
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ UrlFinder.firstUrlFromInput(" ------_---__-https://www.youtube.com/watch?v=dQw4w9WgXcQ !!!!!!")
+ )
+
+ assertEquals(
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ UrlFinder.firstUrlFromInput("****https://www.youtube.com/watch?v=dQw4w9WgXcQ _")
+ )
+ assertEquals(
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
+ UrlFinder.firstUrlFromInput("https://www.youtube.com/watch?v=dQw4w9WgXcQ\"Not PartOfTheUrl")
+ )
}
}
diff --git a/assets/NP logo v2.svg b/assets/NP logo v2.svg
index 3332f4dd9..51fdf95de 100644
--- a/assets/NP logo v2.svg
+++ b/assets/NP logo v2.svg
@@ -1,21 +1,21 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/NewPipe_background_just1.svg b/assets/NewPipe_background_just1.svg
index dae3db6b3..559a622f4 100644
--- a/assets/NewPipe_background_just1.svg
+++ b/assets/NewPipe_background_just1.svg
@@ -1,1252 +1,1252 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 6e9b57cac..9468a2998 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,13 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.3.72'
+ ext.kotlin_version = '1.4.10'
repositories {
+ mavenCentral()
jcenter()
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.1'
+ classpath 'com.android.tools.build:gradle:4.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
@@ -19,6 +20,7 @@ allprojects {
repositories {
jcenter()
google()
+ mavenCentral()
maven { url "https://jitpack.io" }
maven { url "https://clojars.org/repo" }
}
diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml
deleted file mode 100644
index b29789eca..000000000
--- a/checkstyle-suppressions.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/checkstyle.xml b/checkstyle.xml
deleted file mode 100644
index 60a1406f9..000000000
--- a/checkstyle.xml
+++ /dev/null
@@ -1,184 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/fastlane/metadata/android/de/full_description.txt b/fastlane/metadata/android/de/full_description.txt
index 867dc62ea..6245610c2 100644
--- a/fastlane/metadata/android/de/full_description.txt
+++ b/fastlane/metadata/android/de/full_description.txt
@@ -1 +1 @@
-NewPipe verwendet keine Bibliotheken des Google Frameworks oder der YouTube API. Es analysiert die Webseite, um die benötigten Informationen zu erlangen. Aus diesem Grund kann die App ohne die Google Services verwendet werden. Ebenso wird kein YouTube-Konto für NewPipe benötigt und es ist FLOSS.
+FoxPipe verwendet keine Bibliotheken des Google Frameworks oder der YouTube API. Es analysiert die Webseite, um die benötigten Informationen zu erlangen. Aus diesem Grund kann die App ohne die Google Services verwendet werden. Ebenso wird kein YouTube-Konto für FoxPipe benötigt und es ist FLOSS.
diff --git a/fastlane/metadata/android/en-US/changelogs/63.txt b/fastlane/metadata/android/en-US/changelogs/63.txt
deleted file mode 100644
index 9044e96bf..000000000
--- a/fastlane/metadata/android/en-US/changelogs/63.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-### Improvements
-- Import/export settings #1333
-- Reduce overdraw (performance improvement) #1371
-- Small code improvements #1375
-- Add everything about GDPR #1420
-
-### Fixed
-- Downloader: Fix crash on loading unfinished downloads from .giga files #1407
diff --git a/fastlane/metadata/android/en-US/changelogs/64.txt b/fastlane/metadata/android/en-US/changelogs/64.txt
deleted file mode 100644
index d2ab61203..000000000
--- a/fastlane/metadata/android/en-US/changelogs/64.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-### Improvements
-- Added the ability to limit video quality if using mobile data. #1339
-- Remember brightness for the session #1442
-- Improve download performance for weaker CPUs #1431
-- add (working) support for media session #1433
-
-### Fix
-- Fix crash on opening downloads (fix now available for release builds) #1441
diff --git a/fastlane/metadata/android/en-US/changelogs/65.txt b/fastlane/metadata/android/en-US/changelogs/65.txt
deleted file mode 100644
index 90c7a7309..000000000
--- a/fastlane/metadata/android/en-US/changelogs/65.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-### Improvements
-
-- Disable burgermenu icon animation #1486
-- undo delete of downloads #1472
-- Download option in share menu #1498
-- Added share option to long tap menu #1454
-- Minimize main player on exit #1354
-- Library version update and database backup fix #1510
-- ExoPlayer 2.8.2 Update #1392
- - Reworked the playback speed control dialog to support different step sizes for faster speed change.
- - Added a toggle to fast-forward during silences in playback speed control. This should be helpful for audiobooks and certain music genres, and can bring a true seamless experience (and can break a song with lots of silences =\\).
- - Refactored media source resolution to allow passing metadata alongside media internally in the player, rather than doing so manually. Now we have a single source of metadata and is directly available when playback starts.
- - Fixed remote playlist metadata not updating when new metadata is available when playlist fragment is opened.
- - Various UI fixes: #1383, background player notification controls now always white, easier to shutdown popup player through flinging
-- Use new extractor with refactored architecture for multiservice
-
-### Fixes
-
-- Fix #1440 Broken Video Info Layout #1491
-- View history fix #1497
- - #1495, by updating the metadata (thumbnail, title and video count) as soon as the user access the playlist.
- - #1475, by registering a view in the database when the user starts a video on external player on detail fragment.
-- Fix creen timeout in case of popup mode. #1463 (Fixed #640)
-- Main video player fix #1509
- - [#1412] Fixed repeat mode causing player NPE when new intent is received while player activity is in background.
- - Fixed minimizing player to popup does not destroy player when popup permission is not granted.
-
-
diff --git a/fastlane/metadata/android/en-US/changelogs/66.txt b/fastlane/metadata/android/en-US/changelogs/66.txt
deleted file mode 100644
index 89eb0abbb..000000000
--- a/fastlane/metadata/android/en-US/changelogs/66.txt
+++ /dev/null
@@ -1,34 +0,0 @@
-# Changelog of v0.13.7
-
-### Fixed
-- Fix sort filter issues of v0.13.6
-
-# Changelog of v0.13.6
-
-### Improvements
-
-- Disable burgermenu icon animation #1486
-- undo delete of downloads #1472
-- Download option in share menu #1498
-- Added share option to long tap menu #1454
-- Minimize main player on exit #1354
-- Library version update and database backup fix #1510
-- ExoPlayer 2.8.2 Update #1392
- - Reworked the playback speed control dialog to support different step sizes for faster speed change.
- - Added a toggle to fast-forward during silences in playback speed control. This should be helpful for audiobooks and certain music genres, and can bring a true seamless experience (and can break a song with lots of silences =\\).
- - Refactored media source resolution to allow passing metadata alongside media internally in the player, rather than doing so manually. Now we have a single source of metadata and is directly available when playback starts.
- - Fixed remote playlist metadata not updating when new metadata is available when playlist fragment is opened.
- - Various UI fixes: #1383, background player notification controls now always white, easier to shutdown popup player through flinging
-- Use new extractor with refactored architecture for multiservice
-
-### Fixes
-
-- Fix #1440 Broken Video Info Layout #1491
-- View history fix #1497
- - #1495, by updating the metadata (thumbnail, title and video count) as soon as the user access the playlist.
- - #1475, by registering a view in the database when the user starts a video on external player on detail fragment.
-- Fix creen timeout in case of popup mode. #1463 (Fixed #640)
-- Main video player fix #1509
- - [#1412] Fixed repeat mode causing player NPE when new intent is received while player activity is in background.
- - Fixed minimizing player to popup does not destroy player when popup permission is not granted.
-
diff --git a/fastlane/metadata/android/en-US/changelogs/68.txt b/fastlane/metadata/android/en-US/changelogs/68.txt
deleted file mode 100644
index 238b1e0b1..000000000
--- a/fastlane/metadata/android/en-US/changelogs/68.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-# changes of v0.14.1
-
-### Fixed
-- Fixed failed to decrypt video url #1659
-- Fixed description link not extract well #1657
-
-# changes of v0.14.0
-
-### New
-- New Drawer design #1461
-- New customizable front page #1461
-
-### Improvements
-- Reworked Gesture controls #1604
-- New way to close the popup player #1597
-
-### Fixed
-- Fix error when subscription count is not available. Closes #1649.
- - Show "Subscriber count not available" in those cases
-- Fix NPE when a YouTube playlist is empty
-- Quick fix for the kiosks in SoundCloud
-- Refactor and bugfix #1623
- - Fix Cyclic search result #1562
- - Fix Seek bar not statically lay outed
- - Fix YT Premium video are not blocked correctly
- - Fix Videos sometimes not loading (due to DASH parsing)
- - Fix links in video description
- - Show warning when someone tries to download to external sdcard
- - fix nothing shown exception triggers report
- - thumbnail not shown in background player for android 8.1 [see here](https://github.com/TeamNewPipe/NewPipe/issues/943)
-- Fix registering of broadcast receiver. Closes #1641.
diff --git a/fastlane/metadata/android/en-US/changelogs/69.txt b/fastlane/metadata/android/en-US/changelogs/69.txt
deleted file mode 100644
index c8262d1b0..000000000
--- a/fastlane/metadata/android/en-US/changelogs/69.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-### New
-- Long-tap delete and share in subscriptions #1516
-- Tablet UI and grid list layout #1617
-
-### Improvements
-- store and reload the last used aspect ratio #1748
-- Enable linear layout in Downloads activity with full video names #1771
-- Delete and share subscriptions directly from within the subscriptions tab #1516
-- Enqueuing now triggers video playing if the play queue has already ended #1783
-- Separate settings for volume and brightness gestures #1644
-- Add support for Localization #1792
-
-### Fixes
-- Fix time parsing for . format, so NewPipe can be used in Finland
-- Fix subscription count
-- Add foreground service permission for API 28+ devices #1830
-
-### Known Bugs
-- Playback state can not be saved on Android P
diff --git a/fastlane/metadata/android/en-US/changelogs/70.txt b/fastlane/metadata/android/en-US/changelogs/70.txt
deleted file mode 100644
index 755fc3469..000000000
--- a/fastlane/metadata/android/en-US/changelogs/70.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-ATTENTION: This version probably is a bugfest, just like the last one. However due to the full shutdown since the 17. a broken version is better then no version. Right? ¯\_(ツ)_/¯
-
-### Improvements
-* downloaded files can now be opened with one click #1879
-* drop support for android 4.1 - 4.3 #1884
-* remove old player #1884
-* remove streams from current play queue by swiping them to the right #1915
-* remove auto queued stream when a new stream is enqueued manually #1878
-* Postprocessing for downloads and implement missing features #1759 by @kapodamy
- * Post-processing infrastructure
- * Proper error handling "infrastructure" (for downloader)
- * Queue instead of multiple downloads
- * Move serialized pending downloads (`.giga` files) to app data
- * Implement max download retry
- * Proper multi-thread download pausing
- * Stop downloads when swicthing to mobile network (never works, see 2nd point)
- * Save the thread count for next downloads
- * A lot of incoherences fixed
-
-### Fixed
-* Fix crash with default resolution set to best and limited mobile data resolution #1835
-* pop-up player crash fixed #1874
-* NPE when trying to open background player #1901
-* Fix for inserting new streams when auto queuing is enabled #1878
-* Fixed the decypering shuttown issue
diff --git a/fastlane/metadata/android/en-US/changelogs/71.txt b/fastlane/metadata/android/en-US/changelogs/71.txt
deleted file mode 100644
index 51b31102d..000000000
--- a/fastlane/metadata/android/en-US/changelogs/71.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-### Improvements
-* Add app update notification for GitHub build (#1608 by @krtkush)
-* Various improvements to the downloader (#1944 by @kapodamy):
- * add missing white icons and use hardcored way for change the icon colors
- * check if the iterator is initialized (fixes #2031)
- * allow retry downloads with "post-processing failed" error in the new muxer
- * new MPEG-4 muxer fixing non-synchronous video and audio streams (#2039)
-
-### Fixed
-* YouTube live streams stop playing after a short time (#1996 by @yausername)
-
diff --git a/fastlane/metadata/android/en-US/changelogs/730.txt b/fastlane/metadata/android/en-US/changelogs/730.txt
deleted file mode 100644
index c317f53d2..000000000
--- a/fastlane/metadata/android/en-US/changelogs/730.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-# Fixed
-- Hot fix decrypt function error again.
diff --git a/fastlane/metadata/android/en-US/changelogs/740.txt b/fastlane/metadata/android/en-US/changelogs/740.txt
deleted file mode 100644
index c795978a8..000000000
--- a/fastlane/metadata/android/en-US/changelogs/740.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-Improvements
-
-make links in comments clickable, increase text size
-seek on clicking timestamp links in comments
-show preferred tab based on recently selected state
-add playlist to queue when long clicking on 'Background' in playlist window
-search for shared text when it is not an URL
-add "share at current time" button to the main video player
-add close button to main player when video queue is finished
-add "Play directly in Background" to longpress menu for video list items
-improve English translations for Play/Enqueue commands
-small performance improvements
-remove unused files
-update ExoPlayer to 2.9.6
-add support for Invidious links
-
-Fixed
-
-fixed scroll w/ comments and related streams disabled
-fixed CheckForNewAppVersionTask being executed when it shouldn't
-fixed youtube subscription import: ignore ones with invalid url and keep ones with empty title
-fix invalid YouTube url: signature tag name is not always "signature" preventing streams from loading
-
diff --git a/fastlane/metadata/android/en-US/changelogs/750.txt b/fastlane/metadata/android/en-US/changelogs/750.txt
deleted file mode 100644
index a22084523..000000000
--- a/fastlane/metadata/android/en-US/changelogs/750.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-New
-Playback resume #2288
-• Resume streams where you stopped last time
-Downloader Enhancements #2149
-• Use Storage Access Framework to store downloads on external SD-cards
-• New mp4 muxer
-• Optionally change the download directory before starting a download
-• Respect metered networks
-
-
-Improved
-• Removed gema strings #2295
-• Handle (auto)rotation changes during activity lifecycle #2444
-• Make long-press menus consistent #2368
-
-Fixed
-• Fixed selected subtitle track name not being shown #2394
-• Do not crash when check for app update fails (GitHub version) #2423
-• Fixed downloads stuck at 99.9% #2440
-• Update play queue metadata #2453
-• [SoundCloud] Fixed crash when loading playlists TeamNewPipe/NewPipeExtractor#170
-• [YouTube] Fixed duration can not be paresd TeamNewPipe/NewPipeExtractor#177
-
diff --git a/fastlane/metadata/android/en-US/changelogs/760.txt b/fastlane/metadata/android/en-US/changelogs/760.txt
deleted file mode 100644
index 66858f3ad..000000000
--- a/fastlane/metadata/android/en-US/changelogs/760.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-Changes in 0.17.1
-
-New
-• Thai localization
-
-
-Improved
-• Add start playing here action in long-press menus for playlists again #2518
-• Add switch for SAF / legacy file picker #2521
-
-Fixed
-• Fix disappearing buttons in downloads view when switching apps #2487
-• Fix playback position is stored although watch history is disabled
-• Fix reduced performance caused by playback position in list views #2517
-• [Extractor] Fix ReCaptchaActivity #2527, TeamNewPipe/NewPipeExtractor#186
-• [Extractor] [YouTube] Fix casual search error when playlists are in results TeamNewPipe/NewPipeExtractor#185
-
-
-
-Changes in 0.17.0
-
-New
-Playback resume #2288
-• Resume streams where you stopped last time
-Downloader Enhancements #2149
-• Use Storage Access Framework to store downloads on external SD-cards
-• New mp4 muxer
-• Optionally change the download directory before starting a download
-• Respect metered networks
-
-
-Improved
-• Removed gema strings #2295
-• Handle (auto)rotation changes during activity lifecycle #2444
-• Make long-press menus consistent #2368
-
-Fixed
-• Fixed selected subtitle track name not being shown #2394
-• Do not crash when check for app update fails (GitHub version) #2423
-• Fixed downloads stuck at 99.9% #2440
-• Update play queue metadata #2453
-• [SoundCloud] Fixed crash when loading playlists TeamNewPipe/NewPipeExtractor#170
-• [YouTube] Fixed duration can not be paresd TeamNewPipe/NewPipeExtractor#177
-
diff --git a/fastlane/metadata/android/en-US/changelogs/770.txt b/fastlane/metadata/android/en-US/changelogs/770.txt
deleted file mode 100644
index 439c6532b..000000000
--- a/fastlane/metadata/android/en-US/changelogs/770.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Changes in 0.17.2
-
-Fix
-• Fix no video was available
diff --git a/fastlane/metadata/android/en-US/changelogs/780.txt b/fastlane/metadata/android/en-US/changelogs/780.txt
deleted file mode 100644
index 9100d7335..000000000
--- a/fastlane/metadata/android/en-US/changelogs/780.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-Changes in 0.17.3
-
-Improved
-• Added option to clear playback states #2550
-• Show hidden directories in the file picker #2591
-• Support URLs from `invidio.us` instances to be opened with NewPipe #2488
-• Add support for `music.youtube.com` URLs TeamNewPipe/NewPipeExtractor#194
-
-Fixed
-• [YouTube] Fixed 'java.lang.IllegalArgumentException #192
-• [YouTube] Fixed live streams not working TeamNewPipe/NewPipeExtractor#195
-• Fixed performance problem in android pie when downloading a stream #2592
diff --git a/fastlane/metadata/android/en-US/changelogs/790.txt b/fastlane/metadata/android/en-US/changelogs/790.txt
deleted file mode 100644
index ec77b2acb..000000000
--- a/fastlane/metadata/android/en-US/changelogs/790.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-Improved
-• Add more titles to improve accessibility for blind people #2655
-• Make language of download folder setting more consistent and less ambiguous #2637
-
-Fixed
-• Check if last byte in the block is downloaded #2646
-• Fixed scrolling in video detail fragment #2672
-• Remove double search clear box animations to one #2695
-• [SoundCloud] Fix client_id extraction #2745
-
-Development
-• Add missing dependencies inherited from NewPipeExtractor into NewPipe #2535
-• Migrate to AndroidX #2685
-• Update to ExoPlayer 2.10.6 #2697, #2736
diff --git a/fastlane/metadata/android/en-US/changelogs/800.txt b/fastlane/metadata/android/en-US/changelogs/800.txt
deleted file mode 100644
index 0d9a29a64..000000000
--- a/fastlane/metadata/android/en-US/changelogs/800.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-New
-• PeerTube support without P2P (#2201) [Beta]:
- â—¦ Watch and download videos from PeerTube instances
- â—¦ Add instances in the settings to access the complete PeerTube world
- â—¦ There might be problems with SSL handshakes on Android 4.4 and 7.1 when accessing certain instances resulting in a network error.
-
-• Downloader (#2679):
- â—¦ Calculate download ETA
- â—¦ Download opus (webm files) as ogg
- â—¦ Recover expired download links to resume downloads after a long pause
-
-Improved
-• Make the KioskFragment aware of changes in the preferred content country and improve performance of all main tabs #2742
-• Use new Localization and Downloader implementations from extractor #2713
-• Make "Default kiosk" string translatable
-• Black navigation bar for black theme #2569
-
-Fixed
-• Fixed a bug that could not move the popup player if another finger was placed while moving the popup player #2772
-• Allow playlists missing an uploader and fix crashes related to this problem #2724, TeamNewPipe/NewPipeExtractor#219
-• Enabling TLS1.1/1.2 on Android 4.4 devices (API 19/KitKat) to fix TLS handshake with MediaCCC and some PeerTube instances #2792
-• [SoundCloud] Fixed client_id extraction TeamNewPipe/NewPipeExtractor#217
-• [SoundCloud] Fix audio stream extraction
-
-Development
-• Update ExoPlayer to 2.10.8 #2791, #2816
-• Update Gradle to 3.5.1 and add Kotlin support #2714
-
diff --git a/fastlane/metadata/android/en-US/changelogs/810.txt b/fastlane/metadata/android/en-US/changelogs/810.txt
deleted file mode 100644
index 43fcc5287..000000000
--- a/fastlane/metadata/android/en-US/changelogs/810.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-New
-• Show video thumbnail on the lock screen when playing in the background
-
-Improved
-• Add local playlist to queue when long pressing on background / popup button
-• Make main page tabs scrollable and hide when there is only a single tab
-• Limit amount of notification thumbnail updates in background player
-• Add dummy thumbnail for empty local playlists
-• Use *.opus file extension instead of *.webm and show "opus" in format label instead of "WebM Opus" in the download dropdown
-• Add button to delete downloaded files or download history in "Downloads"
-• [YouTube] Add support to /c/shortened_url channel links
-
-Fixed
-• Fixed multiple issues when sharing a video to NewPipe and downloading its streams directly
-• Fixed player access out of its creation thread
-• Fixed search result paging
-• [YouTube] Fixed switching on null causing NPE
-• [YouTube] Fixed viewing comments when opening an invidio.us url
-• [SoundCloud] Updated client_id
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/820.txt b/fastlane/metadata/android/en-US/changelogs/820.txt
deleted file mode 100644
index d56291711..000000000
--- a/fastlane/metadata/android/en-US/changelogs/820.txt
+++ /dev/null
@@ -1 +0,0 @@
-Fixed decrypt function name regex making YouTube unusable.
diff --git a/fastlane/metadata/android/en-US/changelogs/830.txt b/fastlane/metadata/android/en-US/changelogs/830.txt
deleted file mode 100644
index c9876f338..000000000
--- a/fastlane/metadata/android/en-US/changelogs/830.txt
+++ /dev/null
@@ -1 +0,0 @@
-Updated SoundCloud client_id to fix SoundCloud issues.
diff --git a/fastlane/metadata/android/en-US/changelogs/840.txt b/fastlane/metadata/android/en-US/changelogs/840.txt
deleted file mode 100644
index c79321b45..000000000
--- a/fastlane/metadata/android/en-US/changelogs/840.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-New
-• Added language selector to change the app language
-• Added send to Kodi button to player collapsible menu
-• Added ability to copy comments on long press
-
-Improved
-• Fix ReCaptcha activity and correctly save obtained cookies
-• Removed dot-menu in favour of drawer and hide history button when watch history is not enabled in settings
-• Ask for display over other apps permission in settings correctly on Android 6 and later
-• Rename local playlist by long-clicking in BookmarkFragment
-• Various PeerTube improvements
-• Improved several English source strings
-
-Fixed
-• Fixed player starting again although it is paused when option "minimize on app switch" enabled and NewPipe is minimized
-• Fix initial brightness value for gesture
-• Fixed .srt subtitle downloads containing not all line breaks
-• Fixed download to SD card failing because some Android 5 devices are not CTF compliant
-• Fixed downloading on Android KitKat
-• Fixed corrupt video .mp4 file being recognized as audio file
-• Fixed multiple localization problems, including wrong Chinese language codes
-• [YouTube] Timestamps in description are clickable again
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/850.txt b/fastlane/metadata/android/en-US/changelogs/850.txt
deleted file mode 100644
index 90f343f22..000000000
--- a/fastlane/metadata/android/en-US/changelogs/850.txt
+++ /dev/null
@@ -1 +0,0 @@
-Tn this release the YouTube website version was updated. The old website version is going to be discontinued in March and therefore you are required to upgrade NewPipe.
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/860.txt b/fastlane/metadata/android/en-US/changelogs/860.txt
deleted file mode 100644
index f01b86d47..000000000
--- a/fastlane/metadata/android/en-US/changelogs/860.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-Improved
-• Save and restore whether pitch and tempo are unhooked or not
-• Support display cutout in player
-• Round view and subscriber count
-• Optimized YouTube to use less data
-
-More than 15 YouTube-related bugs were fixed in this release.
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/870.txt b/fastlane/metadata/android/en-US/changelogs/870.txt
deleted file mode 100644
index 859a1f8b9..000000000
--- a/fastlane/metadata/android/en-US/changelogs/870.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-This is a hotfix release updating NewPipe to allow using SoundCloud without major hassles again.
-SoundCloud's v2 API is used in the extractor now and the detection of invalid client IDs has been improved.
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/954.txt b/fastlane/metadata/android/en-US/changelogs/954.txt
new file mode 100644
index 000000000..7ebbab526
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/954.txt
@@ -0,0 +1,20 @@
+New
+• Access Background Player Queue from Main menu
+• Added Bandcamp Support
+• Click on Title In Background Player to Open Video Details
+• Add Always Expand Description In Appearance Settings
+• Add playlist tab to main page
+• Feature Swipe Down To Refresh Subscription Group Feed
+• Increased Show Previous Search History Items Upto 60 Entries
+• Allow installation on external storage
+• Remember Last Selected Media Type For Downloads
+• Add formatting removal on paste for search
+• Disable sending metrics to Google in Android System Webview
+• Add basic resize functionality
+• [media.ccc.de] Add recent & live stream kiosk
+• [peertube] implement sepia search
+
+Improved
+• Improve search suggestion experience when remote ones can't be fetched
+• Fixed Download Button Not Visible After Playing Live Stream
+• Change UA to privacy.resistFingerprinting
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/955.txt b/fastlane/metadata/android/en-US/changelogs/955.txt
new file mode 100644
index 000000000..5d5c52f90
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/955.txt
@@ -0,0 +1,7 @@
+New
+• Add ability to Share Stream Link (alpha)
+
+Fixed
+• Fix extraction of YouTube likes count
+• Update github url in app about section to Foxpipe preunified fork
+• Fix comments linkify problem
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
index 4284b3a4a..2f70af360 100644
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -1 +1 @@
-NewPipe does not use any Google framework libraries, or the YouTube API. It only parses the website in order to gain the information it needs. Therefore this app can be used on devices without Google Services installed. Also, you don't need a YouTube account to use NewPipe, and it's FLOSS.
+FoxPipe does not use any Google framework libraries, or the YouTube API. It only parses the website in order to gain the information it needs. Therefore this app can be used on devices without Google Services installed. Also, you don't need a YouTube account to use FoxPipe, and it's FLOSS.
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png
index b5d413142..803fb6428 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png
deleted file mode 100644
index c1f4599c2..000000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png
deleted file mode 100644
index 12cf3002e..000000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png and /dev/null differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png
deleted file mode 100644
index fca7a0128..000000000
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png and /dev/null differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ca28934d5..a79cc04b6 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip
diff --git a/gradlew b/gradlew
index cccdd3d51..218f2f7ca 100755
--- a/gradlew
+++ b/gradlew
@@ -1,5 +1,21 @@
#!/usr/bin/env sh
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
##############################################################################
##
## Gradle start up script for UN*X
@@ -56,7 +72,7 @@ case "`uname`" in
Darwin* )
darwin=true
;;
- MINGW* )
+ MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
@@ -66,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -109,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -138,19 +156,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -159,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
-APP_ARGS=$(save "$@")
+APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index e95643d6a..f9553162f 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,84 +1,84 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
index 9d495b34f..b564e3700 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,11 @@
-include ':app'
\ No newline at end of file
+include ':app'
+
+// Use a local copy of NewPipe Extractor by uncommenting the lines below.
+// We assume, that NewPipe and NewPipe Extractor have the same parent directory.
+// If this is not the case, please change the path in includeBuild().
+
+//includeBuild('../NewPipeExtractor') {
+// dependencySubstitution {
+// substitute module('com.github.TeamNewPipe:NewPipeExtractor') with project(':extractor')
+// }
+//}