Fix Lint: Inconsistent line separators

This commit is contained in:
TobiGr 2020-11-22 10:16:27 +01:00
parent 18fb0a13d7
commit 6f3dfad550
8 changed files with 1032 additions and 1032 deletions

View File

@ -1,416 +1,416 @@
package org.schabi.newpipe.streams; package org.schabi.newpipe.streams;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.schabi.newpipe.streams.WebMReader.Cluster; import org.schabi.newpipe.streams.WebMReader.Cluster;
import org.schabi.newpipe.streams.WebMReader.Segment; import org.schabi.newpipe.streams.WebMReader.Segment;
import org.schabi.newpipe.streams.WebMReader.SimpleBlock; import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
import org.schabi.newpipe.streams.WebMReader.WebMTrack; import org.schabi.newpipe.streams.WebMReader.WebMTrack;
import org.schabi.newpipe.streams.io.SharpStream; import org.schabi.newpipe.streams.io.SharpStream;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
/** /**
* @author kapodamy * @author kapodamy
*/ */
public class OggFromWebMWriter implements Closeable { public class OggFromWebMWriter implements Closeable {
private static final byte FLAG_UNSET = 0x00; private static final byte FLAG_UNSET = 0x00;
//private static final byte FLAG_CONTINUED = 0x01; //private static final byte FLAG_CONTINUED = 0x01;
private static final byte FLAG_FIRST = 0x02; private static final byte FLAG_FIRST = 0x02;
private static final byte FLAG_LAST = 0x04; private static final byte FLAG_LAST = 0x04;
private static final byte HEADER_CHECKSUM_OFFSET = 22; private static final byte HEADER_CHECKSUM_OFFSET = 22;
private static final byte HEADER_SIZE = 27; private static final byte HEADER_SIZE = 27;
private static final int TIME_SCALE_NS = 1000000000; private static final int TIME_SCALE_NS = 1000000000;
private boolean done = false; private boolean done = false;
private boolean parsed = false; private boolean parsed = false;
private final SharpStream source; private final SharpStream source;
private final SharpStream output; private final SharpStream output;
private int sequenceCount = 0; private int sequenceCount = 0;
private final int streamId; private final int streamId;
private byte packetFlag = FLAG_FIRST; private byte packetFlag = FLAG_FIRST;
private WebMReader webm = null; private WebMReader webm = null;
private WebMTrack webmTrack = null; private WebMTrack webmTrack = null;
private Segment webmSegment = null; private Segment webmSegment = null;
private Cluster webmCluster = null; private Cluster webmCluster = null;
private SimpleBlock webmBlock = null; private SimpleBlock webmBlock = null;
private long webmBlockLastTimecode = 0; private long webmBlockLastTimecode = 0;
private long webmBlockNearDuration = 0; private long webmBlockNearDuration = 0;
private short segmentTableSize = 0; private short segmentTableSize = 0;
private final byte[] segmentTable = new byte[255]; private final byte[] segmentTable = new byte[255];
private long segmentTableNextTimestamp = TIME_SCALE_NS; private long segmentTableNextTimestamp = TIME_SCALE_NS;
private final int[] crc32Table = new int[256]; private final int[] crc32Table = new int[256];
public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) { public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
if (!source.canRead() || !source.canRewind()) { if (!source.canRead() || !source.canRewind()) {
throw new IllegalArgumentException("source stream must be readable and allows seeking"); throw new IllegalArgumentException("source stream must be readable and allows seeking");
} }
if (!target.canWrite() || !target.canRewind()) { if (!target.canWrite() || !target.canRewind()) {
throw new IllegalArgumentException("output stream must be writable and allows seeking"); throw new IllegalArgumentException("output stream must be writable and allows seeking");
} }
this.source = source; this.source = source;
this.output = target; this.output = target;
this.streamId = (int) System.currentTimeMillis(); this.streamId = (int) System.currentTimeMillis();
populateCrc32Table(); populateCrc32Table();
} }
public boolean isDone() { public boolean isDone() {
return done; return done;
} }
public boolean isParsed() { public boolean isParsed() {
return parsed; return parsed;
} }
public WebMTrack[] getTracksFromSource() throws IllegalStateException { public WebMTrack[] getTracksFromSource() throws IllegalStateException {
if (!parsed) { if (!parsed) {
throw new IllegalStateException("source must be parsed first"); throw new IllegalStateException("source must be parsed first");
} }
return webm.getAvailableTracks(); return webm.getAvailableTracks();
} }
public void parseSource() throws IOException, IllegalStateException { public void parseSource() throws IOException, IllegalStateException {
if (done) { if (done) {
throw new IllegalStateException("already done"); throw new IllegalStateException("already done");
} }
if (parsed) { if (parsed) {
throw new IllegalStateException("already parsed"); throw new IllegalStateException("already parsed");
} }
try { try {
webm = new WebMReader(source); webm = new WebMReader(source);
webm.parse(); webm.parse();
webmSegment = webm.getNextSegment(); webmSegment = webm.getNextSegment();
} finally { } finally {
parsed = true; parsed = true;
} }
} }
public void selectTrack(final int trackIndex) throws IOException { public void selectTrack(final int trackIndex) throws IOException {
if (!parsed) { if (!parsed) {
throw new IllegalStateException("source must be parsed first"); throw new IllegalStateException("source must be parsed first");
} }
if (done) { if (done) {
throw new IOException("already done"); throw new IOException("already done");
} }
if (webmTrack != null) { if (webmTrack != null) {
throw new IOException("tracks already selected"); throw new IOException("tracks already selected");
} }
switch (webm.getAvailableTracks()[trackIndex].kind) { switch (webm.getAvailableTracks()[trackIndex].kind) {
case Audio: case Audio:
case Video: case Video:
break; break;
default: default:
throw new UnsupportedOperationException("the track must an audio or video stream"); throw new UnsupportedOperationException("the track must an audio or video stream");
} }
try { try {
webmTrack = webm.selectTrack(trackIndex); webmTrack = webm.selectTrack(trackIndex);
} finally { } finally {
parsed = true; parsed = true;
} }
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
done = true; done = true;
parsed = true; parsed = true;
webmTrack = null; webmTrack = null;
webm = null; webm = null;
if (!output.isClosed()) { if (!output.isClosed()) {
output.flush(); output.flush();
} }
source.close(); source.close();
output.close(); output.close();
} }
public void build() throws IOException { public void build() throws IOException {
final float resolution; final float resolution;
SimpleBlock bloq; SimpleBlock bloq;
final ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255)); final ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
final ByteBuffer page = ByteBuffer.allocate(64 * 1024); final ByteBuffer page = ByteBuffer.allocate(64 * 1024);
header.order(ByteOrder.LITTLE_ENDIAN); header.order(ByteOrder.LITTLE_ENDIAN);
/* step 1: get the amount of frames per seconds */ /* step 1: get the amount of frames per seconds */
switch (webmTrack.kind) { switch (webmTrack.kind) {
case Audio: case Audio:
resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata); resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
if (resolution == 0f) { if (resolution == 0f) {
throw new RuntimeException("cannot get the audio sample rate"); throw new RuntimeException("cannot get the audio sample rate");
} }
break; break;
case Video: case Video:
// WARNING: untested // WARNING: untested
if (webmTrack.defaultDuration == 0) { if (webmTrack.defaultDuration == 0) {
throw new RuntimeException("missing default frame time"); throw new RuntimeException("missing default frame time");
} }
resolution = 1000f / ((float) webmTrack.defaultDuration resolution = 1000f / ((float) webmTrack.defaultDuration
/ webmSegment.info.timecodeScale); / webmSegment.info.timecodeScale);
break; break;
default: default:
throw new RuntimeException("not implemented"); throw new RuntimeException("not implemented");
} }
/* step 2: create packet with code init data */ /* step 2: create packet with code init data */
if (webmTrack.codecPrivate != null) { if (webmTrack.codecPrivate != null) {
addPacketSegment(webmTrack.codecPrivate.length); addPacketSegment(webmTrack.codecPrivate.length);
makePacketheader(0x00, header, webmTrack.codecPrivate); makePacketheader(0x00, header, webmTrack.codecPrivate);
write(header); write(header);
output.write(webmTrack.codecPrivate); output.write(webmTrack.codecPrivate);
} }
/* step 3: create packet with metadata */ /* step 3: create packet with metadata */
final byte[] buffer = makeMetadata(); final byte[] buffer = makeMetadata();
if (buffer != null) { if (buffer != null) {
addPacketSegment(buffer.length); addPacketSegment(buffer.length);
makePacketheader(0x00, header, buffer); makePacketheader(0x00, header, buffer);
write(header); write(header);
output.write(buffer); output.write(buffer);
} }
/* step 4: calculate amount of packets */ /* step 4: calculate amount of packets */
while (webmSegment != null) { while (webmSegment != null) {
bloq = getNextBlock(); bloq = getNextBlock();
if (bloq != null && addPacketSegment(bloq)) { if (bloq != null && addPacketSegment(bloq)) {
final int pos = page.position(); final int pos = page.position();
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
bloq.data.read(page.array(), pos, bloq.dataSize); bloq.data.read(page.array(), pos, bloq.dataSize);
page.position(pos + bloq.dataSize); page.position(pos + bloq.dataSize);
continue; continue;
} }
// calculate the current packet duration using the next block // calculate the current packet duration using the next block
double elapsedNs = webmTrack.codecDelay; double elapsedNs = webmTrack.codecDelay;
if (bloq == null) { if (bloq == null) {
packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
elapsedNs += webmBlockLastTimecode; elapsedNs += webmBlockLastTimecode;
if (webmTrack.defaultDuration > 0) { if (webmTrack.defaultDuration > 0) {
elapsedNs += webmTrack.defaultDuration; elapsedNs += webmTrack.defaultDuration;
} else { } else {
// hardcoded way, guess the sample duration // hardcoded way, guess the sample duration
elapsedNs += webmBlockNearDuration; elapsedNs += webmBlockNearDuration;
} }
} else { } else {
elapsedNs += bloq.absoluteTimeCodeNs; elapsedNs += bloq.absoluteTimeCodeNs;
} }
// get the sample count in the page // get the sample count in the page
elapsedNs = elapsedNs / TIME_SCALE_NS; elapsedNs = elapsedNs / TIME_SCALE_NS;
elapsedNs = Math.ceil(elapsedNs * resolution); elapsedNs = Math.ceil(elapsedNs * resolution);
// create header and calculate page checksum // create header and calculate page checksum
int checksum = makePacketheader((long) elapsedNs, header, null); int checksum = makePacketheader((long) elapsedNs, header, null);
checksum = calcCrc32(checksum, page.array(), page.position()); checksum = calcCrc32(checksum, page.array(), page.position());
header.putInt(HEADER_CHECKSUM_OFFSET, checksum); header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
// dump data // dump data
write(header); write(header);
write(page); write(page);
webmBlock = bloq; webmBlock = bloq;
} }
} }
private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer, private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer,
final byte[] immediatePage) { final byte[] immediatePage) {
short length = HEADER_SIZE; short length = HEADER_SIZE;
buffer.putInt(0x5367674f); // "OggS" binary string in little-endian buffer.putInt(0x5367674f); // "OggS" binary string in little-endian
buffer.put((byte) 0x00); // version buffer.put((byte) 0x00); // version
buffer.put(packetFlag); // type buffer.put(packetFlag); // type
buffer.putLong(granPos); // granulate position buffer.putLong(granPos); // granulate position
buffer.putInt(streamId); // bitstream serial number buffer.putInt(streamId); // bitstream serial number
buffer.putInt(sequenceCount++); // page sequence number buffer.putInt(sequenceCount++); // page sequence number
buffer.putInt(0x00); // page checksum buffer.putInt(0x00); // page checksum
buffer.put((byte) segmentTableSize); // segment table buffer.put((byte) segmentTableSize); // segment table
buffer.put(segmentTable, 0, segmentTableSize); // segment size buffer.put(segmentTable, 0, segmentTableSize); // segment size
length += segmentTableSize; length += segmentTableSize;
clearSegmentTable(); // clear segment table for next header clearSegmentTable(); // clear segment table for next header
int checksumCrc32 = calcCrc32(0x00, buffer.array(), length); int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
if (immediatePage != null) { if (immediatePage != null) {
checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length); checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32); buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
segmentTableNextTimestamp -= TIME_SCALE_NS; segmentTableNextTimestamp -= TIME_SCALE_NS;
} }
return checksumCrc32; return checksumCrc32;
} }
@Nullable @Nullable
private byte[] makeMetadata() { private byte[] makeMetadata() {
if ("A_OPUS".equals(webmTrack.codecId)) { if ("A_OPUS".equals(webmTrack.codecId)) {
return new byte[]{ return new byte[]{
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
0x00, 0x00, 0x00, 0x00, // writing application string size (not present) 0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags) 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
}; };
} else if ("A_VORBIS".equals(webmTrack.codecId)) { } else if ("A_VORBIS".equals(webmTrack.codecId)) {
return new byte[]{ return new byte[]{
0x03, // ¿¿¿??? 0x03, // ¿¿¿???
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
0x00, 0x00, 0x00, 0x00, // writing application string size (not present) 0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags) 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
}; };
} }
// not implemented for the desired codec // not implemented for the desired codec
return null; return null;
} }
private void write(final ByteBuffer buffer) throws IOException { private void write(final ByteBuffer buffer) throws IOException {
output.write(buffer.array(), 0, buffer.position()); output.write(buffer.array(), 0, buffer.position());
buffer.position(0); buffer.position(0);
} }
@Nullable @Nullable
private SimpleBlock getNextBlock() throws IOException { private SimpleBlock getNextBlock() throws IOException {
SimpleBlock res; SimpleBlock res;
if (webmBlock != null) { if (webmBlock != null) {
res = webmBlock; res = webmBlock;
webmBlock = null; webmBlock = null;
return res; return res;
} }
if (webmSegment == null) { if (webmSegment == null) {
webmSegment = webm.getNextSegment(); webmSegment = webm.getNextSegment();
if (webmSegment == null) { if (webmSegment == null) {
return null; // no more blocks in the selected track return null; // no more blocks in the selected track
} }
} }
if (webmCluster == null) { if (webmCluster == null) {
webmCluster = webmSegment.getNextCluster(); webmCluster = webmSegment.getNextCluster();
if (webmCluster == null) { if (webmCluster == null) {
webmSegment = null; webmSegment = null;
return getNextBlock(); return getNextBlock();
} }
} }
res = webmCluster.getNextSimpleBlock(); res = webmCluster.getNextSimpleBlock();
if (res == null) { if (res == null) {
webmCluster = null; webmCluster = null;
return getNextBlock(); return getNextBlock();
} }
webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode; webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
webmBlockLastTimecode = res.absoluteTimeCodeNs; webmBlockLastTimecode = res.absoluteTimeCodeNs;
return res; return res;
} }
private float getSampleFrequencyFromTrack(final byte[] bMetadata) { private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
// hardcoded way // hardcoded way
final ByteBuffer buffer = ByteBuffer.wrap(bMetadata); final ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
while (buffer.remaining() >= 6) { while (buffer.remaining() >= 6) {
final int id = buffer.getShort() & 0xFFFF; final int id = buffer.getShort() & 0xFFFF;
if (id == 0x0000B584) { if (id == 0x0000B584) {
return buffer.getFloat(); return buffer.getFloat();
} }
} }
return 0.0f; return 0.0f;
} }
private void clearSegmentTable() { private void clearSegmentTable() {
segmentTableNextTimestamp += TIME_SCALE_NS; segmentTableNextTimestamp += TIME_SCALE_NS;
packetFlag = FLAG_UNSET; packetFlag = FLAG_UNSET;
segmentTableSize = 0; segmentTableSize = 0;
} }
private boolean addPacketSegment(final SimpleBlock block) { private boolean addPacketSegment(final SimpleBlock block) {
final long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay; final long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
if (timestamp >= segmentTableNextTimestamp) { if (timestamp >= segmentTableNextTimestamp) {
return false; return false;
} }
return addPacketSegment(block.dataSize); return addPacketSegment(block.dataSize);
} }
private boolean addPacketSegment(final int size) { private boolean addPacketSegment(final int size) {
if (size > 65025) { if (size > 65025) {
throw new UnsupportedOperationException("page size cannot be larger than 65025"); throw new UnsupportedOperationException("page size cannot be larger than 65025");
} }
int available = (segmentTable.length - segmentTableSize) * 255; int available = (segmentTable.length - segmentTableSize) * 255;
final boolean extra = (size % 255) == 0; final boolean extra = (size % 255) == 0;
if (extra) { if (extra) {
// add a zero byte entry in the table // add a zero byte entry in the table
// required to indicate the sample size is multiple of 255 // required to indicate the sample size is multiple of 255
available -= 255; available -= 255;
} }
// check if possible add the segment, without overflow the table // check if possible add the segment, without overflow the table
if (available < size) { if (available < size) {
return false; // not enough space on the page return false; // not enough space on the page
} }
for (int seg = size; seg > 0; seg -= 255) { for (int seg = size; seg > 0; seg -= 255) {
segmentTable[segmentTableSize++] = (byte) Math.min(seg, 255); segmentTable[segmentTableSize++] = (byte) Math.min(seg, 255);
} }
if (extra) { if (extra) {
segmentTable[segmentTableSize++] = 0x00; segmentTable[segmentTableSize++] = 0x00;
} }
return true; return true;
} }
private void populateCrc32Table() { private void populateCrc32Table() {
for (int i = 0; i < 0x100; i++) { for (int i = 0; i < 0x100; i++) {
int crc = i << 24; int crc = i << 24;
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++) {
final long b = crc >>> 31; final long b = crc >>> 31;
crc <<= 1; crc <<= 1;
crc ^= (int) (0x100000000L - b) & 0x04c11db7; crc ^= (int) (0x100000000L - b) & 0x04c11db7;
} }
crc32Table[i] = crc; crc32Table[i] = crc;
} }
} }
private int calcCrc32(final int initialCrc, final byte[] buffer, final int size) { private int calcCrc32(final int initialCrc, final byte[] buffer, final int size) {
int crc = initialCrc; int crc = initialCrc;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
final int reg = (crc >>> 24) & 0xff; final int reg = (crc >>> 24) & 0xff;
crc = (crc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)]; crc = (crc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
} }
return crc; return crc;
} }
} }

View File

@ -1,313 +1,313 @@
package us.shandian.giga.get; package us.shandian.giga.get;
import android.util.Log; import android.util.Log;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamExtractor; import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ClosedByInterruptException;
import java.util.List; import java.util.List;
import us.shandian.giga.get.DownloadMission.HttpError; import us.shandian.giga.get.DownloadMission.HttpError;
import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE;
public class DownloadMissionRecover extends Thread { public class DownloadMissionRecover extends Thread {
private static final String TAG = "DownloadMissionRecover"; private static final String TAG = "DownloadMissionRecover";
static final int mID = -3; static final int mID = -3;
private final DownloadMission mMission; private final DownloadMission mMission;
private final boolean mNotInitialized; private final boolean mNotInitialized;
private final int mErrCode; private final int mErrCode;
private HttpURLConnection mConn; private HttpURLConnection mConn;
private MissionRecoveryInfo mRecovery; private MissionRecoveryInfo mRecovery;
private StreamExtractor mExtractor; private StreamExtractor mExtractor;
DownloadMissionRecover(DownloadMission mission, int errCode) { DownloadMissionRecover(DownloadMission mission, int errCode) {
mMission = mission; mMission = mission;
mNotInitialized = mission.blocks == null && mission.current == 0; mNotInitialized = mission.blocks == null && mission.current == 0;
mErrCode = errCode; mErrCode = errCode;
} }
@Override @Override
public void run() { public void run() {
if (mMission.source == null) { if (mMission.source == null) {
mMission.notifyError(mErrCode, null); mMission.notifyError(mErrCode, null);
return; return;
} }
Exception err = null; Exception err = null;
int attempt = 0; int attempt = 0;
while (attempt++ < mMission.maxRetry) { while (attempt++ < mMission.maxRetry) {
try { try {
tryRecover(); tryRecover();
return; return;
} catch (InterruptedIOException | ClosedByInterruptException e) { } catch (InterruptedIOException | ClosedByInterruptException e) {
return; return;
} catch (Exception e) { } catch (Exception e) {
if (!mMission.running || super.isInterrupted()) return; if (!mMission.running || super.isInterrupted()) return;
err = e; err = e;
} }
} }
// give up // give up
mMission.notifyError(mErrCode, err); mMission.notifyError(mErrCode, err);
} }
private void tryRecover() throws ExtractionException, IOException, HttpError { private void tryRecover() throws ExtractionException, IOException, HttpError {
if (mExtractor == null) { if (mExtractor == null) {
try { try {
StreamingService svr = NewPipe.getServiceByUrl(mMission.source); StreamingService svr = NewPipe.getServiceByUrl(mMission.source);
mExtractor = svr.getStreamExtractor(mMission.source); mExtractor = svr.getStreamExtractor(mMission.source);
mExtractor.fetchPage(); mExtractor.fetchPage();
} catch (ExtractionException e) { } catch (ExtractionException e) {
mExtractor = null; mExtractor = null;
throw e; throw e;
} }
} }
// maybe the following check is redundant // maybe the following check is redundant
if (!mMission.running || super.isInterrupted()) return; if (!mMission.running || super.isInterrupted()) return;
if (!mNotInitialized) { if (!mNotInitialized) {
// set the current download url to null in case if the recovery // set the current download url to null in case if the recovery
// process is canceled. Next time start() method is called the // process is canceled. Next time start() method is called the
// recovery will be executed, saving time // recovery will be executed, saving time
mMission.urls[mMission.current] = null; mMission.urls[mMission.current] = null;
mRecovery = mMission.recoveryInfo[mMission.current]; mRecovery = mMission.recoveryInfo[mMission.current];
resolveStream(); resolveStream();
return; return;
} }
Log.w(TAG, "mission is not fully initialized, this will take a while"); Log.w(TAG, "mission is not fully initialized, this will take a while");
try { try {
for (; mMission.current < mMission.urls.length; mMission.current++) { for (; mMission.current < mMission.urls.length; mMission.current++) {
mRecovery = mMission.recoveryInfo[mMission.current]; mRecovery = mMission.recoveryInfo[mMission.current];
if (test()) continue; if (test()) continue;
if (!mMission.running) return; if (!mMission.running) return;
resolveStream(); resolveStream();
if (!mMission.running) return; if (!mMission.running) return;
// before continue, check if the current stream was resolved // before continue, check if the current stream was resolved
if (mMission.urls[mMission.current] == null) { if (mMission.urls[mMission.current] == null) {
break; break;
} }
} }
} finally { } finally {
mMission.current = 0; mMission.current = 0;
} }
mMission.writeThisToFile(); mMission.writeThisToFile();
if (!mMission.running || super.isInterrupted()) return; if (!mMission.running || super.isInterrupted()) return;
mMission.running = false; mMission.running = false;
mMission.start(); mMission.start();
} }
private void resolveStream() throws IOException, ExtractionException, HttpError { private void resolveStream() throws IOException, ExtractionException, HttpError {
// FIXME: this getErrorMessage() always returns "video is unavailable" // FIXME: this getErrorMessage() always returns "video is unavailable"
/*if (mExtractor.getErrorMessage() != null) { /*if (mExtractor.getErrorMessage() != null) {
mMission.notifyError(mErrCode, new ExtractionException(mExtractor.getErrorMessage())); mMission.notifyError(mErrCode, new ExtractionException(mExtractor.getErrorMessage()));
return; return;
}*/ }*/
String url = null; String url = null;
switch (mRecovery.getKind()) { switch (mRecovery.getKind()) {
case 'a': case 'a':
for (AudioStream audio : mExtractor.getAudioStreams()) { for (AudioStream audio : mExtractor.getAudioStreams()) {
if (audio.average_bitrate == mRecovery.getDesiredBitrate() && audio.getFormat() == mRecovery.getFormat()) { if (audio.average_bitrate == mRecovery.getDesiredBitrate() && audio.getFormat() == mRecovery.getFormat()) {
url = audio.getUrl(); url = audio.getUrl();
break; break;
} }
} }
break; break;
case 'v': case 'v':
List<VideoStream> videoStreams; List<VideoStream> videoStreams;
if (mRecovery.isDesired2()) if (mRecovery.isDesired2())
videoStreams = mExtractor.getVideoOnlyStreams(); videoStreams = mExtractor.getVideoOnlyStreams();
else else
videoStreams = mExtractor.getVideoStreams(); videoStreams = mExtractor.getVideoStreams();
for (VideoStream video : videoStreams) { for (VideoStream video : videoStreams) {
if (video.resolution.equals(mRecovery.getDesired()) && video.getFormat() == mRecovery.getFormat()) { if (video.resolution.equals(mRecovery.getDesired()) && video.getFormat() == mRecovery.getFormat()) {
url = video.getUrl(); url = video.getUrl();
break; break;
} }
} }
break; break;
case 's': case 's':
for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.getFormat())) { for (SubtitlesStream subtitles : mExtractor.getSubtitles(mRecovery.getFormat())) {
String tag = subtitles.getLanguageTag(); String tag = subtitles.getLanguageTag();
if (tag.equals(mRecovery.getDesired()) && subtitles.isAutoGenerated() == mRecovery.isDesired2()) { if (tag.equals(mRecovery.getDesired()) && subtitles.isAutoGenerated() == mRecovery.isDesired2()) {
url = subtitles.getUrl(); url = subtitles.getUrl();
break; break;
} }
} }
break; break;
default: default:
throw new RuntimeException("Unknown stream type"); throw new RuntimeException("Unknown stream type");
} }
resolve(url); resolve(url);
} }
private void resolve(String url) throws IOException, HttpError { private void resolve(String url) throws IOException, HttpError {
if (mRecovery.getValidateCondition() == null) { if (mRecovery.getValidateCondition() == null) {
Log.w(TAG, "validation condition not defined, the resource can be stale"); Log.w(TAG, "validation condition not defined, the resource can be stale");
} }
if (mMission.unknownLength || mRecovery.getValidateCondition() == null) { if (mMission.unknownLength || mRecovery.getValidateCondition() == null) {
recover(url, false); recover(url, false);
return; return;
} }
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
////// Validate the http resource doing a range request ////// Validate the http resource doing a range request
///////////////////// /////////////////////
try { try {
mConn = mMission.openConnection(url, true, mMission.length - 10, mMission.length); mConn = mMission.openConnection(url, true, mMission.length - 10, mMission.length);
mConn.setRequestProperty("If-Range", mRecovery.getValidateCondition()); mConn.setRequestProperty("If-Range", mRecovery.getValidateCondition());
mMission.establishConnection(mID, mConn); mMission.establishConnection(mID, mConn);
int code = mConn.getResponseCode(); int code = mConn.getResponseCode();
switch (code) { switch (code) {
case 200: case 200:
case 413: case 413:
// stale // stale
recover(url, true); recover(url, true);
return; return;
case 206: case 206:
// in case of validation using the Last-Modified date, check the resource length // in case of validation using the Last-Modified date, check the resource length
long[] contentRange = parseContentRange(mConn.getHeaderField("Content-Range")); long[] contentRange = parseContentRange(mConn.getHeaderField("Content-Range"));
boolean lengthMismatch = contentRange[2] != -1 && contentRange[2] != mMission.length; boolean lengthMismatch = contentRange[2] != -1 && contentRange[2] != mMission.length;
recover(url, lengthMismatch); recover(url, lengthMismatch);
return; return;
} }
throw new HttpError(code); throw new HttpError(code);
} finally { } finally {
disconnect(); disconnect();
} }
} }
private void recover(String url, boolean stale) { private void recover(String url, boolean stale) {
Log.i(TAG, Log.i(TAG,
String.format("recover() name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url) String.format("recover() name=%s isStale=%s url=%s", mMission.storage.getName(), stale, url)
); );
mMission.urls[mMission.current] = url; mMission.urls[mMission.current] = url;
if (url == null) { if (url == null) {
mMission.urls = new String[0]; mMission.urls = new String[0];
mMission.notifyError(ERROR_RESOURCE_GONE, null); mMission.notifyError(ERROR_RESOURCE_GONE, null);
return; return;
} }
if (mNotInitialized) return; if (mNotInitialized) return;
if (stale) { if (stale) {
mMission.resetState(false, false, DownloadMission.ERROR_NOTHING); mMission.resetState(false, false, DownloadMission.ERROR_NOTHING);
} }
mMission.writeThisToFile(); mMission.writeThisToFile();
if (!mMission.running || super.isInterrupted()) return; if (!mMission.running || super.isInterrupted()) return;
mMission.running = false; mMission.running = false;
mMission.start(); mMission.start();
} }
private long[] parseContentRange(String value) { private long[] parseContentRange(String value) {
long[] range = new long[3]; long[] range = new long[3];
if (value == null) { if (value == null) {
// this never should happen // this never should happen
return range; return range;
} }
try { try {
value = value.trim(); value = value.trim();
if (!value.startsWith("bytes")) { if (!value.startsWith("bytes")) {
return range;// unknown range type return range;// unknown range type
} }
int space = value.lastIndexOf(' ') + 1; int space = value.lastIndexOf(' ') + 1;
int dash = value.indexOf('-', space) + 1; int dash = value.indexOf('-', space) + 1;
int bar = value.indexOf('/', dash); int bar = value.indexOf('/', dash);
// start // start
range[0] = Long.parseLong(value.substring(space, dash - 1)); range[0] = Long.parseLong(value.substring(space, dash - 1));
// end // end
range[1] = Long.parseLong(value.substring(dash, bar)); range[1] = Long.parseLong(value.substring(dash, bar));
// resource length // resource length
value = value.substring(bar + 1); value = value.substring(bar + 1);
if (value.equals("*")) { if (value.equals("*")) {
range[2] = -1;// unknown length received from the server but should be valid range[2] = -1;// unknown length received from the server but should be valid
} else { } else {
range[2] = Long.parseLong(value); range[2] = Long.parseLong(value);
} }
} catch (Exception e) { } catch (Exception e) {
// nothing to do // nothing to do
} }
return range; return range;
} }
private boolean test() { private boolean test() {
if (mMission.urls[mMission.current] == null) return false; if (mMission.urls[mMission.current] == null) return false;
try { try {
mConn = mMission.openConnection(mMission.urls[mMission.current], true, -1, -1); mConn = mMission.openConnection(mMission.urls[mMission.current], true, -1, -1);
mMission.establishConnection(mID, mConn); mMission.establishConnection(mID, mConn);
if (mConn.getResponseCode() == 200) return true; if (mConn.getResponseCode() == 200) return true;
} catch (Exception e) { } catch (Exception e) {
// nothing to do // nothing to do
} finally { } finally {
disconnect(); disconnect();
} }
return false; return false;
} }
private void disconnect() { private void disconnect() {
try { try {
try { try {
mConn.getInputStream().close(); mConn.getInputStream().close();
} finally { } finally {
mConn.disconnect(); mConn.disconnect();
} }
} catch (Exception e) { } catch (Exception e) {
// nothing to do // nothing to do
} finally { } finally {
mConn = null; mConn = null;
} }
} }
@Override @Override
public void interrupt() { public void interrupt() {
super.interrupt(); super.interrupt();
if (mConn != null) disconnect(); if (mConn != null) disconnect();
} }
} }

View File

@ -1,18 +1,18 @@
package us.shandian.giga.get; package us.shandian.giga.get;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
public class FinishedMission extends Mission { public class FinishedMission extends Mission {
public FinishedMission() { public FinishedMission() {
} }
public FinishedMission(@NonNull DownloadMission mission) { public FinishedMission(@NonNull DownloadMission mission) {
source = mission.source; source = mission.source;
length = mission.length; length = mission.length;
timestamp = mission.timestamp; timestamp = mission.timestamp;
kind = mission.kind; kind = mission.kind;
storage = mission.storage; storage = mission.storage;
} }
} }

View File

@ -1,64 +1,64 @@
package us.shandian.giga.get; package us.shandian.giga.get;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.io.Serializable; import java.io.Serializable;
import java.util.Calendar; import java.util.Calendar;
import us.shandian.giga.io.StoredFileHelper; import us.shandian.giga.io.StoredFileHelper;
public abstract class Mission implements Serializable { public abstract class Mission implements Serializable {
private static final long serialVersionUID = 1L;// last bump: 27 march 2019 private static final long serialVersionUID = 1L;// last bump: 27 march 2019
/** /**
* Source url of the resource * Source url of the resource
*/ */
public String source; public String source;
/** /**
* Length of the current resource * Length of the current resource
*/ */
public long length; public long length;
/** /**
* creation timestamp (and maybe unique identifier) * creation timestamp (and maybe unique identifier)
*/ */
public long timestamp; public long timestamp;
/** /**
* pre-defined content type * pre-defined content type
*/ */
public char kind; public char kind;
/** /**
* The downloaded file * The downloaded file
*/ */
public StoredFileHelper storage; public StoredFileHelper storage;
public long getTimestamp() { public long getTimestamp() {
return timestamp; return timestamp;
} }
/** /**
* Delete the downloaded file * Delete the downloaded file
* *
* @return {@code true] if and only if the file is successfully deleted, otherwise, {@code false} * @return {@code true] if and only if the file is successfully deleted, otherwise, {@code false}
*/ */
public boolean delete() { public boolean delete() {
if (storage != null) return storage.delete(); if (storage != null) return storage.delete();
return true; return true;
} }
/** /**
* Indicate if this mission is deleted whatever is stored * Indicate if this mission is deleted whatever is stored
*/ */
public transient boolean deleted = false; public transient boolean deleted = false;
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp); calendar.setTimeInMillis(timestamp);
return "[" + calendar.getTime().toString() + "] " + (storage.isInvalid() ? storage.getName() : storage.getUri()); return "[" + calendar.getTime().toString() + "] " + (storage.isInvalid() ? storage.getName() : storage.getUri());
} }
} }

View File

@ -1,11 +1,11 @@
package us.shandian.giga.io; package us.shandian.giga.io;
public interface ProgressReport { public interface ProgressReport {
/** /**
* Report the size of the new file * Report the size of the new file
* *
* @param progress the new size * @param progress the new size
*/ */
void report(long progress); void report(long progress);
} }

View File

@ -1,44 +1,44 @@
package us.shandian.giga.postprocessing; package us.shandian.giga.postprocessing;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.schabi.newpipe.streams.OggFromWebMWriter; import org.schabi.newpipe.streams.OggFromWebMWriter;
import org.schabi.newpipe.streams.io.SharpStream; import org.schabi.newpipe.streams.io.SharpStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
class OggFromWebmDemuxer extends Postprocessing { class OggFromWebmDemuxer extends Postprocessing {
OggFromWebmDemuxer() { OggFromWebmDemuxer() {
super(true, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER); super(true, true, ALGORITHM_OGG_FROM_WEBM_DEMUXER);
} }
@Override @Override
boolean test(SharpStream... sources) throws IOException { boolean test(SharpStream... sources) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(4); ByteBuffer buffer = ByteBuffer.allocate(4);
sources[0].read(buffer.array()); sources[0].read(buffer.array());
// youtube uses WebM as container, but the file extension (format suffix) is "*.opus" // youtube uses WebM as container, but the file extension (format suffix) is "*.opus"
// check if the file is a webm/mkv file before proceed // check if the file is a webm/mkv file before proceed
switch (buffer.getInt()) { switch (buffer.getInt()) {
case 0x1a45dfa3: case 0x1a45dfa3:
return true;// webm/mkv return true;// webm/mkv
case 0x4F676753: case 0x4F676753:
return false;// ogg return false;// ogg
} }
throw new UnsupportedOperationException("file not recognized, failed to demux the audio stream"); throw new UnsupportedOperationException("file not recognized, failed to demux the audio stream");
} }
@Override @Override
int process(SharpStream out, @NonNull SharpStream... sources) throws IOException { int process(SharpStream out, @NonNull SharpStream... sources) throws IOException {
OggFromWebMWriter demuxer = new OggFromWebMWriter(sources[0], out); OggFromWebMWriter demuxer = new OggFromWebMWriter(sources[0], out);
demuxer.parseSource(); demuxer.parseSource();
demuxer.selectTrack(0); demuxer.selectTrack(0);
demuxer.build(); demuxer.build();
return OK_RESULT; return OK_RESULT;
} }
} }

View File

@ -1,138 +1,138 @@
package us.shandian.giga.ui.common; package us.shandian.giga.ui.common;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.os.Handler; import android.os.Handler;
import android.view.View; import android.view.View;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import java.util.ArrayList; import java.util.ArrayList;
import us.shandian.giga.get.FinishedMission; import us.shandian.giga.get.FinishedMission;
import us.shandian.giga.get.Mission; import us.shandian.giga.get.Mission;
import us.shandian.giga.service.DownloadManager; import us.shandian.giga.service.DownloadManager;
import us.shandian.giga.service.DownloadManager.MissionIterator; import us.shandian.giga.service.DownloadManager.MissionIterator;
import us.shandian.giga.ui.adapter.MissionAdapter; import us.shandian.giga.ui.adapter.MissionAdapter;
public class Deleter { public class Deleter {
private static final int TIMEOUT = 5000;// ms private static final int TIMEOUT = 5000;// ms
private static final int DELAY = 350;// ms private static final int DELAY = 350;// ms
private static final int DELAY_RESUME = 400;// ms private static final int DELAY_RESUME = 400;// ms
private Snackbar snackbar; private Snackbar snackbar;
private ArrayList<Mission> items; private ArrayList<Mission> items;
private boolean running = true; private boolean running = true;
private final Context mContext; private final Context mContext;
private final MissionAdapter mAdapter; private final MissionAdapter mAdapter;
private final DownloadManager mDownloadManager; private final DownloadManager mDownloadManager;
private final MissionIterator mIterator; private final MissionIterator mIterator;
private final Handler mHandler; private final Handler mHandler;
private final View mView; private final View mView;
private final Runnable rShow; private final Runnable rShow;
private final Runnable rNext; private final Runnable rNext;
private final Runnable rCommit; private final Runnable rCommit;
public Deleter(View v, Context c, MissionAdapter a, DownloadManager d, MissionIterator i, Handler h) { public Deleter(View v, Context c, MissionAdapter a, DownloadManager d, MissionIterator i, Handler h) {
mView = v; mView = v;
mContext = c; mContext = c;
mAdapter = a; mAdapter = a;
mDownloadManager = d; mDownloadManager = d;
mIterator = i; mIterator = i;
mHandler = h; mHandler = h;
// use variables to know the reference of the lambdas // use variables to know the reference of the lambdas
rShow = this::show; rShow = this::show;
rNext = this::next; rNext = this::next;
rCommit = this::commit; rCommit = this::commit;
items = new ArrayList<>(2); items = new ArrayList<>(2);
} }
public void append(Mission item) { public void append(Mission item) {
mIterator.hide(item); mIterator.hide(item);
items.add(0, item); items.add(0, item);
show(); show();
} }
private void forget() { private void forget() {
mIterator.unHide(items.remove(0)); mIterator.unHide(items.remove(0));
mAdapter.applyChanges(); mAdapter.applyChanges();
show(); show();
} }
private void show() { private void show() {
if (items.size() < 1) return; if (items.size() < 1) return;
pause(); pause();
running = true; running = true;
mHandler.postDelayed(rNext, DELAY); mHandler.postDelayed(rNext, DELAY);
} }
private void next() { private void next() {
if (items.size() < 1) return; if (items.size() < 1) return;
String msg = mContext.getString(R.string.file_deleted).concat(":\n").concat(items.get(0).storage.getName()); String msg = mContext.getString(R.string.file_deleted).concat(":\n").concat(items.get(0).storage.getName());
snackbar = Snackbar.make(mView, msg, Snackbar.LENGTH_INDEFINITE); snackbar = Snackbar.make(mView, msg, Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(R.string.undo, s -> forget()); snackbar.setAction(R.string.undo, s -> forget());
snackbar.setActionTextColor(Color.YELLOW); snackbar.setActionTextColor(Color.YELLOW);
snackbar.show(); snackbar.show();
mHandler.postDelayed(rCommit, TIMEOUT); mHandler.postDelayed(rCommit, TIMEOUT);
} }
private void commit() { private void commit() {
if (items.size() < 1) return; if (items.size() < 1) return;
while (items.size() > 0) { while (items.size() > 0) {
Mission mission = items.remove(0); Mission mission = items.remove(0);
if (mission.deleted) continue; if (mission.deleted) continue;
mIterator.unHide(mission); mIterator.unHide(mission);
mDownloadManager.deleteMission(mission); mDownloadManager.deleteMission(mission);
if (mission instanceof FinishedMission) { if (mission instanceof FinishedMission) {
mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mission.storage.getUri())); mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mission.storage.getUri()));
} }
break; break;
} }
if (items.size() < 1) { if (items.size() < 1) {
pause(); pause();
return; return;
} }
show(); show();
} }
public void pause() { public void pause() {
running = false; running = false;
mHandler.removeCallbacks(rNext); mHandler.removeCallbacks(rNext);
mHandler.removeCallbacks(rShow); mHandler.removeCallbacks(rShow);
mHandler.removeCallbacks(rCommit); mHandler.removeCallbacks(rCommit);
if (snackbar != null) snackbar.dismiss(); if (snackbar != null) snackbar.dismiss();
} }
public void resume() { public void resume() {
if (running) return; if (running) return;
mHandler.postDelayed(rShow, DELAY_RESUME); mHandler.postDelayed(rShow, DELAY_RESUME);
} }
public void dispose() { public void dispose() {
if (items.size() < 1) return; if (items.size() < 1) return;
pause(); pause();
for (Mission mission : items) mDownloadManager.deleteMission(mission); for (Mission mission : items) mDownloadManager.deleteMission(mission);
items = null; items = null;
} }
} }

View File

@ -1,29 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="30dp" android:layout_height="30dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/item_name" android:id="@+id/item_name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="relative header" android:text="relative header"
android:textAppearance="?android:attr/textAppearanceLarge" android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" /> android:textStyle="bold" />
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="2dp" android:layout_height="2dp"
android:background="@color/black_settings_accent_color" /> android:background="@color/black_settings_accent_color" />
</LinearLayout> </LinearLayout>