From f2285c0b191b70aa25961f7ad19516c715fa522b Mon Sep 17 00:00:00 2001 From: kapodamy Date: Mon, 21 Jan 2019 01:30:03 -0300 Subject: [PATCH] MP4 muxer +misc modifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * allow retry downloads with "post-processing failed" error in the new muxer * MPEG-4 muxer ¡¡ no DASH output!! * keep the progress if download fails * remove TODO in SecondaryStreamHelper.java * misc clean-up * delete TestAlgo.java * delete ExtSDDownloadFailedActivity.java and remove it from AndroidManifest.xml * use hardcored version for changing icon colors --- app/src/main/AndroidManifest.xml | 1 - .../newpipe/download/DownloadDialog.java | 7 +- .../download/ExtSDDownloadFailedActivity.java | 38 ----- .../newpipe/util/SecondaryStreamHelper.java | 1 - .../giga/get/DownloadInitializer.java | 1 - .../us/shandian/giga/get/DownloadMission.java | 123 +++++++++++----- .../giga/postprocessing/Mp4DashMuxer.java | 4 +- .../giga/postprocessing/Mp4Muxer.java | 136 ++++++++++++++++++ .../giga/postprocessing/Postprocessing.java | 123 +++++++++------- .../giga/postprocessing/TestAlgo.java | 54 ------- ...TttmlConverter.java => TtmlConverter.java} | 15 +- .../giga/postprocessing/WebMMuxer.java | 4 +- .../giga/service/DownloadManager.java | 30 ++-- .../giga/ui/adapter/MissionAdapter.java | 48 +++---- .../giga/ui/fragment/MissionsFragment.java | 32 ++--- 15 files changed, 357 insertions(+), 260 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/download/ExtSDDownloadFailedActivity.java create mode 100644 app/src/main/java/us/shandian/giga/postprocessing/Mp4Muxer.java delete mode 100644 app/src/main/java/us/shandian/giga/postprocessing/TestAlgo.java rename app/src/main/java/us/shandian/giga/postprocessing/{TttmlConverter.java => TtmlConverter.java} (83%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1bc205f33..5aa1dc982 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -119,7 +119,6 @@ - { - // should be safe run the following code without "getActivity().runOnUiThread()" if (listed) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.download_dialog_title) @@ -511,11 +510,11 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck if (secondaryStream != null) { secondaryStreamUrl = secondaryStream.getStream().getUrl(); - psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_DASH_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER; + psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER; psArgs = null; long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream); - // set nearLength, only, if both sizes are fetched or known. this probably does not work on weak internet connections + // set nearLength, only, if both sizes are fetched or known. this probably does not work on slow networks if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) { nearLength = secondaryStream.getSizeInBytes() + videoSize; } diff --git a/app/src/main/java/org/schabi/newpipe/download/ExtSDDownloadFailedActivity.java b/app/src/main/java/org/schabi/newpipe/download/ExtSDDownloadFailedActivity.java deleted file mode 100644 index c02ef92eb..000000000 --- a/app/src/main/java/org/schabi/newpipe/download/ExtSDDownloadFailedActivity.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.schabi.newpipe.download; - -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.settings.NewPipeSettings; -import org.schabi.newpipe.util.ServiceHelper; -import org.schabi.newpipe.util.ThemeHelper; - -public class ExtSDDownloadFailedActivity extends AppCompatActivity { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); - } - - @Override - protected void onStart() { - super.onStart(); - new AlertDialog.Builder(this) - .setTitle(R.string.download_to_sdcard_error_title) - .setMessage(R.string.download_to_sdcard_error_message) - .setPositiveButton(R.string.yes, (DialogInterface dialogInterface, int i) -> { - NewPipeSettings.resetDownloadFolders(this); - finish(); - }) - .setNegativeButton(R.string.cancel, (DialogInterface dialogInterface, int i) -> { - dialogInterface.dismiss(); - finish(); - }) - .create() - .show(); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java b/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java index a5d3ea3eb..b3522aea0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java @@ -36,7 +36,6 @@ public class SecondaryStreamHelper { * @return selected audio stream or null if a candidate was not found */ public static AudioStream getAudioStreamFor(@NonNull List audioStreams, @NonNull VideoStream videoStream) { - // TODO: check if m4v and m4a selected streams are DASH compliant switch (videoStream.getFormat()) { case WEBM: case MPEG_4: diff --git a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java index ce7ae267c..b864cf4fb 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadInitializer.java @@ -156,7 +156,6 @@ public class DownloadInitializer extends Thread { if (retryCount++ > mMission.maxRetry) { Log.e(TAG, "initializer failed", e); - mMission.running = false; mMission.notifyError(e); return; } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index b41cfc350..243a8585a 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -39,7 +39,7 @@ public class DownloadMission extends Mission { public static final int ERROR_SSL_EXCEPTION = 1004; public static final int ERROR_UNKNOWN_HOST = 1005; public static final int ERROR_CONNECT_HOST = 1006; - public static final int ERROR_POSTPROCESSING_FAILED = 1007; + public static final int ERROR_POSTPROCESSING = 1007; public static final int ERROR_HTTP_NO_CONTENT = 204; public static final int ERROR_HTTP_UNSUPPORTED_RANGE = 206; @@ -79,9 +79,12 @@ public class DownloadMission extends Mission { public String postprocessingName; /** - * Indicates if the post-processing algorithm is actually running, used to detect corrupt downloads + * Indicates if the post-processing state: + * 0: ready + * 1: running + * 2: completed */ - public boolean postprocessingRunning; + public int postprocessingState; /** * Indicate if the post-processing algorithm works on the same file @@ -356,7 +359,7 @@ public class DownloadMission extends Mission { finishCount++; if (finishCount == currentThreadCount) { - if (errCode > ERROR_NOTHING) return; + if (errCode != ERROR_NOTHING) return; if (DEBUG) { Log.d(TAG, "onFinish" + (current + 1) + "/" + urls.length); @@ -382,19 +385,26 @@ public class DownloadMission extends Mission { } } - private void notifyPostProcessing(boolean processing) { + private void notifyPostProcessing(int state) { if (DEBUG) { - Log.d(TAG, (processing ? "enter" : "exit") + " postprocessing on " + location + File.separator + name); + String action; + switch (state) { + case 1: + action = "Running"; + break; + case 2: + action = "Completed"; + break; + default: + action = "Failed"; + } + + Log.d(TAG, action + " postprocessing on " + location + File.separator + name); } synchronized (blockState) { - if (!processing) { - postprocessingName = null; - postprocessingArgs = null; - } - // don't return without fully write the current state - postprocessingRunning = processing; + postprocessingState = state; Utility.writeToFile(metadata, DownloadMission.this); } } @@ -403,7 +413,7 @@ public class DownloadMission extends Mission { * Start downloading with multiple threads. */ public void start() { - if (running || current >= urls.length) return; + if (running || isFinished()) return; // ensure that the previous state is completely paused. joinForThread(init); @@ -414,6 +424,19 @@ public class DownloadMission extends Mission { running = true; errCode = ERROR_NOTHING; + if (current >= urls.length && postprocessingName != null) { + runAsync(1, () -> { + if (doPostprocessing()) { + running = false; + deleteThisFromFile(); + + notify(DownloadManagerService.MESSAGE_FINISHED); + } + }); + + return; + } + if (blocks < 0) { initializer(); return; @@ -445,18 +468,18 @@ public class DownloadMission extends Mission { public synchronized void pause() { if (!running) return; - running = false; - recovered = true; - enqueued = false; - - if (postprocessingRunning) { + if (isPsRunning()) { if (DEBUG) { Log.w(TAG, "pause during post-processing is not applicable."); } return; } - if (init != null && init.isAlive()) { + running = false; + recovered = true; + enqueued = false; + + if (init != null && Thread.currentThread() != init && init.isAlive()) { init.interrupt(); synchronized (blockState) { resetState(); @@ -533,13 +556,36 @@ public class DownloadMission extends Mission { mWritingToFile = false; } + /** + * Indicates if the download if fully finished + * + * @return true, otherwise, false + */ public boolean isFinished() { - return current >= urls.length && postprocessingName == null; + return current >= urls.length && (postprocessingName == null || postprocessingState == 2); + } + + /** + * Indicates if the download file is corrupt due a failed post-processing + * + * @return {@code true} if this mission is unrecoverable + */ + public boolean isPsFailed() { + return postprocessingName != null && errCode == DownloadMission.ERROR_POSTPROCESSING && postprocessingThis; + } + + /** + * Indicates if a post-processing algorithm is running + * + * @return true, otherwise, false + */ + public boolean isPsRunning() { + return postprocessingName != null && postprocessingState == 1; } public long getLength() { long calculated; - if (postprocessingRunning) { + if (postprocessingState == 1) { calculated = length; } else { calculated = offsets[current < offsets.length ? current : (offsets.length - 1)] + length; @@ -551,16 +597,19 @@ public class DownloadMission extends Mission { } private boolean doPostprocessing() { - if (postprocessingName == null) return true; + if (postprocessingName == null || postprocessingState == 2) return true; + + notifyPostProcessing(1); + notifyProgress(0); + + Thread.currentThread().setName("[" + TAG + "] post-processing = " + postprocessingName + " filename = " + name); + + Exception exception = null; try { - notifyPostProcessing(true); - notifyProgress(0); - - Thread.currentThread().setName("[" + TAG + "] post-processing = " + postprocessingName + " filename = " + name); - - Postprocessing algorithm = Postprocessing.getAlgorithm(postprocessingName, this); - algorithm.run(); + Postprocessing + .getAlgorithm(postprocessingName, this) + .run(); } catch (Exception err) { StringBuilder args = new StringBuilder(" "); if (postprocessingArgs != null) { @@ -572,15 +621,21 @@ public class DownloadMission extends Mission { } Log.e(TAG, String.format("Post-processing failed. algorithm = %s args = [%s]", postprocessingName, args), err); - notifyError(ERROR_POSTPROCESSING_FAILED, err); - return false; + if (errCode == ERROR_NOTHING) errCode = ERROR_POSTPROCESSING; + + exception = err; } finally { - notifyPostProcessing(false); + notifyPostProcessing(errCode == ERROR_NOTHING ? 2 : 0); } - if (errCode != ERROR_NOTHING) notify(DownloadManagerService.MESSAGE_ERROR); + if (errCode != ERROR_NOTHING) { + if (exception == null) exception = errObject; + notifyError(ERROR_POSTPROCESSING, exception); - return errCode == ERROR_NOTHING; + return false; + } + + return true; } private boolean deleteThisFromFile() { diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Mp4DashMuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/Mp4DashMuxer.java index b303b66cd..45c06dd4b 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/Mp4DashMuxer.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/Mp4DashMuxer.java @@ -13,9 +13,7 @@ import us.shandian.giga.get.DownloadMission; class Mp4DashMuxer extends Postprocessing { Mp4DashMuxer(DownloadMission mission) { - super(mission); - recommendedReserve = 15360 * 1024;// 15 MiB - worksOnSameFile = true; + super(mission, 15360 * 1024/* 15 MiB */, true); } @Override diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Mp4Muxer.java b/app/src/main/java/us/shandian/giga/postprocessing/Mp4Muxer.java new file mode 100644 index 000000000..bf932d5c1 --- /dev/null +++ b/app/src/main/java/us/shandian/giga/postprocessing/Mp4Muxer.java @@ -0,0 +1,136 @@ +package us.shandian.giga.postprocessing; + +import android.media.MediaCodec.BufferInfo; +import android.media.MediaExtractor; +import android.media.MediaMuxer; +import android.media.MediaMuxer.OutputFormat; +import android.util.Log; + +import static org.schabi.newpipe.BuildConfig.DEBUG; + +import org.schabi.newpipe.streams.io.SharpStream; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +import us.shandian.giga.get.DownloadMission; + + +class Mp4Muxer extends Postprocessing { + private static final String TAG = "Mp4Muxer"; + private static final int NOTIFY_BYTES_INTERVAL = 128 * 1024;// 128 KiB + + Mp4Muxer(DownloadMission mission) { + super(mission, 0, false); + } + + @Override + int process(SharpStream out, SharpStream... sources) throws IOException { + File dlFile = mission.getDownloadedFile(); + File tmpFile = new File(mission.location, mission.name.concat(".tmp")); + + if (tmpFile.exists()) + if (!tmpFile.delete()) return DownloadMission.ERROR_FILE_CREATION; + + if (!tmpFile.createNewFile()) return DownloadMission.ERROR_FILE_CREATION; + + FileInputStream source = null; + MediaMuxer muxer = null; + + //noinspection TryFinallyCanBeTryWithResources + try { + source = new FileInputStream(dlFile); + MediaExtractor tracks[] = { + getMediaExtractor(source, mission.offsets[0], mission.offsets[1] - mission.offsets[0]), + getMediaExtractor(source, mission.offsets[1], mission.length - mission.offsets[1]) + }; + + muxer = new MediaMuxer(tmpFile.getAbsolutePath(), OutputFormat.MUXER_OUTPUT_MPEG_4); + + int tracksIndex[] = { + muxer.addTrack(tracks[0].getTrackFormat(0)), + muxer.addTrack(tracks[1].getTrackFormat(0)) + }; + + ByteBuffer buffer = ByteBuffer.allocate(512 * 1024);// 512 KiB + BufferInfo info = new BufferInfo(); + + long written = 0; + long nextReport = NOTIFY_BYTES_INTERVAL; + + muxer.start(); + + while (true) { + int done = 0; + + for (int i = 0; i < tracks.length; i++) { + if (tracksIndex[i] < 0) continue; + + info.set(0, + tracks[i].readSampleData(buffer, 0), + tracks[i].getSampleTime(), + tracks[i].getSampleFlags() + ); + + if (info.size >= 0) { + muxer.writeSampleData(tracksIndex[i], buffer, info); + written += info.size; + done++; + } + if (!tracks[i].advance()) { + // EOF reached + tracks[i].release(); + tracksIndex[i] = -1; + } + + if (written > nextReport) { + nextReport = written + NOTIFY_BYTES_INTERVAL; + super.progressReport(written); + } + } + + if (done < 1) break; + } + + // this part should not fail + if (!dlFile.delete()) return DownloadMission.ERROR_FILE_CREATION; + if (!tmpFile.renameTo(dlFile)) return DownloadMission.ERROR_FILE_CREATION; + + return OK_RESULT; + } finally { + try { + if (muxer != null) { + muxer.stop(); + muxer.release(); + } + } catch (Exception err) { + if (DEBUG) + Log.e(TAG, "muxer stop/release failed", err); + } + + if (source != null) { + try { + source.close(); + } catch (IOException e) { + // nothing to do + } + } + + // if the operation fails, delete the temporal file + if (tmpFile.exists()) { + //noinspection ResultOfMethodCallIgnored + tmpFile.delete(); + } + } + } + + private MediaExtractor getMediaExtractor(FileInputStream source, long offset, long length) throws IOException { + MediaExtractor extractor = new MediaExtractor(); + extractor.setDataSource(source.getFD(), offset, length); + extractor.selectTrack(0); + + return extractor; + } +} diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java index 80726f705..635140bd3 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java @@ -18,21 +18,21 @@ public abstract class Postprocessing { 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"; - private static final String ALGORITHM_TEST_ALGO = "test"; public static Postprocessing getAlgorithm(String algorithmName, DownloadMission mission) { if (null == algorithmName) { throw new NullPointerException("algorithmName"); } else switch (algorithmName) { case ALGORITHM_TTML_CONVERTER: - return new TttmlConverter(mission); + 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_TEST_ALGO: - return new TestAlgo(mission); /*case "example-algorithm": return new ExampleAlgorithm(mission);*/ default: @@ -52,71 +52,84 @@ public abstract class Postprocessing { */ public int recommendedReserve; + /** + * the download to post-process + */ protected DownloadMission mission; - Postprocessing(DownloadMission mission) { + Postprocessing(DownloadMission mission, int recommendedReserve, boolean worksOnSameFile) { this.mission = mission; + this.recommendedReserve = recommendedReserve; + this.worksOnSameFile = worksOnSameFile; } public void run() throws IOException { File file = mission.getDownloadedFile(); CircularFile out = null; - ChunkFileInputStream[] sources = new ChunkFileInputStream[mission.urls.length]; + int result; + long finalLength = -1; - try { - int i = 0; - for (; i < sources.length - 1; i++) { - sources[i] = new ChunkFileInputStream(file, mission.offsets[i], mission.offsets[i + 1], "rw"); - } - sources[i] = new ChunkFileInputStream(file, mission.offsets[i], mission.getDownloadedFile().length(), "rw"); + mission.done = 0; + mission.length = file.length(); - 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 (worksOnSameFile) { + ChunkFileInputStream[] sources = new ChunkFileInputStream[mission.urls.length]; + try { + int i = 0; + for (; i < sources.length - 1; i++) { + sources[i] = new ChunkFileInputStream(file, mission.offsets[i], mission.offsets[i + 1], "rw"); + } + 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 + } + + return sources[idx[0]].getFilePointer() - 1; } - return sources[idx[0]].getFilePointer() - 1; + return -1; + }; + out = new CircularFile(file, 0, this::progressReport, checker); + + result = process(out, sources); + + if (result == OK_RESULT) + finalLength = out.finalizeFile(); + } finally { + for (SharpStream source : sources) { + if (source != null && !source.isDisposed()) { + source.dispose(); + } } - - return -1; - }; - - out = new CircularFile(file, 0, this::progressReport, checker); - - mission.done = 0; - mission.length = file.length(); - - int result = process(out, sources); - - if (result == OK_RESULT) { - long finalLength = out.finalizeFile(); - mission.done = finalLength; - mission.length = finalLength; - } else { - mission.errCode = DownloadMission.ERROR_UNKNOWN_EXCEPTION; - mission.errObject = new RuntimeException("post-processing algorithm returned " + result); - } - - if (result != OK_RESULT && worksOnSameFile) { - //noinspection ResultOfMethodCallIgnored - new File(mission.location, mission.name).delete(); - } - } finally { - for (SharpStream source : sources) { - if (source != null && !source.isDisposed()) { - source.dispose(); + if (out != null) { + out.dispose(); } } - if (out != null) { - out.dispose(); - } + } else { + result = process(null); + } + + if (result == OK_RESULT) { + if (finalLength < 0) finalLength = file.length(); + mission.done = finalLength; + mission.length = finalLength; + } else { + mission.errCode = DownloadMission.ERROR_UNKNOWN_EXCEPTION; + mission.errObject = new RuntimeException("post-processing algorithm returned " + result); + } + + if (result != OK_RESULT && worksOnSameFile) { + //noinspection ResultOfMethodCallIgnored + file.delete(); } } @@ -138,7 +151,7 @@ public abstract class Postprocessing { return mission.postprocessingArgs[index]; } - private void progressReport(long done) { + void progressReport(long done) { mission.done = done; if (mission.length < mission.done) mission.length = mission.done; diff --git a/app/src/main/java/us/shandian/giga/postprocessing/TestAlgo.java b/app/src/main/java/us/shandian/giga/postprocessing/TestAlgo.java deleted file mode 100644 index 66b235d7c..000000000 --- a/app/src/main/java/us/shandian/giga/postprocessing/TestAlgo.java +++ /dev/null @@ -1,54 +0,0 @@ -package us.shandian.giga.postprocessing; - -import android.util.Log; - -import org.schabi.newpipe.streams.io.SharpStream; - -import java.io.IOException; -import java.util.Random; - -import us.shandian.giga.get.DownloadMission; - -/** - * Algorithm for testing proposes - */ -class TestAlgo extends Postprocessing { - - public TestAlgo(DownloadMission mission) { - super(mission); - - worksOnSameFile = true; - recommendedReserve = 4096 * 1024;// 4 KiB - } - - @Override - int process(SharpStream out, SharpStream... sources) throws IOException { - - int written = 0; - int size = 5 * 1024 * 1024;// 5 MiB - byte[] buffer = new byte[8 * 1024];//8 KiB - mission.length = size; - - Random rnd = new Random(); - - // only write random data - sources[0].dispose(); - - while (written < size) { - rnd.nextBytes(buffer); - - int read = Math.min(buffer.length, size - written); - out.write(buffer, 0, read); - - try { - Thread.sleep((int) (Math.random() * 10)); - } catch (InterruptedException e) { - return -1; - } - - written += read; - } - - return Postprocessing.OK_RESULT; - } -} diff --git a/app/src/main/java/us/shandian/giga/postprocessing/TttmlConverter.java b/app/src/main/java/us/shandian/giga/postprocessing/TtmlConverter.java similarity index 83% rename from app/src/main/java/us/shandian/giga/postprocessing/TttmlConverter.java rename to app/src/main/java/us/shandian/giga/postprocessing/TtmlConverter.java index 4c9d44548..390061840 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/TttmlConverter.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/TtmlConverter.java @@ -18,13 +18,12 @@ import us.shandian.giga.postprocessing.io.SharpInputStream; /** * @author kapodamy */ -class TttmlConverter extends Postprocessing { - private static final String TAG = "TttmlConverter"; +class TtmlConverter extends Postprocessing { + private static final String TAG = "TtmlConverter"; - TttmlConverter(DownloadMission mission) { - super(mission); - recommendedReserve = 0;// due how XmlPullParser works, the xml is fully loaded on the ram - worksOnSameFile = true; + TtmlConverter(DownloadMission mission) { + // due how XmlPullParser works, the xml is fully loaded on the ram + super(mission, 0, true); } @Override @@ -41,7 +40,7 @@ class TttmlConverter extends Postprocessing { out, getArgumentAt(1, "true").equals("true"), getArgumentAt(2, "true").equals("true") - ); + ); } catch (Exception err) { Log.e(TAG, "subtitle parse failed", err); @@ -56,7 +55,7 @@ class TttmlConverter extends Postprocessing { } else if (err instanceof XPathExpressionException) { return 7; } - + return 8; } diff --git a/app/src/main/java/us/shandian/giga/postprocessing/WebMMuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/WebMMuxer.java index 009a9a66b..2ffb0f08d 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/WebMMuxer.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/WebMMuxer.java @@ -15,9 +15,7 @@ import us.shandian.giga.get.DownloadMission; class WebMMuxer extends Postprocessing { WebMMuxer(DownloadMission mission) { - super(mission); - recommendedReserve = 2048 * 1024;// 2 MiB - worksOnSameFile = true; + super(mission, 2048 * 1024/* 2 MiB */, true); } @Override diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManager.java b/app/src/main/java/us/shandian/giga/service/DownloadManager.java index 6bcf84745..883c26850 100644 --- a/app/src/main/java/us/shandian/giga/service/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManager.java @@ -141,15 +141,18 @@ public class DownloadManager { File dl = mis.getDownloadedFile(); boolean exists = dl.exists(); - if (mis.postprocessingRunning && mis.postprocessingThis) { - // Incomplete post-processing results in a corrupted download file - // because the selected algorithm works on the same file to save space. - if (!dl.delete()) { - Log.w(TAG, "Unable to delete incomplete download file: " + sub.getPath()); + if (mis.isPsRunning()) { + if (mis.postprocessingThis) { + // Incomplete post-processing results in a corrupted download file + // because the selected algorithm works on the same file to save space. + if (exists && dl.isFile() && !dl.delete()) + Log.w(TAG, "Unable to delete incomplete download file: " + sub.getPath()); + + exists = true; } - exists = true; - mis.postprocessingRunning = false; - mis.errCode = DownloadMission.ERROR_POSTPROCESSING_FAILED; + + mis.postprocessingState = 0; + mis.errCode = DownloadMission.ERROR_POSTPROCESSING; mis.errObject = new RuntimeException("stopped unexpectedly"); } else if (exists && !dl.isFile()) { // probably a folder, this should never happens @@ -332,7 +335,7 @@ public class DownloadManager { int count = 0; synchronized (this) { for (DownloadMission mission : mMissionsPending) { - if (mission.running && mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED && !mission.isFinished()) + if (mission.running && !mission.isFinished() && !mission.isPsFailed()) count++; } } @@ -471,7 +474,7 @@ public class DownloadManager { boolean flag = false; synchronized (this) { for (DownloadMission mission : mMissionsPending) { - if (mission.running && mission.isFinished() && !mission.postprocessingRunning) { + if (mission.running && !mission.isFinished() && !mission.isPsRunning()) { flag = true; mission.pause(); } @@ -528,6 +531,8 @@ public class DownloadManager { ArrayList current; ArrayList hidden; + boolean hasFinished = false; + private MissionIterator() { hidden = new ArrayList<>(2); current = null; @@ -563,6 +568,7 @@ public class DownloadManager { list.addAll(finished); } + hasFinished = finished.size() > 0; return list; } @@ -637,6 +643,10 @@ public class DownloadManager { hidden.remove(mission); } + public boolean hasFinishedMissions() { + return hasFinished; + } + @Override public int getOldListSize() { diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 28e7ef480..1b5b98145 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -59,7 +59,7 @@ import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_UNSUPPORTED_RANGE; import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; import static us.shandian.giga.get.DownloadMission.ERROR_PATH_CREATION; import static us.shandian.giga.get.DownloadMission.ERROR_PERMISSION_DENIED; -import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_FAILED; +import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING; import static us.shandian.giga.get.DownloadMission.ERROR_SSL_EXCEPTION; import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_EXCEPTION; import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_HOST; @@ -67,7 +67,8 @@ import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_HOST; public class MissionAdapter extends Adapter { private static final SparseArray ALGORITHMS = new SparseArray<>(); private static final String TAG = "MissionAdapter"; - private static final String UNDEFINED_SPEED = "--.-%"; + private static final String UNDEFINED_PROGRESS = "--.-%"; + static { ALGORITHMS.put(R.id.md5, "MD5"); @@ -178,7 +179,7 @@ public class MissionAdapter extends Adapter { if (h.item.mission instanceof DownloadMission) { DownloadMission mission = (DownloadMission) item.mission; String length = Utility.formatBytes(mission.getLength()); - if (mission.running && !mission.postprocessingRunning) length += " --.- kB/s"; + if (mission.running && !mission.isPsRunning()) length += " --.- kB/s"; h.size.setText(length); h.pause.setTitle(mission.unknownLength ? R.string.stop : R.string.pause); @@ -238,11 +239,10 @@ public class MissionAdapter extends Adapter { } if (hasError) { - if (Float.isNaN(progress) || Float.isInfinite(progress)) - h.progress.setProgress(1f); + h.progress.setProgress(isNotFinite(progress) ? 1f : progress); h.status.setText(R.string.msg_error); - } else if (Float.isNaN(progress) || Float.isInfinite(progress)) { - h.status.setText(UNDEFINED_SPEED); + } else if (isNotFinite(progress)) { + h.status.setText(UNDEFINED_PROGRESS); } else { h.status.setText(String.format("%.2f%%", progress * 100)); h.progress.setProgress(progress); @@ -251,11 +251,11 @@ public class MissionAdapter extends Adapter { long length = mission.getLength(); int state; - if (mission.errCode == ERROR_POSTPROCESSING_FAILED) { + if (mission.isPsFailed()) { state = 0; } else if (!mission.running) { state = mission.enqueued ? 1 : 2; - } else if (mission.postprocessingRunning) { + } else if (mission.isPsRunning()) { state = 3; } else { state = 0; @@ -406,7 +406,7 @@ public class MissionAdapter extends Adapter { case ERROR_CONNECT_HOST: str.append(mContext.getString(R.string.error_connect_host)); break; - case ERROR_POSTPROCESSING_FAILED: + case ERROR_POSTPROCESSING: str.append(mContext.getString(R.string.error_postprocessing_failed)); case ERROR_UNKNOWN_EXCEPTION: break; @@ -447,7 +447,7 @@ public class MissionAdapter extends Adapter { if (mission != null) { switch (id) { case R.id.start: - h.status.setText(UNDEFINED_SPEED); + h.status.setText(UNDEFINED_PROGRESS); h.state = -1; h.size.setText(Utility.formatBytes(mission.getLength())); mDownloadManager.resumeMission(mission); @@ -507,7 +507,7 @@ public class MissionAdapter extends Adapter { mIterator.end(); checkEmptyMessageVisibility(); - checkClearButtonVisibility(mClear); + mClear.setVisible(mIterator.hasFinishedMissions()); } public void forceUpdate() { @@ -526,20 +526,10 @@ public class MissionAdapter extends Adapter { } public void setClearButton(MenuItem clearButton) { - if (mClear == null) checkClearButtonVisibility(clearButton); + if (mClear == null) clearButton.setVisible(mIterator.hasFinishedMissions()); mClear = clearButton; } - private void checkClearButtonVisibility(MenuItem clearButton) { - if (mIterator.getOldListSize() < 1) { - clearButton.setVisible(false); - return; - } - - DownloadManager.MissionItem item = mIterator.getItem(mIterator.getOldListSize() - 1); - clearButton.setVisible(item.special == DownloadManager.SPECIAL_FINISHED || item.mission instanceof FinishedMission); - } - private void checkEmptyMessageVisibility() { int flag = mIterator.getOldListSize() > 0 ? View.GONE : View.VISIBLE; if (mEmptyMessage.getVisibility() != flag) mEmptyMessage.setVisibility(flag); @@ -596,6 +586,10 @@ public class MissionAdapter extends Adapter { } } + private boolean isNotFinite(Float value) { + return Float.isNaN(value) || Float.isInfinite(value); + } + class ViewHolderItem extends RecyclerView.ViewHolder { DownloadManager.MissionItem item; @@ -667,7 +661,7 @@ public class MissionAdapter extends Adapter { DownloadMission mission = item.mission instanceof DownloadMission ? (DownloadMission) item.mission : null; if (mission != null) { - if (!mission.postprocessingRunning) { + if (!mission.isPsRunning()) { if (mission.running) { pause.setVisible(true); } else { @@ -678,8 +672,10 @@ public class MissionAdapter extends Adapter { queue.setChecked(mission.enqueued); delete.setVisible(true); - start.setVisible(mission.errCode != ERROR_POSTPROCESSING_FAILED); - queue.setVisible(mission.errCode != ERROR_POSTPROCESSING_FAILED); + + boolean flag = !mission.isPsFailed(); + start.setVisible(flag); + queue.setVisible(flag); } } } else { diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 25960a419..c4fd3b5fd 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -7,16 +7,12 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; -import android.content.res.TypedArray; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; -import android.support.annotation.AttrRes; -import android.support.annotation.DrawableRes; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -24,6 +20,7 @@ import android.view.View; import android.view.ViewGroup; import org.schabi.newpipe.R; +import org.schabi.newpipe.util.ThemeHelper; import us.shandian.giga.service.DownloadManager; import us.shandian.giga.service.DownloadManagerService; @@ -192,29 +189,20 @@ public class MissionsFragment extends Fragment { mList.setAdapter(mAdapter); if (mSwitch != null) { - mSwitch.setIcon(getDrawableFromAttribute(mLinear ? R.attr.ic_grid : R.attr.ic_list)); + boolean isLight = ThemeHelper.isLightThemeSelected(mContext); + int icon; + + if (mLinear) + icon = isLight ? R.drawable.ic_list_black_24dp : R.drawable.ic_list_white_24dp; + else + icon = isLight ? R.drawable.ic_grid_black_24dp : R.drawable.ic_grid_white_24dp; + + mSwitch.setIcon(icon); mSwitch.setTitle(mLinear ? R.string.grid : R.string.list); mPrefs.edit().putBoolean("linear", mLinear).apply(); } } - @DrawableRes - private int getDrawableFromAttribute(@AttrRes int attr) { - TypedArray styledAttributes = mContext.getTheme().obtainStyledAttributes(new int[]{attr}); - int resId = styledAttributes.getResourceId(0, 0); - styledAttributes.recycle(); - - if (resId != 0) { - return resId; - } else { - // work-around - styledAttributes = mContext.obtainStyledAttributes(new int[]{attr}); - resId = styledAttributes.getResourceId(0, 0); - styledAttributes.recycle(); - return resId; - } - } - @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState);