diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54415858e..a184dd83d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,8 +72,8 @@ jobs: - api-level: 21 target: default arch: x86 - - api-level: 33 - target: google_apis # emulator API 33 only exists with Google APIs + - api-level: 35 + target: default arch: x86_64 permissions: @@ -111,6 +111,7 @@ jobs: path: app/build/reports/androidTests/connected/** sonar: + if: ${{ false }} # the key has expired and needs to be regenerated by the sonar admins runs-on: ubuntu-latest permissions: diff --git a/README.md b/README.md index bf1317f17..aa4332165 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,26 @@ -
refactor branch if you want to contribute new features. The current codebase is in maintenance mode and will only receive bugfixes.Screenshots • Supported Services • Description • Features • Installation and updates • Contribution • Donate • License
diff --git a/app/build.gradle b/app/build.gradle index 0841086ad..02146c5f8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,14 +17,14 @@ plugins { } android { - compileSdk 35 + compileSdk 36 namespace 'org.schabi.newpipe' defaultConfig { applicationId "org.schabi.newpipe" resValue "string", "app_name", "NewPipe" minSdk 21 - targetSdk 33 + targetSdk 35 if (System.properties.containsKey('versionCodeOverride')) { versionCode System.getProperty('versionCodeOverride') as Integer } else { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3faa59f7d..0ac368898 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@- * This class contains basic manipulation of a playlist while also functions as a - * message bus, providing all listeners with new updates to the play queue. - *
- *- * This class can be serialized for passing intents, but in order to start the - * message bus, it must be initialized. - *
- */ -public abstract class PlayQueue implements Serializable { - public static final boolean DEBUG = MainActivity.DEBUG; - @NonNull - private final AtomicInteger queueIndex; - private final List- * Also starts a self reporter for logging if debug mode is enabled. - *
- */ - public void init() { - eventBroadcast = BehaviorSubject.create(); - - broadcastReceiver = eventBroadcast.toFlowable(BackpressureStrategy.BUFFER) - .observeOn(AndroidSchedulers.mainThread()) - .startWithItem(new InitEvent()); - } - - /** - * Dispose the play queue by stopping all message buses. - */ - public void dispose() { - if (eventBroadcast != null) { - eventBroadcast.onComplete(); - } - - eventBroadcast = null; - broadcastReceiver = null; - disposed = true; - } - - /** - * Checks if the queue is complete. - *- * A queue is complete if it has loaded all items in an external playlist - * single stream or local queues are always complete. - *
- * - * @return whether the queue is complete - */ - public abstract boolean isComplete(); - - /** - * Load partial queue in the background, does nothing if the queue is complete. - */ - public abstract void fetch(); - - /*////////////////////////////////////////////////////////////////////////// - // Readonly ops - //////////////////////////////////////////////////////////////////////////*/ - - /** - * @return the current index that should be played - */ - public int getIndex() { - return queueIndex.get(); - } - - /** - * Changes the current playing index to a new index. - *- * This method is guarded using in a circular manner for index exceeding the play queue size. - *
- *- * Will emit a {@link SelectEvent} if the index is not the current playing index. - *
- * - * @param index the index to be set - */ - public synchronized void setIndex(final int index) { - final int oldIndex = getIndex(); - - final int newIndex; - - if (index < 0) { - newIndex = 0; - } else if (index < streams.size()) { - // Regular assignment for index in bounds - newIndex = index; - } else if (streams.isEmpty()) { - // Out of bounds from here on - // Need to check if stream is empty to prevent arithmetic error and negative index - newIndex = 0; - } else if (isComplete()) { - // Circular indexing - newIndex = index % streams.size(); - } else { - // Index of last element - newIndex = streams.size() - 1; - } - - queueIndex.set(newIndex); - - if (oldIndex != newIndex) { - history.add(streams.get(newIndex)); - } - - /* - TODO: Documentation states that a SelectEvent will only be emitted if the new index is... - different from the old one but this is emitted regardless? Not sure what this what it does - exactly so I won't touch it - */ - broadcast(new SelectEvent(oldIndex, newIndex)); - } - - /** - * @return the current item that should be played, or null if the queue is empty - */ - @Nullable - public PlayQueueItem getItem() { - return getItem(getIndex()); - } - - /** - * @param index the index of the item to return - * @return the item at the given index, or null if the index is out of bounds - */ - @Nullable - public PlayQueueItem getItem(final int index) { - if (index < 0 || index >= streams.size()) { - return null; - } - return streams.get(index); - } - - /** - * Returns the index of the given item using referential equality. - * May be null despite play queue contains identical item. - * - * @param item the item to find the index of - * @return the index of the given item - */ - public int indexOf(@NonNull final PlayQueueItem item) { - return streams.indexOf(item); - } - - /** - * @return the current size of play queue. - */ - public int size() { - return streams.size(); - } - - /** - * Checks if the play queue is empty. - * - * @return whether the play queue is empty - */ - public boolean isEmpty() { - return streams.isEmpty(); - } - - /** - * Determines if the current play queue is shuffled. - * - * @return whether the play queue is shuffled - */ - public boolean isShuffled() { - return backup != null; - } - - /** - * @return an immutable view of the play queue - */ - @NonNull - public List- * Will emit a {@link SelectEvent} if offset is non-zero. - *
- * - * @param offset the offset relative to the current index - */ - public synchronized void offsetIndex(final int offset) { - setIndex(getIndex() + offset); - } - - /** - * Notifies that a change has occurred. - */ - public synchronized void notifyChange() { - broadcast(new AppendEvent(0)); - } - - /** - * Appends the given {@link PlayQueueItem}s to the current play queue. - *- * If the play queue is shuffled, then append the items to the backup queue as is and - * append the shuffle items to the play queue. - *
- *- * Will emit a {@link AppendEvent} on any given context. - *
- * - * @param items {@link PlayQueueItem}s to append - */ - public synchronized void append(@NonNull final List- * The current playing index will decrement if it is greater than the index being removed. - * On cases where the current playing index exceeds the playlist range, it is set to 0. - *
- *- * Will emit a {@link RemoveEvent} if the index is within the play queue index range. - *
- * - * @param index the index of the item to remove - */ - public synchronized void remove(final int index) { - if (index >= streams.size() || index < 0) { - return; - } - removeInternal(index); - broadcast(new RemoveEvent(index, getIndex())); - } - - /** - * Report an exception for the item at the current index in order and skip to the next one - *- * This is done as a separate event as the underlying manager may have - * different implementation regarding exceptions. - *
- */ - public synchronized void error() { - final int oldIndex = getIndex(); - queueIndex.incrementAndGet(); - if (streams.size() > queueIndex.get()) { - history.add(streams.get(queueIndex.get())); - } - broadcast(new ErrorEvent(oldIndex, getIndex())); - } - - private synchronized void removeInternal(final int removeIndex) { - final int currentIndex = queueIndex.get(); - final int size = size(); - - if (currentIndex > removeIndex) { - queueIndex.decrementAndGet(); - - } else if (currentIndex >= size) { - queueIndex.set(currentIndex % (size - 1)); - - } else if (currentIndex == removeIndex && currentIndex == size - 1) { - queueIndex.set(0); - } - - if (backup != null) { - backup.remove(getItem(removeIndex)); - } - - history.remove(streams.remove(removeIndex)); - if (streams.size() > queueIndex.get()) { - history.add(streams.get(queueIndex.get())); - } - } - - /** - * Moves a queue item at the source index to the target index. - *- * If the item being moved is the currently playing, then the current playing index is set - * to that of the target. - * If the moved item is not the currently playing and moves to an index AFTER the - * current playing index, then the current playing index is decremented. - * Vice versa if the an item after the currently playing is moved BEFORE. - *
- * - * @param source the original index of the item - * @param target the new index of the item - */ - public synchronized void move(final int source, final int target) { - if (source < 0 || target < 0) { - return; - } - if (source >= streams.size() || target >= streams.size()) { - return; - } - - final int current = getIndex(); - if (source == current) { - queueIndex.set(target); - } else if (source < current && target >= current) { - queueIndex.decrementAndGet(); - } else if (source > current && target <= current) { - queueIndex.incrementAndGet(); - } - - final PlayQueueItem playQueueItem = streams.remove(source); - playQueueItem.setAutoQueued(false); - streams.add(target, playQueueItem); - broadcast(new MoveEvent(source, target)); - } - - /** - * Sets the recovery record of the item at the index. - *- * Broadcasts a recovery event. - *
- * - * @param index index of the item - * @param position the recovery position - */ - public synchronized void setRecovery(final int index, final long position) { - if (index < 0 || index >= streams.size()) { - return; - } - - streams.get(index).setRecoveryPosition(position); - broadcast(new RecoveryEvent(index, position)); - } - - /** - * Revoke the recovery record of the item at the index. - *- * Broadcasts a recovery event. - *
- * - * @param index index of the item - */ - public synchronized void unsetRecovery(final int index) { - setRecovery(index, PlayQueueItem.RECOVERY_UNSET); - } - - /** - * Shuffles the current play queue - *- * This method first backs up the existing play queue and item being played. Then a newly - * shuffled play queue will be generated along with currently playing item placed at the - * beginning of the queue. This item will also be added to the history. - *
- *- * Will emit a {@link ReorderEvent} if shuffled. - *
- * - * @implNote Does nothing if the queue has a size <= 2 (the currently playing video must stay on - * top, so shuffling a size-2 list does nothing) - */ - public synchronized void shuffle() { - // Create a backup if it doesn't already exist - // Note: The backup-list has to be created at all cost (even when size <= 2). - // Otherwise it's not possible to enter shuffle-mode! - if (backup == null) { - backup = new ArrayList<>(streams); - } - // Can't shuffle a list that's empty or only has one element - if (size() <= 2) { - return; - } - - final int originalIndex = getIndex(); - final PlayQueueItem currentItem = getItem(); - - Collections.shuffle(streams); - - // Move currentItem to the head of the queue - streams.remove(currentItem); - streams.add(0, currentItem); - queueIndex.set(0); - - history.add(currentItem); - - broadcast(new ReorderEvent(originalIndex, 0)); - } - - /** - * Unshuffles the current play queue if a backup play queue exists. - *- * This method undoes shuffling and index will be set to the previously playing item if found, - * otherwise, the index will reset to 0. - *
- *- * Will emit a {@link ReorderEvent} if a backup exists. - *
- */ - public synchronized void unshuffle() { - if (backup == null) { - return; - } - final int originIndex = getIndex(); - final PlayQueueItem current = getItem(); - - streams = backup; - backup = null; - - final int newIndex = streams.indexOf(current); - if (newIndex != -1) { - queueIndex.set(newIndex); - } else { - queueIndex.set(0); - } - if (streams.size() > queueIndex.get()) { - history.add(streams.get(queueIndex.get())); - } - - broadcast(new ReorderEvent(originIndex, queueIndex.get())); - } - - /** - * Selects previous played item. - * - * This method removes currently playing item from history and - * starts playing the last item from history if it exists - * - * @return true if history is not empty and the item can be played - * */ - public synchronized boolean previous() { - if (history.size() <= 1) { - return false; - } - - history.remove(history.size() - 1); - - final PlayQueueItem last = history.remove(history.size() - 1); - setIndex(indexOf(last)); - - return true; - } - - /* - * Compares two PlayQueues. Useful when a user switches players but queue is the same so - * we don't have to do anything with new queue. - * This method also gives a chance to track history of items in a queue in - * VideoDetailFragment without duplicating items from two identical queues - */ - public boolean equalStreams(@Nullable final PlayQueue other) { - if (other == null) { - return false; - } - if (size() != other.size()) { - return false; - } - for (int i = 0; i < size(); i++) { - final PlayQueueItem stream = streams.get(i); - final PlayQueueItem otherStream = other.streams.get(i); - // Check is based on serviceId and URL - if (stream.getServiceId() != otherStream.getServiceId() - || !stream.getUrl().equals(otherStream.getUrl())) { - return false; - } - } - return true; - } - - public boolean equalStreamsAndIndex(@Nullable final PlayQueue other) { - if (equalStreams(other)) { - //noinspection ConstantConditions - return other.getIndex() == getIndex(); //NOSONAR: other is not null - } - return false; - } - - public boolean isDisposed() { - return disposed; - } - /*////////////////////////////////////////////////////////////////////////// - // Rx Broadcast - //////////////////////////////////////////////////////////////////////////*/ - - private void broadcast(@NonNull final PlayQueueEvent event) { - if (eventBroadcast != null) { - eventBroadcast.onNext(event); - } - } -} - diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.kt b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.kt new file mode 100644 index 000000000..1ae7e5cdb --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.kt @@ -0,0 +1,497 @@ +package org.schabi.newpipe.player.playqueue + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.BackpressureStrategy +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.subjects.BehaviorSubject +import org.schabi.newpipe.player.playqueue.events.AppendEvent +import org.schabi.newpipe.player.playqueue.events.ErrorEvent +import org.schabi.newpipe.player.playqueue.events.InitEvent +import org.schabi.newpipe.player.playqueue.events.MoveEvent +import org.schabi.newpipe.player.playqueue.events.PlayQueueEvent +import org.schabi.newpipe.player.playqueue.events.RecoveryEvent +import org.schabi.newpipe.player.playqueue.events.RemoveEvent +import org.schabi.newpipe.player.playqueue.events.ReorderEvent +import org.schabi.newpipe.player.playqueue.events.SelectEvent +import java.io.Serializable +import java.util.Collections +import java.util.concurrent.atomic.AtomicInteger + +/** + * PlayQueue is responsible for keeping track of a list of streams and the index of + * the stream that should be currently playing. + * + * This class contains basic manipulation of a playlist while also functions as a + * message bus, providing all listeners with new updates to the play queue. + * + * This class can be serialized for passing intents, but in order to start the + * message bus, it must be initialized. + */ +abstract class PlayQueue internal constructor( + index: Int, + startWith: List+ * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + *
+ *+ * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + *
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with NewPipe. If not, see
@@ -156,12 +196,13 @@ public final class SettingMigrations {
MIGRATION_3_4,
MIGRATION_4_5,
MIGRATION_5_6,
+ MIGRATION_6_7
};
/**
* Version number for preferences. Must be incremented every time a migration is necessary.
*/
- private static final int VERSION = 6;
+ private static final int VERSION = 7;
public static void runMigrationsIfNeeded(@NonNull final Context context) {
@@ -208,6 +249,21 @@ public final class SettingMigrations {
sp.edit().putInt(lastPrefVersionKey, currentVersion).apply();
}
+ /**
+ * Perform UI actions informing about migrations that took place if they are present.
+ * @param context Context that can be used to show dialogs/snackbars/toasts
+ */
+ public static void showUserInfoIfPresent(@NonNull final Context context) {
+ for (final Consumer@foobar.
*
* Will correctly handle right-to-left usernames by using a {@link BidiFormatter}.
+ * For right-to-left usernames, it will put the @ on the right side to read more naturally.
*
* @param plainName username, with an optional leading @
* @return a usernames that can include RTL-characters
*/
@NonNull
public static String localizeUserName(final String plainName) {
- final BidiFormatter bidi = BidiFormatter.getInstance();
-
- if (plainName.startsWith("@")) {
- return "@" + bidi.unicodeWrap(plainName.substring(1));
- } else {
- return bidi.unicodeWrap(plainName);
- }
+ return BidiFormatter.getInstance().unicodeWrap(plainName);
}
public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization(
@@ -125,39 +117,35 @@ public final class Localization {
return getLocaleFromPrefs(context, R.string.content_language_key);
}
- public static Locale getAppLocale(@NonNull final Context context) {
- if (Build.VERSION.SDK_INT >= 33) {
- final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0);
- return Objects.requireNonNullElseGet(customLocale, Locale::getDefault);
- }
- return getLocaleFromPrefs(context, R.string.app_language_key);
+ public static Locale getAppLocale() {
+ final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0);
+ return customLocale != null ? customLocale : Locale.getDefault();
}
- public static String localizeNumber(@NonNull final Context context, final long number) {
- return localizeNumber(context, (double) number);
+ public static String localizeNumber(final long number) {
+ return localizeNumber((double) number);
}
- public static String localizeNumber(@NonNull final Context context, final double number) {
- final NumberFormat nf = NumberFormat.getInstance(getAppLocale(context));
+ public static String localizeNumber(final double number) {
+ final NumberFormat nf = NumberFormat.getInstance(getAppLocale());
return nf.format(number);
}
- public static String formatDate(@NonNull final Context context,
- @NonNull final OffsetDateTime offsetDateTime) {
+ public static String formatDate(@NonNull final OffsetDateTime offsetDateTime) {
return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
- .withLocale(getAppLocale(context)).format(offsetDateTime
+ .withLocale(getAppLocale()).format(offsetDateTime
.atZoneSameInstant(ZoneId.systemDefault()));
}
@SuppressLint("StringFormatInvalid")
public static String localizeUploadDate(@NonNull final Context context,
@NonNull final OffsetDateTime offsetDateTime) {
- return context.getString(R.string.upload_date_text, formatDate(context, offsetDateTime));
+ return context.getString(R.string.upload_date_text, formatDate(offsetDateTime));
}
public static String localizeViewCount(@NonNull final Context context, final long viewCount) {
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
- localizeNumber(context, viewCount));
+ localizeNumber(viewCount));
}
public static String localizeStreamCount(@NonNull final Context context,
@@ -171,7 +159,7 @@ public final class Localization {
return context.getResources().getString(R.string.more_than_100_videos);
default:
return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount,
- localizeNumber(context, streamCount));
+ localizeNumber(streamCount));
}
}
@@ -192,27 +180,27 @@ public final class Localization {
public static String localizeWatchingCount(@NonNull final Context context,
final long watchingCount) {
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
- localizeNumber(context, watchingCount));
+ localizeNumber(watchingCount));
}
public static String shortCount(@NonNull final Context context, final long count) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- return CompactDecimalFormat.getInstance(getAppLocale(context),
+ return CompactDecimalFormat.getInstance(getAppLocale(),
CompactDecimalFormat.CompactStyle.SHORT).format(count);
}
final double value = (double) count;
if (count >= 1000000000) {
- return localizeNumber(context, round(value / 1000000000))
+ return localizeNumber(round(value / 1000000000))
+ context.getString(R.string.short_billion);
} else if (count >= 1000000) {
- return localizeNumber(context, round(value / 1000000))
+ return localizeNumber(round(value / 1000000))
+ context.getString(R.string.short_million);
} else if (count >= 1000) {
- return localizeNumber(context, round(value / 1000))
+ return localizeNumber(round(value / 1000))
+ context.getString(R.string.short_thousand);
} else {
- return localizeNumber(context, value);
+ return localizeNumber(value);
}
}
@@ -377,8 +365,8 @@ public final class Localization {
prettyTime.removeUnit(Decade.class);
}
- public static PrettyTime resolvePrettyTime(@NonNull final Context context) {
- return new PrettyTime(getAppLocale(context));
+ public static PrettyTime resolvePrettyTime() {
+ return new PrettyTime(getAppLocale());
}
public static String relativeTime(@NonNull final OffsetDateTime offsetDateTime) {
@@ -410,14 +398,6 @@ public final class Localization {
}
}
- public static void assureCorrectAppLanguage(final Context c) {
- final Resources res = c.getResources();
- final DisplayMetrics dm = res.getDisplayMetrics();
- final Configuration conf = res.getConfiguration();
- conf.setLocale(getAppLocale(c));
- res.updateConfiguration(conf, dm);
- }
-
private static Locale getLocaleFromPrefs(@NonNull final Context context,
@StringRes final int prefKey) {
final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
@@ -452,29 +432,32 @@ public final class Localization {
return context.getResources().getQuantityString(pluralId, safeCount, formattedCount);
}
+ // Starting with pull request #12093, NewPipe exclusively uses Android's
+ // public per-app language APIs to read and set the UI language for NewPipe.
+ // The following code will migrate any existing custom app language in SharedPreferences to
+ // use the public per-app language APIs instead.
+ // For reference, see
+ // https://android-developers.googleblog.com/2022/11/per-app-language-preferences-part-1.html
public static void migrateAppLanguageSettingIfNecessary(@NonNull final Context context) {
- // Starting with pull request #12093, NewPipe on Android 13+ exclusively uses Android's
- // public per-app language APIs to read and set the UI language for NewPipe.
- // If running on Android 13+, the following code will migrate any existing custom
- // app language in SharedPreferences to use the public per-app language APIs instead.
- if (Build.VERSION.SDK_INT >= 33) {
- final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
- final String appLanguageKey = context.getString(R.string.app_language_key);
- final String appLanguageValue = sp.getString(appLanguageKey, null);
- if (appLanguageValue != null) {
+ final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ final String appLanguageKey = context.getString(R.string.app_language_key);
+ final String appLanguageValue = sp.getString(appLanguageKey, null);
+ if (appLanguageValue != null) {
+ // The app language key is used on Android versions < 33
+ // for more info, see ContentSettingsFragment
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
sp.edit().remove(appLanguageKey).apply();
- final String appLanguageDefaultValue =
- context.getString(R.string.default_localization_key);
- if (!appLanguageValue.equals(appLanguageDefaultValue)) {
- try {
- AppCompatDelegate.setApplicationLocales(
- LocaleListCompat.forLanguageTags(appLanguageValue)
- );
- } catch (final RuntimeException e) {
- Log.e(TAG, "Failed to migrate previous custom app language "
- + "setting to public per-app language APIs"
- );
- }
+ }
+ final String appLanguageDefaultValue =
+ context.getString(R.string.default_localization_key);
+ if (!appLanguageValue.equals(appLanguageDefaultValue)) {
+ try {
+ AppCompatDelegate.setApplicationLocales(
+ LocaleListCompat.forLanguageTags(appLanguageValue));
+ } catch (final RuntimeException e) {
+ Log.e(TAG, "Failed to migrate previous custom app language "
+ + "setting to public per-app language APIs"
+ );
}
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index aba27c259..c71836609 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -200,7 +200,7 @@ public final class NavigationHelper {
}
public static void enqueueOnPlayer(final Context context, final PlayQueue queue) {
- PlayerType playerType = PlayerHolder.getInstance().getType();
+ PlayerType playerType = PlayerHolder.INSTANCE.getType();
if (playerType == null) {
Log.e(TAG, "Enqueueing but no player is open; defaulting to background player");
playerType = PlayerType.AUDIO;
@@ -211,7 +211,7 @@ public final class NavigationHelper {
/* ENQUEUE NEXT */
public static void enqueueNextOnPlayer(final Context context, final PlayQueue queue) {
- PlayerType playerType = PlayerHolder.getInstance().getType();
+ PlayerType playerType = PlayerHolder.INSTANCE.getType();
if (playerType == null) {
Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player");
playerType = PlayerType.AUDIO;
@@ -421,13 +421,13 @@ public final class NavigationHelper {
final boolean switchingPlayers) {
final boolean autoPlay;
- @Nullable final PlayerType playerType = PlayerHolder.getInstance().getType();
+ @Nullable final PlayerType playerType = PlayerHolder.INSTANCE.getType();
if (playerType == null) {
// no player open
autoPlay = PlayerHelper.isAutoplayAllowedByUser(context);
} else if (switchingPlayers) {
// switching player to main player
- autoPlay = PlayerHolder.getInstance().isPlaying(); // keep play/pause state
+ autoPlay = PlayerHolder.INSTANCE.isPlaying(); // keep play/pause state
} else if (playerType == PlayerType.MAIN) {
// opening new stream while already playing in main player
autoPlay = PlayerHelper.isAutoplayAllowedByUser(context);
diff --git a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java
index b2aebac42..bccfc7f38 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java
@@ -6,12 +6,12 @@ import org.schabi.newpipe.streams.io.StoredFileHelper;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@@ -37,9 +37,6 @@ import java.util.zip.ZipOutputStream;
*/
public final class ZipHelper {
-
- private static final int BUFFER_SIZE = 2048;
-
@FunctionalInterface
public interface InputStreamConsumer {
void acceptStream(InputStream inputStream) throws IOException;
@@ -55,17 +52,17 @@ public final class ZipHelper {
/**
- * This function helps to create zip files. Caution this will overwrite the original file.
+ * This function helps to create zip files. Caution, this will overwrite the original file.
*
* @param outZip the ZipOutputStream where the data should be stored in
* @param nameInZip the path of the file inside the zip
- * @param fileOnDisk the path of the file on the disk that should be added to zip
+ * @param path the path of the file on the disk that should be added to zip
*/
public static void addFileToZip(final ZipOutputStream outZip,
final String nameInZip,
- final String fileOnDisk) throws IOException {
- try (FileInputStream fi = new FileInputStream(fileOnDisk)) {
- addFileToZip(outZip, nameInZip, fi);
+ final Path path) throws IOException {
+ try (var inputStream = Files.newInputStream(path)) {
+ addFileToZip(outZip, nameInZip, inputStream);
}
}
@@ -80,13 +77,13 @@ public final class ZipHelper {
final String nameInZip,
final OutputStreamConsumer streamConsumer) throws IOException {
final byte[] bytes;
- try (ByteArrayOutputStream byteOutput = new ByteArrayOutputStream()) {
+ try (var byteOutput = new ByteArrayOutputStream()) {
streamConsumer.acceptStream(byteOutput);
bytes = byteOutput.toByteArray();
}
- try (ByteArrayInputStream byteInput = new ByteArrayInputStream(bytes)) {
- ZipHelper.addFileToZip(outZip, nameInZip, byteInput);
+ try (var byteInput = new ByteArrayInputStream(bytes)) {
+ addFileToZip(outZip, nameInZip, byteInput);
}
}
@@ -97,49 +94,26 @@ public final class ZipHelper {
* @param nameInZip the path of the file inside the zip
* @param inputStream the content to put inside the file
*/
- public static void addFileToZip(final ZipOutputStream outZip,
- final String nameInZip,
- final InputStream inputStream) throws IOException {
- final byte[] data = new byte[BUFFER_SIZE];
- try (BufferedInputStream bufferedInputStream =
- new BufferedInputStream(inputStream, BUFFER_SIZE)) {
- final ZipEntry entry = new ZipEntry(nameInZip);
- outZip.putNextEntry(entry);
- int count;
- while ((count = bufferedInputStream.read(data, 0, BUFFER_SIZE)) != -1) {
- outZip.write(data, 0, count);
- }
- }
+ private static void addFileToZip(final ZipOutputStream outZip,
+ final String nameInZip,
+ final InputStream inputStream) throws IOException {
+ outZip.putNextEntry(new ZipEntry(nameInZip));
+ inputStream.transferTo(outZip);
}
/**
- * This will extract data from ZipInputStream. Caution this will overwrite the original file.
+ * This will extract data from ZipInputStream. Caution, this will overwrite the original file.
*
* @param zipFile the zip file to extract from
* @param nameInZip the path of the file inside the zip
- * @param fileOnDisk the path of the file on the disk where the data should be extracted to
+ * @param path the path of the file on the disk where the data should be extracted to
* @return will return true if the file was found within the zip file
*/
public static boolean extractFileFromZip(final StoredFileHelper zipFile,
final String nameInZip,
- final String fileOnDisk) throws IOException {
- return extractFileFromZip(zipFile, nameInZip, input -> {
- // delete old file first
- final File oldFile = new File(fileOnDisk);
- if (oldFile.exists()) {
- if (!oldFile.delete()) {
- throw new IOException("Could not delete " + fileOnDisk);
- }
- }
-
- final byte[] data = new byte[BUFFER_SIZE];
- try (FileOutputStream outFile = new FileOutputStream(fileOnDisk)) {
- int count;
- while ((count = input.read(data)) != -1) {
- outFile.write(data, 0, count);
- }
- }
- });
+ final Path path) throws IOException {
+ return extractFileFromZip(zipFile, nameInZip, input ->
+ Files.copy(input, path, StandardCopyOption.REPLACE_EXISTING));
}
/**
diff --git a/app/src/main/res/layout/fragment_blank.xml b/app/src/main/res/layout/fragment_blank.xml
index 6c2978e95..4d874ebdb 100644
--- a/app/src/main/res/layout/fragment_blank.xml
+++ b/app/src/main/res/layout/fragment_blank.xml
@@ -4,7 +4,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
لقطات الشاشة • الخدمات المدعومة • وصف • سمات • التثبيت والتحديثات • مساهمة • التبرعات • رخصة
موقع • مدونة • الأسئلة الشائعة • إضغط
diff --git a/doc/README.asm.md b/doc/README.asm.md index 8042b3db9..5958fe280 100644 --- a/doc/README.asm.md +++ b/doc/README.asm.md @@ -2,16 +2,22 @@স্ক্ৰীণশ্বট • সমৰ্থিত সেৱাসমূহ • বিৱৰণ • diff --git a/doc/README.de.md b/doc/README.de.md index 5b3275d07..ab1ab2727 100644 --- a/doc/README.de.md +++ b/doc/README.de.md @@ -1,19 +1,26 @@ -
refactor branch als Arbeitsgrundlage, wenn du neue Funktionen beitragen willst. Die aktuelle Codebase ist im reinen Maintenance mode und bekommt nur noch Fehlerbehebungen.Screenshots • Unterstützte Dienste • Beschreibung • Features • Installation und Updates • Beitrag • Spenden • Lizenz
Website • Blog • FAQ • Über NewPipe
diff --git a/doc/README.es.md b/doc/README.es.md index 8ec58e771..155b004a1 100644 --- a/doc/README.es.md +++ b/doc/README.es.md @@ -2,15 +2,22 @@Capturas de Pantalla • Descripción • Características • Instalación y Actualizaciones • Contribución • Donar • Licencia
diff --git a/doc/README.fr.md b/doc/README.fr.md index 772f4a1ae..7b450b04d 100644 --- a/doc/README.fr.md +++ b/doc/README.fr.md @@ -5,15 +5,22 @@Captures d'écran • Services Supportés • Description • Fonctionnalités • Installation et mises à jour • Contribuer • Dons • Licence
diff --git a/doc/README.hi.md b/doc/README.hi.md index 37ae71a4a..3f51960b5 100644 --- a/doc/README.hi.md +++ b/doc/README.hi.md @@ -2,16 +2,22 @@ऐप कैसी दिखती है • समर्थित सेवाएँ • विवरण • सुविधाएँ • स्थापित करना और अपडेट करना • योगदान करें • आर्थिक योगदान करें • लाइसेंस
वेबसाइट • ब्लॉग • साधारण सवाल-जवाब • प्रेस
diff --git a/doc/README.it.md b/doc/README.it.md index 6c227ea2f..b8621a8fe 100644 --- a/doc/README.it.md +++ b/doc/README.it.md @@ -2,15 +2,22 @@Screenshot • Servizi Supportati • Descrizione • Funzionalità • Installazione e aggiornamenti • Contribuire • Donare • Licenza
diff --git a/doc/README.ja.md b/doc/README.ja.md index e8f708a8a..13ddebb02 100644 --- a/doc/README.ja.md +++ b/doc/README.ja.md @@ -2,15 +2,22 @@スクリーンショット • 説明 • 機能 • インストールと更新 • 貢献 • 寄付 • ライセンス
diff --git a/doc/README.ko.md b/doc/README.ko.md index 3215bd713..5f731d076 100644 --- a/doc/README.ko.md +++ b/doc/README.ko.md @@ -2,15 +2,22 @@Screenshots • Description • Features • Updates • Contribution • Donate • License
diff --git a/doc/README.pa.md b/doc/README.pa.md index 0e254adf1..12229f1d8 100644 --- a/doc/README.pa.md +++ b/doc/README.pa.md @@ -2,16 +2,22 @@ਐਪ ਕਿਹੋ-ਜਿਹੀ ਦਿਖਦੀ ਹੈ • ਸਮਰਥਿਤ ਸੇਵਾਵਾਂ • ਵਰਣਨ • ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ • ਇੰਸਟਾਲੇਸ਼ਨ ਅਤੇ ਅੱਪਡੇਟ • ਯੋਗਦਾਨ • ਦਾਨ • ਲਾਈਸੈਂਸ
ਵੈੱਬਸਾਈਟ • ਬਲੌਗ • ਆਮ ਸਵਾਲ ਜਵਾਬ • ਪ੍ਰੈਸ
diff --git a/doc/README.pl.md b/doc/README.pl.md index 96d493153..f95b62e33 100644 --- a/doc/README.pl.md +++ b/doc/README.pl.md @@ -2,15 +2,22 @@Screenshoty • Opis • Funkcje • Instalacja i aktualizacje • Wkład • Wesprzyj • Licencja
diff --git a/doc/README.pt_BR.md b/doc/README.pt_BR.md index da6c4fce6..c8257ce3e 100644 --- a/doc/README.pt_BR.md +++ b/doc/README.pt_BR.md @@ -6,15 +6,22 @@Screenshots • Serviços Suportados • Descrição • Recursos • Instalação e atualizações • Contribuições • Doar • Licença
diff --git a/doc/README.ro.md b/doc/README.ro.md index 29c1d3666..5ce14ca1d 100644 --- a/doc/README.ro.md +++ b/doc/README.ro.md @@ -2,15 +2,22 @@Capturi de ecran • Descriere • Funcţii • Instalare şi actualizări • Contribuţie • Donaţi • Licenţă
diff --git a/doc/README.ru.md b/doc/README.ru.md index e3c76d329..56d3b48ed 100644 --- a/doc/README.ru.md +++ b/doc/README.ru.md @@ -2,15 +2,22 @@Скриншоты • Поддерживаемые сервисы • Описание • Возможности • Установка и обновления • Участие • Пожертвование • Лицензия
diff --git a/doc/README.ryu.md b/doc/README.ryu.md index 2e24aa41c..23081f70d 100644 --- a/doc/README.ryu.md +++ b/doc/README.ryu.md @@ -2,15 +2,22 @@スクリーンショット • しちめい • ちぬー • インストールとぅこうしん • こうきん • ちーふ • ライセンス
diff --git a/doc/README.so.md b/doc/README.so.md index 640feae60..a66f7d12b 100644 --- a/doc/README.so.md +++ b/doc/README.so.md @@ -2,15 +2,22 @@Sawir-shaashadeed • Faahfaahin • Waxqabadka • Kushubida iyo cusboonaysiinta • Kusoo Kordhin • Ugu Deeq • Laysinka
Website-ka • Maqaalada • Su'aalaha Aalaa La-iswaydiiyo • Warbaahinta
diff --git a/doc/README.sr.md b/doc/README.sr.md index 1a9118638..9dbb439d0 100644 --- a/doc/README.sr.md +++ b/doc/README.sr.md @@ -5,15 +5,22 @@Снимци екрана • Подржане услуге • Опис • Карактеристике • Инсталација и ажурирања • Допринос • Донација • Лиценца
Веб-сајт • Блог • ЧПП • Штампа
diff --git a/doc/README.tr.md b/doc/README.tr.md index bbdd85f76..5a0096e55 100644 --- a/doc/README.tr.md +++ b/doc/README.tr.md @@ -2,15 +2,22 @@Ekran fotoğrafları • Açıklama • Özellikler • Kurulum ve güncellemeler • Katkıda bulunma • Bağış • Lisans
Web sitesi • Blog • SSS • Basın
diff --git a/doc/README.zh_TW.md b/doc/README.zh_TW.md index 760a43ad5..8cfcbd640 100644 --- a/doc/README.zh_TW.md +++ b/doc/README.zh_TW.md @@ -2,15 +2,22 @@