Skip to content

Commit 4ed2b97

Browse files
committed
Merge branch 'master' into dev
2 parents 8e389c4 + b892318 commit 4ed2b97

File tree

12 files changed

+126
-30
lines changed

12 files changed

+126
-30
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ android {
4242
minSdk = 21
4343
targetSdk = 35
4444

45-
versionCode = System.getProperty("versionCodeOverride")?.toInt() ?: 1005
45+
versionCode = System.getProperty("versionCodeOverride")?.toInt() ?: 1006
4646

47-
versionName = "0.28.0"
47+
versionName = "0.28.1"
4848
System.getProperty("versionNameSuffix")?.let { versionNameSuffix = it }
4949

5050
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

app/proguard-rules.pro

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
-dontwarn javax.script.**
1717
-keep class jdk.dynalink.** { *; }
1818
-dontwarn jdk.dynalink.**
19+
# Rules for jsoup
20+
# Ignore intended-to-be-optional re2j classes - only needed if using re2j for jsoup regex
21+
# jsoup safely falls back to JDK regex if re2j not on classpath, but has concrete re2j refs
22+
# See https://github.com/jhy/jsoup/issues/2459 - may be resolved in future, then this may be removed
23+
-dontwarn com.google.re2j.**
1924

2025
## Rules for ExoPlayer
2126
-keep class com.google.android.exoplayer2.** { *; }

app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,7 @@ private void continueSelectedDownload(@NonNull final StoredFileHelper storage) {
11331133
}
11341134

11351135
DownloadManagerService.startMission(context, urls, storage, kind, threads,
1136-
currentInfo.getUrl(), psName, psArgs, nearLength, new ArrayList<>(recoveryInfo));
1136+
currentInfo, psName, psArgs, nearLength, new ArrayList<>(recoveryInfo));
11371137

11381138
Toast.makeText(context, getString(R.string.download_has_started),
11391139
Toast.LENGTH_SHORT).show();

app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.schabi.newpipe.local.bookmark;
22

33
import static org.schabi.newpipe.local.bookmark.MergedPlaylistManager.getMergedOrderedPlaylists;
4+
import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
45

56
import android.content.DialogInterface;
67
import android.os.Bundle;
@@ -417,10 +418,11 @@ public void saveImmediate() {
417418
}
418419

419420
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
420-
// if adding grid layout, also include ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT
421-
// with an `if (shouldUseGridLayout()) ...`
422-
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
423-
ItemTouchHelper.ACTION_STATE_IDLE) {
421+
int directions = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
422+
if (shouldUseGridLayout(requireContext())) {
423+
directions |= ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
424+
}
425+
return new ItemTouchHelper.SimpleCallback(directions, ItemTouchHelper.ACTION_STATE_IDLE) {
424426
@Override
425427
public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView,
426428
final int viewSize,

app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
2323
import io.reactivex.rxjava3.core.BackpressureStrategy;
2424
import io.reactivex.rxjava3.core.Flowable;
25-
import io.reactivex.rxjava3.subjects.BehaviorSubject;
25+
import io.reactivex.rxjava3.subjects.PublishSubject;
2626

2727
/**
2828
* PlayQueue is responsible for keeping track of a list of streams and the index of
@@ -45,7 +45,7 @@ public abstract class PlayQueue implements Serializable {
4545
private List<PlayQueueItem> backup;
4646
private List<PlayQueueItem> streams;
4747

48-
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
48+
private transient PublishSubject<PlayQueueEvent> eventBroadcast;
4949
private transient Flowable<PlayQueueEvent> broadcastReceiver;
5050
private transient boolean disposed = false;
5151

@@ -70,7 +70,7 @@ public abstract class PlayQueue implements Serializable {
7070
* </p>
7171
*/
7272
public void init() {
73-
eventBroadcast = BehaviorSubject.create();
73+
eventBroadcast = PublishSubject.create();
7474

7575
broadcastReceiver = eventBroadcast.toFlowable(BackpressureStrategy.BUFFER)
7676
.observeOn(AndroidSchedulers.mainThread())

app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package org.schabi.newpipe.streams;
22

3+
import static org.schabi.newpipe.MainActivity.DEBUG;
4+
5+
import android.util.Log;
6+
import android.util.Pair;
7+
38
import androidx.annotation.NonNull;
49
import androidx.annotation.Nullable;
510

11+
import org.schabi.newpipe.extractor.stream.StreamInfo;
612
import org.schabi.newpipe.streams.WebMReader.Cluster;
713
import org.schabi.newpipe.streams.WebMReader.Segment;
814
import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
@@ -13,6 +19,10 @@
1319
import java.io.IOException;
1420
import java.nio.ByteBuffer;
1521
import java.nio.ByteOrder;
22+
import java.time.format.DateTimeFormatter;
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import java.util.stream.Collectors;
1626

1727
/**
1828
* @author kapodamy
@@ -52,8 +62,10 @@ public class OggFromWebMWriter implements Closeable {
5262
private long segmentTableNextTimestamp = TIME_SCALE_NS;
5363

5464
private final int[] crc32Table = new int[256];
65+
private final StreamInfo streamInfo;
5566

56-
public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
67+
public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target,
68+
@Nullable final StreamInfo streamInfo) {
5769
if (!source.canRead() || !source.canRewind()) {
5870
throw new IllegalArgumentException("source stream must be readable and allows seeking");
5971
}
@@ -63,6 +75,7 @@ public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final Sharp
6375

6476
this.source = source;
6577
this.output = target;
78+
this.streamInfo = streamInfo;
6679

6780
this.streamId = (int) System.currentTimeMillis();
6881

@@ -271,12 +284,31 @@ private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffe
271284

272285
@Nullable
273286
private byte[] makeMetadata() {
287+
if (DEBUG) {
288+
Log.d("OggFromWebMWriter", "Downloading media with codec ID " + webmTrack.codecId);
289+
}
290+
274291
if ("A_OPUS".equals(webmTrack.codecId)) {
275-
return new byte[]{
276-
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
277-
0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
278-
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
279-
};
292+
final var metadata = new ArrayList<Pair<String, String>>();
293+
if (streamInfo != null) {
294+
metadata.add(Pair.create("COMMENT", streamInfo.getUrl()));
295+
metadata.add(Pair.create("GENRE", streamInfo.getCategory()));
296+
metadata.add(Pair.create("ARTIST", streamInfo.getUploaderName()));
297+
metadata.add(Pair.create("TITLE", streamInfo.getName()));
298+
metadata.add(Pair.create("DATE", streamInfo
299+
.getUploadDate()
300+
.getLocalDateTime()
301+
.format(DateTimeFormatter.ISO_DATE)));
302+
}
303+
304+
if (DEBUG) {
305+
Log.d("OggFromWebMWriter", "Creating metadata header with this data:");
306+
metadata.forEach(p -> {
307+
Log.d("OggFromWebMWriter", p.first + "=" + p.second);
308+
});
309+
}
310+
311+
return makeOpusTagsHeader(metadata);
280312
} else if ("A_VORBIS".equals(webmTrack.codecId)) {
281313
return new byte[]{
282314
0x03, // ¿¿¿???
@@ -290,6 +322,59 @@ private byte[] makeMetadata() {
290322
return null;
291323
}
292324

325+
/**
326+
* This creates a single metadata tag for use in opus metadata headers. It contains the four
327+
* byte string length field and includes the string as-is. This cannot be used independently,
328+
* but must follow a proper "OpusTags" header.
329+
*
330+
* @param pair A key-value pair in the format "KEY=some value"
331+
* @return The binary data of the encoded metadata tag
332+
*/
333+
private static byte[] makeOpusMetadataTag(final Pair<String, String> pair) {
334+
final var keyValue = pair.first.toUpperCase() + "=" + pair.second.trim();
335+
336+
final var bytes = keyValue.getBytes();
337+
final var buf = ByteBuffer.allocate(4 + bytes.length);
338+
buf.order(ByteOrder.LITTLE_ENDIAN);
339+
buf.putInt(bytes.length);
340+
buf.put(bytes);
341+
return buf.array();
342+
}
343+
344+
/**
345+
* This returns a complete "OpusTags" header, created from the provided metadata tags.
346+
* <p>
347+
* You probably want to use makeOpusMetadata(), which uses this function to create
348+
* a header with sensible metadata filled in.
349+
*
350+
* @param keyValueLines A list of pairs of the tags. This can also be though of as a mapping
351+
* from one key to multiple values.
352+
* @return The binary header
353+
*/
354+
private static byte[] makeOpusTagsHeader(final List<Pair<String, String>> keyValueLines) {
355+
final var tags = keyValueLines
356+
.stream()
357+
.filter(p -> !p.second.isBlank())
358+
.map(OggFromWebMWriter::makeOpusMetadataTag)
359+
.collect(Collectors.toUnmodifiableList());
360+
361+
final var tagsBytes = tags.stream().collect(Collectors.summingInt(arr -> arr.length));
362+
363+
// Fixed header fields + dynamic fields
364+
final var byteCount = 16 + tagsBytes;
365+
366+
final var head = ByteBuffer.allocate(byteCount);
367+
head.order(ByteOrder.LITTLE_ENDIAN);
368+
head.put(new byte[]{
369+
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
370+
0x00, 0x00, 0x00, 0x00, // vendor (aka. Encoder) string of length 0
371+
});
372+
head.putInt(tags.size()); // 4 bytes for tag count
373+
tags.forEach(head::put); // dynamic amount of tag bytes
374+
375+
return head.array();
376+
}
377+
293378
private void write(final ByteBuffer buffer) throws IOException {
294379
output.write(buffer.array(), 0, buffer.position());
295380
buffer.position(0);

app/src/main/java/org/schabi/newpipe/util/ListHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ private static Comparator<AudioStream> getAudioTrackComparator(
806806
final Locale preferredLanguage = Localization.getPreferredLocale(context);
807807
final boolean preferOriginalAudio =
808808
preferences.getBoolean(context.getString(R.string.prefer_original_audio_key),
809-
false);
809+
true);
810810
final boolean preferDescriptiveAudio =
811811
preferences.getBoolean(context.getString(R.string.prefer_descriptive_audio_key),
812812
false);

app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ boolean test(SharpStream... sources) throws IOException {
3434

3535
@Override
3636
int process(SharpStream out, @NonNull SharpStream... sources) throws IOException {
37-
OggFromWebMWriter demuxer = new OggFromWebMWriter(sources[0], out);
37+
OggFromWebMWriter demuxer = new OggFromWebMWriter(sources[0], out, streamInfo);
3838
demuxer.parseSource();
3939
demuxer.selectTrack(0);
4040
demuxer.build();

app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import androidx.annotation.NonNull;
66

7+
import org.schabi.newpipe.extractor.stream.StreamInfo;
78
import org.schabi.newpipe.streams.io.SharpStream;
89

910
import java.io.File;
@@ -30,7 +31,8 @@ public abstract class Postprocessing implements Serializable {
3031
public transient static final String ALGORITHM_M4A_NO_DASH = "mp4D-m4a";
3132
public transient static final String ALGORITHM_OGG_FROM_WEBM_DEMUXER = "webm-ogg-d";
3233

33-
public static Postprocessing getAlgorithm(@NonNull String algorithmName, String[] args) {
34+
public static Postprocessing getAlgorithm(@NonNull String algorithmName, String[] args,
35+
StreamInfo streamInfo) {
3436
Postprocessing instance;
3537

3638
switch (algorithmName) {
@@ -56,6 +58,7 @@ public static Postprocessing getAlgorithm(@NonNull String algorithmName, String[
5658
}
5759

5860
instance.args = args;
61+
instance.streamInfo = streamInfo;
5962
return instance;
6063
}
6164

@@ -75,8 +78,8 @@ public static Postprocessing getAlgorithm(@NonNull String algorithmName, String[
7578
*/
7679
private final String name;
7780

78-
7981
private String[] args;
82+
protected StreamInfo streamInfo;
8083

8184
private transient DownloadMission mission;
8285

app/src/main/java/us/shandian/giga/service/DownloadManagerService.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
import org.schabi.newpipe.R;
4242
import org.schabi.newpipe.download.DownloadActivity;
43+
import org.schabi.newpipe.extractor.stream.StreamInfo;
4344
import org.schabi.newpipe.player.helper.LockManager;
4445
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
4546
import org.schabi.newpipe.streams.io.StoredFileHelper;
@@ -74,12 +75,12 @@ public class DownloadManagerService extends Service {
7475
private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads";
7576
private static final String EXTRA_POSTPROCESSING_NAME = "DownloadManagerService.extra.postprocessingName";
7677
private static final String EXTRA_POSTPROCESSING_ARGS = "DownloadManagerService.extra.postprocessingArgs";
77-
private static final String EXTRA_SOURCE = "DownloadManagerService.extra.source";
7878
private static final String EXTRA_NEAR_LENGTH = "DownloadManagerService.extra.nearLength";
7979
private static final String EXTRA_PATH = "DownloadManagerService.extra.storagePath";
8080
private static final String EXTRA_PARENT_PATH = "DownloadManagerService.extra.storageParentPath";
8181
private static final String EXTRA_STORAGE_TAG = "DownloadManagerService.extra.storageTag";
8282
private static final String EXTRA_RECOVERY_INFO = "DownloadManagerService.extra.recoveryInfo";
83+
private static final String EXTRA_STREAM_INFO = "DownloadManagerService.extra.streamInfo";
8384

8485
private static final String ACTION_RESET_DOWNLOAD_FINISHED = APPLICATION_ID + ".reset_download_finished";
8586
private static final String ACTION_OPEN_DOWNLOADS_FINISHED = APPLICATION_ID + ".open_downloads_finished";
@@ -353,28 +354,28 @@ public void updateForegroundState(boolean state) {
353354
* @param kind type of file (a: audio v: video s: subtitle ?: file-extension defined)
354355
* @param threads the number of threads maximal used to download chunks of the file.
355356
* @param psName the name of the required post-processing algorithm, or {@code null} to ignore.
356-
* @param source source url of the resource
357+
* @param streamInfo stream metadata that may be written into the downloaded file.
357358
* @param psArgs the arguments for the post-processing algorithm.
358359
* @param nearLength the approximated final length of the file
359360
* @param recoveryInfo array of MissionRecoveryInfo, in case is required recover the download
360361
*/
361362
public static void startMission(Context context, String[] urls, StoredFileHelper storage,
362-
char kind, int threads, String source, String psName,
363+
char kind, int threads, StreamInfo streamInfo, String psName,
363364
String[] psArgs, long nearLength,
364365
ArrayList<MissionRecoveryInfo> recoveryInfo) {
365366
final Intent intent = new Intent(context, DownloadManagerService.class)
366367
.setAction(Intent.ACTION_RUN)
367368
.putExtra(EXTRA_URLS, urls)
368369
.putExtra(EXTRA_KIND, kind)
369370
.putExtra(EXTRA_THREADS, threads)
370-
.putExtra(EXTRA_SOURCE, source)
371371
.putExtra(EXTRA_POSTPROCESSING_NAME, psName)
372372
.putExtra(EXTRA_POSTPROCESSING_ARGS, psArgs)
373373
.putExtra(EXTRA_NEAR_LENGTH, nearLength)
374374
.putExtra(EXTRA_RECOVERY_INFO, recoveryInfo)
375375
.putExtra(EXTRA_PARENT_PATH, storage.getParentUri())
376376
.putExtra(EXTRA_PATH, storage.getUri())
377-
.putExtra(EXTRA_STORAGE_TAG, storage.getTag());
377+
.putExtra(EXTRA_STORAGE_TAG, storage.getTag())
378+
.putExtra(EXTRA_STREAM_INFO, streamInfo);
378379

379380
context.startService(intent);
380381
}
@@ -387,9 +388,9 @@ private void startMission(Intent intent) {
387388
char kind = intent.getCharExtra(EXTRA_KIND, '?');
388389
String psName = intent.getStringExtra(EXTRA_POSTPROCESSING_NAME);
389390
String[] psArgs = intent.getStringArrayExtra(EXTRA_POSTPROCESSING_ARGS);
390-
String source = intent.getStringExtra(EXTRA_SOURCE);
391391
long nearLength = intent.getLongExtra(EXTRA_NEAR_LENGTH, 0);
392392
String tag = intent.getStringExtra(EXTRA_STORAGE_TAG);
393+
StreamInfo streamInfo = (StreamInfo)intent.getSerializableExtra(EXTRA_STREAM_INFO);
393394
final var recovery = IntentCompat.getParcelableArrayListExtra(intent, EXTRA_RECOVERY_INFO,
394395
MissionRecoveryInfo.class);
395396
Objects.requireNonNull(recovery);
@@ -405,11 +406,11 @@ private void startMission(Intent intent) {
405406
if (psName == null)
406407
ps = null;
407408
else
408-
ps = Postprocessing.getAlgorithm(psName, psArgs);
409+
ps = Postprocessing.getAlgorithm(psName, psArgs, streamInfo);
409410

410411
final DownloadMission mission = new DownloadMission(urls, storage, kind, ps);
411412
mission.threadCount = threads;
412-
mission.source = source;
413+
mission.source = streamInfo.getUrl();
413414
mission.nearLength = nearLength;
414415
mission.recoveryInfo = recovery.toArray(new MissionRecoveryInfo[0]);
415416

0 commit comments

Comments
 (0)