Merge branch 'dev' into pr2335

This commit is contained in:
Stypox 2021-12-31 19:20:18 +01:00
commit cd95ec4e12
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
107 changed files with 1434 additions and 832 deletions

View File

@ -39,7 +39,7 @@ jobs:
java-version: 11
distribution: "temurin"
cache: 'gradle'
- name: Build debug APK and run jvm tests
run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint
@ -54,7 +54,7 @@ jobs:
runs-on: macos-latest
strategy:
matrix:
# api-level 19 is min sdk, but throws errors related to desugaring
# api-level 19 is min sdk, but throws errors related to desugaring
api-level: [ 21, 29 ]
steps:
- uses: actions/checkout@v2
@ -72,31 +72,31 @@ jobs:
api-level: ${{ matrix.api-level }}
# workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
emulator-build: 7425822
script: ./gradlew connectedCheck
script: ./gradlew connectedCheck --stacktrace
# sonar:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# with:
# fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
sonar:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
# - name: Set up JDK 11
# uses: actions/setup-java@v2
# with:
# java-version: 11 # Sonar requires JDK 11
# distribution: "temurin"
# cache: 'gradle'
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: 11 # Sonar requires JDK 11
distribution: "temurin"
cache: 'gradle'
# - name: Cache SonarCloud packages
# uses: actions/cache@v2
# with:
# path: ~/.sonar/cache
# key: ${{ runner.os }}-sonar
# restore-keys: ${{ runner.os }}-sonar
- name: Cache SonarCloud packages
uses: actions/cache@v2
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
# - name: Build and analyze
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# run: ./gradlew build sonarqube --info
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew build sonarqube --info

View File

@ -17,7 +17,7 @@
<p align="center"><a href="https://newpipe.net">ウェブサイト</a> &bull; <a href="https://newpipe.net/blog/">ブログ</a> &bull; <a href="https://newpipe.net/FAQ/">FAQ</a> &bull; <a href="https://newpipe.net/press/">ニュース</a></p>
<hr>
*他の言語で読む: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt.br.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md)。*
*他の言語で読む: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md)。*
<b>注意: これはベータ版のため、バグが発生する可能性があります。もしバグが発生した場合、GitHub のリポジトリで Issue を開いてください。</b>

View File

@ -1,13 +1,12 @@
plugins {
id "org.sonarqube" version "3.1.1"
id "com.android.application"
id "kotlin-android"
id "kotlin-kapt"
id "kotlin-parcelize"
id "checkstyle"
id "org.sonarqube" version "3.3"
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
apply plugin: 'checkstyle'
android {
compileSdkVersion 30
buildToolsVersion '30.0.3'
@ -17,8 +16,8 @@ android {
resValue "string", "app_name", "NewPipe"
minSdkVersion 19
targetSdkVersion 29
versionCode 979
versionName "0.21.13"
versionCode 981
versionName "0.21.15"
multiDexEnabled true
@ -80,13 +79,13 @@ android {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
encoding 'utf-8'
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
jvmTarget = JavaVersion.VERSION_11
}
sourceSets {
@ -99,7 +98,7 @@ android {
}
ext {
checkstyleVersion = '8.38'
checkstyleVersion = '9.2'
androidxLifecycleVersion = '2.3.1'
androidxRoomVersion = '2.3.0'
@ -107,13 +106,13 @@ ext {
icepickVersion = '3.2.0'
exoPlayerVersion = '2.14.2'
googleAutoServiceVersion = '1.0'
googleAutoServiceVersion = '1.0.1'
groupieVersion = '2.10.0'
markwonVersion = '4.6.2'
leakCanaryVersion = '2.5'
stethoVersion = '1.6.0'
mockitoVersion = '3.6.0'
mockitoVersion = '4.0.0'
}
configurations {
@ -190,11 +189,11 @@ dependencies {
// name and the commit hash with the commit hash of the (pushed) commit you want to test
// This works thanks to JitPack: https://jitpack.io/
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:f6f2724634'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:10f6cc71'
/** Checkstyle **/
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
ktlint 'com.pinterest:ktlint:0.40.0'
ktlint 'com.pinterest:ktlint:0.43.2'
/** Kotlin **/
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}"
@ -202,7 +201,7 @@ dependencies {
/** AndroidX **/
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.fragment:fragment-ktx:1.3.6'
@ -221,7 +220,7 @@ dependencies {
// https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.webkit:webkit:1.4.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.google.android.material:material:1.4.0'
implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}"
implementation "androidx.work:work-rxjava3:${androidxWorkVersion}"
@ -231,7 +230,7 @@ dependencies {
kapt "frankiesardo:icepick-processor:${icepickVersion}"
// HTML parser
implementation "org.jsoup:jsoup:1.13.1"
implementation "org.jsoup:jsoup:1.14.3"
// HTTP client
//noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users
@ -269,13 +268,13 @@ dependencies {
implementation 'com.jakewharton:process-phoenix:2.1.2'
// Reactive extensions for Java VM
implementation "io.reactivex.rxjava3:rxjava:3.0.7"
implementation "io.reactivex.rxjava3:rxjava:3.0.13"
implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
// RxJava binding APIs for Android UI widgets
implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
// Date and time formatting
implementation "org.ocpsoft.prettytime:prettytime:5.0.1.Final"
implementation "org.ocpsoft.prettytime:prettytime:5.0.2.Final"
/** Debugging **/
// Memory leak detection
@ -291,11 +290,9 @@ dependencies {
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
testImplementation "org.mockito:mockito-inline:${mockitoVersion}"
androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.test:runner:1.4.0"
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0", {
exclude module: 'support-annotations'
}
}
static String getGitWorkingBranch() {

View File

@ -0,0 +1,153 @@
package org.schabi.newpipe.local.history
import androidx.test.core.app.ApplicationProvider
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.Timeout
import org.schabi.newpipe.database.AppDatabase
import org.schabi.newpipe.database.history.model.SearchHistoryEntry
import org.schabi.newpipe.testUtil.TestDatabase
import org.schabi.newpipe.testUtil.TrampolineSchedulerRule
import java.time.OffsetDateTime
import java.util.concurrent.TimeUnit
class HistoryRecordManagerTest {
private lateinit var manager: HistoryRecordManager
private lateinit var database: AppDatabase
@get:Rule
val trampolineScheduler = TrampolineSchedulerRule()
@get:Rule
val timeout = Timeout(1, TimeUnit.SECONDS)
@Before
fun setup() {
database = TestDatabase.createReplacingNewPipeDatabase()
manager = HistoryRecordManager(ApplicationProvider.getApplicationContext())
}
@After
fun cleanUp() {
database.close()
}
@Test
fun onSearched() {
manager.onSearched(0, "Hello").test().await().assertValue(1)
// For some reason the Flowable returned by getAll() never completes, so we can't assert
// that the number of Lists it returns is exactly 1, we can only check if the first List is
// correct. Why on earth has a Flowable been used instead of a Single for getAll()?!?
val entities = database.searchHistoryDAO().all.blockingFirst()
assertEquals(1, entities.size)
assertEquals(1, entities[0].id)
assertEquals(0, entities[0].serviceId)
assertEquals("Hello", entities[0].search)
}
@Test
fun deleteSearchHistory() {
val entries = listOf(
SearchHistoryEntry(OffsetDateTime.now(), 0, "A"),
SearchHistoryEntry(OffsetDateTime.now(), 2, "A"),
SearchHistoryEntry(OffsetDateTime.now(), 1, "B"),
SearchHistoryEntry(OffsetDateTime.now(), 0, "B"),
)
// make sure all 4 were inserted
database.searchHistoryDAO().insertAll(entries)
assertEquals(entries.size, database.searchHistoryDAO().all.blockingFirst().size)
// try to delete only "A" entries, "B" entries should be untouched
manager.deleteSearchHistory("A").test().await().assertValue(2)
val entities = database.searchHistoryDAO().all.blockingFirst()
assertEquals(2, entities.size)
assertTrue(entries[2].hasEqualValues(entities[0]))
assertTrue(entries[3].hasEqualValues(entities[1]))
// assert that nothing happens if we delete a search query that does exist in the db
manager.deleteSearchHistory("A").test().await().assertValue(0)
val entities2 = database.searchHistoryDAO().all.blockingFirst()
assertEquals(2, entities2.size)
assertTrue(entries[2].hasEqualValues(entities2[0]))
assertTrue(entries[3].hasEqualValues(entities2[1]))
// delete all remaining entries
manager.deleteSearchHistory("B").test().await().assertValue(2)
assertEquals(0, database.searchHistoryDAO().all.blockingFirst().size)
}
@Test
fun deleteCompleteSearchHistory() {
val entries = listOf(
SearchHistoryEntry(OffsetDateTime.now(), 1, "A"),
SearchHistoryEntry(OffsetDateTime.now(), 2, "B"),
SearchHistoryEntry(OffsetDateTime.now(), 0, "C"),
)
// make sure all 3 were inserted
database.searchHistoryDAO().insertAll(entries)
assertEquals(entries.size, database.searchHistoryDAO().all.blockingFirst().size)
// should remove everything
manager.deleteCompleteSearchHistory().test().await().assertValue(entries.size)
assertEquals(0, database.searchHistoryDAO().all.blockingFirst().size)
}
@Test
fun getRelatedSearches_emptyQuery() {
// make sure all entries were inserted
database.searchHistoryDAO().insertAll(RELATED_SEARCHES_ENTRIES)
assertEquals(
RELATED_SEARCHES_ENTRIES.size,
database.searchHistoryDAO().all.blockingFirst().size
)
// make sure correct number of searches is returned and in correct order
val searches = manager.getRelatedSearches("", 6, 4).blockingFirst()
assertEquals(4, searches.size)
assertEquals(RELATED_SEARCHES_ENTRIES[6].search, searches[0]) // A (even if in two places)
assertEquals(RELATED_SEARCHES_ENTRIES[4].search, searches[1]) // B
assertEquals(RELATED_SEARCHES_ENTRIES[5].search, searches[2]) // AA
assertEquals(RELATED_SEARCHES_ENTRIES[2].search, searches[3]) // BA
}
@Test
fun getRelatedSearched_nonEmptyQuery() {
// make sure all entries were inserted
database.searchHistoryDAO().insertAll(RELATED_SEARCHES_ENTRIES)
assertEquals(
RELATED_SEARCHES_ENTRIES.size,
database.searchHistoryDAO().all.blockingFirst().size
)
// make sure correct number of searches is returned and in correct order
val searches = manager.getRelatedSearches("A", 3, 5).blockingFirst()
assertEquals(3, searches.size)
assertEquals(RELATED_SEARCHES_ENTRIES[6].search, searches[0]) // A (even if in two places)
assertEquals(RELATED_SEARCHES_ENTRIES[5].search, searches[1]) // AA
assertEquals(RELATED_SEARCHES_ENTRIES[1].search, searches[2]) // BA
// also make sure that the string comparison is case insensitive
val searches2 = manager.getRelatedSearches("a", 3, 5).blockingFirst()
assertEquals(searches, searches2)
}
companion object {
val RELATED_SEARCHES_ENTRIES = listOf(
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(7), 2, "AC"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(6), 0, "ABC"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(5), 1, "BA"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(4), 3, "A"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(2), 0, "B"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(3), 2, "AA"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(1), 1, "A"),
)
}
}

View File

@ -1,7 +1,5 @@
package org.schabi.newpipe.local.playlist
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import org.junit.After
import org.junit.Before
import org.junit.Rule
@ -10,6 +8,7 @@ import org.junit.rules.Timeout
import org.schabi.newpipe.database.AppDatabase
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.testUtil.TestDatabase
import org.schabi.newpipe.testUtil.TrampolineSchedulerRule
import java.util.concurrent.TimeUnit
@ -22,17 +21,11 @@ class LocalPlaylistManagerTest {
val trampolineScheduler = TrampolineSchedulerRule()
@get:Rule
val timeout = Timeout(10, TimeUnit.SECONDS)
val timeout = Timeout(1, TimeUnit.SECONDS)
@Before
fun setup() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java
)
.allowMainThreadQueries()
.build()
database = TestDatabase.createReplacingNewPipeDatabase()
manager = LocalPlaylistManager(database)
}

View File

@ -0,0 +1,32 @@
package org.schabi.newpipe.testUtil
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import org.junit.Assert.assertSame
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.database.AppDatabase
class TestDatabase {
companion object {
fun createReplacingNewPipeDatabase(): AppDatabase {
val database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java
)
.allowMainThreadQueries()
.build()
val databaseField = NewPipeDatabase::class.java.getDeclaredField("databaseInstance")
databaseField.isAccessible = true
databaseField.set(NewPipeDatabase::class, database)
assertSame(
"Mocking database failed!",
database,
NewPipeDatabase.getInstance(ApplicationProvider.getApplicationContext())
)
return database
}
}
}

View File

@ -5,6 +5,9 @@ import android.os.Bundle;
import androidx.preference.Preference;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
@ -19,15 +22,21 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
= findPreference(getString(R.string.show_memory_leaks_key));
final Preference showImageIndicatorsPreference
= findPreference(getString(R.string.show_image_indicators_key));
final Preference crashTheAppPreference
= findPreference(getString(R.string.crash_the_app_key));
final Preference checkNewStreamsPreference
= findPreference(getString(R.string.check_new_streams_key));
final Preference crashTheAppPreference
= findPreference(getString(R.string.crash_the_app_key));
final Preference showErrorSnackbarPreference
= findPreference(getString(R.string.show_error_snackbar_key));
final Preference createErrorNotificationPreference
= findPreference(getString(R.string.create_error_notification_key));
assert showMemoryLeaksPreference != null;
assert showImageIndicatorsPreference != null;
assert crashTheAppPreference != null;
assert checkNewStreamsPreference != null;
assert crashTheAppPreference != null;
assert showErrorSnackbarPreference != null;
assert createErrorNotificationPreference != null;
showMemoryLeaksPreference.setOnPreferenceClickListener(preference -> {
startActivity(LeakCanary.INSTANCE.newLeakDisplayActivityIntent());
@ -39,13 +48,25 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
return true;
});
crashTheAppPreference.setOnPreferenceClickListener(preference -> {
throw new RuntimeException();
});
checkNewStreamsPreference.setOnPreferenceClickListener(preference -> {
NotificationWorker.runNow(preference.getContext());
return true;
});
crashTheAppPreference.setOnPreferenceClickListener(preference -> {
throw new RuntimeException();
});
showErrorSnackbarPreference.setOnPreferenceClickListener(preference -> {
ErrorUtil.showUiErrorSnackbar(DebugSettingsFragment.this,
"Dummy", new RuntimeException("Dummy"));
return true;
});
createErrorNotificationPreference.setOnPreferenceClickListener(preference -> {
ErrorUtil.createNotification(requireContext(),
new ErrorInfo(new RuntimeException("Dummy"), UserAction.UI_ERROR, "Dummy"));
return true;
});
}
}

View File

@ -338,6 +338,7 @@
<data android:host="video.ploud.fr" />
<data android:host="video.lqdn.fr" />
<data android:host="skeptikon.fr" />
<data android:host="media.fsfe.org" />
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
<data android:pathPrefix="/w/" /> <!-- short video URLs -->

View File

@ -63,6 +63,7 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
return consumed == dy;
}
@Override
public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent,
@NonNull final AppBarLayout child,
@NonNull final MotionEvent ev) {

View File

@ -16,8 +16,8 @@ import org.acra.ACRA;
import org.acra.config.ACRAConfigurationException;
import org.acra.config.CoreConfiguration;
import org.acra.config.CoreConfigurationBuilder;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.NewPipe;
@ -217,7 +217,7 @@ public class App extends MultiDexApplication {
ACRA.init(this, acraConfig);
} catch (final ACRAConfigurationException exception) {
exception.printStackTrace();
ErrorActivity.reportError(this, new ErrorInfo(exception,
ErrorUtil.openActivity(this, new ErrorInfo(exception,
UserAction.SOMETHING_ELSE, "Could not initialize ACRA crash report"));
}
}
@ -226,41 +226,44 @@ public class App extends MultiDexApplication {
// Keep the importance below DEFAULT to avoid making noise on every notification update for
// the main and update channels
final NotificationChannelCompat mainChannel = new NotificationChannelCompat
.Builder(
getString(R.string.notification_channel_id),
.Builder(getString(R.string.notification_channel_id),
NotificationManagerCompat.IMPORTANCE_LOW)
.setName(getString(R.string.notification_channel_name))
.setDescription(getString(R.string.notification_channel_description))
.build();
final NotificationChannelCompat appUpdateChannel = new NotificationChannelCompat
.Builder(
getString(R.string.app_update_notification_channel_id),
.Builder(getString(R.string.app_update_notification_channel_id),
NotificationManagerCompat.IMPORTANCE_LOW)
.setName(getString(R.string.app_update_notification_channel_name))
.setDescription(getString(R.string.app_update_notification_channel_description))
.build();
final NotificationChannelCompat hashChannel = new NotificationChannelCompat
.Builder(
getString(R.string.hash_channel_id),
.Builder(getString(R.string.hash_channel_id),
NotificationManagerCompat.IMPORTANCE_HIGH)
.setName(getString(R.string.hash_channel_name))
.setDescription(getString(R.string.hash_channel_description))
.build();
final NotificationChannelCompat errorReportChannel = new NotificationChannelCompat
.Builder(getString(R.string.error_report_channel_id),
NotificationManagerCompat.IMPORTANCE_LOW)
.setName(getString(R.string.error_report_channel_name))
.setDescription(getString(R.string.error_report_channel_description))
.build();
final NotificationChannelCompat newStreamsChannel = new NotificationChannelCompat
.Builder(
getString(R.string.streams_notification_channel_id),
NotificationManagerCompat.IMPORTANCE_DEFAULT)
.Builder(getString(R.string.streams_notification_channel_id),
NotificationManagerCompat.IMPORTANCE_DEFAULT)
.setName(getString(R.string.streams_notification_channel_name))
.setDescription(getString(R.string.streams_notification_channel_description))
.build();
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.createNotificationChannelsCompat(
Arrays.asList(mainChannel, appUpdateChannel, hashChannel, newStreamsChannel)
);
notificationManager.createNotificationChannelsCompat(Arrays.asList(mainChannel,
appUpdateChannel, hashChannel, errorReportChannel, newStreamsChannel));
}
protected boolean isDisposedRxExceptionsReported() {

View File

@ -21,8 +21,8 @@ import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.downloader.Response;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
@ -64,7 +64,7 @@ public final class CheckForNewAppVersion extends IntentService {
signatures = PackageInfoCompat.getSignatures(application.getPackageManager(),
application.getPackageName());
} catch (final PackageManager.NameNotFoundException e) {
ErrorActivity.reportError(application, new ErrorInfo(e,
ErrorUtil.createNotification(application, new ErrorInfo(e,
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info"));
return "";
}
@ -79,7 +79,7 @@ public final class CheckForNewAppVersion extends IntentService {
final CertificateFactory cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (final CertificateException e) {
ErrorActivity.reportError(application, new ErrorInfo(e,
ErrorUtil.createNotification(application, new ErrorInfo(e,
UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error"));
return "";
}
@ -89,7 +89,7 @@ public final class CheckForNewAppVersion extends IntentService {
final byte[] publicKey = md.digest(c.getEncoded());
return byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
ErrorActivity.reportError(application, new ErrorInfo(e,
ErrorUtil.createNotification(application, new ErrorInfo(e,
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not retrieve SHA1 key"));
return "";
}

View File

@ -63,7 +63,7 @@ import org.schabi.newpipe.databinding.DrawerHeaderBinding;
import org.schabi.newpipe.databinding.DrawerLayoutBinding;
import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding;
import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@ -158,7 +158,7 @@ public class MainActivity extends AppCompatActivity {
try {
setupDrawer();
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Setting up drawer", e);
ErrorUtil.showUiErrorSnackbar(this, "Setting up drawer", e);
}
if (DeviceUtils.isTv(this)) {
FocusOverlayView.setupFocusObserver(this);
@ -218,7 +218,7 @@ public class MainActivity extends AppCompatActivity {
/**
* Builds the drawer menu for the current service.
*
* @throws ExtractionException
* @throws ExtractionException if the service didn't provide available kiosks
*/
private void addDrawerMenuForCurrentService() throws ExtractionException {
//Tabs
@ -270,7 +270,7 @@ public class MainActivity extends AppCompatActivity {
try {
tabSelected(item);
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Selecting main page tab", e);
ErrorUtil.showUiErrorSnackbar(this, "Selecting main page tab", e);
}
break;
case R.id.menu_options_about_group:
@ -376,7 +376,7 @@ public class MainActivity extends AppCompatActivity {
try {
addDrawerMenuForCurrentService();
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Showing main page tabs", e);
ErrorUtil.showUiErrorSnackbar(this, "Showing main page tabs", e);
}
}
}
@ -479,7 +479,7 @@ public class MainActivity extends AppCompatActivity {
drawerHeaderBinding.drawerHeaderActionButton.setContentDescription(
getString(R.string.drawer_header_description) + selectedServiceName);
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Setting up service toggle", e);
ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e);
}
final SharedPreferences sharedPreferences
@ -789,7 +789,7 @@ public class MainActivity extends AppCompatActivity {
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
}
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Handling intent", e);
ErrorUtil.showUiErrorSnackbar(this, "Handling intent", e);
}
}

View File

@ -37,8 +37,8 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.Info;
@ -231,7 +231,7 @@ public class RouterActivity extends AppCompatActivity {
} else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) {
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
} else {
ErrorActivity.reportError(context, errorInfo);
ErrorUtil.createNotification(context, errorInfo);
}
if (context instanceof RouterActivity) {

View File

@ -19,6 +19,7 @@ import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE
@Dao
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
String ORDER_BY_MAX_CREATION_DATE = " ORDER BY MAX(" + CREATION_DATE + ") DESC";
@Query("SELECT * FROM " + TABLE_NAME
+ " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
@ -36,16 +37,16 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
@Override
Flowable<List<SearchHistoryEntry>> getAll();
@Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE
+ " LIMIT :limit")
Flowable<List<SearchHistoryEntry>> getUniqueEntries(int limit);
@Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " GROUP BY " + SEARCH
+ ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit")
Flowable<List<String>> getUniqueEntries(int limit);
@Query("SELECT * FROM " + TABLE_NAME
+ " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
@Override
Flowable<List<SearchHistoryEntry>> listByService(int serviceId);
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'"
+ " GROUP BY " + SEARCH + " LIMIT :limit")
Flowable<List<SearchHistoryEntry>> getSimilarEntries(String query, int limit);
@Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'"
+ " GROUP BY " + SEARCH + ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit")
Flowable<List<String>> getSimilarEntries(String query, int limit);
}

View File

@ -41,8 +41,8 @@ import com.nononsenseapps.filepicker.Utils;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.DownloadDialogBinding;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe;
@ -53,6 +53,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.FilePickerActivityHelper;
@ -402,7 +403,7 @@ public class DownloadDialog extends DialogFragment
== R.id.video_button) {
setupVideoSpinner();
}
}, throwable -> ErrorActivity.reportErrorInSnackbar(context,
}, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading video stream size",
currentInfo.getServiceId()))));
@ -412,7 +413,7 @@ public class DownloadDialog extends DialogFragment
== R.id.audio_button) {
setupAudioSpinner();
}
}, throwable -> ErrorActivity.reportErrorInSnackbar(context,
}, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading audio stream size",
currentInfo.getServiceId()))));
@ -422,7 +423,7 @@ public class DownloadDialog extends DialogFragment
== R.id.subtitle_button) {
setupSubtitleSpinner();
}
}, throwable -> ErrorActivity.reportErrorInSnackbar(context,
}, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading subtitle stream size",
currentInfo.getServiceId()))));
@ -687,7 +688,12 @@ public class DownloadDialog extends DialogFragment
}
private void launchDirectoryPicker(final ActivityResultLauncher<Intent> launcher) {
launcher.launch(StoredDirectoryHelper.getPicker(context));
NoFileManagerSafeGuard.launchSafe(
launcher,
StoredDirectoryHelper.getPicker(context),
TAG,
context
);
}
private void prepareSelectedDownload() {
@ -766,8 +772,12 @@ public class DownloadDialog extends DialogFragment
initialPath = Uri.parse(initialSavePath.getAbsolutePath());
}
requestDownloadSaveAsLauncher.launch(StoredFileHelper.getNewPicker(context,
filenameTmp, mimeTmp, initialPath));
NoFileManagerSafeGuard.launchSafe(
requestDownloadSaveAsLauncher,
StoredFileHelper.getNewPicker(context, filenameTmp, mimeTmp, initialPath),
TAG,
context
);
return;
}
@ -799,7 +809,7 @@ public class DownloadDialog extends DialogFragment
mainStorage.getTag());
}
} catch (final Exception e) {
ErrorActivity.reportErrorInSnackbar(this,
ErrorUtil.createNotification(requireContext(),
new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage"));
return;
}

View File

@ -33,12 +33,11 @@ public class AcraReportSender implements ReportSender {
@Override
public void send(@NonNull final Context context, @NonNull final CrashReportData report) {
ErrorActivity.reportError(context, new ErrorInfo(
ErrorUtil.openActivity(context, new ErrorInfo(
new String[]{report.getString(ReportField.STACK_TRACE)},
UserAction.UI_ERROR,
ErrorInfo.SERVICE_NONE,
"ACRA report",
R.string.app_ui_crash,
null));
R.string.app_ui_crash));
}
}

View File

@ -1,103 +0,0 @@
package org.schabi.newpipe.error;
import android.util.Log;
import androidx.annotation.NonNull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Ensures that a Exception is serializable.
* This is
*/
public final class EnsureExceptionSerializable {
private static final String TAG = "EnsureExSerializable";
private EnsureExceptionSerializable() {
// No instance
}
/**
* Ensures that an exception is serializable.
* <br/>
* If that is not the case a {@link WorkaroundNotSerializableException} is created.
*
* @param exception
* @return if an exception is not serializable a new {@link WorkaroundNotSerializableException}
* otherwise the exception from the parameter
*/
public static Exception ensureSerializable(@NonNull final Exception exception) {
return checkIfSerializable(exception)
? exception
: WorkaroundNotSerializableException.create(exception);
}
public static boolean checkIfSerializable(@NonNull final Exception exception) {
try {
// Check by creating a new ObjectOutputStream which does the serialization
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)
) {
oos.writeObject(exception);
oos.flush();
bos.toByteArray();
}
return true;
} catch (final IOException ex) {
Log.d(TAG, "Exception is not serializable", ex);
return false;
}
}
public static class WorkaroundNotSerializableException extends Exception {
protected WorkaroundNotSerializableException(
final Throwable notSerializableException,
final Throwable cause) {
super(notSerializableException.toString(), cause);
setStackTrace(notSerializableException.getStackTrace());
}
protected WorkaroundNotSerializableException(final Throwable notSerializableException) {
super(notSerializableException.toString());
setStackTrace(notSerializableException.getStackTrace());
}
public static WorkaroundNotSerializableException create(
@NonNull final Exception notSerializableException
) {
// Build a list of the exception + all causes
final List<Throwable> throwableList = new ArrayList<>();
int pos = 0;
Throwable throwableToProcess = notSerializableException;
while (throwableToProcess != null) {
throwableList.add(throwableToProcess);
pos++;
throwableToProcess = throwableToProcess.getCause();
}
// Reverse list so that it starts with the last one
Collections.reverse(throwableList);
// Build exception stack
WorkaroundNotSerializableException cause = null;
for (final Throwable t : throwableList) {
cause = cause == null
? new WorkaroundNotSerializableException(t)
: new WorkaroundNotSerializableException(t, cause);
}
return cause;
}
}
}

View File

@ -1,9 +1,10 @@
package org.schabi.newpipe.error;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -11,15 +12,12 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import com.google.android.material.snackbar.Snackbar;
import com.grack.nanojson.JsonWriter;
import org.schabi.newpipe.BuildConfig;
@ -27,15 +25,13 @@ import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.ActivityErrorBinding;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
/*
* Created by Christian Schabesberger on 24.10.15.
*
@ -56,6 +52,10 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* This activity is used to show error details and allow reporting them in various ways. Use {@link
* ErrorUtil#openActivity(Context, ErrorInfo)} to correctly open this activity.
*/
public class ErrorActivity extends AppCompatActivity {
// LOG TAGS
public static final String TAG = ErrorActivity.class.toString();
@ -77,67 +77,6 @@ public class ErrorActivity extends AppCompatActivity {
private ActivityErrorBinding activityErrorBinding;
/**
* Reports a new error by starting a new activity.
* <br/>
* Ensure that the data within errorInfo is serializable otherwise
* an exception will be thrown!<br/>
* {@link EnsureExceptionSerializable} might help.
*
* @param context
* @param errorInfo
*/
public static void reportError(final Context context, final ErrorInfo errorInfo) {
final Intent intent = new Intent(context, ErrorActivity.class);
intent.putExtra(ERROR_INFO, errorInfo);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
public static void reportErrorInSnackbar(final Context context, final ErrorInfo errorInfo) {
final View rootView = context instanceof Activity
? ((Activity) context).findViewById(android.R.id.content) : null;
reportErrorInSnackbar(context, rootView, errorInfo);
}
public static void reportErrorInSnackbar(final Fragment fragment, final ErrorInfo errorInfo) {
View rootView = fragment.getView();
if (rootView == null && fragment.getActivity() != null) {
rootView = fragment.getActivity().findViewById(android.R.id.content);
}
reportErrorInSnackbar(fragment.requireContext(), rootView, errorInfo);
}
public static void reportUiErrorInSnackbar(final Context context,
final String request,
final Throwable throwable) {
reportErrorInSnackbar(context, new ErrorInfo(throwable, UserAction.UI_ERROR, request));
}
public static void reportUiErrorInSnackbar(final Fragment fragment,
final String request,
final Throwable throwable) {
reportErrorInSnackbar(fragment, new ErrorInfo(throwable, UserAction.UI_ERROR, request));
}
////////////////////////////////////////////////////////////////////////
// Utils
////////////////////////////////////////////////////////////////////////
private static void reportErrorInSnackbar(final Context context,
@Nullable final View rootView,
final ErrorInfo errorInfo) {
if (rootView != null) {
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
.setActionTextColor(Color.YELLOW)
.setAction(context.getString(R.string.error_snackbar_action).toUpperCase(), v ->
reportError(context, errorInfo)).show();
} else {
reportError(context, errorInfo);
}
}
////////////////////////////////////////////////////////////////////////
// Activity lifecycle

View File

@ -2,6 +2,8 @@ package org.schabi.newpipe.error
import android.os.Parcelable
import androidx.annotation.StringRes
import com.google.android.exoplayer2.ExoPlaybackException
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.Info
@ -21,11 +23,14 @@ class ErrorInfo(
val userAction: UserAction,
val serviceName: String,
val request: String,
val messageStringId: Int,
@Transient // no need to store throwable, all data for report is in other variables
var throwable: Throwable? = null
val messageStringId: Int
) : Parcelable {
// no need to store throwable, all data for report is in other variables
// also, the throwable might not be serializable, see TeamNewPipe/NewPipe#7302
@IgnoredOnParcel
var throwable: Throwable? = null
private constructor(
throwable: Throwable,
userAction: UserAction,
@ -36,9 +41,10 @@ class ErrorInfo(
userAction,
serviceName,
request,
getMessageStringId(throwable, userAction),
throwable
)
getMessageStringId(throwable, userAction)
) {
this.throwable = throwable
}
private constructor(
throwable: List<Throwable>,
@ -50,9 +56,10 @@ class ErrorInfo(
userAction,
serviceName,
request,
getMessageStringId(throwable.firstOrNull(), userAction),
throwable.firstOrNull()
)
getMessageStringId(throwable.firstOrNull(), userAction)
) {
this.throwable = throwable.firstOrNull()
}
// constructors with single throwable
constructor(throwable: Throwable, userAction: UserAction, request: String) :
@ -102,6 +109,13 @@ class ErrorInfo(
throwable is ContentNotSupportedException -> R.string.content_not_supported
throwable is DeobfuscateException -> R.string.youtube_signature_deobfuscation_error
throwable is ExtractionException -> R.string.parsing_error
throwable is ExoPlaybackException -> {
when (throwable.type) {
ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure
ExoPlaybackException.TYPE_UNEXPECTED -> R.string.player_recoverable_failure
else -> R.string.player_unrecoverable_failure
}
}
action == UserAction.UI_ERROR -> R.string.app_ui_crash
action == UserAction.REQUESTED_COMMENTS -> R.string.error_unable_to_load_comments
action == UserAction.SUBSCRIPTION_CHANGE -> R.string.subscription_change_failed

View File

@ -118,7 +118,7 @@ class ErrorPanelHelper(
showAndSetErrorButtonAction(
R.string.error_snackbar_action
) {
ErrorActivity.reportError(context, errorInfo)
ErrorUtil.openActivity(context, errorInfo)
}
errorTextView.setText(getExceptionDescription(errorInfo.throwable))
@ -178,7 +178,7 @@ class ErrorPanelHelper(
val DEBUG: Boolean = MainActivity.DEBUG
@StringRes
public fun getExceptionDescription(throwable: Throwable?): Int {
fun getExceptionDescription(throwable: Throwable?): Int {
return when (throwable) {
is AgeRestrictedContentException -> R.string.restricted_video_no_stream
is GeographicRestrictionException -> R.string.georestricted_content

View File

@ -0,0 +1,165 @@
package org.schabi.newpipe.error
import android.app.Activity
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.view.View
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
import org.schabi.newpipe.R
/**
* This class contains all of the methods that should be used to let the user know that an error has
* occurred in the least intrusive way possible for each case. This class is for unexpected errors,
* for handled errors (e.g. network errors) use e.g. [ErrorPanelHelper] instead.
* - Use a snackbar if the exception is not critical and it happens in a place where a root view
* is available.
* - Use a notification if the exception happens inside a background service (player, subscription
* import, ...) or there is no activity/fragment from which to extract a root view.
* - Finally use the error activity only as a last resort in case the exception is critical and
* happens in an open activity (since the workflow would be interrupted anyway in that case).
*/
class ErrorUtil {
companion object {
private const val ERROR_REPORT_NOTIFICATION_ID = 5340681
/**
* Starts a new error activity allowing the user to report the provided error. Only use this
* method directly as a last resort in case the exception is critical and happens in an open
* activity (since the workflow would be interrupted anyway in that case). So never use this
* for background services.
*
* @param context the context to use to start the new activity
* @param errorInfo the error info to be reported
*/
@JvmStatic
fun openActivity(context: Context, errorInfo: ErrorInfo) {
context.startActivity(getErrorActivityIntent(context, errorInfo))
}
/**
* Show a bottom snackbar to the user, with a report button that opens the error activity.
* Use this method if the exception is not critical and it happens in a place where a root
* view is available.
*
* @param context will be used to obtain the root view if it is an [Activity]; if no root
* view can be found an error notification is shown instead
* @param errorInfo the error info to be reported
*/
@JvmStatic
fun showSnackbar(context: Context, errorInfo: ErrorInfo) {
val rootView = if (context is Activity) context.findViewById<View>(R.id.content) else null
showSnackbar(context, rootView, errorInfo)
}
/**
* Show a bottom snackbar to the user, with a report button that opens the error activity.
* Use this method if the exception is not critical and it happens in a place where a root
* view is available.
*
* @param fragment will be used to obtain the root view if it has a connected [Activity]; if
* no root view can be found an error notification is shown instead
* @param errorInfo the error info to be reported
*/
@JvmStatic
fun showSnackbar(fragment: Fragment, errorInfo: ErrorInfo) {
var rootView = fragment.view
if (rootView == null && fragment.activity != null) {
rootView = fragment.requireActivity().findViewById(R.id.content)
}
showSnackbar(fragment.requireContext(), rootView, errorInfo)
}
/**
* Shortcut to calling [showSnackbar] with an [ErrorInfo] of type [UserAction.UI_ERROR]
*/
@JvmStatic
fun showUiErrorSnackbar(context: Context, request: String, throwable: Throwable) {
showSnackbar(context, ErrorInfo(throwable, UserAction.UI_ERROR, request))
}
/**
* Shortcut to calling [showSnackbar] with an [ErrorInfo] of type [UserAction.UI_ERROR]
*/
@JvmStatic
fun showUiErrorSnackbar(fragment: Fragment, request: String, throwable: Throwable) {
showSnackbar(fragment, ErrorInfo(throwable, UserAction.UI_ERROR, request))
}
/**
* Create an error notification. Tapping on the notification opens the error activity. Use
* this method if the exception happens inside a background service (player, subscription
* import, ...) or there is no activity/fragment from which to extract a root view.
*
* @param context the context to use to show the notification
* @param errorInfo the error info to be reported; the error message
* [ErrorInfo.messageStringId] will be shown in the notification
* description
*/
@JvmStatic
fun createNotification(context: Context, errorInfo: ErrorInfo) {
val notificationManager =
ContextCompat.getSystemService(context, NotificationManager::class.java)
if (notificationManager == null) {
// this should never happen, but just in case open error activity
openActivity(context, errorInfo)
}
var pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
pendingIntentFlags = pendingIntentFlags or PendingIntent.FLAG_IMMUTABLE
}
val notificationBuilder: NotificationCompat.Builder =
NotificationCompat.Builder(
context,
context.getString(R.string.error_report_channel_id)
)
.setSmallIcon(R.drawable.ic_bug_report)
.setContentTitle(context.getString(R.string.error_report_notification_title))
.setContentText(context.getString(errorInfo.messageStringId))
.setAutoCancel(true)
.setContentIntent(
PendingIntent.getActivity(
context,
0,
getErrorActivityIntent(context, errorInfo),
pendingIntentFlags
)
)
notificationManager!!.notify(ERROR_REPORT_NOTIFICATION_ID, notificationBuilder.build())
// since the notification is silent, also show a toast, otherwise the user is confused
Toast.makeText(context, R.string.error_report_notification_toast, Toast.LENGTH_SHORT)
.show()
}
private fun getErrorActivityIntent(context: Context, errorInfo: ErrorInfo): Intent {
val intent = Intent(context, ErrorActivity::class.java)
intent.putExtra(ErrorActivity.ERROR_INFO, errorInfo)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
return intent
}
private fun showSnackbar(context: Context, rootView: View?, errorInfo: ErrorInfo) {
if (rootView == null) {
// fallback to showing a notification if no root view is available
createNotification(context, errorInfo)
} else {
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
.setActionTextColor(Color.YELLOW)
.setAction(context.getString(R.string.error_snackbar_action).uppercase()) {
openActivity(context, errorInfo)
}.show()
}
}
}
}

View File

@ -7,12 +7,13 @@ import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorPanelHelper;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.util.InfoCache;
import java.util.concurrent.atomic.AtomicBoolean;
@ -198,9 +199,8 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
}
/**
* Show a SnackBar and only call
* {@link ErrorActivity#reportErrorInSnackbar(androidx.fragment.app.Fragment, ErrorInfo)}
* IF we a find a valid view (otherwise the error screen appears).
* Directly calls {@link ErrorUtil#showSnackbar(Fragment, ErrorInfo)}, that shows a snackbar if
* a valid view can be found, otherwise creates an error report notification.
*
* @param errorInfo The error information
*/
@ -208,6 +208,6 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
if (DEBUG) {
Log.d(TAG, "showSnackBarError() called with: errorInfo = [" + errorInfo + "]");
}
ErrorActivity.reportErrorInSnackbar(this, errorInfo);
ErrorUtil.showSnackbar(this, errorInfo);
}
}

View File

@ -23,7 +23,7 @@ import com.google.android.material.tabs.TabLayout;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentMainBinding;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager;
@ -145,7 +145,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
NavigationHelper.openSearchFragment(getFM(),
ServiceHelper.getSelectedServiceId(activity), "");
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Opening search fragment", e);
ErrorUtil.showUiErrorSnackbar(this, "Opening search fragment", e);
}
return true;
}
@ -227,16 +227,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public Fragment getItem(final int position) {
final Tab tab = internalTabsList.get(position);
Throwable throwable = null;
Fragment fragment = null;
final Fragment fragment;
try {
fragment = tab.getFragment(context);
} catch (final ExtractionException e) {
throwable = e;
}
if (throwable != null) {
ErrorActivity.reportUiErrorInSnackbar(context, "Getting fragment item", throwable);
ErrorUtil.showUiErrorSnackbar(context, "Getting fragment item", e);
return new BlankFragment();
}

View File

@ -55,8 +55,8 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
@ -533,7 +533,7 @@ public final class VideoDetailFragment
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
subChannelUrl, subChannelName);
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
}
}
@ -685,7 +685,7 @@ public final class VideoDetailFragment
});
setupBottomPlayer();
if (!playerHolder.bound) {
if (!playerHolder.isBound()) {
setHeightThumbnail();
} else {
playerHolder.startService(false, this);
@ -1434,7 +1434,7 @@ public final class VideoDetailFragment
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
// Rebound to the service if it was closed via notification or mini player
if (!playerHolder.bound) {
if (!playerHolder.isBound()) {
playerHolder.startService(
false, VideoDetailFragment.this);
}
@ -1521,6 +1521,8 @@ public final class VideoDetailFragment
animate(binding.detailThumbnailPlayButton, true, 200);
binding.detailVideoTitleView.setText(title);
binding.detailSubChannelThumbnailView.setVisibility(View.GONE);
if (!isEmpty(info.getSubChannelName())) {
displayBothUploaderAndSubChannel(info);
} else if (!isEmpty(info.getUploaderName())) {
@ -1681,9 +1683,8 @@ public final class VideoDetailFragment
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (final Exception e) {
ErrorActivity.reportErrorInSnackbar(activity,
new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog",
currentInfo));
ErrorUtil.showSnackbar(activity, new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG,
"Showing download dialog", currentInfo));
}
}
@ -1981,7 +1982,9 @@ public final class VideoDetailFragment
// Prevent jumping of the player on devices with cutout
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
isMultiWindowOrFullscreen()
? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
: WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
}
activity.getWindow().getDecorView().setSystemUiVisibility(0);
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
@ -2003,7 +2006,9 @@ public final class VideoDetailFragment
// Prevent jumping of the player on devices with cutout
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
isMultiWindowOrFullscreen()
? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
: WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@ -2020,7 +2025,7 @@ public final class VideoDetailFragment
activity.getWindow().getDecorView().setSystemUiVisibility(visibility);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen()))) {
&& isMultiWindowOrFullscreen()) {
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
activity.getWindow().setNavigationBarColor(Color.TRANSPARENT);
}
@ -2036,6 +2041,11 @@ public final class VideoDetailFragment
}
}
private boolean isMultiWindowOrFullscreen() {
return DeviceUtils.isInMultiWindow(activity)
|| (isPlayerAvailable() && player.isFullscreen());
}
private boolean playerIsNotStopped() {
return isPlayerAvailable() && !player.isStopped();
}

View File

@ -21,7 +21,7 @@ import androidx.viewbinding.ViewBinding;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.PignateFooterBinding;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
@ -293,7 +293,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
selectedItem.getUrl(),
selectedItem.getName());
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(
ErrorUtil.showUiErrorSnackbar(
BaseListFragment.this, "Opening channel fragment", e);
}
}
@ -309,7 +309,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
selectedItem.getUrl(),
selectedItem.getName());
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(BaseListFragment.this,
ErrorUtil.showUiErrorSnackbar(BaseListFragment.this,
"Opening playlist fragment", e);
}
}

View File

@ -33,8 +33,8 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.databinding.ChannelHeaderBinding;
import org.schabi.newpipe.databinding.FragmentChannelBinding;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
@ -466,7 +466,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
currentInfo.getParentChannelUrl(),
currentInfo.getParentChannelName());
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
}
} else if (DEBUG) {
Log.i(TAG, "Can't open parent channel because we got no channel URL");

View File

@ -24,8 +24,8 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.databinding.PlaylistHeaderBinding;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
@ -310,7 +310,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
result.getUploaderUrl(), result.getUploaderName());
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
}
});
}

View File

@ -1,5 +1,10 @@
package org.schabi.newpipe.fragments.list.search;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
import static java.util.Arrays.asList;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@ -36,10 +41,9 @@ import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import org.schabi.newpipe.databinding.FragmentSearchBinding;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
@ -68,12 +72,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
@ -84,11 +87,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage<?>>
implements BackPressable {
/*//////////////////////////////////////////////////////////////////////////
@ -225,8 +223,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
try {
service = NewPipe.getService(serviceId);
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this,
"Getting service for id " + serviceId, e);
ErrorUtil.showUiErrorSnackbar(this, "Getting service for id " + serviceId, e);
}
}
@ -727,7 +724,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public boolean onBackPressed() {
if (suggestionsPanelVisible
&& infoListAdapter.getItemsList().size() > 0
&& !infoListAdapter.getItemsList().isEmpty()
&& !isLoading.get()) {
hideSuggestionsPanel();
hideKeyboardSearch();
@ -743,13 +740,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
return historyRecordManager
.getRelatedSearches(query, similarQueryLimit, 25)
.toObservable()
.map(searchHistoryEntries -> {
final Set<SuggestionItem> result = new HashSet<>(); // remove duplicates
for (final SearchHistoryEntry entry : searchHistoryEntries) {
result.add(new SuggestionItem(true, entry.getSearch()));
}
return new ArrayList<>(result);
});
.map(searchHistoryEntries ->
searchHistoryEntries.stream()
.map(entry -> new SuggestionItem(true, entry))
.collect(Collectors.toList()));
}
private Observable<List<SuggestionItem>> getRemoteSuggestionsObservable(final String query) {

View File

@ -34,12 +34,14 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
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
@ -55,5 +57,7 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
itemTitleView.setText(item.getUploaderName());
itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
}
}

View File

@ -13,7 +13,7 @@ import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
@ -171,7 +171,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
item.getUploaderUrl(),
item.getUploaderName());
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(activity, "Opening channel fragment", e);
ErrorUtil.showUiErrorSnackbar(activity, "Opening channel fragment", e);
}
}

View File

@ -84,27 +84,22 @@ class FeedLoadManager(private val context: Context) {
return outdatedSubscriptions
.take(1)
.doOnNext {
currentProgress.set(0)
maxProgress.set(it.size)
}
.filter { it.isNotEmpty() }
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
notificationUpdater.onNext("")
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 ->
var error: Throwable? = null
try {
@ -154,14 +149,11 @@ class FeedLoadManager(private val context: Context) {
}
}
.sequential()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(NotificationConsumer())
.observeOn(Schedulers.io())
.buffer(BUFFER_COUNT_BEFORE_INSERT)
.doOnNext(DatabaseConsumer())
.subscribeOn(Schedulers.io())
.toList()
.flatMap { x -> postProcessFeed().toSingleDefault(x.flatten()) }

View File

@ -154,7 +154,9 @@ class FeedLoadService : Service() {
private fun createNotification(): NotificationCompat.Builder {
val cancelActionIntent = PendingIntent.getBroadcast(
this,
NOTIFICATION_ID, Intent(ACTION_CANCEL), 0
NOTIFICATION_ID,
Intent(ACTION_CANCEL),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
)
return NotificationCompat.Builder(this, getString(R.string.notification_channel_id))

View File

@ -244,9 +244,9 @@ public class HistoryRecordManager {
.subscribeOn(Schedulers.io());
}
public Flowable<List<SearchHistoryEntry>> getRelatedSearches(final String query,
final int similarQueryLimit,
final int uniqueQueryLimit) {
public Flowable<List<String>> getRelatedSearches(final String query,
final int similarQueryLimit,
final int uniqueQueryLimit) {
return query.length() > 0
? searchHistoryTable.getSimilarEntries(query, similarQueryLimit)
: searchHistoryTable.getUniqueEntries(uniqueQueryLimit);

View File

@ -55,6 +55,7 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard
import org.schabi.newpipe.streams.io.StoredFileHelper
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture
@ -179,15 +180,23 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
}
private fun onImportPreviousSelected() {
requestImportLauncher.launch(StoredFileHelper.getPicker(activity, JSON_MIME_TYPE))
NoFileManagerSafeGuard.launchSafe(
requestImportLauncher,
StoredFileHelper.getPicker(activity, JSON_MIME_TYPE),
TAG,
requireContext()
)
}
private fun onExportSelected() {
val date = SimpleDateFormat("yyyyMMddHHmm", Locale.ENGLISH).format(Date())
val exportName = "newpipe_subscriptions_$date.json"
requestExportLauncher.launch(
StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null)
NoFileManagerSafeGuard.launchSafe(
requestExportLauncher,
StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null),
TAG,
requireContext()
)
}

View File

@ -23,13 +23,14 @@ import androidx.core.text.util.LinkifyCompat;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ServiceHelper;
@ -86,12 +87,11 @@ public class SubscriptionsImportFragment extends BaseFragment {
setupServiceVariables();
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
ErrorActivity.reportErrorInSnackbar(activity,
ErrorUtil.showSnackbar(activity,
new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT,
NewPipe.getNameOfService(currentServiceId),
"Service does not support importing subscriptions",
R.string.general_error,
null));
R.string.general_error));
activity.finish();
}
}
@ -175,8 +175,14 @@ public class SubscriptionsImportFragment extends BaseFragment {
}
public void onImportFile() {
// leave */* mime type to support all services with different mime types and file extensions
requestImportFileLauncher.launch(StoredFileHelper.getPicker(activity, "*/*"));
NoFileManagerSafeGuard.launchSafe(
requestImportFileLauncher,
// leave */* mime type to support all services
// with different mime types and file extensions
StoredFileHelper.getPicker(activity, "*/*"),
TAG,
getContext()
);
}
private void requestImportFileResult(final ActivityResult result) {

View File

@ -54,11 +54,9 @@ class ChannelItem(
context.getString(R.string.subscribers_count_not_available)
}
if (itemVersion == ItemVersion.NORMAL) {
if (infoItem.streamCount >= 0) {
val formattedVideoAmount = Localization.localizeStreamCount(context, infoItem.streamCount)
details = Localization.concatenateStrings(details, formattedVideoAmount)
}
if (itemVersion == ItemVersion.NORMAL && infoItem.streamCount >= 0) {
val formattedVideoAmount = Localization.localizeStreamCount(context, infoItem.streamCount)
details = Localization.concatenateStrings(details, formattedVideoAmount)
}
return details
}

View File

@ -35,8 +35,8 @@ import androidx.core.app.ServiceCompat;
import org.reactivestreams.Publisher;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.ktx.ExceptionUtils;
@ -153,7 +153,7 @@ public abstract class BaseImportExportService extends Service {
protected void stopAndReportError(final Throwable throwable, final String request) {
stopService();
ErrorActivity.reportError(this, new ErrorInfo(
ErrorUtil.createNotification(this, new ErrorInfo(
throwable, UserAction.SUBSCRIPTION_IMPORT_EXPORT, request));
}

View File

@ -141,6 +141,9 @@ import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamSegment;
@ -165,7 +168,6 @@ import org.schabi.newpipe.player.playback.MediaSourceManager;
import org.schabi.newpipe.player.playback.PlaybackListener;
import org.schabi.newpipe.player.playback.PlayerMediaSession;
import org.schabi.newpipe.player.playback.SurfaceHolderCallback;
import org.schabi.newpipe.player.playererror.PlayerErrorHandler;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
@ -268,8 +270,6 @@ public final class Player implements
@Nullable private MediaSourceTag currentMetadata;
@Nullable private Bitmap currentThumbnail;
@NonNull private PlayerErrorHandler playerErrorHandler;
/*//////////////////////////////////////////////////////////////////////////
// Player
//////////////////////////////////////////////////////////////////////////*/
@ -413,8 +413,6 @@ public final class Player implements
videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
audioResolver = new AudioPlaybackResolver(context, dataSource);
playerErrorHandler = new PlayerErrorHandler(context);
windowManager = ContextCompat.getSystemService(context, WindowManager.class);
}
@ -2350,7 +2348,8 @@ public final class Player implements
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
}
private void setRepeatModeButton(final AppCompatImageButton imageButton, final int repeatMode) {
private void setRepeatModeButton(final AppCompatImageButton imageButton,
@RepeatMode final int repeatMode) {
switch (repeatMode) {
case REPEAT_MODE_OFF:
imageButton.setImageResource(R.drawable.exo_controls_repeat_off);
@ -2364,7 +2363,7 @@ public final class Player implements
}
}
private void setShuffleButton(final ImageButton button, final boolean shuffled) {
private void setShuffleButton(@NonNull final ImageButton button, final boolean shuffled) {
button.setImageAlpha(shuffled ? 255 : 77);
}
//endregion
@ -2389,7 +2388,7 @@ public final class Player implements
return !exoPlayerIsNull() && simpleExoPlayer.getVolume() == 0;
}
private void setMuteButton(final ImageButton button, final boolean isMuted) {
private void setMuteButton(@NonNull final ImageButton button, final boolean isMuted) {
button.setImageDrawable(AppCompatResources.getDrawable(context, isMuted
? R.drawable.ic_volume_off : R.drawable.ic_volume_up));
}
@ -2518,29 +2517,30 @@ public final class Player implements
saveStreamProgressState();
// create error notification
final ErrorInfo errorInfo;
if (currentMetadata == null) {
errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
"Player error[type=" + error.type + "] occurred, currentMetadata is null");
} else {
errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
"Player error[type=" + error.type + "] occurred while playing "
+ currentMetadata.getMetadata().getUrl(),
currentMetadata.getMetadata());
}
ErrorUtil.createNotification(context, errorInfo);
switch (error.type) {
case ExoPlaybackException.TYPE_SOURCE:
processSourceError(error.getSourceException());
playerErrorHandler.showPlayerError(
error,
currentMetadata.getMetadata(),
R.string.player_stream_failure);
break;
case ExoPlaybackException.TYPE_UNEXPECTED:
playerErrorHandler.showPlayerError(
error,
currentMetadata.getMetadata(),
R.string.player_recoverable_failure);
setRecovery();
reloadPlayQueueManager();
break;
case ExoPlaybackException.TYPE_REMOTE:
case ExoPlaybackException.TYPE_RENDERER:
default:
playerErrorHandler.showPlayerError(
error,
currentMetadata.getMetadata(),
R.string.player_unrecoverable_failure);
onPlaybackShutdown();
break;
}
@ -2877,7 +2877,7 @@ public final class Player implements
databaseUpdateDisposable
.add(recordManager.saveStreamState(currentMetadata.getMetadata(), progressMillis)
.observeOn(AndroidSchedulers.mainThread())
.doOnError((e) -> {
.doOnError(e -> {
if (DEBUG) {
e.printStackTrace();
}
@ -3387,7 +3387,7 @@ public final class Player implements
playbackSpeedPopupMenu.setOnDismissListener(this);
}
private void buildCaptionMenu(final List<String> availableLanguages) {
private void buildCaptionMenu(@NonNull final List<String> availableLanguages) {
if (captionPopupMenu == null) {
return;
}
@ -3455,7 +3455,7 @@ public final class Player implements
* Called when an item of the quality selector or the playback speed selector is selected.
*/
@Override
public boolean onMenuItemClick(final MenuItem menuItem) {
public boolean onMenuItemClick(@NonNull final MenuItem menuItem) {
if (DEBUG) {
Log.d(TAG, "onMenuItemClick() called with: "
+ "menuItem = [" + menuItem + "], "
@ -3492,7 +3492,7 @@ public final class Player implements
* Called when some popup menu is dismissed.
*/
@Override
public void onDismiss(final PopupMenu menu) {
public void onDismiss(@Nullable final PopupMenu menu) {
if (DEBUG) {
Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]");
}
@ -3545,7 +3545,7 @@ public final class Player implements
isSomePopupMenuVisible = true;
}
private void setPlaybackQuality(final String quality) {
private void setPlaybackQuality(@Nullable final String quality) {
videoResolver.setPlaybackQuality(quality);
}
//endregion
@ -3569,7 +3569,7 @@ public final class Player implements
final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels);
final float captionRatioInverse = 20f + 4f * (1.0f - captionScale);
binding.subtitleView.setFixedTextSize(
TypedValue.COMPLEX_UNIT_PX, (float) minimumLength / captionRatioInverse);
TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse);
}
binding.subtitleView.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT);
binding.subtitleView.setStyle(captionStyle);
@ -3846,7 +3846,7 @@ public final class Player implements
}
@Override // exoplayer listener
public void onVideoSizeChanged(final VideoSize videoSize) {
public void onVideoSizeChanged(@NonNull final VideoSize videoSize) {
if (DEBUG) {
Log.d(TAG, "onVideoSizeChanged() called with: "
+ "width / height = [" + videoSize.width + " / " + videoSize.height
@ -3960,7 +3960,7 @@ public final class Player implements
}
}
private int distanceFromCloseButton(final MotionEvent popupMotionEvent) {
private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) {
final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft()
+ closeOverlayBinding.closeButton.getWidth() / 2;
final int closeOverlayButtonY = closeOverlayBinding.closeButton.getTop()
@ -3979,7 +3979,7 @@ public final class Player implements
return buttonRadius * 1.2f;
}
public boolean isInsideClosingRadius(final MotionEvent popupMotionEvent) {
public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent) {
return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius();
}
//endregion
@ -4099,6 +4099,7 @@ public final class Player implements
}
}
@Nullable
public AppCompatActivity getParentActivity() {
// ! instanceof ViewGroup means that view was added via windowManager for Popup
if (binding == null || !(binding.getRoot().getParent() instanceof ViewGroup)) {

View File

@ -1,5 +1,12 @@
package org.schabi.newpipe.player.event;
import static org.schabi.newpipe.ktx.AnimationType.ALPHA;
import static org.schabi.newpipe.ktx.AnimationType.SCALE_AND_ALPHA;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_DURATION;
import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_HIDE_TIME;
import static org.schabi.newpipe.player.Player.STATE_PLAYING;
import android.app.Activity;
import android.util.Log;
import android.view.MotionEvent;
@ -8,22 +15,15 @@ import android.view.Window;
import android.view.WindowManager;
import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import org.jetbrains.annotations.NotNull;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.helper.PlayerHelper;
import static org.schabi.newpipe.ktx.AnimationType.ALPHA;
import static org.schabi.newpipe.ktx.AnimationType.SCALE_AND_ALPHA;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_DURATION;
import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_HIDE_TIME;
import static org.schabi.newpipe.player.Player.STATE_PLAYING;
/**
* GestureListener for the player
*
@ -45,8 +45,8 @@ public class PlayerGestureListener
}
@Override
public void onDoubleTap(@NotNull final MotionEvent event,
@NotNull final DisplayPortion portion) {
public void onDoubleTap(@NonNull final MotionEvent event,
@NonNull final DisplayPortion portion) {
if (DEBUG) {
Log.d(TAG, "onDoubleTap called with playerType = ["
+ player.getPlayerType() + "], portion = [" + portion + "]");
@ -65,7 +65,7 @@ public class PlayerGestureListener
}
@Override
public void onSingleTap(@NotNull final MainPlayer.PlayerType playerType) {
public void onSingleTap(@NonNull final MainPlayer.PlayerType playerType) {
if (DEBUG) {
Log.d(TAG, "onSingleTap called with playerType = [" + player.getPlayerType() + "]");
}
@ -85,10 +85,10 @@ public class PlayerGestureListener
}
@Override
public void onScroll(@NotNull final MainPlayer.PlayerType playerType,
@NotNull final DisplayPortion portion,
@NotNull final MotionEvent initialEvent,
@NotNull final MotionEvent movingEvent,
public void onScroll(@NonNull final MainPlayer.PlayerType playerType,
@NonNull final DisplayPortion portion,
@NonNull final MotionEvent initialEvent,
@NonNull final MotionEvent movingEvent,
final float distanceX, final float distanceY) {
if (DEBUG) {
Log.d(TAG, "onScroll called with playerType = ["
@ -197,8 +197,8 @@ public class PlayerGestureListener
}
@Override
public void onScrollEnd(@NotNull final MainPlayer.PlayerType playerType,
@NotNull final MotionEvent event) {
public void onScrollEnd(@NonNull final MainPlayer.PlayerType playerType,
@NonNull final MotionEvent event) {
if (DEBUG) {
Log.d(TAG, "onScrollEnd called with playerType = ["
+ player.getPlayerType() + "]");

View File

@ -1,18 +1,14 @@
package org.schabi.newpipe.player.helper;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import com.google.android.exoplayer2.source.MediaParserExtractorAdapter;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.SingleSampleMediaSource;
import com.google.android.exoplayer2.source.chunk.MediaParserChunkExtractor;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.hls.MediaParserHlsMediaChunkExtractor;
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
@ -46,17 +42,10 @@ public class PlayerDataSource {
}
public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() {
final HlsMediaSource.Factory factory =
new HlsMediaSource.Factory(cachelessDataSourceFactory)
.setAllowChunklessPreparation(true)
.setLoadErrorHandlingPolicy(
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
factory.setExtractorFactory(MediaParserHlsMediaChunkExtractor.FACTORY);
}
return factory;
return new HlsMediaSource.Factory(cachelessDataSourceFactory)
.setAllowChunklessPreparation(true)
.setLoadErrorHandlingPolicy(
new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY));
}
public DashMediaSource.Factory getLiveDashMediaSourceFactory() {
@ -71,26 +60,11 @@ public class PlayerDataSource {
private DefaultDashChunkSource.Factory getDefaultDashChunkSourceFactory(
final DataSource.Factory dataSourceFactory
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return new DefaultDashChunkSource.Factory(
MediaParserChunkExtractor.FACTORY,
dataSourceFactory,
1
);
}
return new DefaultDashChunkSource.Factory(dataSourceFactory);
}
public HlsMediaSource.Factory getHlsMediaSourceFactory() {
final HlsMediaSource.Factory factory = new HlsMediaSource.Factory(cacheDataSourceFactory);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return factory;
}
// *** >= Android 11 / R / API 30 ***
return factory.setExtractorFactory(MediaParserHlsMediaChunkExtractor.FACTORY);
return new HlsMediaSource.Factory(cacheDataSourceFactory);
}
public DashMediaSource.Factory getDashMediaSourceFactory() {
@ -101,18 +75,9 @@ public class PlayerDataSource {
}
public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory() {
final ProgressiveMediaSource.Factory factory;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
factory = new ProgressiveMediaSource.Factory(
cacheDataSourceFactory,
MediaParserExtractorAdapter.FACTORY
);
} else {
factory = new ProgressiveMediaSource.Factory(cacheDataSourceFactory);
}
return factory.setLoadErrorHandlingPolicy(
new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
return new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.setLoadErrorHandlingPolicy(
new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY));
}
public SingleSampleMediaSource.Factory getSampleMediaSourceFactory() {

View File

@ -35,13 +35,13 @@ public final class PlayerHolder {
return PlayerHolder.instance;
}
private final boolean DEBUG = MainActivity.DEBUG;
private final String TAG = PlayerHolder.class.getSimpleName();
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = PlayerHolder.class.getSimpleName();
private PlayerServiceExtendedEventListener listener;
private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection();
public boolean bound;
private boolean bound;
private MainPlayer playerService;
private Player player;
@ -70,6 +70,10 @@ public final class PlayerHolder {
return player != null;
}
public boolean isBound() {
return bound;
}
public int getQueueSize() {
return isPlayerOpen() ? player.getPlayQueue().size() : 0;
}
@ -148,7 +152,7 @@ public final class PlayerHolder {
}
startPlayerListener();
}
};
}
private void bind(final Context context) {
if (DEBUG) {

View File

@ -1,89 +0,0 @@
package org.schabi.newpipe.player.playererror;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
import com.google.android.exoplayer2.ExoPlaybackException;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.EnsureExceptionSerializable;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.Info;
/**
* Handles (exoplayer)errors that occur in the player.
*/
public class PlayerErrorHandler {
// This has to be <= 23 chars on devices running Android 7 or lower (API <= 25)
// or it fails with an IllegalArgumentException
// https://stackoverflow.com/a/54744028
private static final String TAG = "PlayerErrorHandler";
@Nullable
private Toast errorToast;
@NonNull
private final Context context;
public PlayerErrorHandler(@NonNull final Context context) {
this.context = context;
}
public void showPlayerError(
@NonNull final ExoPlaybackException exception,
@NonNull final Info info,
@StringRes final int textResId
) {
// Hide existing toast message
if (errorToast != null) {
Log.d(TAG, "Trying to cancel previous player error error toast");
errorToast.cancel();
errorToast = null;
}
if (shouldReportError()) {
try {
reportError(exception, info);
// When a report pops up we need no toast
return;
} catch (final Exception ex) {
Log.w(TAG, "Unable to report error:", ex);
// This will show the toast as fallback
}
}
Log.d(TAG, "Showing player error toast");
errorToast = Toast.makeText(context, textResId, Toast.LENGTH_SHORT);
errorToast.show();
}
private void reportError(@NonNull final ExoPlaybackException exception,
@NonNull final Info info) {
ErrorActivity.reportError(
context,
new ErrorInfo(
EnsureExceptionSerializable.ensureSerializable(exception),
UserAction.PLAY_STREAM,
"Player error[type=" + exception.type + "] occurred while playing: "
+ info.getUrl(),
info
)
);
}
private boolean shouldReportError() {
return PreferenceManager
.getDefaultSharedPreferences(context)
.getBoolean(
context.getString(R.string.report_player_errors_key),
false);
}
}

View File

@ -18,7 +18,7 @@ import java.util.Objects;
public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
protected final boolean DEBUG = MainActivity.DEBUG;
protected static final boolean DEBUG = MainActivity.DEBUG;
SharedPreferences defaultPreferences;

View File

@ -20,11 +20,12 @@ import androidx.preference.PreferenceManager;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PicassoHelper;
@ -73,19 +74,28 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
final Preference importDataPreference = requirePreference(R.string.import_data);
importDataPreference.setOnPreferenceClickListener((Preference p) -> {
requestImportPathLauncher.launch(
NoFileManagerSafeGuard.launchSafe(
requestImportPathLauncher,
StoredFileHelper.getPicker(requireContext(),
ZIP_MIME_TYPE, getImportExportDataUri()));
ZIP_MIME_TYPE, getImportExportDataUri()),
TAG,
getContext()
);
return true;
});
final Preference exportDataPreference = requirePreference(R.string.export_data);
exportDataPreference.setOnPreferenceClickListener((final Preference p) -> {
requestExportPathLauncher.launch(
NoFileManagerSafeGuard.launchSafe(
requestExportPathLauncher,
StoredFileHelper.getNewPicker(requireContext(),
"NewPipeData-" + exportDateFormat.format(new Date()) + ".zip",
ZIP_MIME_TYPE, getImportExportDataUri()));
ZIP_MIME_TYPE, getImportExportDataUri()),
TAG,
getContext()
);
return true;
});
@ -205,7 +215,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
saveLastImportExportDataUri(exportDataUri); // save export path only on success
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Exporting database", e);
ErrorUtil.showUiErrorSnackbar(this, "Exporting database", e);
}
}
@ -247,7 +257,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
finishImport(importDataUri);
}
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Importing database", e);
ErrorUtil.showUiErrorSnackbar(this, "Importing database", e);
}
}

View File

@ -21,6 +21,7 @@ import androidx.preference.SwitchPreferenceCompat;
import com.nononsenseapps.filepicker.Utils;
import org.schabi.newpipe.R;
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import org.schabi.newpipe.util.FilePickerActivityHelper;
@ -214,7 +215,12 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
}
private void launchDirectoryPicker(final ActivityResultLauncher<Intent> launcher) {
launcher.launch(StoredDirectoryHelper.getPicker(ctx));
NoFileManagerSafeGuard.launchSafe(
launcher,
StoredDirectoryHelper.getPicker(ctx),
TAG,
ctx
);
}
private void requestDownloadVideoPathResult(final ActivityResult result) {

View File

@ -9,8 +9,8 @@ import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.InfoCache;
@ -64,7 +64,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
.subscribe(
howManyDeleted -> Toast.makeText(context,
R.string.watch_history_states_deleted, Toast.LENGTH_SHORT).show(),
throwable -> ErrorActivity.reportError(context,
throwable -> ErrorUtil.openActivity(context,
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
"Delete playback states")));
}
@ -76,7 +76,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
.subscribe(
howManyDeleted -> Toast.makeText(context,
R.string.watch_history_deleted, Toast.LENGTH_SHORT).show(),
throwable -> ErrorActivity.reportError(context,
throwable -> ErrorUtil.openActivity(context,
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
"Delete from history")));
}
@ -87,7 +87,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> { },
throwable -> ErrorActivity.reportError(context,
throwable -> ErrorUtil.openActivity(context,
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
"Clear orphaned records")));
}
@ -99,7 +99,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
.subscribe(
howManyDeleted -> Toast.makeText(context,
R.string.search_history_deleted, Toast.LENGTH_SHORT).show(),
throwable -> ErrorActivity.reportError(context,
throwable -> ErrorUtil.openActivity(context,
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
"Delete search history")));
}

View File

@ -11,8 +11,8 @@ import io.reactivex.rxjava3.disposables.Disposable
import org.schabi.newpipe.R
import org.schabi.newpipe.database.subscription.NotificationMode
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.error.ErrorActivity
import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.error.ErrorUtil
import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.local.feed.notifications.NotificationHelper
import org.schabi.newpipe.local.feed.notifications.NotificationWorker
@ -118,7 +118,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen
}
private fun onError(e: Throwable) {
ErrorActivity.reportErrorInSnackbar(
ErrorUtil.showSnackbar(
this,
ErrorInfo(e, UserAction.SUBSCRIPTION_GET, "Get subscriptions list")
)

View File

@ -16,7 +16,7 @@ import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.ThemeHelper;
@ -153,7 +153,7 @@ public class SelectChannelFragment extends DialogFragment {
@Override
public void onError(@NonNull final Throwable exception) {
ErrorActivity.reportUiErrorInSnackbar(SelectChannelFragment.this,
ErrorUtil.showUiErrorSnackbar(SelectChannelFragment.this,
"Loading subscription", exception);
}

View File

@ -16,7 +16,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.util.KioskTranslator;
@ -48,7 +48,6 @@ import java.util.Vector;
*/
public class SelectKioskFragment extends DialogFragment {
private RecyclerView recyclerView = null;
private SelectKioskAdapter selectKioskAdapter = null;
private OnSelectedListener onSelectedListener = null;
@ -76,12 +75,12 @@ public class SelectKioskFragment extends DialogFragment {
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false);
recyclerView = v.findViewById(R.id.items_list);
final RecyclerView recyclerView = v.findViewById(R.id.items_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
try {
selectKioskAdapter = new SelectKioskAdapter();
} catch (final Exception e) {
ErrorActivity.reportUiErrorInSnackbar(this, "Selecting kiosk", e);
ErrorUtil.showUiErrorSnackbar(this, "Selecting kiosk", e);
}
recyclerView.setAdapter(selectKioskAdapter);

View File

@ -1,6 +1,5 @@
package org.schabi.newpipe.settings;
import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -21,8 +20,8 @@ import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
@ -105,8 +104,7 @@ public class SelectPlaylistFragment extends DialogFragment {
}
protected void onError(final Throwable e) {
final Activity activity = requireActivity();
ErrorActivity.reportErrorInSnackbar(activity, new ErrorInfo(e,
ErrorUtil.showSnackbar(requireActivity(), new ErrorInfo(e,
UserAction.UI_ERROR, "Loading playlists"));
}

View File

@ -8,8 +8,8 @@ import android.util.Log;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.util.DeviceUtils;
@ -157,7 +157,7 @@ public final class SettingMigrations {
} catch (final Exception e) {
// save the version with the last successful migration and report the error
sp.edit().putInt(lastPrefVersionKey, currentVersion).apply();
ErrorActivity.reportError(context, new ErrorInfo(
ErrorUtil.openActivity(context, new ErrorInfo(
e,
UserAction.PREFERENCES_MIGRATION,
"Migrating preferences from version " + lastPrefVersion + " to "

View File

@ -27,8 +27,8 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.settings.SelectChannelFragment;
@ -182,7 +182,7 @@ public class ChooseTabsFragment extends Fragment {
final Tab.Type type = typeFrom(tabId);
if (type == null) {
ErrorActivity.reportErrorInSnackbar(this,
ErrorUtil.showSnackbar(this,
new ErrorInfo(new IllegalStateException("Tab id not found: " + tabId),
UserAction.SOMETHING_ELSE, "Choosing tabs on settings"));
return;

View File

@ -12,8 +12,8 @@ import com.grack.nanojson.JsonSink;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem.LocalItemType;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
@ -36,6 +36,10 @@ import java.util.Objects;
public abstract class Tab {
private static final String JSON_TAB_ID_KEY = "tab_id";
private static final String NO_NAME = "<no-name>";
private static final String NO_ID = "<no-id>";
private static final String NO_URL = "<no-url>";
Tab() {
}
@ -185,7 +189,9 @@ public abstract class Tab {
@Override
public String getTabName(final Context context) {
return "NewPipe"; //context.getString(R.string.blank_page_summary);
// TODO: find a better name for the blank tab (maybe "blank_tab") or replace it with
// context.getString(R.string.app_name);
return "NewPipe"; // context.getString(R.string.blank_page_summary);
}
@DrawableRes
@ -309,7 +315,7 @@ public abstract class Tab {
private String kioskId;
private KioskTab() {
this(-1, "<no-id>");
this(-1, NO_ID);
}
public KioskTab(final int kioskServiceId, final String kioskId) {
@ -357,7 +363,7 @@ public abstract class Tab {
@Override
protected void readDataFromJson(final JsonObject jsonObject) {
kioskServiceId = jsonObject.getInt(JSON_KIOSK_SERVICE_ID_KEY, -1);
kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, "<no-id>");
kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, NO_ID);
}
@Override
@ -395,7 +401,7 @@ public abstract class Tab {
private String channelName;
private ChannelTab() {
this(-1, "<no-url>", "<no-name>");
this(-1, NO_URL, NO_NAME);
}
public ChannelTab(final int channelServiceId, final String channelUrl,
@ -440,8 +446,8 @@ public abstract class Tab {
@Override
protected void readDataFromJson(final JsonObject jsonObject) {
channelServiceId = jsonObject.getInt(JSON_CHANNEL_SERVICE_ID_KEY, -1);
channelUrl = jsonObject.getString(JSON_CHANNEL_URL_KEY, "<no-url>");
channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, "<no-name>");
channelUrl = jsonObject.getString(JSON_CHANNEL_URL_KEY, NO_URL);
channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, NO_NAME);
}
@Override
@ -506,7 +512,7 @@ public abstract class Tab {
final StreamingService service = NewPipe.getService(kioskServiceId);
kioskId = service.getKioskList().getDefaultKioskId();
} catch (final ExtractionException e) {
ErrorActivity.reportErrorInSnackbar(context, new ErrorInfo(e,
ErrorUtil.showSnackbar(context, new ErrorInfo(e,
UserAction.REQUESTED_KIOSK, "Loading default kiosk for selected service"));
}
return kioskId;
@ -527,7 +533,7 @@ public abstract class Tab {
private LocalItemType playlistType;
private PlaylistTab() {
this(-1, "<no-name>");
this(-1, NO_NAME);
}
public PlaylistTab(final long playlistId, final String playlistName) {
@ -535,7 +541,7 @@ public abstract class Tab {
this.playlistId = playlistId;
this.playlistType = LocalItemType.PLAYLIST_LOCAL_ITEM;
this.playlistServiceId = -1;
this.playlistUrl = "<no-url>";
this.playlistUrl = NO_URL;
}
public PlaylistTab(final int playlistServiceId, final String playlistUrl,
@ -589,8 +595,8 @@ public abstract class Tab {
@Override
protected void readDataFromJson(final JsonObject jsonObject) {
playlistServiceId = jsonObject.getInt(JSON_PLAYLIST_SERVICE_ID_KEY, -1);
playlistUrl = jsonObject.getString(JSON_PLAYLIST_URL_KEY, "<no-url>");
playlistName = jsonObject.getString(JSON_PLAYLIST_NAME_KEY, "<no-name>");
playlistUrl = jsonObject.getString(JSON_PLAYLIST_URL_KEY, NO_URL);
playlistName = jsonObject.getString(JSON_PLAYLIST_NAME_KEY, NO_NAME);
playlistId = jsonObject.getInt(JSON_PLAYLIST_ID_KEY, -1);
playlistType = LocalItemType.valueOf(
jsonObject.getString(JSON_PLAYLIST_TYPE_KEY,

View File

@ -0,0 +1,75 @@
package org.schabi.newpipe.streams.io;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import androidx.activity.result.ActivityResultLauncher;
import androidx.appcompat.app.AlertDialog;
import org.schabi.newpipe.R;
/**
* Helper for when no file-manager/activity was found.
*/
public final class NoFileManagerSafeGuard {
private NoFileManagerSafeGuard() {
// No impl
}
/**
* Shows an alert dialog when no file-manager is found.
* @param context Context
*/
private static void showActivityNotFoundAlert(final Context context) {
if (context == null) {
throw new IllegalArgumentException(
"Unable to open no file manager alert dialog: Context is null");
}
final String message;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+ only allows SAF
message = context.getString(R.string.no_appropriate_file_manager_message_android_10);
} else {
message = context.getString(
R.string.no_appropriate_file_manager_message,
context.getString(R.string.downloads_storage_use_saf_title));
}
new AlertDialog.Builder(context)
.setTitle(R.string.no_app_to_open_intent)
.setMessage(message)
.setPositiveButton(R.string.ok, null)
.show();
}
/**
* Launches the file manager safely.
*
* If no file manager is found (which is normally only the case when the user uninstalled
* the default file manager or the OS lacks one) an alert dialog shows up, asking the user
* to fix the situation.
*
* @param activityResultLauncher see {@link ActivityResultLauncher#launch(Object)}
* @param input see {@link ActivityResultLauncher#launch(Object)}
* @param tag Tag used for logging
* @param context Context
* @param <I> see {@link ActivityResultLauncher#launch(Object)}
*/
public static <I> void launchSafe(
final ActivityResultLauncher<I> activityResultLauncher,
final I input,
final String tag,
final Context context
) {
try {
activityResultLauncher.launch(input);
} catch (final ActivityNotFoundException aex) {
Log.w(tag, "Unable to launch file/directory picker", aex);
NoFileManagerSafeGuard.showActivityNotFoundAlert(context);
}
}
}

View File

@ -168,10 +168,14 @@ public final class NavigationHelper {
public static void playOnBackgroundPlayer(final Context context,
final PlayQueue queue,
final boolean resumePlayback) {
if (PlayerHolder.getInstance().getType() != MainPlayer.PlayerType.AUDIO) {
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
.show();
}
Toast.makeText(
context,
PlayerHolder.getInstance().getType() == PlayerType.AUDIO
? R.string.background_player_already_playing_toast
: R.string.background_player_playing_toast,
Toast.LENGTH_SHORT)
.show();
final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback);
intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.AUDIO.ordinal());
ContextCompat.startForegroundService(context, intent);

View File

@ -33,7 +33,9 @@ import static org.schabi.newpipe.util.external_communication.InternalUrlsHandler
public final class TextLinkifier {
public static final String TAG = TextLinkifier.class.getSimpleName();
private static final Pattern HASHTAGS_PATTERN = Pattern.compile("(#[A-Za-z0-9_]+)");
// Looks for hashtags with characters from any language (\p{L}), numbers, or underscores
private static final Pattern HASHTAGS_PATTERN =
Pattern.compile("(#[\\p{L}0-9_]+)");
private TextLinkifier() {
}

View File

@ -108,10 +108,12 @@ public class FileStreamSAF extends SharpStream {
return true;
}
@Override
public boolean canSetLength() {
return true;
}
@Override
public boolean canSeek() {
return true;
}
@ -131,10 +133,12 @@ public class FileStreamSAF extends SharpStream {
out.write(buffer, offset, count);
}
@Override
public void setLength(long length) throws IOException {
channel.truncate(length);
}
@Override
public void seek(long offset) throws IOException {
channel.position(offset);
}

View File

@ -39,8 +39,8 @@ import com.google.android.material.snackbar.Snackbar;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.util.Localization;
@ -581,9 +581,9 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
service = ErrorInfo.SERVICE_NONE;
}
ErrorActivity.reportError(mContext,
ErrorUtil.createNotification(mContext,
new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action,
service, request.toString(), reason, null));
service, request.toString(), reason));
}
public void clearFinishedDownloads(boolean delete) {

View File

@ -32,6 +32,7 @@ import com.nononsenseapps.filepicker.Utils;
import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.FilePickerActivityHelper;
@ -46,6 +47,7 @@ import us.shandian.giga.ui.adapter.MissionAdapter;
public class MissionsFragment extends Fragment {
private static final String TAG = "MissionsFragment";
private static final int SPAN_SIZE = 2;
private SharedPreferences mPrefs;
@ -257,9 +259,13 @@ public class MissionsFragment extends Fragment {
initialPath = Uri.parse(initialSavePath.getAbsolutePath());
}
requestDownloadSaveAsLauncher.launch(
NoFileManagerSafeGuard.launchSafe(
requestDownloadSaveAsLauncher,
StoredFileHelper.getNewPicker(mContext, mission.storage.getName(),
mission.storage.getType(), initialPath));
mission.storage.getType(), initialPath),
TAG,
mContext
);
}
@Override

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M14.566,1.729l7.705,7.704c0.355,0.355 0.354,0.93 0,1.284l-1.926,1.926c-0.354,0.355 -0.93,0.355 -1.285,0l-7.704,-7.704c-0.354,-0.354 -0.354,-0.929 0,-1.284l1.926,-1.926C13.639,1.374 14.213,1.374 14.566,1.729z"
android:fillColor="#FF000000"/>
<path
android:pathData="M2.369,12.643l8.988,8.989c0.356,0.352 0.929,0.352 1.284,0c1.417,-1.418 1.417,-3.719 0,-5.137l-5.136,-5.136c-1.418,-1.418 -3.718,-1.418 -5.136,0C2.016,11.714 2.016,12.287 2.369,12.643z"
android:fillColor="#FF000000"/>
<path
android:pathData="M13.2823,15.8538l-5.1357,-5.1357l3.8523,-3.8523l5.1357,5.1357z"
android:fillColor="#FF000000"/>
<path
android:pathData="M1.727,22.273c0.355,0.353 0.929,0.353 1.284,0l3.852,-3.853l-1.284,-1.283l-3.852,3.852C1.374,21.345 1.375,21.918 1.727,22.273z"
android:fillColor="#FF000000"/>
</vector>

View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M14.566,1.729l7.705,7.704c0.355,0.355 0.354,0.93 0,1.284l-1.926,1.926c-0.354,0.355 -0.93,0.355 -1.285,0l-7.704,-7.704c-0.354,-0.354 -0.354,-0.929 0,-1.284l1.926,-1.926C13.639,1.374 14.213,1.374 14.566,1.729z"
android:fillColor="#FF000000"/>
<path
android:pathData="M2.369,12.643l8.988,8.989c0.356,0.352 0.929,0.352 1.284,0c1.417,-1.418 1.417,-3.719 0,-5.137l-5.136,-5.136c-1.418,-1.418 -3.718,-1.418 -5.136,0C2.016,11.714 2.016,12.287 2.369,12.643z"
android:fillColor="#FF000000"/>
<path
android:pathData="M13.2823,15.8538l-5.1357,-5.1357l3.8523,-3.8523l5.1357,5.1357z"
android:fillColor="#FF000000"/>
<path
android:pathData="M1.727,22.273c0.355,0.353 0.929,0.353 1.284,0l3.852,-3.853l-1.284,-1.283l-3.852,3.852C1.374,21.345 1.375,21.918 1.727,22.273z"
android:fillColor="#FF000000"/>
</vector>

View File

@ -23,13 +23,25 @@
android:src="@drawable/buddy"
tools:ignore="RtlHardcoded" />
<ImageView
android:id="@+id/detail_pinned_view"
android:layout_width="@dimen/video_item_detail_pinned_image_width"
android:layout_height="@dimen/video_item_detail_pinned_image_height"
android:layout_alignParentTop="true"
android:layout_marginRight="@dimen/video_item_detail_pinned_right_margin"
android:layout_toEndOf="@+id/itemThumbnailView"
android:contentDescription="@string/detail_pinned_comment_view_description"
android:visibility="gone"
app:srcCompat="@drawable/ic_pin"
tools:visibility="visible" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/itemTitleView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginBottom="@dimen/video_item_search_image_right_margin"
android:layout_toEndOf="@+id/itemThumbnailView"
android:layout_toEndOf="@+id/detail_pinned_view"
android:ellipsize="end"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"

View File

@ -62,7 +62,7 @@
<string name="grid">网格</string>
<string name="app_update_notification_content_title">NewPipe 可更新!</string>
<string name="error_http_unsupported_range">服务器不接受多线程下载, 使用 @string/msg_threads = 1 重试</string>
<string name="autoplay_title">自动恢复上次播放</string>
<string name="autoplay_title">自动播放</string>
<string name="settings_category_clear_data_title">清空数据</string>
<string name="watch_history_deleted">播放历史已删除</string>
<string name="detail_likes_img_view_description">喜欢</string>
@ -211,7 +211,7 @@
<string name="trending">时下流行</string>
<string name="top_50">前 50</string>
<string name="new_and_hot">最新与热门</string>
<string name="show_hold_to_append_title">显示“长按添加”说明</string>
<string name="show_hold_to_append_title">显示“长按加入播放队列”提示</string>
<string name="show_hold_to_append_summary">在视频详情页中,长按后台播放或悬浮窗播放按钮时显示提示</string>
<string name="player_stream_failure">无法播放此串流</string>
<string name="player_unrecoverable_failure">发生无法处理的播放器错误</string>
@ -534,7 +534,7 @@
<string name="search_showing_result_for">显示结果:%s</string>
<string name="never">从不</string>
<string name="wifi_only">仅在 Wi-Fi 下</string>
<string name="autoplay_summary">视频开始播放后,自动定位到上次播放时的位置 — %s</string>
<string name="autoplay_summary">自动开始播放 — %s</string>
<string name="title_activity_play_queue">播放队列</string>
<string name="unsupported_url_dialog_message">无法识别此 URL。是否用其他应用打开\?</string>
<string name="auto_queue_toggle">自动加入播放队列</string>
@ -610,7 +610,7 @@
<string name="metadata_language">语言</string>
<string name="metadata_age_limit">年龄限制</string>
<string name="metadata_privacy">私有性</string>
<string name="metadata_licence">发行许可</string>
<string name="metadata_licence">许可</string>
<string name="metadata_tags">标签</string>
<string name="metadata_category">类别</string>
<string name="description_select_enable">启用简介中的文本选择功能</string>

View File

@ -184,7 +184,7 @@
<string name="show_hold_to_append_summary">Tipp anzeigen, wenn der Hintergrundwiedergabe- oder Pop-up-Knopf „Details:“ im Video gedrückt wird</string>
<string name="new_and_hot">Neu und Heiß</string>
<string name="hold_to_append">Halten, um zur Wiedergabeliste hinzuzufügen</string>
<string name="show_hold_to_append_title">Zum Anhängen gedrückt halten“ Tipp anzeigen</string>
<string name="show_hold_to_append_title">Halten zum Einreihen“ Tipp anzeigen</string>
<string name="unknown_content">[Unbekannt]</string>
<string name="start_here_on_background">Wiedergabe im Hintergrund starten</string>
<string name="start_here_on_popup">Wiedergabe in einem Pop-up starten</string>
@ -567,7 +567,7 @@
<string name="notification_action_repeat">Wiederholen</string>
<string name="notification_action_nothing">Nichts</string>
<string name="title_activity_play_queue">Warteschlange abspielen</string>
<string name="auto_queue_toggle">Automatische Warteschlange</string>
<string name="auto_queue_toggle">Auto-Einreihung</string>
<string name="clear_queue_confirmation_summary">Der Wechsel von einem Player zu einem anderen kann deine Warteschlange überschreiben</string>
<string name="clear_queue_confirmation_title">Überschreiben der Warteschlange bestätigen</string>
<string name="clear_queue_confirmation_description">Die aktive Player-Warteschlange wird ersetzt</string>

View File

@ -106,7 +106,7 @@
<string name="enable_watch_history_summary">Κρατήστε ιστορικό των αναπαραχθέντων βίντεο</string>
<string name="resume_on_audio_focus_gain_title">Ανάκτηση αναπαραγωγής</string>
<string name="resume_on_audio_focus_gain_summary">Συνέχιση της αναπαραγωγής έπειτα από διακοπές (π.χ. κλήσεις)</string>
<string name="show_hold_to_append_title">Εμφάνιση επεξήγησης του «Πιέστε παρατεταμένα για προσθήκη»</string>
<string name="show_hold_to_append_title">Εμφάνιση επεξήγησης του «Πιέστε παρατεταμένα για προσθήκη στην ουρά»</string>
<string name="show_hold_to_append_summary">Εμφάνιση υπόδειξης όταν πατηθεί το κουμπί παρασκηνίου ή αναδυόμενου παραθύρου στη σελίδα λεπτομερειών του βίντεο</string>
<string name="default_content_country_title">Προεπιλεγμένη χώρα περιεχομένου</string>
<string name="settings_category_player_title">Αναπαραγωγός</string>
@ -252,7 +252,7 @@
<string name="play_queue_remove">Αφαίρεση</string>
<string name="play_queue_stream_detail">Λεπτομέρειες</string>
<string name="play_queue_audio_settings">Ρυθμίσεις ήχου</string>
<string name="hold_to_append">Πιέστε παρατεταμένα για να προστεθεί στην ουρά</string>
<string name="hold_to_append">Πιέστε παρατεταμένα για προσθήκη στην ουρά</string>
<string name="start_here_on_background">Εκκίνηση αναπαραγωγής στο παρασκήνιο</string>
<string name="start_here_on_popup">Εκκίνηση αναπαραγωγής σε ένα αναδυόμενο παράθυρο</string>
<string name="drawer_open">Άνοιγμα συρταριού</string>
@ -486,7 +486,7 @@
\nΕνεργοποιήστε το «%1$s» στις ρυθμίσεις εάν θέλετε να το δείτε.</string>
<string name="youtube_restricted_mode_enabled_title">Λειτουργία περιορισμένης πρόσβασης του YouTube</string>
<string name="unsupported_url_dialog_message">Δεν ήταν δυνατή η αναγνώριση της διεύθυνσης URL. Άνοιγμα με άλλη εφαρμογή;</string>
<string name="auto_queue_toggle">Αυτόματη ουρά</string>
<string name="auto_queue_toggle">Αυτόματη προσθήκη στην ουρά</string>
<string name="clear_queue_confirmation_description">Η ουρά του ενεργού αναπαραγωγού θα αντικατασταθεί</string>
<string name="clear_queue_confirmation_summary">Η εναλλαγή από έναν αναπαραγωγό σε άλλον, μπορεί να αντικαταστήσει την ουρά σας</string>
<string name="clear_queue_confirmation_title">Ζητήστε επιβεβαίωση πριν από την εκκαθάριση μιας ουράς</string>

View File

@ -183,7 +183,7 @@
<string name="unknown_content">[Desconocido]</string>
<string name="start_here_on_background">Comenzar a reproducir en segundo plano</string>
<string name="start_here_on_popup">Reproducir en modo emergente</string>
<string name="show_hold_to_append_title">Mostrar la sugerencia \"Mantener pulsado para añadir a la cola\"</string>
<string name="show_hold_to_append_title">Mostrar sugerencia de \"Mantener pulsado para añadir a la cola\"</string>
<string name="new_and_hot">Nuevo y lo mejor</string>
<string name="hold_to_append">Mantener pulsado para añadir a la cola</string>
<string name="donation_title">Donar</string>
@ -250,7 +250,7 @@
<string name="enable_disposed_exceptions_summary">Forzar informe de excepciones no entregables de RX fuera del fragmento o del ciclo de actividad después del descarte</string>
<string name="use_inexact_seek_title">Usar búsqueda rápida e inexacta</string>
<string name="use_inexact_seek_summary">La búsqueda inexacta permite al reproductor buscar posiciones más rápido con menor precisión. Buscar de a 5, 15 o 25 segundos no funciona</string>
<string name="auto_queue_title">Poner en cola vídeo relacionado siguiente</string>
<string name="auto_queue_title">Poner en cola automáticamente la siguiente transmisión</string>
<string name="auto_queue_summary">Continuar reproducción sin repetir al añadir de forma automática un vídeo relacionado con el último visto</string>
<string name="file">Archivo</string>
<string name="missing_file">Archivo movido o borrado</string>
@ -552,7 +552,7 @@
<string name="autoplay_summary">Comenzar reproducción automáticamente — %s</string>
<string name="title_activity_play_queue">Reproducir cola</string>
<string name="unsupported_url_dialog_message">No se pudo reconocer la URL. ¿Abrir con otra aplicación\?</string>
<string name="auto_queue_toggle">Poner en cola</string>
<string name="auto_queue_toggle">Poner en cola automáticamente</string>
<string name="clear_queue_confirmation_summary">Cambiar de un reproductor a otro puede reemplazar la cola de reproducción</string>
<string name="clear_queue_confirmation_description">La cola de reproducción activa será reemplazada</string>
<string name="clear_queue_confirmation_title">Pedir confirmación antes de vaciar una cola</string>

View File

@ -675,4 +675,9 @@
<string name="checking_updates_toast">Vérification des mises à jour…</string>
<string name="manual_update_title">Vérifier les mises à jours</string>
<string name="feed_new_items">Nouveaux éléments du flux</string>
<string name="report_player_errors_summary">Reporter les erreurs du lecteur en détail au lieu de montrer un bref message éphémère (utile pour diagnostiquer les problèmes)</string>
<string name="report_player_errors_title">Reporter des erreurs du lecteur</string>
<string name="crash_the_player">Couper le lecteur</string>
<string name="show_crash_the_player_title">Montrer \"Couper le lecteur\"</string>
<string name="show_crash_the_player_summary">Montrer une option couper lors de l\'utilisation du lecteur</string>
</resources>

View File

@ -660,4 +660,9 @@
<string name="manual_update_description">Periksa manual untuk versi baru</string>
<string name="checking_updates_toast">Memeriksa pembaruan…</string>
<string name="feed_new_items">Item feed baru</string>
<string name="show_crash_the_player_title">Tampilkan \"hentikan pemain video\"</string>
<string name="show_crash_the_player_summary">Menampilkan opsi penghentian ketika menggunakan pemain video</string>
<string name="report_player_errors_summary">Melaporkan kesalahan pemain video dalam detail yang penuh daripada menampilkan pesan toast yang muncul sebentar (berguna untuk memeriksa masalah)</string>
<string name="report_player_errors_title">Laporkan kesalahan pemain video</string>
<string name="crash_the_player">Hentikan pemain video</string>
</resources>

View File

@ -660,4 +660,8 @@
<string name="enqueue_next_stream">次をキューに追加</string>
<string name="enqueued_next">次をキューに追加しました</string>
<string name="detail_heart_img_view_description">クリエイターの心をこめて</string>
<string name="report_player_errors_summary">プレーヤーのエラーを、短時間のトーストメッセージではなく、詳細に報告する(問題の診断に役立ちます)</string>
<string name="report_player_errors_title">プレイヤーのエラーを報告</string>
<string name="show_crash_the_player_title">\"プレイヤーがクラッシュ\"を表示</string>
<string name="crash_the_player">プレイヤーがクラッシュ</string>
</resources>

View File

@ -38,6 +38,7 @@
<dimen name="channel_avatar_size">90dp</dimen>
<dimen name="sub_channel_avatar_size">45dp</dimen>
<!-- Paddings & Margins -->
<dimen name="video_item_detail_pinned_right_margin">8dp</dimen>
<dimen name="video_item_detail_like_margin">8dp</dimen>
<dimen name="video_item_detail_error_panel_margin">4dp</dimen>

View File

@ -563,7 +563,7 @@
<string name="notification_action_0_title">Eerste actieknop</string>
<string name="notification_scale_to_square_image_summary">Schaal de miniatuurafbeelding van de video die getoond wordt in de notificatie van verhouding 16:9 naar 1:1 (dit kan vervorming creëren)</string>
<string name="notification_scale_to_square_image_title">Schaal de miniatuurafbeelding tot verhouding 1:1</string>
<string name="auto_queue_toggle">Auto-wachtrij</string>
<string name="auto_queue_toggle">Automatisch in de wachtrij plaatsen</string>
<string name="show_memory_leaks">Toon memory leaks</string>
<string name="enqueued">In de wachtrij geplaatst</string>
<string name="enqueue_stream">In de wachtrij plaatsen</string>
@ -668,4 +668,14 @@
<string name="main_page_content_swipe_remove">Veeg items om ze te verwijderen</string>
<string name="start_main_player_fullscreen_summary">Start geen video\'s in de minispeler, maar ga direct naar de volledige schermmodus, als automatisch draaien is vergrendeld. Je hebt nog steeds toegang tot de minispeler door de volledige schermmodus af te sluiten</string>
<string name="start_main_player_fullscreen_title">Start hoofdspeler als volledig scherm</string>
<string name="report_player_errors_summary">Rapporteer fouten van de speler in volledig detail in plaats van een kortstondige toastmelding te tonen (handig voor het diagnosticeren van problemen)</string>
<string name="processing_may_take_a_moment">Verwerken... Dit kan even duren</string>
<string name="crash_the_player">Crash de speler</string>
<string name="report_player_errors_title">Rapporteer fouten van de speler</string>
<string name="show_crash_the_player_title">Toon \"crash de speler\"</string>
<string name="show_crash_the_player_summary">Toon een crash overzicht bij gebruik van de speler</string>
<string name="manual_update_description">Controleer handmatig op nieuwe versies</string>
<string name="manual_update_title">Controleer op updates</string>
<string name="checking_updates_toast">Bezig met controleren op updates…</string>
<string name="feed_new_items">Nieuwe feed items</string>
</resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="main_bg_subtitle">ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਸਰਚ ਦਬਾਓ</string>
<string name="main_bg_subtitle">ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਵੱਡਦਰਸ਼ੀ ਕੱਚ ਨੂੰ ਦਬਾਓ</string>
<string name="upload_date_text">%1$s ਨੂੰ ਜਾਰੀ ਕੀਤੀ ਗਈ</string>
<string name="no_player_found">ਕੋਈ ਸਟ੍ਰੀਮ ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ। ਤੁਸੀਂ ਵੀਐੱਲਸੀ ਸਥਾਪਤ ਕਰਨਾ ਚਾਹੋਗੇ\?</string>
<string name="no_player_found_toast">ਸਟ੍ਰੀਮ ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ (ਤੁਸੀਂ ਵੀਐੱਲਸੀ ਸਥਾਪਤ ਕਰਕੇ ਇਸਨੂੰ ਚਲਾ ਸਕਦੇ ਹੋ)।</string>

View File

@ -252,8 +252,8 @@
<string name="resize_zoom">Powiększ</string>
<string name="caption_auto_generated">Wygenerowane automatycznie</string>
<string name="enable_leak_canary_summary">Monitorowanie wycieków pamięci może powodować niestabilność aplikacji podczas zrzutu pamięci</string>
<string name="enable_disposed_exceptions_title">Zgł błędy spoza cyklu życia</string>
<string name="enable_disposed_exceptions_summary">Wymuś raportowanie niedostarczonych wyjątków Rx poza cyklem życia fragmentu lub aktywności</string>
<string name="enable_disposed_exceptions_title">Zgłaszaj błędy spoza cyklu życia</string>
<string name="enable_disposed_exceptions_summary">Wymuś raportowanie niedostarczonych wyjątków Rx poza cyklem życia fragmentu lub aktywności po usunięciu</string>
<string name="use_inexact_seek_title">Używaj szybkiego, niedokładnego przewijania</string>
<string name="use_inexact_seek_summary">Niedokładne przewijanie umożliwia szybsze przewijanie ze zmniejszoną dokładnością. Przewijanie o 5, 15 lub 25 sekund nie działa w tym przypadku</string>
<string name="download_thumbnail_title">Wczytuj miniatury</string>
@ -544,7 +544,7 @@
<string name="remove_watched_popup_title">Usunąć obejrzane wideo\?</string>
<string name="remove_watched">Usuń obejrzane</string>
<string name="show_original_time_ago_summary">Oryginalne teksty z usług będą widoczne w strumieniowanych pozycjach</string>
<string name="show_original_time_ago_title">Pokaż oryginalny czas</string>
<string name="show_original_time_ago_title">Pokazuj oryginalny czas na pozycjach</string>
<string name="youtube_restricted_mode_enabled_title">Włącz tryb ograniczonego dostępu YouTube\'a</string>
<string name="video_detail_by">Przez %s</string>
<string name="channel_created_by">Utworzony przez %s</string>
@ -666,8 +666,8 @@
<string name="mark_as_watched">Oznacz jako obejrzane</string>
<string name="loading_channel_details">Ładowanie szczegółów kanału…</string>
<string name="error_show_channel_details">Błąd podczas wyświetlania szczegółów kanału</string>
<string name="show_image_indicators_summary">Pokaż kolorowe wstążki Picasso nad obrazami wskazujące ich źródło: czerwone dla sieci, niebieskie dla dysku i zielone dla pamięci</string>
<string name="show_image_indicators_title">Pokaż wskaźniki obrazu</string>
<string name="show_image_indicators_summary">Pokazuj kolorowe wstążki Picasso nad obrazami wskazujące ich źródło: czerwone dla sieci, niebieskie dla dysku i zielone dla pamięci</string>
<string name="show_image_indicators_title">Pokazuj wskaźniki obrazu</string>
<string name="remote_search_suggestions">Zdalne podpowiedzi wyszukiwania</string>
<string name="local_search_suggestions">Lokalne podpowiedzi wyszukiwania</string>
<plurals name="deleted_downloads_toast">

View File

@ -672,4 +672,10 @@
<string name="manual_update_title">Procurar por atualizações</string>
<string name="manual_update_description">Procurar manualmente por novas versões</string>
<string name="checking_updates_toast">Procurando por atualizações…</string>
<string name="crash_the_player">Travar o player</string>
<string name="report_player_errors_summary">Reporta os erros do player em detalhes completos, em vez de mostrar uma mensagem de notificação de curta duração (útil para diagnosticar problemas)</string>
<string name="show_crash_the_player_title">Mostrar \"travar o player\"</string>
<string name="report_player_errors_title">Reportar erros do player</string>
<string name="show_crash_the_player_summary">Mostra uma opção de travamento ao usar o player</string>
<string name="feed_new_items">Novos itens do feed</string>
</resources>

View File

@ -351,7 +351,7 @@
<item quantity="one">%d hora</item>
<item quantity="other">%d horas</item>
</plurals>
<string name="auto_queue_title">Colocar vídeo seguinte na fila</string>
<string name="auto_queue_title">Enfileirar o próximo stream automaticamente</string>
<string name="peertube_instance_url_summary">Defina as suas instâncias favoritas PeerTube</string>
<string name="export_data_summary">Exportar histórico, subscrições, listas de reprodução e definições</string>
<string name="best_resolution">Melhor resolução</string>
@ -468,7 +468,7 @@
<string name="feed_group_dialog_select_subscriptions">Selecionar subscrições</string>
<string name="playlist_add_stream_success">Adicionado à lista de reprodução</string>
<string name="default_video_format_title">Formato padrão de vídeo</string>
<string name="show_hold_to_append_title">Mostrar dica \"Toque longo para colocar na fila\"</string>
<string name="show_hold_to_append_title">Mostrar dica \"Toque longo para enfileirar\"</string>
<string name="choose_instance_prompt">Escolha uma instância</string>
<string name="metadata_cache_wipe_summary">Limpar todos os dados da página web</string>
<string name="drawer_close">Fechar menu</string>
@ -546,7 +546,7 @@
<string name="autoplay_summary">Iniciar reprodução automaticamente — %s</string>
<string name="title_activity_play_queue">Reproduzir fila</string>
<string name="unsupported_url_dialog_message">URL não reconhecido. Abrir com outra aplicação\?</string>
<string name="auto_queue_toggle">Colocar na fila automaticamente</string>
<string name="auto_queue_toggle">Enfileiramento automático</string>
<string name="clear_queue_confirmation_description">A fila do reprodutor ativo será substituída</string>
<string name="clear_queue_confirmation_summary">Mudar de um reprodutor para outro pode substituir a sua fila</string>
<string name="clear_queue_confirmation_title">Pedir confirmação antes de limpar uma fila</string>
@ -673,4 +673,9 @@
<string name="manual_update_description">Verificar manualmente se existe uma nova versão</string>
<string name="checking_updates_toast">A procurar atualizações…</string>
<string name="feed_new_items">Novos itens</string>
<string name="crash_the_player">Travar o reprodutor</string>
<string name="report_player_errors_summary">Relata os erros do reprodutor com todos os detalhes em vez de mostrar uma mensagem de notificação por pouco tempo (útil para diagnosticar problemas)</string>
<string name="report_player_errors_title">Relatar erros do reprodutor</string>
<string name="show_crash_the_player_title">Mostrar \"travar o reprodutor\"</string>
<string name="show_crash_the_player_summary">Mostra uma opção de travamento ao usar o reprodutor</string>
</resources>

View File

@ -282,7 +282,7 @@
<string name="playback_tempo">Ritmo</string>
<string name="clear_views_history_title">Limpar histórico de visualizações</string>
<string name="auto_queue_summary">Continuar (sem repetição) a fila de reprodução anexando um vídeo relacionado</string>
<string name="show_hold_to_append_title">Mostrar dica \"Toque longo para colocar na fila\"</string>
<string name="show_hold_to_append_title">Mostrar dica \"Toque longo para enfileirar\"</string>
<string name="show_hold_to_append_summary">Mostrar dica ao premir em segundo plano ou no botão \"Detalhes:\" da janela popup</string>
<string name="channels">Canais</string>
<string name="playlists">Listas de reprodução</string>
@ -303,7 +303,7 @@
<string name="privacy_policy_encouragement">O projeto NewPipe leva a sua privacidade muito a sério. Por isso, não recolhe nenhum dado sem o seu consentimento.
\nA polícia de privacidade do NewPipe explica, em detalhe, os tipos de dados enviados sempre que submete um relatório de erro.</string>
<string name="read_privacy_policy">Ver política de privacidade</string>
<string name="auto_queue_title">Colocar vídeo seguinte na fila</string>
<string name="auto_queue_title">Enfileirar o próximo stream automaticamente</string>
<string name="app_license">NewPipe é um software livre \"copyleft\": pode utilizar, estudar, partilhar e melhorar a aplicação. Especificamente, pode redistribuir e/ou modificar a aplicação nos termos da GNU General Public License, conforme publicada pela Free Software Foundation, tanto a versão 3 da licença ou (por opção) qualquer versão posterior.</string>
<string name="import_settings">Também deseja importar as definições\?</string>
<string name="hold_to_append">Toque longo para colocar na fila</string>
@ -554,7 +554,7 @@
<string name="notification_action_buffering">A carregar</string>
<string name="clear_queue_confirmation_description">A fila do reprodutor ativo será substituída</string>
<string name="unsupported_url_dialog_message">URL não reconhecido. Abrir com outra aplicação\?</string>
<string name="auto_queue_toggle">Colocar na fila automaticamente</string>
<string name="auto_queue_toggle">Enfileiramento automático</string>
<string name="notification_action_shuffle">Baralhar</string>
<string name="wifi_only">Apenas em Wi-Fi</string>
<string name="notification_action_nothing">Nada</string>
@ -673,4 +673,9 @@
<string name="manual_update_description">Verificar manualmente se existe uma nova versão</string>
<string name="checking_updates_toast">A procurar atualizações…</string>
<string name="feed_new_items">Novos itens</string>
<string name="crash_the_player">Travar o reprodutor</string>
<string name="report_player_errors_summary">Relata os erros do reprodutor com todos os detalhes em vez de mostrar uma mensagem de notificação por pouco tempo (útil para diagnosticar problemas)</string>
<string name="report_player_errors_title">Relatar erros do reprodutor</string>
<string name="show_crash_the_player_title">Mostrar \"travar o reprodutor\"</string>
<string name="show_crash_the_player_summary">Mostra uma opção de travamento ao usar o reprodutor</string>
</resources>

View File

@ -714,4 +714,7 @@
<string name="get_notified">Уведомлять</string>
<string name="you_successfully_subscribed">Вы подписались на канал</string>
<string name="toggle_all">Переключить все</string>
<string name="show_crash_the_player_title">Показать \"Вызвать сбой плеера\"</string>
<string name="show_crash_the_player_summary">Показать функцию вызова сбоя при работе плеера</string>
<string name="crash_the_player">Вызвать сбой плеера</string>
</resources>

View File

@ -194,7 +194,7 @@
<string name="always">Увек</string>
<string name="just_once">Само једном</string>
<string name="unknown_content">[непознато]</string>
<string name="show_hold_to_append_title">Прикажи поруку „задржи ради стављања у ред</string>
<string name="show_hold_to_append_title">Прикажи поруку „задржи ради заказивања</string>
<string name="donation_title">Донација</string>
<string name="donation_encouragement">ЊуПајп развијају волонтери у своје слободно време како би вам пружили најбоље искуство. Узвратите им како би наставили са побољшавањем ЊуПајпа док уживају уз шољицу кафе.</string>
<string name="give_back">Узврати</string>
@ -506,9 +506,9 @@
<string name="brightness_gesture_control_title">Контрола осветљености потезом</string>
<string name="volume_gesture_control_summary">Користите потезе за контролу јачине звука плејера</string>
<string name="volume_gesture_control_title">Контрола јачине звука потезом</string>
<string name="auto_queue_toggle">Аутоматски ред</string>
<string name="auto_queue_toggle">Самостално заказивање</string>
<string name="auto_queue_summary">Наставите да завршавате (не понављајући) ред репродукције додавањем повезаног стрима</string>
<string name="auto_queue_title">Аутоматски стави у ред следећи ток</string>
<string name="auto_queue_title">Самостално закажи следећи ток</string>
<string name="metadata_cache_wipe_complete_notice">Кеш метаподатака обрисан</string>
<string name="show_meta_info_summary">Искључите за сакривање поља мета-података са додатним информацијама о творцу тока, садржају или захтеву за претрагу</string>
<string name="show_meta_info_title">Прикажи мета-податке</string>
@ -651,4 +651,15 @@
<string name="on">укљ</string>
<string name="tablet_mode_title">Режим таблета</string>
<string name="feed_toggle_show_played_items">Прикажи пуштано</string>
<string name="enqueue_next_stream">Закажи следеће</string>
<string name="enqueued_next">Заказано је следеће</string>
<string name="local_search_suggestions">Местни предлози претраге</string>
<string name="remote_search_suggestions">Удаљени предлози претраге</string>
<string name="mark_as_watched">Означи као одгледано</string>
<string name="comments_are_disabled">Разговори су онемогућени</string>
<string name="processing_may_take_a_moment">Обрађујем… Може потрајати пар тренутака</string>
<string name="show_image_indicators_title">Приказуј указиваче слике</string>
<string name="start_main_player_fullscreen_summary">Не покрећи видео у малом прозору, већ пређи одмах у пун приказ заслона, уколико је обртање приказа закључано. И даље можете приступити малом приказу извођача изласком из пуног приказа</string>
<string name="report_player_errors_title">Пријави грешке програма извођача видеа</string>
<string name="start_main_player_fullscreen_title">Покрени пуни главни приказ извођача</string>
</resources>

View File

@ -91,7 +91,7 @@
<string name="enable_watch_history_summary">Håll koll på videor som du tittat på</string>
<string name="resume_on_audio_focus_gain_title">Återuppta uppspelning</string>
<string name="resume_on_audio_focus_gain_summary">Fortsätt uppspelning efter avbrott (t.ex. telefonsamtal)</string>
<string name="show_hold_to_append_title">Visa \"Håll för att lägga till\"-tips</string>
<string name="show_hold_to_append_title">Visa \"Håll för att placera i kön\"-tips</string>
<string name="show_hold_to_append_summary">Visa tips när bakgrunds- eller popup-knappen trycks på sidan för videodetaljer</string>
<string name="settings_category_player_title">Spelare</string>
<string name="settings_category_player_behavior_title">Beteende</string>
@ -673,4 +673,9 @@
<string name="manual_update_description">Kolla manuellt efter nya versioner</string>
<string name="checking_updates_toast">Söker efter uppdateringar…</string>
<string name="feed_new_items">Nya flödes objekt</string>
<string name="show_crash_the_player_title">Visa \"krascha spelaren\"</string>
<string name="report_player_errors_title">Rapportera spelarfel</string>
<string name="crash_the_player">Krascha spelaren</string>
<string name="show_crash_the_player_summary">Visar ett kraschalternativ vid användning av spelaren</string>
<string name="report_player_errors_summary">Rapporterar spelarfel i detalj i stället för att visa ett kortvarigt popup-meddelande (användbart för att diagnostisera problem)</string>
</resources>

View File

@ -18,6 +18,7 @@
<dimen name="video_item_detail_like_image_height">18sp</dimen>
<dimen name="video_item_detail_like_image_width">18sp</dimen>
<!-- Paddings & Margins -->
<dimen name="video_item_detail_pinned_right_margin">10dp</dimen>
<dimen name="video_item_detail_like_margin">10dp</dimen>
<dimen name="search_suggestion_text_size">14sp</dimen>

View File

@ -1,25 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="main_bg_subtitle">ప్రారంభించడానికి శోధనను క్లిక్</string>
<string name="upload_date_text">%1$s ప్రచురించబడింది</string>
<string name="main_bg_subtitle">మొదలుపెట్టుటకు బూతద్దము గురుతుని తట్టండి.</string>
<string name="upload_date_text">%1$s ప్రచురించబడింది</string>
<string name="install">ఇన్స్టాల్</string>
<string name="cancel">రద్దు చేయి</string>
<string name="open_in_browser">బ్రౌజర్ లో తెరవండి</string>
<string name="share">షేర్ చేయండి</string>
<string name="share">షేర్</string>
<string name="download">డౌన్లోడ్</string>
<string name="search">వెతుకు</string>
<string name="settings">సెట్టింగ్‌లు</string>
<string name="did_you_mean">అంటే నువ్వు అనేది: %1$s\?</string>
<string name="share_dialog_title">తో పంచు</string>
<string name="use_external_audio_player_title">బాహ్య ఆడియో ప్లేయర్ని ఉపయోగించండి</string>
<string name="settings">అమరికలు</string>
<string name="did_you_mean">మీ ఉద్దేశ్యం \"%1$s\"\?</string>
<string name="share_dialog_title">పంచుకో</string>
<string name="use_external_audio_player_title">బాహ్య ఆడియో ప్లేయర్ని ఉపయోగించండి</string>
<string name="subscribe_button_title">సభ్యత్వం</string>
<string name="subscribed_button_title">సబ్ స్క్రైబ్</string>
<string name="channel_unsubscribed">అన్ సబ్స్క్రైబ్ చెసిరు</string>
<string name="channel_unsubscribed">ఛానెల్ సభ్యత్వం తీసివేయబడినది</string>
<string name="subscription_change_failed">సబ్ స్క్రైబ్ సాధ్యం కాలేదు</string>
<string name="subscription_update_failed">సబ్ స్క్రైబ్ నవీకరించలేరు</string>
<string name="tab_subscriptions">సభ్యత్వం</string>
<string name="fragment_feed_title">కొత్తది ఏమిటి</string>
<string name="controls_background_title">వెనకవైప</string>
<string name="controls_background_title">వెనుకగా</string>
<string name="controls_popup_title">పాపప్</string>
<string name="download_path_title">వీడియో డౌన్లోడ్ మార్గం</string>
<string name="download_path_summary">డౌన్లోడ్ చేసిన వీడియోలను నిల్వ చేయడానికి మార్గం</string>
@ -28,26 +28,26 @@
<string name="download_path_audio_summary">ఆడియో కోసం డౌన్లోడ్ మార్గాన్ని నమోదు చేయండి</string>
<string name="download_path_audio_dialog_title">ఆడియో ఫైల్లకు డౌన్లోడ్ మార్గాన్ని ఇవ్వండి</string>
<string name="play_with_kodi_title">కోడితో ప్లే చేయండి</string>
<string name="kore_not_found">కోరే అనువర్తనం కనుగొనబడలేదు. దీన్ని ఇన్స్టాల్ చేయండి</string>
<string name="kore_not_found">కోరే ఆప్ కనుగొనబడలేదు. దీన్ని ఇన్స్టాల్ చెయ్యాలా\?</string>
<string name="show_play_with_kodi_title">కోరే అనువర్తనం కనుగొనబడలేదు. దీన్ని ఇన్స్టాల్ చేయండి</string>
<string name="play_audio">ఆడియో</string>
<string name="default_audio_format_title">డిఫాల్ట్ ఆడియో ఫార్మాట్</string>
<string name="theme_title">థీమ్</string>
<string name="light_theme_title">ప్రకాశం</string>
<string name="enable_watch_history_title">చరిత్ర</string>
<string name="download_dialog_title">డౌన్లోడ్</string>
<string name="show_next_and_similar_title">తదుపరి వీడియో మరియు ఇలాంటి వీడియో</string>
<string name="enable_watch_history_title">చరిత్రను చూడండి</string>
<string name="download_dialog_title">డౌన్లోడ్</string>
<string name="show_next_and_similar_title">\'తదుపరి\' మరియు \'ఇలాంటి\' వీడియోలను చూపించు</string>
<string name="show_hold_to_append_title">చిట్కాను అనుబంధించడానికి హోల్డ్ను చూపు</string>
<string name="unsupported_url">Url మద్దతు లేదు</string>
<string name="content_language_title">డిఫాల్ట్ భాష</string>
<string name="settings_category_player_title">ప్లేయర్</string>
<string name="settings_category_player_behavior_title">ప్రవర్తన</string>
<string name="settings_category_video_audio_title">వీడియో &amp; ఆడియో</string>
<string name="settings_category_history_title">చరిత్ర</string>
<string name="settings_category_history_title">చరిత్ర మరియు కాష్</string>
<string name="settings_category_appearance_title">స్వరూపం</string>
<string name="background_player_playing_toast">వెనకవైపులో ఆడుతున్నారు</string>
<string name="popup_playing_toast">పాపప్ రీతిలో ప్లే చేస్తోంది</string>
<string name="content">కంటెంట్</string>
<string name="content">విషయము</string>
<string name="downloads">డౌన్ లోడ్ల</string>
<string name="downloads_title">డౌన్ లోడ్</string>
<string name="error_report_title">లోపం నివేదిక</string>
@ -62,17 +62,17 @@
<string name="content_not_available">కంటెంట్ అందుబాటులో లేదు</string>
<string name="could_not_setup_download_menu">డౌన్లోడ్ మెనుని సెటప్ చేయడం సాధ్యపడలేదు</string>
<string name="player_stream_failure">ఈ స్ట్రీమ్ని ప్లే చేయడం విఫలమైంది</string>
<string name="player_recoverable_failure">ఆటగాడు లోపం నుండి పునరుద్ధరించడం</string>
<string name="player_recoverable_failure">ప్లేయర్ లోపం నుండి పునరుద్ధరించబడుతున్నది</string>
<string name="sorry_string">క్షమించాలి, అది జరగకూడదు</string>
<string name="error_report_button_text">ఇ-మెయిల్ ద్వారా నివేదన లోపం</string>
<string name="error_snackbar_message">క్షమించండి, కొన్ని లోపాలు సంభవించాయి</string>
<string name="error_report_button_text">ఇ-మెయిల్ ద్వారా లోపాన్ని నివేదించుము</string>
<string name="error_snackbar_message">క్షమించండి, ఏదో తప్పు జరిగింది.</string>
<string name="error_snackbar_action">నివేదిక</string>
<string name="what_device_headline">సమాచారం</string>
<string name="what_happened_headline">ఏం జరిగింది</string>
<string name="your_comment">మీ వ్యాఖ్య(ఆంగ్లం లో)</string>
<string name="error_details_headline">వివరాలు</string>
<string name="what_device_headline">సమాచారం:</string>
<string name="what_happened_headline">ఏం జరిగింది:</string>
<string name="your_comment">మీ వ్యాఖ్య(ఆంగ్లం లో):</string>
<string name="error_details_headline">వివరములు:</string>
<string name="list_thumbnail_view_description">వీడియో ప్రివ్యూ సూక్ష్మచిత్రం</string>
<string name="detail_thumbnail_view_description">Video preview thumbnail</string>
<string name="detail_thumbnail_view_description">వీడియోని ప్లే చేయండి, వ్యవధి:</string>
<string name="detail_likes_img_view_description">ఇష్టాలు</string>
<string name="detail_dislikes_img_view_description">మంది ఇష్టపడలేదు</string>
<string name="search_no_results">ఫలితాలు లేవు</string>
@ -82,10 +82,10 @@
<string name="short_thousand">కె</string>
<string name="short_million">ఎం</string>
<string name="short_billion">బి</string>
<string name="no_subscribers">చందాదారులు లేరు</string>
<string name="no_subscribers">సభ్యులు లేరు</string>
<plurals name="subscribers">
<item quantity="one">%s సబ్స్క్రయిబ్</item>
<item quantity="other">%s సబ్స్క్రయిబలు</item>
<item quantity="one">%s సభ్యుడు</item>
<item quantity="other">%s సభ్యులు</item>
</plurals>
<string name="no_views">వీక్షణలు లేవు</string>
<plurals name="views">
@ -98,7 +98,7 @@
<item quantity="other">%s వీడియోలు</item>
</plurals>
<string name="start">ప్రారంభం</string>
<string name="pause">ఆపు</string>
<string name="pause">విరామం</string>
<string name="delete">తొలగించు</string>
<string name="ok">అలాగే</string>
<string name="msg_name">ఫైలుపేరు</string>
@ -112,7 +112,7 @@
<string name="settings_category_downloads_title">డౌన్లోడ్</string>
<string name="settings_file_charset_title">ఫైల్ పేర్లలో అనుమతించిన అక్షరాలు</string>
<string name="settings_file_replacement_character_summary">చెల్లని అక్షరాలు ఈ విలువతో భర్తీ చేయబడతాయి</string>
<string name="settings_file_replacement_character_title">ప్రత్యామ్నాయ పాత్ర</string>
<string name="settings_file_replacement_character_title">ప్రత్యామ్నాయ అక్షరం</string>
<string name="charset_letters_and_digits">లెటర్స్ మరియు అంకెలు</string>
<string name="title_activity_about">న్యూపిప్ గురించి</string>
<string name="title_licenses">మూడవ పార్టీ లైసెన్స్</string>
@ -120,9 +120,9 @@
<string name="tab_licenses">లైసెన్సుల</string>
<string name="view_on_github">GitHub పై చూడండి</string>
<string name="app_license_title">న్యూపెయిప్స్ లైసెన్స్</string>
<string name="contribution_encouragement">మీరు ఆలోచనలు ఉన్నాయా లేదో; అనువాదం, డిజైన్ మార్పులు, కోడ్ క్లీనింగ్ లేదా రియల్ భారీ కోడ్ మార్పులు-సహాయం ఎల్లప్పుడూ స్వాగతం. మరింత అది గెట్స్ మంచి జరుగుతుంది</string>
<string name="contribution_encouragement">మీకు ఆలోచనలు ఉన్నాయా; అనువాదం, డిజైన్ మార్పులు, కోడ్ శుభ్రపరచడం లేదా నిజమైన భారీ కోడ్ మార్పులు-సహాయం ఎల్లప్పుడూ స్వాగతం. ఎంత ఎక్కువ చేస్తే అంత మంచిది!</string>
<string name="read_full_license">లైసెన్స్ చదువు</string>
<string name="contribution_title">కాంట్రిబ్యూషన్</string>
<string name="contribution_title">సహకరించటానికి</string>
<string name="title_activity_history">చరిత్ర</string>
<string name="action_history">చరిత్ర</string>
<string name="delete_item_search_history">మీరు ఈ అంశాన్ని శోధన చరిత్ర నుండి తొలగించాలనుకుంటున్నారా?</string>
@ -138,11 +138,100 @@
<string name="play_queue_stream_detail">వివరాలు</string>
<string name="play_queue_audio_settings">ఆడియో సెట్టింగ్లు</string>
<string name="hold_to_append">ఎన్క్యూలో పట్టుకోండి</string>
<string name="no_player_found">మీదగార వీడియో కి కావాల్సిన ప్లేయర్ లేదు. VLC ప్లేయర్ ఇన్స్టాల్ చేసుకుంటారా\?</string>
<string name="no_player_found_toast">మీదగార వీడియో కి కావాల్సిన ప్లేయర్ లేదు (మీరు VLC ఇసన్తాల్ చేసుకోండి )</string>
<string name="open_in_popup_mode">"పాపప్ మోడ్ లో తెరవండి"</string>
<string name="no_player_found">మీదగ్గర వీడియోకి కావాల్సిన ప్లేయర్ లేదు. VLC ప్లేయర్ని ఇన్స్టాల్ చేసుకుంటారా\?</string>
<string name="no_player_found_toast">మీదగ్గర వీడియోకి కావాల్సిన ప్లేయర్ లేదు (మీరు VLCని ఇన్స్టాల్ చేసుకోండి ).</string>
<string name="open_in_popup_mode">పాపప్ మోడ్లో తెరవండి</string>
<string name="default_popup_resolution_title">డిఫాల్ట్ పాపప్ స్పష్టత</string>
<string name="controls_download_desc">ప్రసార ఫైల్ను డౌన్లోడ్ చేయండి</string>
<string name="controls_download_desc">ప్రసార ఫైలుని డౌన్లోడ్ చేయండి</string>
<string name="use_external_video_player_title">బాహ్య వీడియో ప్లేయర్ని ఉపయోగించండి</string>
<string name="use_external_video_player_summary">కొన్ని రెసొల్యూషన్స్ లో ఆడియో తీసేస్తుంది</string>
<string name="use_external_video_player_summary">కొన్ని స్పష్టతల్లో ఆడియోను తొలగిస్తుంది</string>
<string name="start_main_player_fullscreen_title">పూర్తి స్క్రీన్‌లో ప్రధాన ప్లేయర్‌ని ప్రారంభించండి</string>
<string name="settings_category_debug_title">డీబగ్ చేయండి</string>
<string name="notification_action_nothing">ఏమిలేదు</string>
<string name="notification_scale_to_square_image_summary">నోటిఫికేషన్‌లో చూపబడిన వీడియో థంబ్‌నెయిల్‌ను 16:9 నుండి 1:1 కారక నిష్పత్తికి స్కేల్ చేయండి (వక్రీకరణలను ప్రవేశపెట్టవచ్చు)</string>
<string name="notification_action_repeat">పునరావృతం చేయండి</string>
<string name="show_hold_to_append_summary">వీడియో \"వివరాలు:\"లో బ్యాక్‌గ్రౌండ్ లేదా పాప్‌అప్ బటన్‌ను నొక్కినప్పుడు చిట్కాను చూపు</string>
<string name="unsupported_url_dialog_message">URLని గుర్తించడం సాధ్యపడలేదు. మరొక యాప్‌తో తెరవాలా\?</string>
<string name="notification_colorize_title">నోటిఫికేషన్‌ను రంగులమయం చేయండి</string>
<string name="local_search_suggestions">స్థానిక శోధన సూచనలు</string>
<string name="remote_search_suggestions">రిమోట్ శోధన సూచనలు</string>
<string name="autoplay_title">ఆటోప్లే</string>
<string name="resume_on_audio_focus_gain_summary">అంతరాయాలు (ఉదా. ఫోన్‌కాల్స్) తర్వాత ప్లే చేయడం కొనసాగించండి</string>
<string name="show_meta_info_title">మెటా సమాచారాన్ని చూపు</string>
<string name="show_play_with_kodi_summary">కోడి మీడియా సెంటర్ ద్వారా వీడియోను ప్లే చేయడానికి ఎంపికను ప్రదర్శించండి</string>
<string name="show_higher_resolutions_summary">కొన్ని పరికరాలు మాత్రమే 2K/4K వీడియోలను ప్లే చేయగలవు</string>
<string name="mark_as_watched">వీక్షించినట్లు గుర్తుపెట్టుము</string>
<string name="open_with">దీనితో తెరువుము</string>
<string name="search_showing_result_for">దీని కోసం ఫలితాలను చూపుతోంది: %s</string>
<string name="default_resolution_title">డిఫాల్ట్ స్పష్టత</string>
<string name="show_higher_resolutions_title">అధిక స్పష్టతను చూపుము</string>
<string name="black_theme_title">నలుపు</string>
<string name="popup_remember_size_pos_title">పాప్అప్ లక్షణాలను గుర్తుంచుకో</string>
<string name="download_thumbnail_title">సూక్ష్మచిత్రాలను లోడ్ చేయండి</string>
<string name="show_comments_title">వ్యాఖ్యలను చూపించు</string>
<string name="show_comments_summary">వ్యాఖ్యలను దాచడాన్ని ఆఫ్ చేయండి</string>
<string name="peertube_instance_url_help">%sలో మీకు నచ్చిన సందర్భాలను కనుగొనండి</string>
<string name="peertube_instance_url_title">పీర్‌ట్యూబ్ ఉదాహరణలు</string>
<string name="notification_action_2_title">మూడవ చర్య బటన్</string>
<string name="download_thumbnail_summary">థంబ్‌నెయిల్‌లను లోడ్ చేయడం, డేటాను సేవ్ చేయడం మరియు మెమరీ వినియోగాన్ని నిరోధించడానికి ఆఫ్ చేయండి. మార్పులు ఇన్-మెమరీ మరియు ఆన్-డిస్క్ ఇమేజ్ కాష్ రెండింటినీ క్లియర్ చేస్తాయి</string>
<string name="use_inexact_seek_summary">ఖచ్చితమైన శోధన తగ్గిన ఖచ్చితత్వంతో వేగంగా స్థానాలను పొందేందుకు ఆటగాడిని అనుమతిస్తుంది. 5, 15 లేదా 25 సెకన్ల పాటు కోరడం దీనితో పని చేయదు</string>
<string name="use_inexact_seek_title">వేగవంతమైన ఖచ్చితమైన శోధనను ఉపయోగించండి</string>
<string name="controls_add_to_playlist_title">జోడించండి</string>
<string name="tab_bookmarks">బుక్‌మార్క్ చేయబడిన వినోదజాబితాలు</string>
<string name="tab_choose">ట్యాబ్‌ని ఎంచుకోండి</string>
<string name="unsubscribe">సభ్యత్వాన్ని తొలగించుము</string>
<string name="show_info">సమాచారాన్ని చూపుము</string>
<string name="notification_scale_to_square_image_title">సూక్ష్మచిత్రాన్ని 1:1 కారక నిష్పత్తికి స్కేల్ చేయండి</string>
<string name="clear_queue_confirmation_summary">ఒక ప్లేయర్ నుండి మరొక ప్లేయర్‌కు మారడం వలన మీ క్యూను భర్తీ చేయవచ్చు</string>
<string name="auto_queue_summary">సంబంధిత స్ట్రీమ్‌ను జోడించడం ద్వారా (పునరావృతం కాని) ప్లేబ్యాక్ క్యూను ముగించడాన్ని కొనసాగించండి</string>
<string name="notification_actions_summary">దిగువన ఉన్న ప్రతి నోటిఫికేషన్ చర్యను దానిపై నొక్కడం ద్వారా సవరించండి. కుడివైపు ఉన్న చెక్‌బాక్స్‌లను ఉపయోగించడం ద్వారా కాంపాక్ట్ నోటిఫికేషన్‌లో చూపబడే వాటిలో మూడు వరకు ఎంచుకోండి</string>
<string name="popup_remember_size_pos_summary">పాప్అప్ చివరి పరిమాణం మరియు స్థానాన్ని గుర్తుంచుకోండి</string>
<string name="show_description_summary">వీడియో వివరణ మరియు అదనపు సమాచారాన్ని దాచడాన్ని ఆఫ్ చేయండి</string>
<string name="show_meta_info_summary">స్ట్రీమ్ సృష్టికర్త, స్ట్రీమ్ కంటెంట్ లేదా శోధన అభ్యర్థన గురించి అదనపు సమాచారంతో మెటా సమాచార పెట్టెలను దాచడానికి ఆఫ్ చేయండి</string>
<string name="brightness_gesture_control_summary">ప్లేయర్ ప్రకాశాన్ని నియంత్రించడానికి సంజ్ఞలను ఉపయోగించండి</string>
<string name="show_search_suggestions_summary">శోధిస్తున్నప్పుడు చూపాల్సిన సూచనలను ఎంచుకోండి</string>
<string name="enable_playback_resume_summary">చివరి ప్లేబ్యాక్ స్థానాన్ని పునరుద్ధరించండి</string>
<string name="peertube_instance_url_summary">మీకు ఇష్టమైన పీర్‌ట్యూబ్ సందర్భాలను ఎంచుకోండి</string>
<string name="enable_watch_history_summary">వీక్షించిన వీడియోలను ట్రాక్ చేయండి</string>
<string name="start_main_player_fullscreen_summary">మినీ ప్లేయర్‌లో వీడియోలను ప్రారంభించవద్దు, ఆటో రొటేషన్ లాక్ చేయబడితే, నేరుగా పూర్తి స్క్రీన్ మోడ్‌కి మారండి. మీరు పూర్తి స్క్రీన్ నుండి నిష్క్రమించడం ద్వారా ఇప్పటికీ మినీ ప్లేయర్‌ని యాక్సెస్ చేయవచ్చు</string>
<string name="peertube_instance_add_title">సందర్భాన్ని జోడించండి</string>
<string name="settings_category_updates_title">నవీకరణలు</string>
<string name="peertube_instance_add_https_only">HTTPS URLలకు మాత్రమే మద్దతు ఉంది</string>
<string name="notification_action_1_title">రెండవ చర్య బటన్</string>
<string name="notification_action_3_title">నాల్గవ చర్య బటన్</string>
<string name="report_player_errors_summary">స్వల్పకాలిక టోస్ట్ సందేశాన్ని చూపించే బదులు పూర్తి వివరాలతో ప్లేయర్ లోపాలను నివేదిస్తుంది (సమస్యలను నిర్ధారించడానికి ఉపయోగపడుతుంది)</string>
<string name="crash_the_player">ప్లేయర్ క్రాష్ చేయండి</string>
<string name="report_player_errors_title">ప్లేయర్ లోపాలను నివేదించండి</string>
<string name="notification_action_0_title">మొదటి చర్య బటన్</string>
<string name="notification_action_4_title">ఐదవ చర్య బటన్</string>
<string name="notification_actions_at_most_three">మీరు కాంపాక్ట్ నోటిఫికేషన్‌లో చూపడానికి గరిష్టంగా మూడు చర్యలను ఎంచుకోవచ్చు!</string>
<string name="notification_action_shuffle">షఫుల్ చేయండి</string>
<string name="notification_action_buffering">బఫరింగ్</string>
<string name="default_video_format_title">డిఫాల్ట్ వీడియో ఫార్మాట్</string>
<string name="night_theme_title">రాత్రి థీమ్</string>
<string name="dark_theme_title">చీకటి</string>
<string name="seek_duration_title">ఫాస్ట్-ఫార్వర్డ్/-రివైండ్ సీక్ వ్యవధి</string>
<string name="clear_queue_confirmation_title">క్యూను క్లియర్ చేయడానికి ముందు నిర్ధారణ కోసం అడగండి</string>
<string name="clear_queue_confirmation_description">క్రియాశీల ప్లేయర్ క్యూ భర్తీ చేయబడుతుంది</string>
<string name="show_description_title">వివరణను చూపు</string>
<string name="thumbnail_cache_wipe_complete_notice">చిత్రం కాష్ తుడిచివేయబడింది</string>
<string name="metadata_cache_wipe_title">కాష్ చేయబడిన మెటాడేటాను తుడిచివేయండి</string>
<string name="metadata_cache_wipe_summary">కాష్ చేసిన వెబ్‌పేజీ డేటా మొత్తాన్ని తీసివేయండి</string>
<string name="metadata_cache_wipe_complete_notice">మెటాడేటా కాష్ తుడిచివేయబడింది</string>
<string name="volume_gesture_control_title">వాల్యూమ్ సంజ్ఞ నియంత్రణ</string>
<string name="volume_gesture_control_summary">ప్లేయర్ వాల్యూమ్‌ను నియంత్రించడానికి సంజ్ఞలను ఉపయోగించండి</string>
<string name="brightness_gesture_control_title">ప్రకాశం సంజ్ఞ నియంత్రణ</string>
<string name="show_search_suggestions_title">సూచనలను శోధించండి</string>
<string name="enable_search_history_title">శోధన చరిత్ర</string>
<string name="enable_search_history_summary">శోధన ప్రశ్నలను స్థానికంగా నిల్వ చేయండి</string>
<string name="enable_playback_resume_title">ప్లేబ్యాక్ పునఃప్రారంభించండి</string>
<string name="enable_playback_state_lists_title">జాబితాలలో స్థానాలు</string>
<string name="enable_playback_state_lists_summary">జాబితాలలో ప్లేబ్యాక్ స్థాన సూచికలను చూపు</string>
<string name="settings_category_clear_data_title">డేటాను క్లియర్ చేయండి</string>
<string name="resume_on_audio_focus_gain_title">ప్లే చేయడం కొనసాగించండి</string>
<string name="default_content_country_title">డిఫాల్ట్ కంటెంట్ దేశం</string>
<string name="peertube_instance_add_help">ఉదాహరణ URLని నమోదు చేయండి</string>
<string name="peertube_instance_add_fail">ఉదాహరణను ధృవీకరించడం సాధ్యపడలేదు</string>
<string name="peertube_instance_add_exists">ఉదాహరణ ఇప్పటికే ఉంది</string>
<string name="settings_category_notification_title">నోటిఫికేషన్</string>
</resources>

View File

@ -115,7 +115,7 @@
<string name="enable_watch_history_title">Історія переглядів</string>
<string name="resume_on_audio_focus_gain_title">Відновлювати відтворення</string>
<string name="resume_on_audio_focus_gain_summary">Продовжувати відтворення після переривань (напр. телефонних дзвінків)</string>
<string name="show_hold_to_append_title">Показати пораду «Утримуй, щоб додати»</string>
<string name="show_hold_to_append_title">Показувати підказку «Утримуйте, щоб додати в чергу»</string>
<string name="default_content_country_title">Типова країна вмісту</string>
<string name="settings_category_player_title">Програвач</string>
<string name="settings_category_player_behavior_title">Поведінка</string>
@ -130,7 +130,7 @@
<string name="notification_channel_name">Сповіщення NewPipe</string>
<string name="notification_channel_description">Сповіщення для програвачів NewPipe на тлі й у вікні</string>
<string name="unknown_content">[Невідомо]</string>
<string name="switch_to_background">Перемкнути на тло</string>
<string name="switch_to_background">Перейти у фоновий режим</string>
<string name="switch_to_popup">Перемкнути у вікно</string>
<string name="switch_to_main">Перемкнути на головну</string>
<string name="import_data_title">Імпортувати базу даних</string>
@ -258,7 +258,7 @@
<string name="enable_disposed_exceptions_summary">Примусове звітування про неможливість доставлення Rx-винятків, які відбуваються за межами фрагменту або діяльності життєвого циклу після усунення</string>
<string name="use_inexact_seek_title">Викор. швидкий неточний пошук</string>
<string name="use_inexact_seek_summary">Неточний пошук дозволяє програвачеві рухатися позиціями швидше, проте з меншою точністю</string>
<string name="auto_queue_title">Автододавання в чергу наступного запису</string>
<string name="auto_queue_title">Автододавання в чергу наступної трансляції</string>
<string name="auto_queue_summary">Продовжити при завершені (не повторюваної) черги, додавши повʼязаний запис</string>
<string name="file">Файл</string>
<string name="invalid_directory">Такої теки не існує</string>
@ -583,7 +583,7 @@
<string name="comments_tab_description">Коментарі</string>
<string name="settings_category_player_notification_summary">Налаштувати повідомлення про відтворюваний наразі потік</string>
<string name="unsupported_url_dialog_message">Не розпізнано URL. Відкрити через іншу програму\?</string>
<string name="auto_queue_toggle">Самододавання в чергу</string>
<string name="auto_queue_toggle">Автоматична черга</string>
<string name="show_meta_info_title">Показувати метадані</string>
<string name="show_description_title">Показувати описи</string>
<string name="night_theme_title">Нічна тема</string>

View File

@ -71,6 +71,8 @@
<dimen name="channel_rss_title_size">12sp</dimen>
<!-- Elements Size -->
<dimen name="video_item_detail_uploader_image_size">32dp</dimen>
<dimen name="video_item_detail_pinned_image_width">18sp</dimen>
<dimen name="video_item_detail_pinned_image_height">18sp</dimen>
<dimen name="video_item_detail_sub_channel_image_size">16dp</dimen>
<dimen name="video_item_detail_like_image_height">18sp</dimen>
<dimen name="video_item_detail_like_image_width">18sp</dimen>
@ -79,6 +81,7 @@
<dimen name="sub_channel_avatar_size">35dp</dimen>
<dimen name="mini_player_height">60dp</dimen>
<!-- Paddings & Margins -->
<dimen name="video_item_detail_pinned_right_margin">5dp</dimen>
<dimen name="video_item_detail_like_margin">5dp</dimen>
<dimen name="video_item_detail_heart_margin">5dp</dimen>
<dimen name="video_item_detail_error_panel_margin">50dp</dimen>

View File

@ -89,8 +89,6 @@
<item>@string/never</item>
</string-array>
<string name="report_player_errors_key" translatable="false">report_player_errors_key</string>
<string name="seekbar_preview_thumbnail_key" translatable="false">seekbar_preview_thumbnail_key</string>
<string name="seekbar_preview_thumbnail_high_quality" translatable="false">seekbar_preview_thumbnail_high_quality</string>
<string name="seekbar_preview_thumbnail_low_quality" translatable="false">seekbar_preview_thumbnail_low_quality</string>
@ -188,10 +186,12 @@
<string name="allow_disposed_exceptions_key" translatable="false">allow_disposed_exceptions_key</string>
<string name="show_original_time_ago_key" translatable="false">show_original_time_ago_key</string>
<string name="disable_media_tunneling_key" translatable="false">disable_media_tunneling_key</string>
<string name="crash_the_app_key" translatable="false">crash_the_app_key</string>
<string name="show_image_indicators_key" translatable="false">show_image_indicators_key</string>
<string name="show_crash_the_player_key" translatable="false">show_crash_the_player_key</string>
<string name="check_new_streams_key" translatable="false">check_new_streams</string>
<string name="crash_the_app_key" translatable="false">crash_the_app_key</string>
<string name="show_error_snackbar_key" translatable="false">show_error_snackbar_key</string>
<string name="create_error_notification_key" translatable="false">create_error_notification_key</string>
<!-- THEMES -->
<string name="theme_key" translatable="false">theme</string>

View File

@ -53,8 +53,6 @@
<string name="show_play_with_kodi_title">Show \"Play with Kodi\" option</string>
<string name="show_play_with_kodi_summary">Display an option to play a video via Kodi media center</string>
<string name="crash_the_player">Crash the player</string>
<string name="report_player_errors_title">Report player errors</string>
<string name="report_player_errors_summary">Reports player errors in full detail instead of showing a short-lived toast message (useful for diagnosing problems)</string>
<string name="notification_scale_to_square_image_title">Scale thumbnail to 1:1 aspect ratio</string>
<string name="notification_scale_to_square_image_summary">Scale the video thumbnail shown in the notification from 16:9 to 1:1 aspect ratio (may introduce distortions)</string>
<string name="notification_action_0_title">First action button</string>
@ -99,9 +97,9 @@
<string name="metadata_cache_wipe_title">Wipe cached metadata</string>
<string name="metadata_cache_wipe_summary">Remove all cached webpage data</string>
<string name="metadata_cache_wipe_complete_notice">Metadata cache wiped</string>
<string name="auto_queue_title">Auto-queue next stream</string>
<string name="auto_queue_title">Auto-enqueue next stream</string>
<string name="auto_queue_summary">Continue ending (non-repeating) playback queue by appending a related stream</string>
<string name="auto_queue_toggle">Auto-queue</string>
<string name="auto_queue_toggle">Auto-enqueuing</string>
<string name="volume_gesture_control_title">Volume gesture control</string>
<string name="volume_gesture_control_summary">Use gestures to control player volume</string>
<string name="brightness_gesture_control_title">Brightness gesture control</string>
@ -125,7 +123,7 @@
<string name="start_main_player_fullscreen_title">Start main player in fullscreen</string>
<string name="start_main_player_fullscreen_summary">Do not start videos in the mini player, but turn to fullscreen mode directly, if auto rotation is locked. You can still access the mini player by exiting fullscreen</string>
<string name="autoplay_title">Autoplay</string>
<string name="show_hold_to_append_title">Show \"Hold to append\" tip</string>
<string name="show_hold_to_append_title">Show \"Hold to enqueue\" tip</string>
<string name="show_hold_to_append_summary">Show tip when pressing the background or the popup button in video \"Details:\"</string>
<string name="unsupported_url">Unsupported URL</string>
<string name="unsupported_url_dialog_message">Could not recognize the URL. Open with another app?</string>
@ -150,6 +148,7 @@
<string name="settings_category_player_notification_title">Player notification</string>
<string name="settings_category_player_notification_summary">Configure current playing stream notification</string>
<string name="background_player_playing_toast">Playing in background</string>
<string name="background_player_already_playing_toast">Already playing in background</string>
<string name="popup_playing_toast">Playing in popup mode</string>
<string name="content">Content</string>
<string name="show_age_restricted_content_title">Show age restricted content</string>
@ -184,17 +183,20 @@
<string name="file">File</string>
<string name="notifications">Notifications</string>
<string name="notification_channel_id" translatable="false">newpipe</string>
<string name="notification_channel_name">NewPipe Notification</string>
<string name="notification_channel_description">Notifications for NewPipe background and popup players</string>
<string name="notification_channel_name">NewPipe notification</string>
<string name="notification_channel_description">Notifications for NewPipe\'s player</string>
<string name="app_update_notification_channel_id" translatable="false">newpipeAppUpdate</string>
<string name="app_update_notification_channel_name">App Update Notification</string>
<string name="app_update_notification_channel_description">Notifications for new NewPipe version</string>
<string name="app_update_notification_channel_name">App update notification</string>
<string name="app_update_notification_channel_description">Notifications for new NewPipe versions</string>
<string name="hash_channel_id" translatable="false">newpipeHash</string>
<string name="hash_channel_name">Video Hash Notification</string>
<string name="hash_channel_name">Video hash notification</string>
<string name="hash_channel_description">Notifications for video hashing progress</string>
<string name="streams_notification_channel_id" translatable="false">newpipeNewStreams</string>
<string name="streams_notification_channel_name">New streams</string>
<string name="streams_notification_channel_description">Notifications about new streams for subscriptions</string>
<string name="error_report_channel_id" translatable="false">newpipeErrorReport</string>
<string name="error_report_channel_name">Error report notification</string>
<string name="error_report_channel_description">Notifications to report errors</string>
<string name="unknown_content">[Unknown]</string>
<string name="switch_to_background">Switch to Background</string>
<string name="switch_to_popup">Switch to Popup</string>
@ -248,6 +250,8 @@
<string name="restore_defaults_confirmation">Do you want to restore defaults?</string>
<string name="permission_display_over_apps">Give permission to display over other apps</string>
<!-- error activity -->
<string name="error_report_notification_title">NewPipe encountered an error, tap to report</string>
<string name="error_report_notification_toast">An error occurred, see the notification</string>
<string name="sorry_string">Sorry, that should not have happened.</string>
<string name="guru_meditation" translatable="false">Guru Meditation.</string>
<string name="error_report_button_text">Report this error via e-mail</string>
@ -484,10 +488,12 @@
<string name="disable_media_tunneling_summary">Disable media tunneling if you experience a black screen or stuttering on video playback</string>
<string name="show_image_indicators_title">Show image indicators</string>
<string name="show_image_indicators_summary">Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory</string>
<string name="crash_the_app">Crash the app</string>
<string name="show_crash_the_player_title">Show \"crash the player\"</string>
<string name="show_crash_the_player_summary">Shows a crash option when using the player</string>
<string name="check_new_streams">Run check for new streams</string>
<string name="crash_the_app">Crash the app</string>
<string name="show_error_snackbar">Show an error snackbar</string>
<string name="create_error_notification">Create an error notification</string>
<!-- Subscriptions import/export -->
<string name="import_title">Import</string>
<string name="import_from">Import from</string>
@ -697,6 +703,8 @@
<string name="recent">Recent</string>
<string name="chapters">Chapters</string>
<string name="no_app_to_open_intent">No app on your device can open this</string>
<string name="no_appropriate_file_manager_message">No appropriate file manager was found for this action.\nPlease install a file manager or try to disable \'%s\' in the download settings.</string>
<string name="no_appropriate_file_manager_message_android_10">No appropriate file manager was found for this action.\nPlease install a Storage Access Framework compatible file manager.</string>
<string name="georestricted_content">This content is not available in your country.</string>
<string name="soundcloud_go_plus_content">This is a SoundCloud Go+ track, at least in your country, so it cannot be streamed or downloaded by NewPipe.</string>
<string name="private_content">This content is private, so it cannot be streamed or downloaded by NewPipe.</string>
@ -726,6 +734,7 @@
<string name="metadata_privacy_unlisted">Unlisted</string>
<string name="metadata_privacy_private">Private</string>
<string name="metadata_privacy_internal">Internal</string>
<string name="detail_pinned_comment_view_description">Pinned comment</string>
<string name="detail_heart_img_view_description">Hearted by creator</string>
<string name="open_website_license">Open website</string>
<string name="tablet_mode_title">Tablet mode</string>

View File

@ -51,10 +51,9 @@
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/report_player_errors_key"
android:summary="@string/report_player_errors_summary"
android:title="@string/report_player_errors_title"
app:singleLineTitle="false"
android:key="@string/show_crash_the_player_key"
android:summary="@string/show_crash_the_player_summary"
android:title="@string/show_crash_the_player_title"
app:iconSpaceReserved="false" />
<Preference
@ -69,13 +68,15 @@
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:defaultValue="false"
android:key="@string/show_crash_the_player_key"
android:summary="@string/show_crash_the_player_summary"
android:title="@string/show_crash_the_player_title"
<Preference
android:key="@string/show_error_snackbar_key"
android:title="@string/show_error_snackbar"
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
<Preference
android:key="@string/create_error_notification_key"
android:title="@string/create_error_notification"
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
</PreferenceScreen>

View File

@ -9,12 +9,12 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.Mockito.withSettings
import org.mockito.junit.MockitoJUnitRunner
import org.schabi.newpipe.streams.io.StoredFileHelper

View File

@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong

View File

@ -1,14 +1,15 @@
Neu und verbessert:
Einstellung, kein Vorschaubild auf dem Lockscreen anzuzeigen, wurde hinzugefügt
Feeds durch Gesten aktualisieren
Verbesserte Performance beim laden von lokalen Listen
Neu+verbessert
Option Miniansicht Ausblenden auf Sperrbildschirm wieder hinzugefügt
Ziehen zum Feed aktualisieren
Verbesserte Leistung beim Abruf lokaler Listen
Behoben:
• Absturz beim Starten von NewPipe ohne Internetverbindung
• Absturz beim Wiederherstellen von NewPipe aus dem Arbeitsspeicher
• [YouTube] Lange Wiedergabelisten
Behoben
•Absturz, Start von NewPipe, nachdem es aus dem RAM entfernt wurde
•Absturz, Starten von NewPipe ohne Internetverbindung
•Einstellungen Helligkeits- und Lautstärkegesten
•[YT] Lange Wiedergabelisten
Anderes:
Code Verbesserungen und mehrere interne Verbesserungen
Bibliotheken wurden aktualisiert
aktualisierte Übersetzungen
Sonstiges
Codebereinigung, verschiedene interne Verbesserungen
Aktualisierung Abhängigkeiten
Aktualisierte Übersetzungen

View File

@ -0,0 +1,2 @@
Removed MediaParser support to fix failing playback resume after buffering on Android 11+.
Disabled media tunneling on Philips QM16XE to fix playback problems.

View File

@ -0,0 +1,13 @@
חדש
• נוספה האפשרות „הוספה לרשימת נגינה” לתפריט השיתוף
• נוספה תמיכה ב־y2u.be וקישורים מקוצרים של PeerTube
שיפורים
• פקדי מהירות הנגינה צומצמו
• ההזנה מדגישה מעתה פריטים חדשים
• האפשרות „הצגת פריטים שנצפו” בהזנה נשמרת מעתה
תיקונים
• תוקן חילוץ הלייקים והלא לייקים ב־YouTube
• תוקנה נגינה חוזרת אוטומטית בחזרה מעבודה ברקע
קצרה היריעה מלהכיל

View File

@ -0,0 +1,8 @@
### Fejlesztések
- Beállítások importálása/exportálása #1333
- Túlrajzolás csökkentése (teljesítménybeli javítás) #1371
- Apró kódfejlesztések #1375
- GDPR információk hozzáadása #1420
### Javítva
- Letöltés: A befejezetlen .giga fájlokból történő betöltés közbeni összeomlás javítása #1407

View File

@ -1 +1 @@
A NewPipe nem használ semmilyen Google vázkönyvtárat vagy a YouTube API-t. Csupán a weboldalt elemzi, hogy megszerezze a szükséges információt. Így ez az alkalmazás a Google Szolgáltatások nélkül futó ezközökön is használható. Emellett, a NewPipe használatához nincs szükséged YouTube fiókra,
A NewPipe nem használ semmilyen Google keretrendszer könyvtárat, sem a YouTube API-t. Csupán a weboldalt dolgozza fel, hogy kinyerje a szükséges információkat. Így ez az alkalmazás a Google Szolgáltatások nélkül futó eszközökön is használható. Továbbá a NewPipe használatához nincs szükség YouTube-fiókra sem. A NewPipe szabad és nyílt forráskódú szoftver.

View File

@ -0,0 +1,8 @@
### ਸੁਧਾਰ
- ਆਯਾਤ/ਨਿਰਯਾਤ ਸੈਟਿੰਗਾਂ #1333
- ਓਵਰ ਡਰਾਅ ਘਟਾਇਆ ਗਿਆ (ਕਾਰਗੁਜ਼ਾਰੀ ਵਿੱਚ ਸੁਧਾਰ) #1371
- ਨਿੱਕੇ ਕੋਡ ਸੁਧਾਰ #1375
- ਜੀ.ਡੀ. ਪੀ.ਆਰ. ਬਾਰੇ ਸਾਰਾ ਕੁਝ ਜੋੜਿਆ #1420
### ਸਹੀ ਕੀਤਾ ਗਿਆ
- ਡਾਊਨਲੋਡਰ: .giga ਨਾਮਕ ਫਾਈਲਾਂ ਤੋਂ ਅਧੂਰੇ ਡਾਊਨਲੋਡ ਲੋਡ ਕਰਨ ਤੇ ਬੰਦ ਹੋਣਾ ਸਹੀ ਕੀਤਾ ਗਿਆ

View File

@ -0,0 +1,8 @@
### ਸੁਧਾਰ
- ਮੋਬਾਈਲ ਡਾਟਾ ਦੀ ਵਰਤੋਂ ਕਰਨ ਤੇ ਵੀਡੀਓ ਗੁਣਤਾ ਨੂੰ ਸੀਮਤ ਕਰਨ ਦੀ ਸਮਰੱਥਾ ਨੂੰ ਜੋੜਿਆ। #1339
- ਸੈਸ਼ਨ ਲਈ ਚਮਕ ਯਾਦ ਰੱਖੋ #1442
- ਕਮਜ਼ੋਰ ਸੀ ਪੀ ਯੂ #1431 ਲਈ ਡਾਊਨਲੋਡ ਪ੍ਰਦਰਸ਼ਨ ਵਿੱਚ ਸੁਧਾਰ ਕੀਤਾ
- ਮੀਡੀਆ ਸੈਸ਼ਨ #1433 ਲਈ (ਕੰਮ ਕਰਨਾ) ਸਮਰਥਨ ਸ਼ਾਮਲ ਕਰੋ
### ਸਹੀ ਕੀਤਾ ਗਿਆ
- ਸ਼ੁਰੂਆਤੀ ਡਾਊਨਲੋਡਾਂ 'ਤੇ ਕਰੈਸ਼ ਨੂੰ ਠੀਕ ਕੀਤਾ (ਰਿਲੀਜ਼ ਬਿਲਡਾਂ ਲਈ ਹੁਣ ਇਹ ਫਿਕਸ ਉਪਲਬਧ ਹੈ) #1441

View File

@ -0,0 +1,13 @@
Nowe
• Dodano opcję „Dodaj do playlisty” do menu kontekstowego
• Dodano obsługę krótkich linków dla y2u.be and PeerTube
Poprawione
• Bardziej kompaktowe sterowanie prędkością odtwarzania
• Kanał wyróżnia teraz nowe pozycje
• Opcja „Pokaż obejrzane pozycje” w kanale jest teraz zapisywana
Naprawione
• Naprawiono wyciąganie polubień i łapek w dół z YouTube'a
• Naprawiono automatyczne odtwarzanie po powracaniu z tła
I wiele więcej

View File

@ -0,0 +1,8 @@
### மேம்பாடுகள்
- இறக்குமதி/ஏற்றுமதி அமைப்புகள் #1333
- ஓவர் டிராவைக் குறைக்கவும் (செயல்திறன் மேம்பாடு) #1371
- சிறிய குறியீடு மேம்பாடுகள் #1375
- GDPR #1420 பற்றிய அனைத்தையும் சேர்க்கவும்
### சரி செய்யப்பட்டது
- டவுன்லோடர்: .giga கோப்புகள் #1407 இலிருந்து முடிக்கப்படாத பதிவிறக்கங்களை ஏற்றுவதில் ஏற்படும் செயலிழப்பை சரிசெய்யவும்

View File

@ -0,0 +1,8 @@
### மேம்பாடுகள்
- மொபைல் டேட்டாவைப் பயன்படுத்தினால் வீடியோ தரத்தைக் கட்டுப்படுத்தும் திறன் சேர்க்கப்பட்டது. #1339
- அமர்வு #1442 க்கான பிரகாசத்தை நினைவில் கொள்க
- பலவீனமான CPUகளுக்கான பதிவிறக்க செயல்திறனை மேம்படுத்தவும் #1431
- மீடியா அமர்வு #1433க்கு (வேலை செய்யும்) ஆதரவைச் சேர்க்கவும்
### சரி
- பதிவிறக்கங்களைத் திறப்பதில் ஏற்படும் செயலிழப்பைச் சரிசெய்தல் (வெளியீட்டு உருவாக்கங்களுக்கு இப்போது சரிசெய்தல் கிடைக்கிறது) #1441

View File

@ -0,0 +1 @@
NewPipe எந்த Google கட்டமைப்பு நூலகங்களையும் அல்லது YouTube API ஐயும் பயன்படுத்துவதில்லை. இணையத்தளத்திற்குத் தேவையான தகவல்களைப் பெறுவதற்காக மட்டுமே இது அலசுகிறது. எனவே Google சேவைகள் நிறுவப்படாத சாதனங்களில் இந்தப் பயன்பாட்டைப் பயன்படுத்தலாம். மேலும், NewPipe ஐப் பயன்படுத்த உங்களுக்கு YouTube கணக்கு தேவையில்லை, அது FLOSS ஆகும்.

View File

@ -0,0 +1,8 @@
### మెరుగుదలలు
- దిగుమతి/ఎగుమతి సెట్టింగ్‌లు #1333
- ఓవర్‌డ్రాను తగ్గించండి (పనితీరు మెరుగుదల) #1371
- చిన్న కోడ్ మెరుగుదలలు #1375
- GDPR #1420 గురించి అన్నింటినీ జోడించండి
### పరిష్కరించబడినవి
- డౌన్‌లోడర్: .giga ఫైల్స్ #1407 నుండి అసంపూర్తి డౌన్‌లోడ్‌లను లోడ్ చేయడంలో క్రాష్‌ని పరిష్కరించండి

Some files were not shown because too many files have changed in this diff Show More