Fix download percentage reporting
- When calculating the downloaded percentage in DASH, there was no way to disambiguate between 0 of 0 segments being downloaded because there are no cached indexes (i.e. 0% downloaded) and 0 of 0 segments being downloaded because the index defines 0 segments (i.e. 100% downloaded). - Also replace use of NaN with a named constant. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=194453202
This commit is contained in:
parent
fe32401792
commit
3e76464666
@ -204,7 +204,8 @@ public class DownloadActivity extends Activity {
|
|||||||
public RepresentationItem(Parcelable key, String title, float percentDownloaded) {
|
public RepresentationItem(Parcelable key, String title, float percentDownloaded) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.percentDownloaded = (int) percentDownloaded;
|
this.percentDownloaded =
|
||||||
|
(int) (percentDownloaded == C.PERCENTAGE_UNSET ? 0 : percentDownloaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,6 +64,9 @@ public final class C {
|
|||||||
*/
|
*/
|
||||||
public static final int LENGTH_UNSET = -1;
|
public static final int LENGTH_UNSET = -1;
|
||||||
|
|
||||||
|
/** Represents an unset or unknown percentage. */
|
||||||
|
public static final int PERCENTAGE_UNSET = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of microseconds in one second.
|
* The number of microseconds in one second.
|
||||||
*/
|
*/
|
||||||
|
@ -541,14 +541,11 @@ public final class DownloadManager {
|
|||||||
/** The state of the task. See {@link State}. */
|
/** The state of the task. See {@link State}. */
|
||||||
public final @State int state;
|
public final @State int state;
|
||||||
/**
|
/**
|
||||||
* The download percentage, or {@link Float#NaN} if it can't be calculated or the task is for
|
* The estimated download percentage, or {@link C#PERCENTAGE_UNSET} if no estimate is available
|
||||||
* removing.
|
* or if this is a removal task.
|
||||||
*/
|
*/
|
||||||
public final float downloadPercentage;
|
public final float downloadPercentage;
|
||||||
/**
|
/** The total number of downloaded bytes. */
|
||||||
* The downloaded bytes, or {@link C#LENGTH_UNSET} if it hasn't been calculated yet or the task
|
|
||||||
* is for removing.
|
|
||||||
*/
|
|
||||||
public final long downloadedBytes;
|
public final long downloadedBytes;
|
||||||
/** If {@link #state} is {@link #STATE_ERROR} then this is the cause, otherwise null. */
|
/** If {@link #state} is {@link #STATE_ERROR} then this is the cause, otherwise null. */
|
||||||
public final Throwable error;
|
public final Throwable error;
|
||||||
@ -648,19 +645,16 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the download percentage, or {@link Float#NaN} if it can't be calculated yet. This
|
* Returns the estimated download percentage, or {@link C#PERCENTAGE_UNSET} if no estimate is
|
||||||
* value can be an estimation.
|
* available.
|
||||||
*/
|
*/
|
||||||
public float getDownloadPercentage() {
|
public float getDownloadPercentage() {
|
||||||
return downloader != null ? downloader.getDownloadPercentage() : Float.NaN;
|
return downloader != null ? downloader.getDownloadPercentage() : C.PERCENTAGE_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns the total number of downloaded bytes. */
|
||||||
* Returns the total number of downloaded bytes, or {@link C#LENGTH_UNSET} if it hasn't been
|
|
||||||
* calculated yet.
|
|
||||||
*/
|
|
||||||
public long getDownloadedBytes() {
|
public long getDownloadedBytes() {
|
||||||
return downloader != null ? downloader.getDownloadedBytes() : C.LENGTH_UNSET;
|
return downloader != null ? downloader.getDownloadedBytes() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -29,8 +29,6 @@ public interface Downloader {
|
|||||||
* @throws DownloadException Thrown if the media cannot be downloaded.
|
* @throws DownloadException Thrown if the media cannot be downloaded.
|
||||||
* @throws InterruptedException If the thread has been interrupted.
|
* @throws InterruptedException If the thread has been interrupted.
|
||||||
* @throws IOException Thrown when there is an io error while reading from cache.
|
* @throws IOException Thrown when there is an io error while reading from cache.
|
||||||
* @see #getDownloadedBytes()
|
|
||||||
* @see #getDownloadPercentage()
|
|
||||||
*/
|
*/
|
||||||
void init() throws InterruptedException, IOException;
|
void init() throws InterruptedException, IOException;
|
||||||
|
|
||||||
@ -50,20 +48,12 @@ public interface Downloader {
|
|||||||
*/
|
*/
|
||||||
void remove() throws InterruptedException;
|
void remove() throws InterruptedException;
|
||||||
|
|
||||||
/**
|
/** Returns the total number of downloaded bytes. */
|
||||||
* Returns the total number of downloaded bytes, or {@link C#LENGTH_UNSET} if it hasn't been
|
|
||||||
* calculated yet.
|
|
||||||
*
|
|
||||||
* @see #init()
|
|
||||||
*/
|
|
||||||
long getDownloadedBytes();
|
long getDownloadedBytes();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the download percentage, or {@link Float#NaN} if it can't be calculated yet. This
|
* Returns the estimated download percentage, or {@link C#PERCENTAGE_UNSET} if no estimate is
|
||||||
* value can be an estimation.
|
* available.
|
||||||
*
|
|
||||||
* @see #init()
|
|
||||||
*/
|
*/
|
||||||
float getDownloadPercentage();
|
float getDownloadPercentage();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,8 @@ public final class ProgressiveDownloader implements Downloader {
|
|||||||
@Override
|
@Override
|
||||||
public float getDownloadPercentage() {
|
public float getDownloadPercentage() {
|
||||||
long contentLength = cachingCounters.contentLength;
|
long contentLength = cachingCounters.contentLength;
|
||||||
return contentLength == C.LENGTH_UNSET ? Float.NaN
|
return contentLength == C.LENGTH_UNSET
|
||||||
|
? C.PERCENTAGE_UNSET
|
||||||
: ((cachingCounters.totalCachedBytes() * 100f) / contentLength);
|
: ((cachingCounters.totalCachedBytes() * 100f) / contentLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.offline;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
@ -33,9 +34,6 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* Base class for multi segment stream downloaders.
|
* Base class for multi segment stream downloaders.
|
||||||
*
|
*
|
||||||
* <p>All of the methods are blocking. Also they are not thread safe, except {@link
|
|
||||||
* #getTotalSegments()}, {@link #getDownloadedSegments()} and {@link #getDownloadedBytes()}.
|
|
||||||
*
|
|
||||||
* @param <M> The type of the manifest object.
|
* @param <M> The type of the manifest object.
|
||||||
* @param <K> The type of the representation key object.
|
* @param <K> The type of the representation key object.
|
||||||
*/
|
*/
|
||||||
@ -101,6 +99,9 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
|
|||||||
return getManifestIfNeeded(false);
|
return getManifestIfNeeded(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns keys for all representations. */
|
||||||
|
public abstract K[] getAllRepresentationKeys() throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects multiple representations pointed to by the keys for downloading, checking status. Any
|
* Selects multiple representations pointed to by the keys for downloading, checking status. Any
|
||||||
* previous selection is cleared. If keys array is null or empty then all representations are
|
* previous selection is cleared. If keys array is null or empty then all representations are
|
||||||
@ -113,25 +114,13 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns keys for all representations.
|
* Initializes the downloader for the selected representations.
|
||||||
*
|
*
|
||||||
* @see #selectRepresentations(Object[])
|
* @throws IOException Thrown when there is an error downloading.
|
||||||
*/
|
|
||||||
public abstract K[] getAllRepresentationKeys() throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the total segments, downloaded segments and downloaded bytes counters for the
|
|
||||||
* selected representations.
|
|
||||||
*
|
|
||||||
* @throws IOException Thrown when there is an io error while reading from cache.
|
|
||||||
* @throws DownloadException Thrown if the media cannot be downloaded.
|
|
||||||
* @throws InterruptedException If the thread has been interrupted.
|
* @throws InterruptedException If the thread has been interrupted.
|
||||||
* @see #getTotalSegments()
|
|
||||||
* @see #getDownloadedSegments()
|
|
||||||
* @see #getDownloadedBytes()
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final void init() throws InterruptedException, IOException {
|
public final void init() throws IOException, InterruptedException {
|
||||||
try {
|
try {
|
||||||
getManifestIfNeeded(true);
|
getManifestIfNeeded(true);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -140,7 +129,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
initStatus(true);
|
initStatus(true);
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException e) {
|
||||||
resetCounters();
|
resetCounters();
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -150,8 +139,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
|
|||||||
* Downloads the content for the selected representations in sync or resumes a previously stopped
|
* Downloads the content for the selected representations in sync or resumes a previously stopped
|
||||||
* download.
|
* download.
|
||||||
*
|
*
|
||||||
* @throws IOException Thrown when there is an io error while downloading.
|
* @throws IOException Thrown when there is an error downloading.
|
||||||
* @throws DownloadException Thrown if the media cannot be downloaded.
|
|
||||||
* @throws InterruptedException If the thread has been interrupted.
|
* @throws InterruptedException If the thread has been interrupted.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@ -174,32 +162,6 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total number of segments in the representations which are selected, or {@link
|
|
||||||
* C#LENGTH_UNSET} if it hasn't been calculated yet.
|
|
||||||
*
|
|
||||||
* @see #init()
|
|
||||||
*/
|
|
||||||
public final int getTotalSegments() {
|
|
||||||
return totalSegments;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total number of downloaded segments in the representations which are selected, or
|
|
||||||
* {@link C#LENGTH_UNSET} if it hasn't been calculated yet.
|
|
||||||
*
|
|
||||||
* @see #init()
|
|
||||||
*/
|
|
||||||
public final int getDownloadedSegments() {
|
|
||||||
return downloadedSegments;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total number of downloaded bytes in the representations which are selected, or
|
|
||||||
* {@link C#LENGTH_UNSET} if it hasn't been calculated yet.
|
|
||||||
*
|
|
||||||
* @see #init()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final long getDownloadedBytes() {
|
public final long getDownloadedBytes() {
|
||||||
return downloadedBytes;
|
return downloadedBytes;
|
||||||
@ -211,7 +173,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
|
|||||||
int totalSegments = this.totalSegments;
|
int totalSegments = this.totalSegments;
|
||||||
int downloadedSegments = this.downloadedSegments;
|
int downloadedSegments = this.downloadedSegments;
|
||||||
if (totalSegments == C.LENGTH_UNSET || downloadedSegments == C.LENGTH_UNSET) {
|
if (totalSegments == C.LENGTH_UNSET || downloadedSegments == C.LENGTH_UNSET) {
|
||||||
return Float.NaN;
|
return C.PERCENTAGE_UNSET;
|
||||||
}
|
}
|
||||||
return totalSegments == 0 ? 100f : (downloadedSegments * 100f) / totalSegments;
|
return totalSegments == 0 ? 100f : (downloadedSegments * 100f) / totalSegments;
|
||||||
}
|
}
|
||||||
@ -228,7 +190,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
|
|||||||
if (manifest != null) {
|
if (manifest != null) {
|
||||||
List<Segment> segments = null;
|
List<Segment> segments = null;
|
||||||
try {
|
try {
|
||||||
segments = getSegments(offlineDataSource, manifest, true);
|
segments = getSegments(offlineDataSource, manifest, true).first;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Ignore exceptions. We do our best with what's available offline.
|
// Ignore exceptions. We do our best with what's available offline.
|
||||||
}
|
}
|
||||||
@ -263,16 +225,17 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
|
|||||||
* @throws InterruptedException Thrown if the thread was interrupted.
|
* @throws InterruptedException Thrown if the thread was interrupted.
|
||||||
* @throws IOException Thrown if {@code allowPartialIndex} is false and a load error occurs, or if
|
* @throws IOException Thrown if {@code allowPartialIndex} is false and a load error occurs, or if
|
||||||
* the media is not in a form that allows for its segments to be listed.
|
* the media is not in a form that allows for its segments to be listed.
|
||||||
* @return A list of {@link Segment}s for given keys.
|
* @return A list of {@link Segment}s for given keys, and a boolean indicating whether the list is
|
||||||
|
* complete.
|
||||||
*/
|
*/
|
||||||
protected abstract List<Segment> getSegments(
|
protected abstract Pair<List<Segment>, Boolean> getSegments(
|
||||||
DataSource dataSource, M manifest, boolean allowIncompleteIndex)
|
DataSource dataSource, M manifest, boolean allowIncompleteIndex)
|
||||||
throws InterruptedException, IOException;
|
throws InterruptedException, IOException;
|
||||||
|
|
||||||
private void resetCounters() {
|
private void resetCounters() {
|
||||||
totalSegments = C.LENGTH_UNSET;
|
totalSegments = C.LENGTH_UNSET;
|
||||||
downloadedSegments = C.LENGTH_UNSET;
|
downloadedSegments = 0;
|
||||||
downloadedBytes = C.LENGTH_UNSET;
|
downloadedBytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void remove(Uri uri) {
|
private void remove(Uri uri) {
|
||||||
@ -289,9 +252,11 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
|
|||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
DataSource dataSource = getDataSource(offline);
|
DataSource dataSource = getDataSource(offline);
|
||||||
M filteredManifest = keys.isEmpty() ? manifest : manifest.copy(keys);
|
M filteredManifest = keys.isEmpty() ? manifest : manifest.copy(keys);
|
||||||
List<Segment> segments = getSegments(dataSource, filteredManifest, offline);
|
Pair<List<Segment>, Boolean> result = getSegments(dataSource, filteredManifest, offline);
|
||||||
|
List<Segment> segments = result.first;
|
||||||
|
boolean isSegmentListComplete = result.second;
|
||||||
CachingCounters cachingCounters = new CachingCounters();
|
CachingCounters cachingCounters = new CachingCounters();
|
||||||
totalSegments = segments.size();
|
totalSegments = isSegmentListComplete ? segments.size() : C.LENGTH_UNSET;
|
||||||
downloadedSegments = 0;
|
downloadedSegments = 0;
|
||||||
downloadedBytes = 0;
|
downloadedBytes = 0;
|
||||||
for (int i = segments.size() - 1; i >= 0; i--) {
|
for (int i = segments.size() - 1; i >= 0; i--) {
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.source.dash.offline;
|
package com.google.android.exoplayer2.source.dash.offline;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
||||||
import com.google.android.exoplayer2.offline.DownloadException;
|
import com.google.android.exoplayer2.offline.DownloadException;
|
||||||
@ -39,9 +40,6 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* Helper class to download DASH streams.
|
* Helper class to download DASH streams.
|
||||||
*
|
*
|
||||||
* <p>Except {@link #getTotalSegments()}, {@link #getDownloadedSegments()} and {@link
|
|
||||||
* #getDownloadedBytes()}, this class isn't thread safe.
|
|
||||||
*
|
|
||||||
* <p>Example usage:
|
* <p>Example usage:
|
||||||
*
|
*
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
@ -89,29 +87,32 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Segment> getSegments(
|
protected Pair<List<Segment>, Boolean> getSegments(
|
||||||
DataSource dataSource, DashManifest manifest, boolean allowIndexLoadErrors)
|
DataSource dataSource, DashManifest manifest, boolean allowIndexLoadErrors)
|
||||||
throws InterruptedException, IOException {
|
throws InterruptedException, IOException {
|
||||||
ArrayList<Segment> segments = new ArrayList<>();
|
ArrayList<Segment> segments = new ArrayList<>();
|
||||||
|
boolean segmentListComplete = true;
|
||||||
for (int i = 0; i < manifest.getPeriodCount(); i++) {
|
for (int i = 0; i < manifest.getPeriodCount(); i++) {
|
||||||
Period period = manifest.getPeriod(i);
|
Period period = manifest.getPeriod(i);
|
||||||
long periodStartUs = C.msToUs(period.startMs);
|
long periodStartUs = C.msToUs(period.startMs);
|
||||||
long periodDurationUs = manifest.getPeriodDurationUs(i);
|
long periodDurationUs = manifest.getPeriodDurationUs(i);
|
||||||
List<AdaptationSet> adaptationSets = period.adaptationSets;
|
List<AdaptationSet> adaptationSets = period.adaptationSets;
|
||||||
for (int j = 0; j < adaptationSets.size(); j++) {
|
for (int j = 0; j < adaptationSets.size(); j++) {
|
||||||
addSegmentsForAdaptationSet(
|
if (!addSegmentsForAdaptationSet(
|
||||||
dataSource,
|
dataSource,
|
||||||
adaptationSets.get(j),
|
adaptationSets.get(j),
|
||||||
periodStartUs,
|
periodStartUs,
|
||||||
periodDurationUs,
|
periodDurationUs,
|
||||||
allowIndexLoadErrors,
|
allowIndexLoadErrors,
|
||||||
segments);
|
segments)) {
|
||||||
|
segmentListComplete = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return segments;
|
}
|
||||||
|
return Pair.<List<Segment>, Boolean>create(segments, segmentListComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addSegmentsForAdaptationSet(
|
private static boolean addSegmentsForAdaptationSet(
|
||||||
DataSource dataSource,
|
DataSource dataSource,
|
||||||
AdaptationSet adaptationSet,
|
AdaptationSet adaptationSet,
|
||||||
long periodStartUs,
|
long periodStartUs,
|
||||||
@ -119,6 +120,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
|
|||||||
boolean allowIndexLoadErrors,
|
boolean allowIndexLoadErrors,
|
||||||
ArrayList<Segment> out)
|
ArrayList<Segment> out)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
boolean segmentListComplete = true;
|
||||||
for (int i = 0; i < adaptationSet.representations.size(); i++) {
|
for (int i = 0; i < adaptationSet.representations.size(); i++) {
|
||||||
Representation representation = adaptationSet.representations.get(i);
|
Representation representation = adaptationSet.representations.get(i);
|
||||||
DashSegmentIndex index;
|
DashSegmentIndex index;
|
||||||
@ -129,12 +131,12 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
|
|||||||
throw new DownloadException("Missing segment index");
|
throw new DownloadException("Missing segment index");
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (allowIndexLoadErrors) {
|
if (!allowIndexLoadErrors) {
|
||||||
// Loading failed, but load errors are allowed. Advance to the next representation.
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
// Loading failed, but load errors are allowed. Advance to the next representation.
|
||||||
|
segmentListComplete = false;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int segmentCount = index.getSegmentCount(periodDurationUs);
|
int segmentCount = index.getSegmentCount(periodDurationUs);
|
||||||
@ -157,6 +159,8 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
|
|||||||
addSegment(periodStartUs + index.getTimeUs(j), baseUrl, index.getSegmentUrl(j), out);
|
addSegment(periodStartUs + index.getTimeUs(j), baseUrl, index.getSegmentUrl(j), out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return segmentListComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addSegment(
|
private static void addSegment(
|
||||||
|
@ -26,7 +26,6 @@ import static org.junit.Assert.fail;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.offline.DownloadException;
|
import com.google.android.exoplayer2.offline.DownloadException;
|
||||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||||
@ -65,7 +64,7 @@ public class DashDownloaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() {
|
||||||
Util.recursiveDelete(tempFolder);
|
Util.recursiveDelete(tempFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,11 +310,11 @@ public class DashDownloaderTest {
|
|||||||
.setRandomData("audio_segment_3", 6);
|
.setRandomData("audio_segment_3", 6);
|
||||||
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
|
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
|
||||||
|
|
||||||
assertCounters(dashDownloader, C.LENGTH_UNSET, C.LENGTH_UNSET, C.LENGTH_UNSET);
|
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(0);
|
||||||
|
|
||||||
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
|
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
|
||||||
dashDownloader.init();
|
dashDownloader.init();
|
||||||
assertCounters(dashDownloader, C.LENGTH_UNSET, C.LENGTH_UNSET, C.LENGTH_UNSET);
|
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(0);
|
||||||
|
|
||||||
// downloadRepresentations fails after downloading init data, segment 1 and 2 bytes in segment 2
|
// downloadRepresentations fails after downloading init data, segment 1 and 2 bytes in segment 2
|
||||||
try {
|
try {
|
||||||
@ -325,11 +324,10 @@ public class DashDownloaderTest {
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
dashDownloader.init();
|
dashDownloader.init();
|
||||||
assertCounters(dashDownloader, 4, 2, 10 + 4 + 2);
|
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(10 + 4 + 2);
|
||||||
|
|
||||||
dashDownloader.download();
|
dashDownloader.download();
|
||||||
|
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(10 + 4 + 5 + 6);
|
||||||
assertCounters(dashDownloader, 4, 4, 10 + 4 + 5 + 6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -399,13 +397,4 @@ public class DashDownloaderTest {
|
|||||||
return new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory));
|
return new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertCounters(
|
|
||||||
DashDownloader dashDownloader,
|
|
||||||
int totalSegments,
|
|
||||||
int downloadedSegments,
|
|
||||||
int downloadedBytes) {
|
|
||||||
assertThat(dashDownloader.getTotalSegments()).isEqualTo(totalSegments);
|
|
||||||
assertThat(dashDownloader.getDownloadedSegments()).isEqualTo(downloadedSegments);
|
|
||||||
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(downloadedBytes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.source.hls.offline;
|
package com.google.android.exoplayer2.source.hls.offline;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||||
import com.google.android.exoplayer2.offline.SegmentDownloader;
|
import com.google.android.exoplayer2.offline.SegmentDownloader;
|
||||||
@ -70,17 +71,17 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Segment> getSegments(
|
protected Pair<List<Segment>, Boolean> getSegments(
|
||||||
DataSource dataSource,
|
DataSource dataSource, HlsMasterPlaylist manifest, boolean allowIndexLoadErrors)
|
||||||
HlsMasterPlaylist manifest,
|
throws IOException {
|
||||||
boolean allowIndexLoadErrors)
|
|
||||||
throws InterruptedException, IOException {
|
|
||||||
HashSet<Uri> encryptionKeyUris = new HashSet<>();
|
HashSet<Uri> encryptionKeyUris = new HashSet<>();
|
||||||
ArrayList<HlsUrl> renditionUrls = new ArrayList<>();
|
ArrayList<HlsUrl> renditionUrls = new ArrayList<>();
|
||||||
renditionUrls.addAll(manifest.variants);
|
renditionUrls.addAll(manifest.variants);
|
||||||
renditionUrls.addAll(manifest.audios);
|
renditionUrls.addAll(manifest.audios);
|
||||||
renditionUrls.addAll(manifest.subtitles);
|
renditionUrls.addAll(manifest.subtitles);
|
||||||
ArrayList<Segment> segments = new ArrayList<>();
|
ArrayList<Segment> segments = new ArrayList<>();
|
||||||
|
|
||||||
|
boolean segmentListComplete = true;
|
||||||
for (HlsUrl renditionUrl : renditionUrls) {
|
for (HlsUrl renditionUrl : renditionUrls) {
|
||||||
HlsMediaPlaylist mediaPlaylist = null;
|
HlsMediaPlaylist mediaPlaylist = null;
|
||||||
Uri uri = UriUtil.resolveToUri(manifest.baseUri, renditionUrl.url);
|
Uri uri = UriUtil.resolveToUri(manifest.baseUri, renditionUrl.url);
|
||||||
@ -90,6 +91,7 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re
|
|||||||
if (!allowIndexLoadErrors) {
|
if (!allowIndexLoadErrors) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
segmentListComplete = false;
|
||||||
}
|
}
|
||||||
segments.add(new Segment(mediaPlaylist != null ? mediaPlaylist.startTimeUs : Long.MIN_VALUE,
|
segments.add(new Segment(mediaPlaylist != null ? mediaPlaylist.startTimeUs : Long.MIN_VALUE,
|
||||||
new DataSpec(uri)));
|
new DataSpec(uri)));
|
||||||
@ -109,7 +111,7 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, Re
|
|||||||
addSegment(segments, mediaPlaylist, segment, encryptionKeyUris);
|
addSegment(segments, mediaPlaylist, segment, encryptionKeyUris);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return segments;
|
return Pair.<List<Segment>, Boolean>create(segments, segmentListComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HlsPlaylist loadManifest(DataSource dataSource, Uri uri) throws IOException {
|
private static HlsPlaylist loadManifest(DataSource dataSource, Uri uri) throws IOException {
|
||||||
|
@ -111,8 +111,6 @@ public class HlsDownloaderTest {
|
|||||||
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
|
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
|
||||||
hlsDownloader.download();
|
hlsDownloader.download();
|
||||||
|
|
||||||
assertThat(hlsDownloader.getTotalSegments()).isEqualTo(4);
|
|
||||||
assertThat(hlsDownloader.getDownloadedSegments()).isEqualTo(4);
|
|
||||||
assertThat(hlsDownloader.getDownloadedBytes())
|
assertThat(hlsDownloader.getDownloadedBytes())
|
||||||
.isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12);
|
.isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12);
|
||||||
}
|
}
|
||||||
@ -126,8 +124,6 @@ public class HlsDownloaderTest {
|
|||||||
newHlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
|
newHlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
|
||||||
newHlsDownloader.init();
|
newHlsDownloader.init();
|
||||||
|
|
||||||
assertThat(newHlsDownloader.getTotalSegments()).isEqualTo(4);
|
|
||||||
assertThat(newHlsDownloader.getDownloadedSegments()).isEqualTo(4);
|
|
||||||
assertThat(newHlsDownloader.getDownloadedBytes())
|
assertThat(newHlsDownloader.getDownloadedBytes())
|
||||||
.isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12);
|
.isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.source.smoothstreaming.offline;
|
package com.google.android.exoplayer2.source.smoothstreaming.offline;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||||
import com.google.android.exoplayer2.offline.SegmentDownloader;
|
import com.google.android.exoplayer2.offline.SegmentDownloader;
|
||||||
@ -34,9 +35,6 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* Helper class to download SmoothStreaming streams.
|
* Helper class to download SmoothStreaming streams.
|
||||||
*
|
*
|
||||||
* <p>Except {@link #getTotalSegments()}, {@link #getDownloadedSegments()} and {@link
|
|
||||||
* #getDownloadedBytes()}, this class isn't thread safe.
|
|
||||||
*
|
|
||||||
* <p>Example usage:
|
* <p>Example usage:
|
||||||
*
|
*
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
@ -84,9 +82,8 @@ public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Segment> getSegments(
|
protected Pair<List<Segment>, Boolean> getSegments(
|
||||||
DataSource dataSource, SsManifest manifest, boolean allowIndexLoadErrors)
|
DataSource dataSource, SsManifest manifest, boolean allowIndexLoadErrors) throws IOException {
|
||||||
throws InterruptedException, IOException {
|
|
||||||
ArrayList<Segment> segments = new ArrayList<>();
|
ArrayList<Segment> segments = new ArrayList<>();
|
||||||
for (StreamElement streamElement : manifest.streamElements) {
|
for (StreamElement streamElement : manifest.streamElements) {
|
||||||
for (int i = 0; i < streamElement.formats.length; i++) {
|
for (int i = 0; i < streamElement.formats.length; i++) {
|
||||||
@ -98,7 +95,7 @@ public final class SsDownloader extends SegmentDownloader<SsManifest, TrackKey>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return segments;
|
return Pair.<List<Segment>, Boolean>create(segments, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import android.app.Notification;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.offline.DownloadManager;
|
import com.google.android.exoplayer2.offline.DownloadManager;
|
||||||
import com.google.android.exoplayer2.offline.DownloadManager.DownloadState;
|
import com.google.android.exoplayer2.offline.DownloadManager.DownloadState;
|
||||||
import com.google.android.exoplayer2.util.ErrorMessageProvider;
|
import com.google.android.exoplayer2.util.ErrorMessageProvider;
|
||||||
@ -48,31 +49,31 @@ public final class DownloadNotificationUtil {
|
|||||||
String channelId,
|
String channelId,
|
||||||
@Nullable String message) {
|
@Nullable String message) {
|
||||||
float totalPercentage = 0;
|
float totalPercentage = 0;
|
||||||
int determinatePercentageCount = 0;
|
int downloadTaskCount = 0;
|
||||||
boolean isAnyDownloadActive = false;
|
boolean allDownloadPercentagesUnknown = true;
|
||||||
|
boolean haveDownloadedBytes = false;
|
||||||
for (DownloadState downloadState : downloadStates) {
|
for (DownloadState downloadState : downloadStates) {
|
||||||
if (downloadState.downloadAction.isRemoveAction
|
if (downloadState.downloadAction.isRemoveAction
|
||||||
|| downloadState.state != DownloadState.STATE_STARTED) {
|
|| downloadState.state != DownloadState.STATE_STARTED) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
float percentage = downloadState.downloadPercentage;
|
if (downloadState.downloadPercentage != C.PERCENTAGE_UNSET) {
|
||||||
if (!Float.isNaN(percentage)) {
|
allDownloadPercentagesUnknown = false;
|
||||||
totalPercentage += percentage;
|
totalPercentage += downloadState.downloadPercentage;
|
||||||
determinatePercentageCount++;
|
|
||||||
}
|
}
|
||||||
isAnyDownloadActive = true;
|
haveDownloadedBytes |= downloadState.downloadedBytes > 0;
|
||||||
|
downloadTaskCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
int titleStringId = isAnyDownloadActive ? R.string.exo_download_downloading : NULL_STRING_ID;
|
boolean haveDownloadTasks = downloadTaskCount > 0;
|
||||||
|
int titleStringId = haveDownloadTasks ? R.string.exo_download_downloading : NULL_STRING_ID;
|
||||||
NotificationCompat.Builder notificationBuilder =
|
NotificationCompat.Builder notificationBuilder =
|
||||||
createNotificationBuilder(context, smallIcon, channelId, message, titleStringId);
|
createNotificationBuilder(context, smallIcon, channelId, message, titleStringId);
|
||||||
|
|
||||||
|
int progress = haveDownloadTasks ? (int) (totalPercentage / downloadTaskCount) : 0;
|
||||||
|
boolean indeterminate = allDownloadPercentagesUnknown && haveDownloadedBytes;
|
||||||
|
notificationBuilder.setProgress(/* max= */ 100, progress, indeterminate);
|
||||||
notificationBuilder.setOngoing(true);
|
notificationBuilder.setOngoing(true);
|
||||||
int max = 100;
|
|
||||||
int progress = (int) (totalPercentage / determinatePercentageCount);
|
|
||||||
boolean indeterminate = determinatePercentageCount == 0;
|
|
||||||
notificationBuilder.setProgress(max, progress, indeterminate);
|
|
||||||
|
|
||||||
notificationBuilder.setShowWhen(false);
|
notificationBuilder.setShowWhen(false);
|
||||||
return notificationBuilder.build();
|
return notificationBuilder.build();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user