New MP4 muxer + Queue changes + Storage fixes
Main changes: * correctly check the available space (CircularFile.java) * misc cleanup (CircularFile.java) * use the "Error Reporter" for non-http errors * rewrite network state checking and add better support for API 21 (Lollipop) or higher * implement "metered networks" * add buttons in "Downloads" activity to start/pause all pending downloads, ignoring the queue flag or if the network is "metered" * add workaround for VPN connections and/or network switching. Example: switching WiFi to 3G * rewrite DataReader ¡Webm muxer is now 57% more faster! * rewrite CircularFile, use file buffers instead of memory buffers. Less troubles in low-end devices * fix missing offset for KaxCluster (WebMWriter.java), manifested as no thumbnails on file explorers Download queue: * remember queue status, unless the user pause the download (un-queue) * semi-automatic downloads, between networks. Effective if the user create a new download or the downloads activity is starts * allow enqueue failed downloads * new option, queue limit, enabled by default. Used to allow one or multiple downloads at same time Miscellaneous: * fix crash while selecting details/error menu (mistake on MissionFragment.java) * misc serialize changes (DownloadMission.java) * minor UI tweaks * allow overwrite paused downloads * fix wrong icons for grid/list button in downloads * add share option * implement #2006 * correct misspelled word in strings.xml (es) (cmn) * fix MissionAdapter crash during device shutdown New Mp4Muxer + required changes: * new mp4 muxer (from dash only) with this, muxing on Android 7 is possible now!!! * re-work in SharpStream * drop mp4 dash muxer * misc changes: add warning in SecondaryStreamHelper.java, * strip m4a DASH files to normal m4a format (youtube only) Fix storage issues: * warn to the user if is choosing a "read only" download directory (for external SD Cards), useless is rooted :) * "write proof" allow post-processing resuming only if the device ran out of space * implement "insufficient storage" error for downloads
This commit is contained in:
parent
1684a2110c
commit
9e34fee58c
49 changed files with 2715 additions and 1936 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package us.shandian.giga.postprocessing;
|
||||
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.streams.io.SharpStream;
|
||||
|
||||
|
|
@ -9,17 +10,22 @@ import java.io.IOException;
|
|||
|
||||
import us.shandian.giga.get.DownloadMission;
|
||||
import us.shandian.giga.postprocessing.io.ChunkFileInputStream;
|
||||
import us.shandian.giga.postprocessing.io.CircularFile;
|
||||
import us.shandian.giga.postprocessing.io.CircularFileWriter;
|
||||
import us.shandian.giga.postprocessing.io.CircularFileWriter.OffsetChecker;
|
||||
import us.shandian.giga.service.DownloadManagerService;
|
||||
|
||||
import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING;
|
||||
import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_HOLD;
|
||||
import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_EXCEPTION;
|
||||
|
||||
public abstract class Postprocessing {
|
||||
|
||||
static final byte OK_RESULT = DownloadMission.ERROR_NOTHING;
|
||||
static final byte OK_RESULT = ERROR_NOTHING;
|
||||
|
||||
public static final String ALGORITHM_TTML_CONVERTER = "ttml";
|
||||
public static final String ALGORITHM_MP4_DASH_MUXER = "mp4D";
|
||||
public static final String ALGORITHM_MP4_MUXER = "mp4";
|
||||
public static final String ALGORITHM_WEBM_MUXER = "webm";
|
||||
public static final String ALGORITHM_MP4_FROM_DASH_MUXER = "mp4D-mp4";
|
||||
public static final String ALGORITHM_M4A_NO_DASH = "mp4D-m4a";
|
||||
|
||||
public static Postprocessing getAlgorithm(String algorithmName, DownloadMission mission) {
|
||||
if (null == algorithmName) {
|
||||
|
|
@ -27,14 +33,14 @@ public abstract class Postprocessing {
|
|||
} else switch (algorithmName) {
|
||||
case ALGORITHM_TTML_CONVERTER:
|
||||
return new TtmlConverter(mission);
|
||||
case ALGORITHM_MP4_DASH_MUXER:
|
||||
return new Mp4DashMuxer(mission);
|
||||
case ALGORITHM_MP4_MUXER:
|
||||
return new Mp4Muxer(mission);
|
||||
case ALGORITHM_WEBM_MUXER:
|
||||
return new WebMMuxer(mission);
|
||||
case ALGORITHM_MP4_FROM_DASH_MUXER:
|
||||
return new Mp4FromDashMuxer(mission);
|
||||
case ALGORITHM_M4A_NO_DASH:
|
||||
return new M4aNoDash(mission);
|
||||
/*case "example-algorithm":
|
||||
return new ExampleAlgorithm(mission);*/
|
||||
return new ExampleAlgorithm(mission);*/
|
||||
default:
|
||||
throw new RuntimeException("Unimplemented post-processing algorithm: " + algorithmName);
|
||||
}
|
||||
|
|
@ -65,7 +71,8 @@ public abstract class Postprocessing {
|
|||
|
||||
public void run() throws IOException {
|
||||
File file = mission.getDownloadedFile();
|
||||
CircularFile out = null;
|
||||
File temp = null;
|
||||
CircularFileWriter out = null;
|
||||
int result;
|
||||
long finalLength = -1;
|
||||
|
||||
|
|
@ -81,29 +88,54 @@ public abstract class Postprocessing {
|
|||
}
|
||||
sources[i] = new ChunkFileInputStream(file, mission.offsets[i], mission.getDownloadedFile().length(), "rw");
|
||||
|
||||
int[] idx = {0};
|
||||
CircularFile.OffsetChecker checker = () -> {
|
||||
while (idx[0] < sources.length) {
|
||||
/*
|
||||
* WARNING: never use rewind() in any chunk after any writing (especially on first chunks)
|
||||
* or the CircularFile can lead to unexpected results
|
||||
*/
|
||||
if (sources[idx[0]].isDisposed() || sources[idx[0]].available() < 1) {
|
||||
idx[0]++;
|
||||
continue;// the selected source is not used anymore
|
||||
if (test(sources)) {
|
||||
for (SharpStream source : sources) source.rewind();
|
||||
|
||||
OffsetChecker checker = () -> {
|
||||
for (ChunkFileInputStream source : sources) {
|
||||
/*
|
||||
* WARNING: never use rewind() in any chunk after any writing (especially on first chunks)
|
||||
* or the CircularFileWriter can lead to unexpected results
|
||||
*/
|
||||
if (source.isDisposed() || source.available() < 1) {
|
||||
continue;// the selected source is not used anymore
|
||||
}
|
||||
|
||||
return source.getFilePointer() - 1;
|
||||
}
|
||||
|
||||
return sources[idx[0]].getFilePointer() - 1;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
return -1;
|
||||
};
|
||||
out = new CircularFile(file, 0, this::progressReport, checker);
|
||||
temp = new File(mission.location, mission.name + ".tmp");
|
||||
|
||||
result = process(out, sources);
|
||||
out = new CircularFileWriter(file, temp, checker);
|
||||
out.onProgress = this::progressReport;
|
||||
|
||||
if (result == OK_RESULT)
|
||||
finalLength = out.finalizeFile();
|
||||
out.onWriteError = (err) -> {
|
||||
mission.postprocessingState = 3;
|
||||
mission.notifyError(ERROR_POSTPROCESSING_HOLD, err);
|
||||
|
||||
try {
|
||||
synchronized (this) {
|
||||
while (mission.postprocessingState == 3)
|
||||
wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// nothing to do
|
||||
Log.e(this.getClass().getSimpleName(), "got InterruptedException");
|
||||
}
|
||||
|
||||
return mission.errCode == ERROR_NOTHING;
|
||||
};
|
||||
|
||||
result = process(out, sources);
|
||||
|
||||
if (result == OK_RESULT)
|
||||
finalLength = out.finalizeFile();
|
||||
} else {
|
||||
result = OK_RESULT;
|
||||
}
|
||||
} finally {
|
||||
for (SharpStream source : sources) {
|
||||
if (source != null && !source.isDisposed()) {
|
||||
|
|
@ -113,17 +145,22 @@ public abstract class Postprocessing {
|
|||
if (out != null) {
|
||||
out.dispose();
|
||||
}
|
||||
if (temp != null) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
temp.delete();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = process(null);
|
||||
result = test() ? process(null) : OK_RESULT;
|
||||
}
|
||||
|
||||
if (result == OK_RESULT) {
|
||||
if (finalLength < 0) finalLength = file.length();
|
||||
mission.done = finalLength;
|
||||
mission.length = finalLength;
|
||||
if (finalLength != -1) {
|
||||
mission.done = finalLength;
|
||||
mission.length = finalLength;
|
||||
}
|
||||
} else {
|
||||
mission.errCode = DownloadMission.ERROR_UNKNOWN_EXCEPTION;
|
||||
mission.errCode = ERROR_UNKNOWN_EXCEPTION;
|
||||
mission.errObject = new RuntimeException("post-processing algorithm returned " + result);
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +171,18 @@ public abstract class Postprocessing {
|
|||
}
|
||||
|
||||
/**
|
||||
* Abstract method to execute the pos-processing algorithm
|
||||
* Test if the post-processing algorithm can be skipped
|
||||
*
|
||||
* @param sources files to be processed
|
||||
* @return {@code true} if the post-processing is required, otherwise, {@code false}
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
boolean test(SharpStream... sources) throws IOException {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method to execute the post-processing algorithm
|
||||
*
|
||||
* @param out output stream
|
||||
* @param sources files to be processed
|
||||
|
|
@ -151,7 +199,7 @@ public abstract class Postprocessing {
|
|||
return mission.postprocessingArgs[index];
|
||||
}
|
||||
|
||||
void progressReport(long done) {
|
||||
private void progressReport(long done) {
|
||||
mission.done = done;
|
||||
if (mission.length < mission.done) mission.length = mission.done;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue