mirror of
https://github.com/samsonjs/media.git
synced 2026-04-04 11:05:47 +00:00
Offline refactor step 1a - Make DownloadAction concrete
1. Pull up all subclasses of DownloadAction into DownloadAction 2. Add DownloaderFactory for Downloader instantiation, and DefaultDownloaderFactory to replace the instantiation logic being removed from the DownloadAction subclasses. This change will upgrade existing action files gracefully (i.e. it does not lose compatibility with the existing offline implementation, other than some minor breaking changes to the API). TODOs: 1. Move test methods from the XDownloadActionTest classes into DownloadActionTest. This will be done in a subsequent CL. There's a lot of consolidation that can be done here, including de-duplicating some of the test code added in this CL. 2. Look at merging DownloaderConstructorHelper into DefaultDownloaderFactory. 3. Use customCacheKey in DASH/HLS/SS Downloaders, for completeness. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=222258983
This commit is contained in:
parent
527f2cf730
commit
6ebb6124bb
30 changed files with 955 additions and 1178 deletions
|
|
@ -16,6 +16,7 @@
|
|||
package com.google.android.exoplayer2.demo;
|
||||
|
||||
import android.app.Application;
|
||||
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
|
||||
import com.google.android.exoplayer2.offline.DownloadManager;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
|
|
@ -87,10 +88,10 @@ public class DemoApplication extends Application {
|
|||
new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory());
|
||||
downloadManager =
|
||||
new DownloadManager(
|
||||
downloaderConstructorHelper,
|
||||
new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE),
|
||||
new DefaultDownloaderFactory(downloaderConstructorHelper),
|
||||
MAX_SIMULTANEOUS_DOWNLOADS,
|
||||
DownloadManager.DEFAULT_MIN_RETRY_COUNT,
|
||||
new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE));
|
||||
DownloadManager.DEFAULT_MIN_RETRY_COUNT);
|
||||
downloadTracker =
|
||||
new DownloadTracker(
|
||||
/* context= */ this,
|
||||
|
|
|
|||
|
|
@ -81,11 +81,7 @@ public class DownloadTracker implements DownloadManager.Listener {
|
|||
private final ActionFile actionFile;
|
||||
private final Handler actionFileWriteHandler;
|
||||
|
||||
public DownloadTracker(
|
||||
Context context,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
File actionFile,
|
||||
DownloadAction.Deserializer... deserializers) {
|
||||
public DownloadTracker(Context context, DataSource.Factory dataSourceFactory, File actionFile) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.actionFile = new ActionFile(actionFile);
|
||||
|
|
@ -95,8 +91,7 @@ public class DownloadTracker implements DownloadManager.Listener {
|
|||
HandlerThread actionFileWriteThread = new HandlerThread("DownloadTracker");
|
||||
actionFileWriteThread.start();
|
||||
actionFileWriteHandler = new Handler(actionFileWriteThread.getLooper());
|
||||
loadTrackedActions(
|
||||
deserializers.length > 0 ? deserializers : DownloadAction.getDefaultDeserializers());
|
||||
loadTrackedActions();
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
|
|
@ -158,9 +153,9 @@ public class DownloadTracker implements DownloadManager.Listener {
|
|||
|
||||
// Internal methods
|
||||
|
||||
private void loadTrackedActions(DownloadAction.Deserializer[] deserializers) {
|
||||
private void loadTrackedActions() {
|
||||
try {
|
||||
DownloadAction[] allActions = actionFile.load(deserializers);
|
||||
DownloadAction[] allActions = actionFile.load();
|
||||
for (DownloadAction action : allActions) {
|
||||
trackedDownloadStates.put(action.uri, action);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,10 +30,19 @@
|
|||
<init>();
|
||||
}
|
||||
|
||||
# Constructors accessed via reflection in DownloadAction
|
||||
-dontnote com.google.android.exoplayer2.source.dash.offline.DashDownloadAction
|
||||
-dontnote com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction
|
||||
-dontnote com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction
|
||||
# Constructors accessed via reflection in DefaultDownloaderFactory
|
||||
-dontnote com.google.android.exoplayer2.source.dash.offline.DashDownloader
|
||||
-keepclassmembers class com.google.android.exoplayer2.source.dash.offline.DashDownloader {
|
||||
<init>(android.net.Uri, java.util.List, com.google.android.exoplayer2.offlineDownloaderConstructorHelper);
|
||||
}
|
||||
-dontnote com.google.android.exoplayer2.source.hls.offline.HlsDownloader
|
||||
-keepclassmembers class com.google.android.exoplayer2.source.hls.offline.HlsDownloader {
|
||||
<init>(android.net.Uri, java.util.List, com.google.android.exoplayer2.offlineDownloaderConstructorHelper);
|
||||
}
|
||||
-dontnote com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloader
|
||||
-keepclassmembers class com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloader {
|
||||
<init>(android.net.Uri, java.util.List, com.google.android.exoplayer2.offlineDownloaderConstructorHelper);
|
||||
}
|
||||
|
||||
# Don't warn about checkerframework
|
||||
-dontwarn org.checkerframework.**
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.offline;
|
||||
|
||||
import com.google.android.exoplayer2.offline.DownloadAction.Deserializer;
|
||||
import com.google.android.exoplayer2.util.AtomicFile;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.DataInputStream;
|
||||
|
|
@ -45,11 +44,10 @@ public final class ActionFile {
|
|||
/**
|
||||
* Loads {@link DownloadAction}s from file.
|
||||
*
|
||||
* @param deserializers {@link Deserializer}s to deserialize DownloadActions.
|
||||
* @return Loaded DownloadActions. If the action file doesn't exists returns an empty array.
|
||||
* @throws IOException If there is an error during loading.
|
||||
*/
|
||||
public DownloadAction[] load(Deserializer... deserializers) throws IOException {
|
||||
public DownloadAction[] load() throws IOException {
|
||||
if (!actionFile.exists()) {
|
||||
return new DownloadAction[0];
|
||||
}
|
||||
|
|
@ -64,7 +62,7 @@ public final class ActionFile {
|
|||
int actionCount = dataInputStream.readInt();
|
||||
DownloadAction[] actions = new DownloadAction[actionCount];
|
||||
for (int i = 0; i < actionCount; i++) {
|
||||
actions[i] = DownloadAction.deserializeFromStream(deserializers, dataInputStream);
|
||||
actions[i] = DownloadAction.deserializeFromStream(dataInputStream);
|
||||
}
|
||||
return actions;
|
||||
} finally {
|
||||
|
|
@ -85,7 +83,7 @@ public final class ActionFile {
|
|||
output.writeInt(VERSION);
|
||||
output.writeInt(downloadActions.length);
|
||||
for (DownloadAction action : downloadActions) {
|
||||
DownloadAction.serializeToStream(action, output);
|
||||
action.serializeToStream(output);
|
||||
}
|
||||
atomicFile.endWrite(output);
|
||||
// Avoid calling close twice.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.offline;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Default {@link DownloaderFactory}, supporting creation of progressive, DASH, HLS and
|
||||
* SmoothStreaming downloaders. Note that for the latter three, the corresponding library module
|
||||
* must be built into the application.
|
||||
*/
|
||||
public class DefaultDownloaderFactory implements DownloaderFactory {
|
||||
|
||||
@Nullable private static final Constructor<? extends Downloader> DASH_DOWNLOADER_CONSTRUCTOR;
|
||||
@Nullable private static final Constructor<? extends Downloader> HLS_DOWNLOADER_CONSTRUCTOR;
|
||||
@Nullable private static final Constructor<? extends Downloader> SS_DOWNLOADER_CONSTRUCTOR;
|
||||
|
||||
static {
|
||||
Constructor<? extends Downloader> dashDownloaderConstructor = null;
|
||||
try {
|
||||
// LINT.IfChange
|
||||
dashDownloaderConstructor =
|
||||
getDownloaderConstructor(
|
||||
Class.forName("com.google.android.exoplayer2.source.dash.offline.DashDownloader"));
|
||||
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Expected if the app was built without the DASH module.
|
||||
}
|
||||
DASH_DOWNLOADER_CONSTRUCTOR = dashDownloaderConstructor;
|
||||
Constructor<? extends Downloader> hlsDownloaderConstructor = null;
|
||||
try {
|
||||
// LINT.IfChange
|
||||
hlsDownloaderConstructor =
|
||||
getDownloaderConstructor(
|
||||
Class.forName("com.google.android.exoplayer2.source.hls.offline.HlsDownloader"));
|
||||
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Expected if the app was built without the HLS module.
|
||||
}
|
||||
HLS_DOWNLOADER_CONSTRUCTOR = hlsDownloaderConstructor;
|
||||
Constructor<? extends Downloader> ssDownloaderConstructor = null;
|
||||
try {
|
||||
// LINT.IfChange
|
||||
ssDownloaderConstructor =
|
||||
getDownloaderConstructor(
|
||||
Class.forName(
|
||||
"com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloader"));
|
||||
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Expected if the app was built without the SmoothStreaming module.
|
||||
}
|
||||
SS_DOWNLOADER_CONSTRUCTOR = ssDownloaderConstructor;
|
||||
}
|
||||
|
||||
private final DownloaderConstructorHelper downloaderConstructorHelper;
|
||||
|
||||
/** @param downloaderConstructorHelper A helper for instantiating downloaders. */
|
||||
public DefaultDownloaderFactory(DownloaderConstructorHelper downloaderConstructorHelper) {
|
||||
this.downloaderConstructorHelper = downloaderConstructorHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Downloader createDownloader(DownloadAction action) {
|
||||
switch (action.type) {
|
||||
case DownloadAction.TYPE_PROGRESSIVE:
|
||||
return new ProgressiveDownloader(
|
||||
action.uri, action.customCacheKey, downloaderConstructorHelper);
|
||||
case DownloadAction.TYPE_DASH:
|
||||
return createDownloader(action, DASH_DOWNLOADER_CONSTRUCTOR);
|
||||
case DownloadAction.TYPE_HLS:
|
||||
return createDownloader(action, HLS_DOWNLOADER_CONSTRUCTOR);
|
||||
case DownloadAction.TYPE_SS:
|
||||
return createDownloader(action, SS_DOWNLOADER_CONSTRUCTOR);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported type: " + action.type);
|
||||
}
|
||||
}
|
||||
|
||||
private Downloader createDownloader(
|
||||
DownloadAction action, @Nullable Constructor<? extends Downloader> constructor) {
|
||||
if (constructor == null) {
|
||||
throw new IllegalStateException("Module missing for: " + action.type);
|
||||
}
|
||||
try {
|
||||
// TODO: Support customCacheKey in DASH/HLS/SS, for completeness.
|
||||
return constructor.newInstance(action.uri, action.getKeys(), downloaderConstructorHelper);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to instantiate downloader for: " + action.type, e);
|
||||
}
|
||||
}
|
||||
|
||||
// LINT.IfChange
|
||||
private static Constructor<? extends Downloader> getDownloaderConstructor(Class<?> clazz) {
|
||||
try {
|
||||
return clazz
|
||||
.asSubclass(Downloader.class)
|
||||
.getConstructor(Uri.class, List.class, DownloaderConstructorHelper.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// The downloader is present, but the expected constructor is missing.
|
||||
throw new RuntimeException("DASH downloader constructor missing", e);
|
||||
}
|
||||
}
|
||||
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
||||
}
|
||||
|
|
@ -25,147 +25,124 @@ import java.io.DataOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Contains the necessary parameters for a download or remove action. */
|
||||
public abstract class DownloadAction {
|
||||
public final class DownloadAction {
|
||||
|
||||
/** Used to deserialize {@link DownloadAction}s. */
|
||||
public abstract static class Deserializer {
|
||||
/** Type for progressive downloads. */
|
||||
public static final String TYPE_PROGRESSIVE = "progressive";
|
||||
/** Type for DASH downloads. */
|
||||
public static final String TYPE_DASH = "dash";
|
||||
/** Type for HLS downloads. */
|
||||
public static final String TYPE_HLS = "hls";
|
||||
/** Type for SmoothStreaming downloads. */
|
||||
public static final String TYPE_SS = "ss";
|
||||
|
||||
public final String type;
|
||||
public final int version;
|
||||
|
||||
public Deserializer(String type, int version) {
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes an action from the {@code input}.
|
||||
*
|
||||
* @param version The version of the serialized action.
|
||||
* @param input The stream from which to read the action.
|
||||
* @see DownloadAction#writeToStream(DataOutputStream)
|
||||
*/
|
||||
public abstract DownloadAction readFromStream(int version, DataInputStream input)
|
||||
throws IOException;
|
||||
}
|
||||
|
||||
private static @Nullable Deserializer[] defaultDeserializers;
|
||||
|
||||
/** Returns available default {@link Deserializer}s. */
|
||||
public static synchronized Deserializer[] getDefaultDeserializers() {
|
||||
if (defaultDeserializers != null) {
|
||||
return defaultDeserializers;
|
||||
}
|
||||
Deserializer[] deserializers = new Deserializer[4];
|
||||
int count = 0;
|
||||
deserializers[count++] = ProgressiveDownloadAction.DESERIALIZER;
|
||||
Class<?> clazz;
|
||||
// Full class names used for constructor args so the LINT rule triggers if any of them move.
|
||||
try {
|
||||
// LINT.IfChange
|
||||
clazz = Class.forName("com.google.android.exoplayer2.source.dash.offline.DashDownloadAction");
|
||||
// LINT.ThenChange(../../../../../../../../../dash/proguard-rules.txt)
|
||||
deserializers[count++] = getDeserializer(clazz);
|
||||
} catch (Exception e) {
|
||||
// Do nothing.
|
||||
}
|
||||
try {
|
||||
// LINT.IfChange
|
||||
clazz = Class.forName("com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction");
|
||||
// LINT.ThenChange(../../../../../../../../../hls/proguard-rules.txt)
|
||||
deserializers[count++] = getDeserializer(clazz);
|
||||
} catch (Exception e) {
|
||||
// Do nothing.
|
||||
}
|
||||
try {
|
||||
// LINT.IfChange
|
||||
clazz =
|
||||
Class.forName(
|
||||
"com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction");
|
||||
// LINT.ThenChange(../../../../../../../../../smoothstreaming/proguard-rules.txt)
|
||||
deserializers[count++] = getDeserializer(clazz);
|
||||
} catch (Exception e) {
|
||||
// Do nothing.
|
||||
}
|
||||
defaultDeserializers = Arrays.copyOf(Assertions.checkNotNull(deserializers), count);
|
||||
return defaultDeserializers;
|
||||
}
|
||||
private static final int VERSION = 2;
|
||||
|
||||
/**
|
||||
* Deserializes one action that was serialized with {@link #serializeToStream(DownloadAction,
|
||||
* OutputStream)} from the {@code input}, using the {@link Deserializer}s that supports the
|
||||
* action's type.
|
||||
* Deserializes one action that was serialized with {@link #serializeToStream(OutputStream)} from
|
||||
* the {@code input}.
|
||||
*
|
||||
* <p>The caller is responsible for closing the given {@link InputStream}.
|
||||
*
|
||||
* @param deserializers {@link Deserializer}s for supported actions.
|
||||
* @param input The stream from which to read the action.
|
||||
* @param input The stream from which to read.
|
||||
* @return The deserialized action.
|
||||
* @throws IOException If there is an IO error reading from {@code input}, or if the action type
|
||||
* isn't supported by any of the {@code deserializers}.
|
||||
*/
|
||||
public static DownloadAction deserializeFromStream(
|
||||
Deserializer[] deserializers, InputStream input) throws IOException {
|
||||
// Don't close the stream as it closes the underlying stream too.
|
||||
DataInputStream dataInputStream = new DataInputStream(input);
|
||||
String type = dataInputStream.readUTF();
|
||||
int version = dataInputStream.readInt();
|
||||
for (Deserializer deserializer : deserializers) {
|
||||
if (type.equals(deserializer.type) && deserializer.version >= version) {
|
||||
return deserializer.readFromStream(version, dataInputStream);
|
||||
}
|
||||
}
|
||||
throw new DownloadException("No deserializer found for:" + type + ", " + version);
|
||||
public static DownloadAction deserializeFromStream(InputStream input) throws IOException {
|
||||
return readFromStream(new DataInputStream(input));
|
||||
}
|
||||
|
||||
/** Serializes {@code action} type and data into the {@code output}. */
|
||||
public static void serializeToStream(DownloadAction action, OutputStream output)
|
||||
throws IOException {
|
||||
// Don't close the stream as it closes the underlying stream too.
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(output);
|
||||
dataOutputStream.writeUTF(action.type);
|
||||
dataOutputStream.writeInt(action.version);
|
||||
action.writeToStream(dataOutputStream);
|
||||
dataOutputStream.flush();
|
||||
/**
|
||||
* Creates a DASH download action.
|
||||
*
|
||||
* @param type The type of the action.
|
||||
* @param uri The URI of the media to be downloaded.
|
||||
* @param keys Keys of tracks to be downloaded. If empty, all tracks will be downloaded.
|
||||
* @param customCacheKey A custom key for cache indexing, or null.
|
||||
* @param data Optional custom data for this action. If {@code null} an empty array will be used.
|
||||
*/
|
||||
public static DownloadAction createDownloadAction(
|
||||
String type,
|
||||
Uri uri,
|
||||
List<StreamKey> keys,
|
||||
@Nullable String customCacheKey,
|
||||
@Nullable byte[] data) {
|
||||
return new DownloadAction(type, uri, /* isRemoveAction= */ false, keys, customCacheKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DASH remove action.
|
||||
*
|
||||
* @param type The type of the action.
|
||||
* @param uri The URI of the media to be removed.
|
||||
* @param customCacheKey A custom key for cache indexing, or null.
|
||||
* @param data Optional custom data for this action. If {@code null} an empty array will be used.
|
||||
*/
|
||||
public static DownloadAction createRemoveAction(
|
||||
String type, Uri uri, @Nullable String customCacheKey, @Nullable byte[] data) {
|
||||
return new DownloadAction(
|
||||
type, uri, /* isRemoveAction= */ true, Collections.emptyList(), customCacheKey, data);
|
||||
}
|
||||
|
||||
/** The type of the action. */
|
||||
public final String type;
|
||||
/** The action version. */
|
||||
public final int version;
|
||||
/** The uri being downloaded or removed. */
|
||||
public final Uri uri;
|
||||
/** Whether this is a remove action. If false, this is a download action. */
|
||||
public final boolean isRemoveAction;
|
||||
/**
|
||||
* Keys of tracks to be downloaded. If empty, all tracks will be downloaded. Empty if this action
|
||||
* is a remove action.
|
||||
*/
|
||||
public final List<StreamKey> keys;
|
||||
/** A custom key for cache indexing, or null. */
|
||||
@Nullable public final String customCacheKey;
|
||||
/** Custom data for this action. May be empty. */
|
||||
public final byte[] data;
|
||||
|
||||
/**
|
||||
* @param type The type of the action.
|
||||
* @param version The action version.
|
||||
* @param uri The uri being downloaded or removed.
|
||||
* @param isRemoveAction Whether this is a remove action. If false, this is a download action.
|
||||
* @param keys Keys of tracks to be downloaded. If empty, all tracks will be downloaded. Empty if
|
||||
* this action is a remove action.
|
||||
* @param customCacheKey A custom key for cache indexing, or null.
|
||||
* @param data Optional custom data for this action.
|
||||
*/
|
||||
protected DownloadAction(
|
||||
String type, int version, Uri uri, boolean isRemoveAction, @Nullable byte[] data) {
|
||||
private DownloadAction(
|
||||
String type,
|
||||
Uri uri,
|
||||
boolean isRemoveAction,
|
||||
List<StreamKey> keys,
|
||||
@Nullable String customCacheKey,
|
||||
@Nullable byte[] data) {
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
this.uri = uri;
|
||||
this.isRemoveAction = isRemoveAction;
|
||||
this.customCacheKey = customCacheKey;
|
||||
this.data = data != null ? data : Util.EMPTY_BYTE_ARRAY;
|
||||
if (isRemoveAction) {
|
||||
Assertions.checkArgument(keys.isEmpty());
|
||||
this.keys = Collections.emptyList();
|
||||
} else {
|
||||
ArrayList<StreamKey> mutableKeys = new ArrayList<>(keys);
|
||||
Collections.sort(mutableKeys);
|
||||
this.keys = Collections.unmodifiableList(mutableKeys);
|
||||
}
|
||||
}
|
||||
|
||||
/** Serializes itself into a byte array. */
|
||||
public final byte[] toByteArray() {
|
||||
public byte[] toByteArray() {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
try {
|
||||
serializeToStream(this, output);
|
||||
serializeToStream(output);
|
||||
} catch (IOException e) {
|
||||
// ByteArrayOutputStream shouldn't throw IOException.
|
||||
throw new IllegalStateException();
|
||||
|
|
@ -175,46 +152,118 @@ public abstract class DownloadAction {
|
|||
|
||||
/** Returns whether this is an action for the same media as the {@code other}. */
|
||||
public boolean isSameMedia(DownloadAction other) {
|
||||
return uri.equals(other.uri);
|
||||
return customCacheKey == null
|
||||
? other.customCacheKey == null && uri.equals(other.uri)
|
||||
: customCacheKey.equals(other.customCacheKey);
|
||||
}
|
||||
|
||||
/** Returns keys of tracks to be downloaded. */
|
||||
public List<StreamKey> getKeys() {
|
||||
return Collections.emptyList();
|
||||
return keys;
|
||||
}
|
||||
|
||||
/** Serializes itself into the {@code output}. */
|
||||
protected abstract void writeToStream(DataOutputStream output) throws IOException;
|
||||
|
||||
/** Creates a {@link Downloader} with the given parameters. */
|
||||
public abstract Downloader createDownloader(
|
||||
DownloaderConstructorHelper downloaderConstructorHelper);
|
||||
|
||||
@SuppressWarnings("EqualsGetClass")
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
if (!(o instanceof DownloadAction)) {
|
||||
return false;
|
||||
}
|
||||
DownloadAction that = (DownloadAction) o;
|
||||
return type.equals(that.type)
|
||||
&& version == that.version
|
||||
&& uri.equals(that.uri)
|
||||
&& isRemoveAction == that.isRemoveAction
|
||||
&& keys.equals(that.keys)
|
||||
&& Util.areEqual(customCacheKey, that.customCacheKey)
|
||||
&& Arrays.equals(data, that.data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = uri.hashCode();
|
||||
public final int hashCode() {
|
||||
int result = type.hashCode();
|
||||
result = 31 * result + uri.hashCode();
|
||||
result = 31 * result + (isRemoveAction ? 1 : 0);
|
||||
result = 31 * result + keys.hashCode();
|
||||
result = 31 * result + (customCacheKey != null ? customCacheKey.hashCode() : 0);
|
||||
result = 31 * result + Arrays.hashCode(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Deserializer getDeserializer(Class<?> clazz)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
Object value = clazz.getDeclaredField("DESERIALIZER").get(null);
|
||||
return (Deserializer) Assertions.checkNotNull(value);
|
||||
// Serialization.
|
||||
|
||||
/**
|
||||
* Serializes this action into an {@link OutputStream}.
|
||||
*
|
||||
* @param output The stream to write to.
|
||||
*/
|
||||
public final void serializeToStream(OutputStream output) throws IOException {
|
||||
// Don't close the stream as it closes the underlying stream too.
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(output);
|
||||
dataOutputStream.writeUTF(type);
|
||||
dataOutputStream.writeInt(VERSION);
|
||||
dataOutputStream.writeUTF(uri.toString());
|
||||
dataOutputStream.writeBoolean(isRemoveAction);
|
||||
dataOutputStream.writeInt(data.length);
|
||||
dataOutputStream.write(data);
|
||||
dataOutputStream.writeInt(keys.size());
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
StreamKey key = keys.get(i);
|
||||
dataOutputStream.writeInt(key.periodIndex);
|
||||
dataOutputStream.writeInt(key.groupIndex);
|
||||
dataOutputStream.writeInt(key.trackIndex);
|
||||
}
|
||||
dataOutputStream.writeBoolean(customCacheKey != null);
|
||||
if (customCacheKey != null) {
|
||||
dataOutputStream.writeUTF(customCacheKey);
|
||||
}
|
||||
dataOutputStream.flush();
|
||||
}
|
||||
|
||||
private static DownloadAction readFromStream(DataInputStream input) throws IOException {
|
||||
String type = input.readUTF();
|
||||
int version = input.readInt();
|
||||
|
||||
Uri uri = Uri.parse(input.readUTF());
|
||||
boolean isRemoveAction = input.readBoolean();
|
||||
int dataLength = input.readInt();
|
||||
byte[] data = new byte[dataLength];
|
||||
input.readFully(data);
|
||||
|
||||
// Serialized version 0 progressive actions did not contain keys.
|
||||
boolean isLegacyProgressive = version == 0 && TYPE_PROGRESSIVE.equals(type);
|
||||
List<StreamKey> keys = new ArrayList<>();
|
||||
if (!isLegacyProgressive) {
|
||||
int keyCount = input.readInt();
|
||||
for (int i = 0; i < keyCount; i++) {
|
||||
keys.add(readKey(type, version, input));
|
||||
}
|
||||
}
|
||||
|
||||
// Serialized version 0 and 1 DASH/HLS/SS actions did not contain a custom cache key.
|
||||
boolean isLegacySegmented =
|
||||
version < 2 && (TYPE_DASH.equals(type) || TYPE_HLS.equals(type) || TYPE_SS.equals(type));
|
||||
String customCacheKey = null;
|
||||
if (!isLegacySegmented) {
|
||||
customCacheKey = input.readBoolean() ? input.readUTF() : null;
|
||||
}
|
||||
|
||||
return new DownloadAction(type, uri, isRemoveAction, keys, customCacheKey, data);
|
||||
}
|
||||
|
||||
private static StreamKey readKey(String type, int version, DataInputStream input)
|
||||
throws IOException {
|
||||
int periodIndex;
|
||||
int groupIndex;
|
||||
int trackIndex;
|
||||
|
||||
// Serialized version 0 HLS/SS actions did not contain a period index.
|
||||
if ((TYPE_HLS.equals(type) || TYPE_SS.equals(type)) && version == 0) {
|
||||
periodIndex = 0;
|
||||
groupIndex = input.readInt();
|
||||
trackIndex = input.readInt();
|
||||
} else {
|
||||
periodIndex = input.readInt();
|
||||
groupIndex = input.readInt();
|
||||
trackIndex = input.readInt();
|
||||
}
|
||||
return new StreamKey(periodIndex, groupIndex, trackIndex);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,9 +28,6 @@ import android.os.Looper;
|
|||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction.Deserializer;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
|
@ -85,11 +82,10 @@ public final class DownloadManager {
|
|||
private static final String TAG = "DownloadManager";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final DownloaderConstructorHelper downloaderConstructorHelper;
|
||||
private final int maxActiveDownloadTasks;
|
||||
private final int minRetryCount;
|
||||
private final ActionFile actionFile;
|
||||
private final DownloadAction.Deserializer[] deserializers;
|
||||
private final DownloaderFactory downloaderFactory;
|
||||
private final ArrayList<Task> tasks;
|
||||
private final ArrayList<Task> activeDownloadTasks;
|
||||
private final Handler handler;
|
||||
|
|
@ -102,71 +98,34 @@ public final class DownloadManager {
|
|||
private boolean released;
|
||||
private boolean downloadsStopped;
|
||||
|
||||
/**
|
||||
* Creates a {@link DownloadManager}.
|
||||
*
|
||||
* @param cache Cache instance to be used to store downloaded data.
|
||||
* @param upstreamDataSourceFactory A {@link DataSource.Factory} for creating data sources for
|
||||
* downloading upstream data.
|
||||
* @param actionSaveFile File to save active actions.
|
||||
* @param deserializers Used to deserialize {@link DownloadAction}s. If empty, {@link
|
||||
* DownloadAction#getDefaultDeserializers()} is used instead.
|
||||
*/
|
||||
public DownloadManager(
|
||||
Cache cache,
|
||||
DataSource.Factory upstreamDataSourceFactory,
|
||||
File actionSaveFile,
|
||||
Deserializer... deserializers) {
|
||||
this(
|
||||
new DownloaderConstructorHelper(cache, upstreamDataSourceFactory),
|
||||
actionSaveFile,
|
||||
deserializers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link DownloadManager}.
|
||||
*
|
||||
* @param constructorHelper A {@link DownloaderConstructorHelper} to create {@link Downloader}s
|
||||
* for downloading data.
|
||||
* @param actionFile The file in which active actions are saved.
|
||||
* @param deserializers Used to deserialize {@link DownloadAction}s. If empty, {@link
|
||||
* DownloadAction#getDefaultDeserializers()} is used instead.
|
||||
* @param downloaderFactory A factory for creating {@link Downloader}s.
|
||||
*/
|
||||
public DownloadManager(
|
||||
DownloaderConstructorHelper constructorHelper,
|
||||
File actionFile,
|
||||
Deserializer... deserializers) {
|
||||
public DownloadManager(File actionFile, DownloaderFactory downloaderFactory) {
|
||||
this(
|
||||
constructorHelper,
|
||||
DEFAULT_MAX_SIMULTANEOUS_DOWNLOADS,
|
||||
DEFAULT_MIN_RETRY_COUNT,
|
||||
actionFile,
|
||||
deserializers);
|
||||
actionFile, downloaderFactory, DEFAULT_MAX_SIMULTANEOUS_DOWNLOADS, DEFAULT_MIN_RETRY_COUNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link DownloadManager}.
|
||||
*
|
||||
* @param constructorHelper A {@link DownloaderConstructorHelper} to create {@link Downloader}s
|
||||
* for downloading data.
|
||||
* @param actionFile The file in which active actions are saved.
|
||||
* @param downloaderFactory A factory for creating {@link Downloader}s.
|
||||
* @param maxSimultaneousDownloads The maximum number of simultaneous download tasks.
|
||||
* @param minRetryCount The minimum number of times a task must be retried before failing.
|
||||
* @param actionFile The file in which active actions are saved.
|
||||
* @param deserializers Used to deserialize {@link DownloadAction}s. If empty, {@link
|
||||
* DownloadAction#getDefaultDeserializers()} is used instead.
|
||||
*/
|
||||
public DownloadManager(
|
||||
DownloaderConstructorHelper constructorHelper,
|
||||
int maxSimultaneousDownloads,
|
||||
int minRetryCount,
|
||||
File actionFile,
|
||||
Deserializer... deserializers) {
|
||||
this.downloaderConstructorHelper = constructorHelper;
|
||||
DownloaderFactory downloaderFactory,
|
||||
int maxSimultaneousDownloads,
|
||||
int minRetryCount) {
|
||||
this.actionFile = new ActionFile(actionFile);
|
||||
this.downloaderFactory = downloaderFactory;
|
||||
this.maxActiveDownloadTasks = maxSimultaneousDownloads;
|
||||
this.minRetryCount = minRetryCount;
|
||||
this.actionFile = new ActionFile(actionFile);
|
||||
this.deserializers =
|
||||
deserializers.length > 0 ? deserializers : DownloadAction.getDefaultDeserializers();
|
||||
this.downloadsStopped = true;
|
||||
|
||||
tasks = new ArrayList<>();
|
||||
|
|
@ -239,7 +198,7 @@ public final class DownloadManager {
|
|||
public int handleAction(byte[] actionData) throws IOException {
|
||||
Assertions.checkState(!released);
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(actionData);
|
||||
DownloadAction action = DownloadAction.deserializeFromStream(deserializers, input);
|
||||
DownloadAction action = DownloadAction.deserializeFromStream(input);
|
||||
return handleAction(action);
|
||||
}
|
||||
|
||||
|
|
@ -344,7 +303,7 @@ public final class DownloadManager {
|
|||
}
|
||||
|
||||
private Task addTaskForAction(DownloadAction action) {
|
||||
Task task = new Task(nextTaskId++, this, action, minRetryCount);
|
||||
Task task = new Task(nextTaskId++, this, downloaderFactory, action, minRetryCount);
|
||||
tasks.add(task);
|
||||
logd("Task is added", task);
|
||||
return task;
|
||||
|
|
@ -450,7 +409,7 @@ public final class DownloadManager {
|
|||
() -> {
|
||||
DownloadAction[] loadedActions;
|
||||
try {
|
||||
loadedActions = actionFile.load(DownloadManager.this.deserializers);
|
||||
loadedActions = actionFile.load();
|
||||
logd("Action file is loaded.");
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Action file loading failed.", e);
|
||||
|
|
@ -642,6 +601,7 @@ public final class DownloadManager {
|
|||
|
||||
private final int id;
|
||||
private final DownloadManager downloadManager;
|
||||
private final DownloaderFactory downloaderFactory;
|
||||
private final DownloadAction action;
|
||||
private final int minRetryCount;
|
||||
private volatile @InternalState int currentState;
|
||||
|
|
@ -650,9 +610,14 @@ public final class DownloadManager {
|
|||
private Throwable error;
|
||||
|
||||
private Task(
|
||||
int id, DownloadManager downloadManager, DownloadAction action, int minRetryCount) {
|
||||
int id,
|
||||
DownloadManager downloadManager,
|
||||
DownloaderFactory downloaderFactory,
|
||||
DownloadAction action,
|
||||
int minRetryCount) {
|
||||
this.id = id;
|
||||
this.downloadManager = downloadManager;
|
||||
this.downloaderFactory = downloaderFactory;
|
||||
this.action = action;
|
||||
this.currentState = STATE_QUEUED;
|
||||
this.minRetryCount = minRetryCount;
|
||||
|
|
@ -807,7 +772,7 @@ public final class DownloadManager {
|
|||
logd("Task is started", this);
|
||||
Throwable error = null;
|
||||
try {
|
||||
downloader = action.createDownloader(downloadManager.downloaderConstructorHelper);
|
||||
downloader = downloaderFactory.createDownloader(action);
|
||||
if (action.isRemoveAction) {
|
||||
downloader.remove();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.offline;
|
||||
|
||||
/** Creates {@link Downloader Downloaders} for given {@link DownloadAction DownloadActions}. */
|
||||
public interface DownloaderFactory {
|
||||
|
||||
/**
|
||||
* Creates a {@link Downloader} to perform the given {@link DownloadAction}.
|
||||
*
|
||||
* @param action The action.
|
||||
* @return The downloader.
|
||||
*/
|
||||
Downloader createDownloader(DownloadAction action);
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.offline;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/** An action to download or remove downloaded progressive streams. */
|
||||
public final class ProgressiveDownloadAction extends DownloadAction {
|
||||
|
||||
private static final String TYPE = "progressive";
|
||||
private static final int VERSION = 0;
|
||||
|
||||
public static final Deserializer DESERIALIZER =
|
||||
new Deserializer(TYPE, VERSION) {
|
||||
@Override
|
||||
public ProgressiveDownloadAction readFromStream(int version, DataInputStream input)
|
||||
throws IOException {
|
||||
Uri uri = Uri.parse(input.readUTF());
|
||||
boolean isRemoveAction = input.readBoolean();
|
||||
int dataLength = input.readInt();
|
||||
byte[] data = new byte[dataLength];
|
||||
input.readFully(data);
|
||||
String customCacheKey = input.readBoolean() ? input.readUTF() : null;
|
||||
return new ProgressiveDownloadAction(uri, isRemoveAction, data, customCacheKey);
|
||||
}
|
||||
};
|
||||
|
||||
private final @Nullable String customCacheKey;
|
||||
|
||||
/**
|
||||
* Creates a progressive stream download action.
|
||||
*
|
||||
* @param uri Uri of the data to be downloaded.
|
||||
* @param data Optional custom data for this action.
|
||||
* @param customCacheKey A custom key that uniquely identifies the original stream. If not null it
|
||||
* is used for cache indexing.
|
||||
*/
|
||||
public static ProgressiveDownloadAction createDownloadAction(
|
||||
Uri uri, @Nullable byte[] data, @Nullable String customCacheKey) {
|
||||
return new ProgressiveDownloadAction(uri, /* isRemoveAction= */ false, data, customCacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a progressive stream remove action.
|
||||
*
|
||||
* @param uri Uri of the data to be removed.
|
||||
* @param data Optional custom data for this action.
|
||||
* @param customCacheKey A custom key that uniquely identifies the original stream. If not null it
|
||||
* is used for cache indexing.
|
||||
*/
|
||||
public static ProgressiveDownloadAction createRemoveAction(
|
||||
Uri uri, @Nullable byte[] data, @Nullable String customCacheKey) {
|
||||
return new ProgressiveDownloadAction(uri, /* isRemoveAction= */ true, data, customCacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri Uri of the data to be downloaded.
|
||||
* @param isRemoveAction Whether this is a remove action. If false, this is a download action.
|
||||
* @param data Optional custom data for this action.
|
||||
* @param customCacheKey A custom key that uniquely identifies the original stream. If not null it
|
||||
* is used for cache indexing.
|
||||
* @deprecated Use {@link #createDownloadAction(Uri, byte[], String)} or {@link
|
||||
* #createRemoveAction(Uri, byte[], String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public ProgressiveDownloadAction(
|
||||
Uri uri, boolean isRemoveAction, @Nullable byte[] data, @Nullable String customCacheKey) {
|
||||
super(TYPE, VERSION, uri, isRemoveAction, data);
|
||||
this.customCacheKey = customCacheKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgressiveDownloader createDownloader(DownloaderConstructorHelper constructorHelper) {
|
||||
return new ProgressiveDownloader(uri, customCacheKey, constructorHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToStream(DataOutputStream output) throws IOException {
|
||||
output.writeUTF(uri.toString());
|
||||
output.writeBoolean(isRemoveAction);
|
||||
output.writeInt(data.length);
|
||||
output.write(data);
|
||||
boolean customCacheKeySet = customCacheKey != null;
|
||||
output.writeBoolean(customCacheKeySet);
|
||||
if (customCacheKeySet) {
|
||||
output.writeUTF(customCacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameMedia(DownloadAction other) {
|
||||
return ((other instanceof ProgressiveDownloadAction)
|
||||
&& getCacheKey().equals(((ProgressiveDownloadAction) other).getCacheKey()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
ProgressiveDownloadAction that = (ProgressiveDownloadAction) o;
|
||||
return Util.areEqual(customCacheKey, that.customCacheKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + (customCacheKey != null ? customCacheKey.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getCacheKey() {
|
||||
return customCacheKey != null ? customCacheKey : CacheUtil.generateKey(uri);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.offline;
|
|||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** A {@link DownloadHelper} for progressive streams. */
|
||||
|
|
@ -51,13 +52,18 @@ public final class ProgressiveDownloadHelper extends DownloadHelper {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ProgressiveDownloadAction getDownloadAction(
|
||||
@Nullable byte[] data, List<TrackKey> trackKeys) {
|
||||
return ProgressiveDownloadAction.createDownloadAction(uri, data, customCacheKey);
|
||||
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
|
||||
return DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_PROGRESSIVE,
|
||||
uri,
|
||||
/* keys= */ Collections.emptyList(),
|
||||
customCacheKey,
|
||||
data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgressiveDownloadAction getRemoveAction(@Nullable byte[] data) {
|
||||
return ProgressiveDownloadAction.createRemoveAction(uri, data, customCacheKey);
|
||||
public DownloadAction getRemoveAction(@Nullable byte[] data) {
|
||||
return DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_PROGRESSIVE, uri, customCacheKey, data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
package com.google.android.exoplayer2.offline;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
|
|
@ -47,7 +48,7 @@ public final class ProgressiveDownloader implements Downloader {
|
|||
* @param constructorHelper A {@link DownloaderConstructorHelper} instance.
|
||||
*/
|
||||
public ProgressiveDownloader(
|
||||
Uri uri, String customCacheKey, DownloaderConstructorHelper constructorHelper) {
|
||||
Uri uri, @Nullable String customCacheKey, DownloaderConstructorHelper constructorHelper) {
|
||||
this.dataSpec = new DataSpec(uri, 0, C.LENGTH_UNSET, customCacheKey, 0);
|
||||
this.cache = constructorHelper.getCache();
|
||||
this.dataSource = constructorHelper.createCacheDataSource();
|
||||
|
|
|
|||
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.offline;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** {@link DownloadAction} for {@link SegmentDownloader}s. */
|
||||
public abstract class SegmentDownloadAction extends DownloadAction {
|
||||
|
||||
/** Base class for {@link SegmentDownloadAction} {@link Deserializer}s. */
|
||||
protected abstract static class SegmentDownloadActionDeserializer extends Deserializer {
|
||||
|
||||
public SegmentDownloadActionDeserializer(String type, int version) {
|
||||
super(type, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final DownloadAction readFromStream(int version, DataInputStream input)
|
||||
throws IOException {
|
||||
Uri uri = Uri.parse(input.readUTF());
|
||||
boolean isRemoveAction = input.readBoolean();
|
||||
int dataLength = input.readInt();
|
||||
byte[] data = new byte[dataLength];
|
||||
input.readFully(data);
|
||||
int keyCount = input.readInt();
|
||||
List<StreamKey> keys = new ArrayList<>();
|
||||
for (int i = 0; i < keyCount; i++) {
|
||||
keys.add(readKey(version, input));
|
||||
}
|
||||
return createDownloadAction(uri, isRemoveAction, data, keys);
|
||||
}
|
||||
|
||||
/** Deserializes a key from the {@code input}. */
|
||||
protected StreamKey readKey(int version, DataInputStream input) throws IOException {
|
||||
int periodIndex = input.readInt();
|
||||
int groupIndex = input.readInt();
|
||||
int trackIndex = input.readInt();
|
||||
return new StreamKey(periodIndex, groupIndex, trackIndex);
|
||||
}
|
||||
|
||||
/** Returns a {@link DownloadAction}. */
|
||||
protected abstract DownloadAction createDownloadAction(
|
||||
Uri manifestUri, boolean isRemoveAction, byte[] data, List<StreamKey> keys);
|
||||
}
|
||||
|
||||
public final List<StreamKey> keys;
|
||||
|
||||
/**
|
||||
* @param type The type of the action.
|
||||
* @param version The action version.
|
||||
* @param uri The URI of the media being downloaded.
|
||||
* @param isRemoveAction Whether the data will be removed. If {@code false} it will be downloaded.
|
||||
* @param data Optional custom data for this action. If {@code null} an empty array will be used.
|
||||
* @param keys Keys of tracks to be downloaded. If empty, all tracks will be downloaded. If {@code
|
||||
* removeAction} is true, {@code keys} must be empty.
|
||||
*/
|
||||
protected SegmentDownloadAction(
|
||||
String type,
|
||||
int version,
|
||||
Uri uri,
|
||||
boolean isRemoveAction,
|
||||
@Nullable byte[] data,
|
||||
List<StreamKey> keys) {
|
||||
super(type, version, uri, isRemoveAction, data);
|
||||
if (isRemoveAction) {
|
||||
Assertions.checkArgument(keys.isEmpty());
|
||||
this.keys = Collections.emptyList();
|
||||
} else {
|
||||
ArrayList<StreamKey> mutableKeys = new ArrayList<>(keys);
|
||||
Collections.sort(mutableKeys);
|
||||
this.keys = Collections.unmodifiableList(mutableKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StreamKey> getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void writeToStream(DataOutputStream output) throws IOException {
|
||||
output.writeUTF(uri.toString());
|
||||
output.writeBoolean(isRemoveAction);
|
||||
output.writeInt(data.length);
|
||||
output.write(data);
|
||||
output.writeInt(keys.size());
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
writeKey(output, keys.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
SegmentDownloadAction that = (SegmentDownloadAction) o;
|
||||
return keys.equals(that.keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + keys.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Serializes the {@code key} into the {@code output}. */
|
||||
private void writeKey(DataOutputStream output, StreamKey key) throws IOException {
|
||||
output.writeInt(key.periodIndex);
|
||||
output.writeInt(key.groupIndex);
|
||||
output.writeInt(key.trackIndex);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,13 +18,13 @@ package com.google.android.exoplayer2.offline;
|
|||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction.Deserializer;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
|
@ -38,10 +38,22 @@ import org.robolectric.RuntimeEnvironment;
|
|||
public class ActionFileTest {
|
||||
|
||||
private File tempFile;
|
||||
private DownloadAction action1;
|
||||
private DownloadAction action2;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
tempFile = Util.createTempFile(RuntimeEnvironment.application, "ExoPlayerTest");
|
||||
action1 =
|
||||
buildAction(
|
||||
DownloadAction.TYPE_PROGRESSIVE,
|
||||
Uri.parse("http://test1.uri"),
|
||||
TestUtil.buildTestData(16));
|
||||
action2 =
|
||||
buildAction(
|
||||
DownloadAction.TYPE_PROGRESSIVE,
|
||||
Uri.parse("http://test2.uri"),
|
||||
TestUtil.buildTestData(32));
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
@ -78,44 +90,32 @@ public class ActionFileTest {
|
|||
|
||||
@Test
|
||||
public void testLoadAction() throws Exception {
|
||||
byte[] data = Util.getUtf8Bytes("321");
|
||||
DownloadAction[] actions =
|
||||
loadActions(
|
||||
new Object[] {
|
||||
ActionFile.VERSION,
|
||||
1, // Action count
|
||||
"type2", // Action 1
|
||||
FakeDownloadAction.VERSION,
|
||||
data,
|
||||
},
|
||||
new FakeDeserializer("type2"));
|
||||
action1
|
||||
});
|
||||
assertThat(actions).isNotNull();
|
||||
assertThat(actions).hasLength(1);
|
||||
assertAction(actions[0], "type2", FakeDownloadAction.VERSION, data);
|
||||
assertThat(actions[0]).isEqualTo(action1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadActions() throws Exception {
|
||||
byte[] data1 = Util.getUtf8Bytes("123");
|
||||
byte[] data2 = Util.getUtf8Bytes("321");
|
||||
DownloadAction[] actions =
|
||||
loadActions(
|
||||
new Object[] {
|
||||
ActionFile.VERSION,
|
||||
2, // Action count
|
||||
"type1", // Action 1
|
||||
FakeDownloadAction.VERSION,
|
||||
data1,
|
||||
"type2", // Action 2
|
||||
FakeDownloadAction.VERSION,
|
||||
data2,
|
||||
},
|
||||
new FakeDeserializer("type1"),
|
||||
new FakeDeserializer("type2"));
|
||||
action1,
|
||||
action2,
|
||||
});
|
||||
assertThat(actions).isNotNull();
|
||||
assertThat(actions).hasLength(2);
|
||||
assertAction(actions[0], "type1", FakeDownloadAction.VERSION, data1);
|
||||
assertAction(actions[1], "type2", FakeDownloadAction.VERSION, data2);
|
||||
assertThat(actions[0]).isEqualTo(action1);
|
||||
assertThat(actions[1]).isEqualTo(action2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -125,90 +125,39 @@ public class ActionFileTest {
|
|||
new Object[] {
|
||||
ActionFile.VERSION + 1,
|
||||
1, // Action count
|
||||
"type2", // Action 1
|
||||
FakeDownloadAction.VERSION,
|
||||
Util.getUtf8Bytes("321"),
|
||||
},
|
||||
new FakeDeserializer("type2"));
|
||||
action1,
|
||||
});
|
||||
Assert.fail();
|
||||
} catch (IOException e) {
|
||||
// Expected exception.
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadNotSupportedActionVersion() throws Exception {
|
||||
try {
|
||||
loadActions(
|
||||
new Object[] {
|
||||
ActionFile.VERSION,
|
||||
1, // Action count
|
||||
"type2", // Action 1
|
||||
FakeDownloadAction.VERSION + 1,
|
||||
Util.getUtf8Bytes("321"),
|
||||
},
|
||||
new FakeDeserializer("type2"));
|
||||
Assert.fail();
|
||||
} catch (IOException e) {
|
||||
// Expected exception.
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadNotSupportedType() throws Exception {
|
||||
try {
|
||||
loadActions(
|
||||
new Object[] {
|
||||
ActionFile.VERSION,
|
||||
1, // Action count
|
||||
"type2", // Action 1
|
||||
FakeDownloadAction.VERSION,
|
||||
Util.getUtf8Bytes("321"),
|
||||
},
|
||||
new FakeDeserializer("type1"));
|
||||
Assert.fail();
|
||||
} catch (DownloadException e) {
|
||||
// Expected exception.
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndLoadNoActions() throws Exception {
|
||||
doTestSerializationRoundTrip(new DownloadAction[0]);
|
||||
doTestSerializationRoundTrip();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndLoadActions() throws Exception {
|
||||
doTestSerializationRoundTrip(
|
||||
new DownloadAction[] {
|
||||
new FakeDownloadAction("type1", Util.getUtf8Bytes("123")),
|
||||
new FakeDownloadAction("type2", Util.getUtf8Bytes("321")),
|
||||
},
|
||||
new FakeDeserializer("type1"),
|
||||
new FakeDeserializer("type2"));
|
||||
doTestSerializationRoundTrip(action1, action2);
|
||||
}
|
||||
|
||||
private void doTestSerializationRoundTrip(DownloadAction[] actions,
|
||||
Deserializer... deserializers) throws IOException {
|
||||
private void doTestSerializationRoundTrip(DownloadAction... actions) throws IOException {
|
||||
ActionFile actionFile = new ActionFile(tempFile);
|
||||
actionFile.store(actions);
|
||||
assertThat(actionFile.load(deserializers)).isEqualTo(actions);
|
||||
assertThat(actionFile.load()).isEqualTo(actions);
|
||||
}
|
||||
|
||||
private DownloadAction[] loadActions(Object[] values, Deserializer... deserializers)
|
||||
throws IOException {
|
||||
private DownloadAction[] loadActions(Object[] values) throws IOException {
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
|
||||
try {
|
||||
for (Object value : values) {
|
||||
if (value instanceof Integer) {
|
||||
dataOutputStream.writeInt((Integer) value);
|
||||
} else if (value instanceof String) {
|
||||
dataOutputStream.writeUTF((String) value);
|
||||
} else if (value instanceof byte[]) {
|
||||
byte[] data = (byte[]) value;
|
||||
dataOutputStream.writeInt(data.length);
|
||||
dataOutputStream.write(data);
|
||||
} else if (value instanceof DownloadAction) {
|
||||
((DownloadAction) value).serializeToStream(dataOutputStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
|
@ -216,50 +165,11 @@ public class ActionFileTest {
|
|||
} finally {
|
||||
dataOutputStream.close();
|
||||
}
|
||||
return new ActionFile(tempFile).load(deserializers);
|
||||
return new ActionFile(tempFile).load();
|
||||
}
|
||||
|
||||
private static void assertAction(DownloadAction action, String type, int version, byte[] data) {
|
||||
assertThat(action).isInstanceOf(FakeDownloadAction.class);
|
||||
assertThat(action.type).isEqualTo(type);
|
||||
assertThat(((FakeDownloadAction) action).version).isEqualTo(version);
|
||||
assertThat(((FakeDownloadAction) action).data).isEqualTo(data);
|
||||
private static DownloadAction buildAction(String type, Uri uri, byte[] data) {
|
||||
return DownloadAction.createDownloadAction(
|
||||
type, uri, /* keys= */ Collections.emptyList(), /* customCacheKey= */ null, data);
|
||||
}
|
||||
|
||||
private static class FakeDeserializer extends Deserializer {
|
||||
|
||||
FakeDeserializer(String type) {
|
||||
super(type, FakeDownloadAction.VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadAction readFromStream(int version, DataInputStream input) throws IOException {
|
||||
int dataLength = input.readInt();
|
||||
byte[] data = new byte[dataLength];
|
||||
input.readFully(data);
|
||||
return new FakeDownloadAction(type, data);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FakeDownloadAction extends DownloadAction {
|
||||
|
||||
public static final int VERSION = 0;
|
||||
|
||||
private FakeDownloadAction(String type, byte[] data) {
|
||||
super(type, VERSION, Uri.parse("http://test.com"), /* isRemoveAction= */ false, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToStream(DataOutputStream output) throws IOException {
|
||||
output.writeInt(data.length);
|
||||
output.write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Downloader createDownloader(DownloaderConstructorHelper downloaderConstructorHelper) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.offline;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
import java.util.Collections;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** Unit tests for {@link DefaultDownloaderFactory}. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public final class DefaultDownloaderFactoryTest {
|
||||
|
||||
@Test
|
||||
public void createProgressiveDownloader() throws Exception {
|
||||
DownloaderConstructorHelper constructorHelper =
|
||||
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
||||
DownloaderFactory factory = new DefaultDownloaderFactory(constructorHelper);
|
||||
|
||||
Downloader downloader =
|
||||
factory.createDownloader(
|
||||
DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_PROGRESSIVE,
|
||||
Uri.parse("https://www.test.com/download"),
|
||||
/* keys= */ Collections.emptyList(),
|
||||
/* customCacheKey= */ null,
|
||||
/* data= */ null));
|
||||
assertThat(downloader).isInstanceOf(ProgressiveDownloader.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -25,19 +25,17 @@ import com.google.android.exoplayer2.offline.DownloadManager.TaskState.State;
|
|||
import com.google.android.exoplayer2.testutil.DummyMainThread;
|
||||
import com.google.android.exoplayer2.testutil.RobolectricUtil;
|
||||
import com.google.android.exoplayer2.testutil.TestDownloadManagerListener;
|
||||
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
|
@ -63,6 +61,7 @@ public class DownloadManagerTest {
|
|||
private DummyMainThread dummyMainThread;
|
||||
private File actionFile;
|
||||
private TestDownloadManagerListener downloadManagerListener;
|
||||
private FakeDownloaderFactory downloaderFactory;
|
||||
private DownloadManager downloadManager;
|
||||
|
||||
@Before
|
||||
|
|
@ -73,6 +72,7 @@ public class DownloadManagerTest {
|
|||
uri3 = Uri.parse("http://abc.com/media3");
|
||||
dummyMainThread = new DummyMainThread();
|
||||
actionFile = Util.createTempFile(RuntimeEnvironment.application, "ExoPlayerTest");
|
||||
downloaderFactory = new FakeDownloaderFactory();
|
||||
setUpDownloadManager(100);
|
||||
}
|
||||
|
||||
|
|
@ -85,24 +85,24 @@ public class DownloadManagerTest {
|
|||
|
||||
@Test
|
||||
public void testDownloadActionRuns() throws Throwable {
|
||||
doTestActionRuns(createDownloadAction(uri1));
|
||||
doTestDownloaderRuns(createDownloadRunner(uri1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveActionRuns() throws Throwable {
|
||||
doTestActionRuns(createRemoveAction(uri1));
|
||||
doTestDownloaderRuns(createRemoveRunner(uri1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownloadRetriesThenFails() throws Throwable {
|
||||
FakeDownloadAction downloadAction = createDownloadAction(uri1);
|
||||
downloadAction.post();
|
||||
FakeDownloader fakeDownloader = downloadAction.getFakeDownloader();
|
||||
DownloadRunner downloadRunner = createDownloadRunner(uri1);
|
||||
downloadRunner.postAction();
|
||||
FakeDownloader fakeDownloader = downloadRunner.downloader;
|
||||
fakeDownloader.enableDownloadIOException = true;
|
||||
for (int i = 0; i <= MIN_RETRY_COUNT; i++) {
|
||||
fakeDownloader.assertStarted(MAX_RETRY_DELAY).unblock();
|
||||
}
|
||||
downloadAction.assertFailed();
|
||||
downloadRunner.assertFailed();
|
||||
downloadManagerListener.clearDownloadError();
|
||||
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
|
|
@ -110,23 +110,23 @@ public class DownloadManagerTest {
|
|||
|
||||
@Test
|
||||
public void testDownloadNoRetryWhenCanceled() throws Throwable {
|
||||
FakeDownloadAction downloadAction = createDownloadAction(uri1).ignoreInterrupts();
|
||||
downloadAction.getFakeDownloader().enableDownloadIOException = true;
|
||||
downloadAction.post().assertStarted();
|
||||
DownloadRunner downloadRunner = createDownloadRunner(uri1).ignoreInterrupts();
|
||||
downloadRunner.downloader.enableDownloadIOException = true;
|
||||
downloadRunner.postAction().assertStarted();
|
||||
|
||||
FakeDownloadAction removeAction = createRemoveAction(uri1).post();
|
||||
DownloadRunner removeRunner = createRemoveRunner(uri1).postAction();
|
||||
|
||||
downloadAction.unblock().assertCanceled();
|
||||
removeAction.unblock();
|
||||
downloadRunner.unblock().assertCanceled();
|
||||
removeRunner.unblock();
|
||||
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownloadRetriesThenContinues() throws Throwable {
|
||||
FakeDownloadAction downloadAction = createDownloadAction(uri1);
|
||||
downloadAction.post();
|
||||
FakeDownloader fakeDownloader = downloadAction.getFakeDownloader();
|
||||
DownloadRunner downloadRunner = createDownloadRunner(uri1);
|
||||
downloadRunner.postAction();
|
||||
FakeDownloader fakeDownloader = downloadRunner.downloader;
|
||||
fakeDownloader.enableDownloadIOException = true;
|
||||
for (int i = 0; i <= MIN_RETRY_COUNT; i++) {
|
||||
fakeDownloader.assertStarted(MAX_RETRY_DELAY);
|
||||
|
|
@ -135,7 +135,7 @@ public class DownloadManagerTest {
|
|||
}
|
||||
fakeDownloader.unblock();
|
||||
}
|
||||
downloadAction.assertCompleted();
|
||||
downloadRunner.assertCompleted();
|
||||
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
}
|
||||
|
|
@ -143,9 +143,9 @@ public class DownloadManagerTest {
|
|||
@Test
|
||||
@SuppressWarnings({"NonAtomicVolatileUpdate", "NonAtomicOperationOnVolatileField"})
|
||||
public void testDownloadRetryCountResetsOnProgress() throws Throwable {
|
||||
FakeDownloadAction downloadAction = createDownloadAction(uri1);
|
||||
downloadAction.post();
|
||||
FakeDownloader fakeDownloader = downloadAction.getFakeDownloader();
|
||||
DownloadRunner downloadRunner = createDownloadRunner(uri1);
|
||||
downloadRunner.postAction();
|
||||
FakeDownloader fakeDownloader = downloadRunner.downloader;
|
||||
fakeDownloader.enableDownloadIOException = true;
|
||||
fakeDownloader.downloadedBytes = 0;
|
||||
for (int i = 0; i <= MIN_RETRY_COUNT + 10; i++) {
|
||||
|
|
@ -156,61 +156,61 @@ public class DownloadManagerTest {
|
|||
}
|
||||
fakeDownloader.unblock();
|
||||
}
|
||||
downloadAction.assertCompleted();
|
||||
downloadRunner.assertCompleted();
|
||||
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentMediaDownloadActionsStartInParallel() throws Throwable {
|
||||
doTestActionsRunInParallel(createDownloadAction(uri1), createDownloadAction(uri2));
|
||||
doTestDownloadersRunInParallel(createDownloadRunner(uri1), createDownloadRunner(uri2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentMediaDifferentActionsStartInParallel() throws Throwable {
|
||||
doTestActionsRunInParallel(createDownloadAction(uri1), createRemoveAction(uri2));
|
||||
doTestDownloadersRunInParallel(createDownloadRunner(uri1), createRemoveRunner(uri2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameMediaDownloadActionsStartInParallel() throws Throwable {
|
||||
doTestActionsRunInParallel(createDownloadAction(uri1), createDownloadAction(uri1));
|
||||
doTestDownloadersRunInParallel(createDownloadRunner(uri1), createDownloadRunner(uri1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameMediaRemoveActionWaitsDownloadAction() throws Throwable {
|
||||
doTestActionsRunSequentially(createDownloadAction(uri1), createRemoveAction(uri1));
|
||||
doTestDownloadersRunSequentially(createDownloadRunner(uri1), createRemoveRunner(uri1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameMediaDownloadActionWaitsRemoveAction() throws Throwable {
|
||||
doTestActionsRunSequentially(createRemoveAction(uri1), createDownloadAction(uri1));
|
||||
doTestDownloadersRunSequentially(createRemoveRunner(uri1), createDownloadRunner(uri1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameMediaRemoveActionWaitsRemoveAction() throws Throwable {
|
||||
doTestActionsRunSequentially(createRemoveAction(uri1), createRemoveAction(uri1));
|
||||
doTestDownloadersRunSequentially(createRemoveRunner(uri1), createRemoveRunner(uri1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameMediaMultipleActions() throws Throwable {
|
||||
FakeDownloadAction downloadAction1 = createDownloadAction(uri1).ignoreInterrupts();
|
||||
FakeDownloadAction downloadAction2 = createDownloadAction(uri1).ignoreInterrupts();
|
||||
FakeDownloadAction removeAction1 = createRemoveAction(uri1);
|
||||
FakeDownloadAction downloadAction3 = createDownloadAction(uri1);
|
||||
FakeDownloadAction removeAction2 = createRemoveAction(uri1);
|
||||
DownloadRunner downloadAction1 = createDownloadRunner(uri1).ignoreInterrupts();
|
||||
DownloadRunner downloadAction2 = createDownloadRunner(uri1).ignoreInterrupts();
|
||||
DownloadRunner removeAction1 = createRemoveRunner(uri1);
|
||||
DownloadRunner downloadAction3 = createDownloadRunner(uri1);
|
||||
DownloadRunner removeAction2 = createRemoveRunner(uri1);
|
||||
|
||||
// Two download actions run in parallel.
|
||||
downloadAction1.post().assertStarted();
|
||||
downloadAction2.post().assertStarted();
|
||||
downloadAction1.postAction().assertStarted();
|
||||
downloadAction2.postAction().assertStarted();
|
||||
// removeAction1 is added. It interrupts the two download actions' threads but they are
|
||||
// configured to ignore it so removeAction1 doesn't start.
|
||||
removeAction1.post().assertDoesNotStart();
|
||||
removeAction1.postAction().assertDoesNotStart();
|
||||
|
||||
// downloadAction2 finishes but it isn't enough to start removeAction1.
|
||||
downloadAction2.unblock().assertCanceled();
|
||||
removeAction1.assertDoesNotStart();
|
||||
// downloadAction3 is post to DownloadManager but it waits for removeAction1 to finish.
|
||||
downloadAction3.post().assertDoesNotStart();
|
||||
// downloadAction3 is postAction to DownloadManager but it waits for removeAction1 to finish.
|
||||
downloadAction3.postAction().assertDoesNotStart();
|
||||
|
||||
// When downloadAction1 finishes, removeAction1 starts.
|
||||
downloadAction1.unblock().assertCanceled();
|
||||
|
|
@ -220,7 +220,7 @@ public class DownloadManagerTest {
|
|||
|
||||
// removeAction2 is posted. removeAction1 and downloadAction3 is canceled so removeAction2
|
||||
// starts immediately.
|
||||
removeAction2.post();
|
||||
removeAction2.postAction();
|
||||
removeAction1.assertCanceled();
|
||||
downloadAction3.assertCanceled();
|
||||
removeAction2.assertStarted().unblock().assertCompleted();
|
||||
|
|
@ -229,13 +229,13 @@ public class DownloadManagerTest {
|
|||
|
||||
@Test
|
||||
public void testMultipleRemoveActionWaitsLastCancelsAllOther() throws Throwable {
|
||||
FakeDownloadAction removeAction1 = createRemoveAction(uri1).ignoreInterrupts();
|
||||
FakeDownloadAction removeAction2 = createRemoveAction(uri1);
|
||||
FakeDownloadAction removeAction3 = createRemoveAction(uri1);
|
||||
DownloadRunner removeAction1 = createRemoveRunner(uri1).ignoreInterrupts();
|
||||
DownloadRunner removeAction2 = createRemoveRunner(uri1);
|
||||
DownloadRunner removeAction3 = createRemoveRunner(uri1);
|
||||
|
||||
removeAction1.post().assertStarted();
|
||||
removeAction2.post().assertDoesNotStart();
|
||||
removeAction3.post().assertDoesNotStart();
|
||||
removeAction1.postAction().assertStarted();
|
||||
removeAction2.postAction().assertDoesNotStart();
|
||||
removeAction3.postAction().assertDoesNotStart();
|
||||
|
||||
removeAction2.assertCanceled();
|
||||
|
||||
|
|
@ -247,30 +247,30 @@ public class DownloadManagerTest {
|
|||
|
||||
@Test
|
||||
public void testGetTasks() throws Throwable {
|
||||
FakeDownloadAction removeAction = createRemoveAction(uri1);
|
||||
FakeDownloadAction downloadAction1 = createDownloadAction(uri1);
|
||||
FakeDownloadAction downloadAction2 = createDownloadAction(uri1);
|
||||
DownloadRunner removeAction = createRemoveRunner(uri1);
|
||||
DownloadRunner downloadAction1 = createDownloadRunner(uri1);
|
||||
DownloadRunner downloadAction2 = createDownloadRunner(uri1);
|
||||
|
||||
removeAction.post().assertStarted();
|
||||
downloadAction1.post().assertDoesNotStart();
|
||||
downloadAction2.post().assertDoesNotStart();
|
||||
removeAction.postAction().assertStarted();
|
||||
downloadAction1.postAction().assertDoesNotStart();
|
||||
downloadAction2.postAction().assertDoesNotStart();
|
||||
|
||||
TaskState[] states = downloadManager.getAllTaskStates();
|
||||
assertThat(states).hasLength(3);
|
||||
assertThat(states[0].action).isEqualTo(removeAction);
|
||||
assertThat(states[1].action).isEqualTo(downloadAction1);
|
||||
assertThat(states[2].action).isEqualTo(downloadAction2);
|
||||
assertThat(states[0].action).isEqualTo(removeAction.action);
|
||||
assertThat(states[1].action).isEqualTo(downloadAction1.action);
|
||||
assertThat(states[2].action).isEqualTo(downloadAction2.action);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleWaitingDownloadActionStartsInParallel() throws Throwable {
|
||||
FakeDownloadAction removeAction = createRemoveAction(uri1);
|
||||
FakeDownloadAction downloadAction1 = createDownloadAction(uri1);
|
||||
FakeDownloadAction downloadAction2 = createDownloadAction(uri1);
|
||||
DownloadRunner removeAction = createRemoveRunner(uri1);
|
||||
DownloadRunner downloadAction1 = createDownloadRunner(uri1);
|
||||
DownloadRunner downloadAction2 = createDownloadRunner(uri1);
|
||||
|
||||
removeAction.post().assertStarted();
|
||||
downloadAction1.post().assertDoesNotStart();
|
||||
downloadAction2.post().assertDoesNotStart();
|
||||
removeAction.postAction().assertStarted();
|
||||
downloadAction1.postAction().assertDoesNotStart();
|
||||
downloadAction2.postAction().assertDoesNotStart();
|
||||
|
||||
removeAction.unblock().assertCompleted();
|
||||
downloadAction1.assertStarted();
|
||||
|
|
@ -283,75 +283,75 @@ public class DownloadManagerTest {
|
|||
|
||||
@Test
|
||||
public void testDifferentMediaDownloadActionsPreserveOrder() throws Throwable {
|
||||
FakeDownloadAction removeAction = createRemoveAction(uri1).ignoreInterrupts();
|
||||
FakeDownloadAction downloadAction1 = createDownloadAction(uri1);
|
||||
FakeDownloadAction downloadAction2 = createDownloadAction(uri2);
|
||||
DownloadRunner removeRunner = createRemoveRunner(uri1).ignoreInterrupts();
|
||||
DownloadRunner downloadRunner1 = createDownloadRunner(uri1);
|
||||
DownloadRunner downloadRunner2 = createDownloadRunner(uri2);
|
||||
|
||||
removeAction.post().assertStarted();
|
||||
downloadAction1.post().assertDoesNotStart();
|
||||
downloadAction2.post().assertDoesNotStart();
|
||||
removeRunner.postAction().assertStarted();
|
||||
downloadRunner1.postAction().assertDoesNotStart();
|
||||
downloadRunner2.postAction().assertDoesNotStart();
|
||||
|
||||
removeAction.unblock().assertCompleted();
|
||||
downloadAction1.assertStarted();
|
||||
downloadAction2.assertStarted();
|
||||
downloadAction1.unblock().assertCompleted();
|
||||
downloadAction2.unblock().assertCompleted();
|
||||
removeRunner.unblock().assertCompleted();
|
||||
downloadRunner1.assertStarted();
|
||||
downloadRunner2.assertStarted();
|
||||
downloadRunner1.unblock().assertCompleted();
|
||||
downloadRunner2.unblock().assertCompleted();
|
||||
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentMediaRemoveActionsDoNotPreserveOrder() throws Throwable {
|
||||
FakeDownloadAction downloadAction = createDownloadAction(uri1).ignoreInterrupts();
|
||||
FakeDownloadAction removeAction1 = createRemoveAction(uri1);
|
||||
FakeDownloadAction removeAction2 = createRemoveAction(uri2);
|
||||
DownloadRunner downloadRunner = createDownloadRunner(uri1).ignoreInterrupts();
|
||||
DownloadRunner removeRunner1 = createRemoveRunner(uri1);
|
||||
DownloadRunner removeRunner2 = createRemoveRunner(uri2);
|
||||
|
||||
downloadAction.post().assertStarted();
|
||||
removeAction1.post().assertDoesNotStart();
|
||||
removeAction2.post().assertStarted();
|
||||
downloadRunner.postAction().assertStarted();
|
||||
removeRunner1.postAction().assertDoesNotStart();
|
||||
removeRunner2.postAction().assertStarted();
|
||||
|
||||
downloadAction.unblock().assertCanceled();
|
||||
removeAction2.unblock().assertCompleted();
|
||||
downloadRunner.unblock().assertCanceled();
|
||||
removeRunner2.unblock().assertCompleted();
|
||||
|
||||
removeAction1.assertStarted();
|
||||
removeAction1.unblock().assertCompleted();
|
||||
removeRunner1.assertStarted();
|
||||
removeRunner1.unblock().assertCompleted();
|
||||
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopAndResume() throws Throwable {
|
||||
FakeDownloadAction download1Action = createDownloadAction(uri1);
|
||||
FakeDownloadAction remove2Action = createRemoveAction(uri2);
|
||||
FakeDownloadAction download2Action = createDownloadAction(uri2);
|
||||
FakeDownloadAction remove1Action = createRemoveAction(uri1);
|
||||
FakeDownloadAction download3Action = createDownloadAction(uri3);
|
||||
DownloadRunner download1Runner = createDownloadRunner(uri1);
|
||||
DownloadRunner remove2Runner = createRemoveRunner(uri2);
|
||||
DownloadRunner download2Runner = createDownloadRunner(uri2);
|
||||
DownloadRunner remove1Runner = createRemoveRunner(uri1);
|
||||
DownloadRunner download3Runner = createDownloadRunner(uri3);
|
||||
|
||||
download1Action.post().assertStarted();
|
||||
remove2Action.post().assertStarted();
|
||||
download2Action.post().assertDoesNotStart();
|
||||
download1Runner.postAction().assertStarted();
|
||||
remove2Runner.postAction().assertStarted();
|
||||
download2Runner.postAction().assertDoesNotStart();
|
||||
|
||||
runOnMainThread(() -> downloadManager.stopDownloads());
|
||||
|
||||
download1Action.assertStopped();
|
||||
download1Runner.assertStopped();
|
||||
|
||||
// remove actions aren't stopped.
|
||||
remove2Action.unblock().assertCompleted();
|
||||
// Although remove2Action is finished, download2Action doesn't start.
|
||||
download2Action.assertDoesNotStart();
|
||||
remove2Runner.unblock().assertCompleted();
|
||||
// Although remove2 is finished, download2 doesn't start.
|
||||
download2Runner.assertDoesNotStart();
|
||||
|
||||
// When a new remove action is added, it cancels stopped download actions with the same media.
|
||||
remove1Action.post();
|
||||
download1Action.assertCanceled();
|
||||
remove1Action.assertStarted().unblock().assertCompleted();
|
||||
remove1Runner.postAction();
|
||||
download1Runner.assertCanceled();
|
||||
remove1Runner.assertStarted().unblock().assertCompleted();
|
||||
|
||||
// New download actions can be added but they don't start.
|
||||
download3Action.post().assertDoesNotStart();
|
||||
download3Runner.postAction().assertDoesNotStart();
|
||||
|
||||
runOnMainThread(() -> downloadManager.startDownloads());
|
||||
|
||||
download2Action.assertStarted().unblock().assertCompleted();
|
||||
download3Action.assertStarted().unblock().assertCompleted();
|
||||
download2Runner.assertStarted().unblock().assertCompleted();
|
||||
download3Runner.assertStarted().unblock().assertCompleted();
|
||||
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
}
|
||||
|
|
@ -359,34 +359,34 @@ public class DownloadManagerTest {
|
|||
@Test
|
||||
public void testResumeBeforeTotallyStopped() throws Throwable {
|
||||
setUpDownloadManager(2);
|
||||
FakeDownloadAction download1Action = createDownloadAction(uri1).ignoreInterrupts();
|
||||
FakeDownloadAction download2Action = createDownloadAction(uri2);
|
||||
FakeDownloadAction download3Action = createDownloadAction(uri3);
|
||||
DownloadRunner download1Runner = createDownloadRunner(uri1).ignoreInterrupts();
|
||||
DownloadRunner download2Runner = createDownloadRunner(uri2);
|
||||
DownloadRunner download3Runner = createDownloadRunner(uri3);
|
||||
|
||||
download1Action.post().assertStarted();
|
||||
download2Action.post().assertStarted();
|
||||
// download3Action doesn't start as DM was configured to run two downloads in parallel.
|
||||
download3Action.post().assertDoesNotStart();
|
||||
download1Runner.postAction().assertStarted();
|
||||
download2Runner.postAction().assertStarted();
|
||||
// download3 doesn't start as DM was configured to run two downloads in parallel.
|
||||
download3Runner.postAction().assertDoesNotStart();
|
||||
|
||||
runOnMainThread(() -> downloadManager.stopDownloads());
|
||||
|
||||
// download1Action doesn't stop yet as it ignores interrupts.
|
||||
download2Action.assertStopped();
|
||||
// download1 doesn't stop yet as it ignores interrupts.
|
||||
download2Runner.assertStopped();
|
||||
|
||||
runOnMainThread(() -> downloadManager.startDownloads());
|
||||
|
||||
// download2Action starts immediately.
|
||||
download2Action.assertStarted();
|
||||
// download2 starts immediately.
|
||||
download2Runner.assertStarted();
|
||||
|
||||
// download3Action doesn't start as download1Action still holds its slot.
|
||||
download3Action.assertDoesNotStart();
|
||||
// download3 doesn't start as download1 still holds its slot.
|
||||
download3Runner.assertDoesNotStart();
|
||||
|
||||
// when unblocked download1Action stops and starts immediately.
|
||||
download1Action.unblock().assertStopped().assertStarted();
|
||||
// when unblocked download1 stops and starts immediately.
|
||||
download1Runner.unblock().assertStopped().assertStarted();
|
||||
|
||||
download1Action.unblock();
|
||||
download2Action.unblock();
|
||||
download3Action.unblock();
|
||||
download1Runner.unblock();
|
||||
download2Runner.unblock();
|
||||
download3Runner.unblock();
|
||||
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
}
|
||||
|
|
@ -400,12 +400,7 @@ public class DownloadManagerTest {
|
|||
() -> {
|
||||
downloadManager =
|
||||
new DownloadManager(
|
||||
new DownloaderConstructorHelper(
|
||||
Mockito.mock(Cache.class), DummyDataSource.FACTORY),
|
||||
maxActiveDownloadTasks,
|
||||
MIN_RETRY_COUNT,
|
||||
actionFile,
|
||||
ProgressiveDownloadAction.DESERIALIZER);
|
||||
actionFile, downloaderFactory, maxActiveDownloadTasks, MIN_RETRY_COUNT);
|
||||
downloadManagerListener =
|
||||
new TestDownloadManagerListener(downloadManager, dummyMainThread);
|
||||
downloadManager.addListener(downloadManagerListener);
|
||||
|
|
@ -424,104 +419,104 @@ public class DownloadManagerTest {
|
|||
}
|
||||
}
|
||||
|
||||
private void doTestActionRuns(FakeDownloadAction action) throws Throwable {
|
||||
action.post().assertStarted().unblock().assertCompleted();
|
||||
private void doTestDownloaderRuns(DownloadRunner runner) throws Throwable {
|
||||
runner.postAction().assertStarted().unblock().assertCompleted();
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
}
|
||||
|
||||
private void doTestActionsRunSequentially(FakeDownloadAction action1, FakeDownloadAction action2)
|
||||
private void doTestDownloadersRunSequentially(DownloadRunner runner1, DownloadRunner runner2)
|
||||
throws Throwable {
|
||||
action1.ignoreInterrupts().post().assertStarted();
|
||||
action2.post().assertDoesNotStart();
|
||||
runner1.ignoreInterrupts().postAction().assertStarted();
|
||||
runner2.postAction().assertDoesNotStart();
|
||||
|
||||
action1.unblock();
|
||||
action2.assertStarted();
|
||||
runner1.unblock();
|
||||
runner2.assertStarted();
|
||||
|
||||
action2.unblock().assertCompleted();
|
||||
runner2.unblock().assertCompleted();
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
}
|
||||
|
||||
private void doTestActionsRunInParallel(FakeDownloadAction action1, FakeDownloadAction action2)
|
||||
private void doTestDownloadersRunInParallel(DownloadRunner runner1, DownloadRunner runner2)
|
||||
throws Throwable {
|
||||
action1.post().assertStarted();
|
||||
action2.post().assertStarted();
|
||||
action1.unblock().assertCompleted();
|
||||
action2.unblock().assertCompleted();
|
||||
runner1.postAction().assertStarted();
|
||||
runner2.postAction().assertStarted();
|
||||
runner1.unblock().assertCompleted();
|
||||
runner2.unblock().assertCompleted();
|
||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||
}
|
||||
|
||||
private FakeDownloadAction createDownloadAction(Uri uri) {
|
||||
return new FakeDownloadAction(uri, /* isRemoveAction= */ false);
|
||||
private DownloadRunner createDownloadRunner(Uri uri) {
|
||||
return new DownloadRunner(uri, /* isRemoveAction= */ false);
|
||||
}
|
||||
|
||||
private FakeDownloadAction createRemoveAction(Uri uri) {
|
||||
return new FakeDownloadAction(uri, /* isRemoveAction= */ true);
|
||||
private DownloadRunner createRemoveRunner(Uri uri) {
|
||||
return new DownloadRunner(uri, /* isRemoveAction= */ true);
|
||||
}
|
||||
|
||||
private void runOnMainThread(final Runnable r) {
|
||||
dummyMainThread.runOnMainThread(r);
|
||||
}
|
||||
|
||||
private class FakeDownloadAction extends DownloadAction {
|
||||
private class DownloadRunner {
|
||||
|
||||
private final FakeDownloader downloader;
|
||||
public final DownloadAction action;
|
||||
public final FakeDownloader downloader;
|
||||
|
||||
private FakeDownloadAction(Uri uri, boolean isRemoveAction) {
|
||||
super("Fake", /* version= */ 0, uri, isRemoveAction, /* data= */ null);
|
||||
this.downloader = new FakeDownloader(isRemoveAction);
|
||||
private DownloadRunner(Uri uri, boolean isRemoveAction) {
|
||||
action =
|
||||
isRemoveAction
|
||||
? DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_PROGRESSIVE,
|
||||
uri,
|
||||
/* customCacheKey= */ null,
|
||||
/* data= */ null)
|
||||
: DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_PROGRESSIVE,
|
||||
uri,
|
||||
/* keys= */ Collections.emptyList(),
|
||||
/* customCacheKey= */ null,
|
||||
/* data= */ null);
|
||||
downloader = new FakeDownloader(isRemoveAction);
|
||||
downloaderFactory.putFakeDownloader(action, downloader);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToStream(DataOutputStream output) {
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public Downloader createDownloader(DownloaderConstructorHelper downloaderConstructorHelper) {
|
||||
return downloader;
|
||||
}
|
||||
|
||||
private FakeDownloader getFakeDownloader() {
|
||||
return downloader;
|
||||
}
|
||||
|
||||
private FakeDownloadAction post() {
|
||||
runOnMainThread(() -> downloadManager.handleAction(FakeDownloadAction.this));
|
||||
private DownloadRunner postAction() {
|
||||
runOnMainThread(() -> downloadManager.handleAction(action));
|
||||
return this;
|
||||
}
|
||||
|
||||
private FakeDownloadAction assertDoesNotStart() throws InterruptedException {
|
||||
private DownloadRunner assertDoesNotStart() throws InterruptedException {
|
||||
Thread.sleep(ASSERT_FALSE_TIME);
|
||||
assertThat(downloader.started.getCount()).isEqualTo(1);
|
||||
return this;
|
||||
}
|
||||
|
||||
private FakeDownloadAction assertStarted() throws InterruptedException {
|
||||
private DownloadRunner assertStarted() throws InterruptedException {
|
||||
downloader.assertStarted(ASSERT_TRUE_TIMEOUT);
|
||||
return assertState(TaskState.STATE_STARTED);
|
||||
}
|
||||
|
||||
private FakeDownloadAction assertCompleted() {
|
||||
private DownloadRunner assertCompleted() {
|
||||
return assertState(TaskState.STATE_COMPLETED);
|
||||
}
|
||||
|
||||
private FakeDownloadAction assertFailed() {
|
||||
private DownloadRunner assertFailed() {
|
||||
return assertState(TaskState.STATE_FAILED);
|
||||
}
|
||||
|
||||
private FakeDownloadAction assertCanceled() {
|
||||
private DownloadRunner assertCanceled() {
|
||||
return assertState(TaskState.STATE_CANCELED);
|
||||
}
|
||||
|
||||
private FakeDownloadAction assertStopped() {
|
||||
private DownloadRunner assertStopped() {
|
||||
return assertState(TaskState.STATE_QUEUED);
|
||||
}
|
||||
|
||||
private FakeDownloadAction assertState(@State int expectedState) {
|
||||
private DownloadRunner assertState(@State int expectedState) {
|
||||
while (true) {
|
||||
Integer state = null;
|
||||
try {
|
||||
state = downloadManagerListener.pollStateChange(this, ASSERT_TRUE_TIMEOUT);
|
||||
state = downloadManagerListener.pollStateChange(action, ASSERT_TRUE_TIMEOUT);
|
||||
} catch (InterruptedException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
|
@ -531,36 +526,54 @@ public class DownloadManagerTest {
|
|||
}
|
||||
}
|
||||
|
||||
private FakeDownloadAction unblock() {
|
||||
private DownloadRunner unblock() {
|
||||
downloader.unblock();
|
||||
return this;
|
||||
}
|
||||
|
||||
private FakeDownloadAction ignoreInterrupts() {
|
||||
private DownloadRunner ignoreInterrupts() {
|
||||
downloader.ignoreInterrupts = true;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static class FakeDownloaderFactory implements DownloaderFactory {
|
||||
|
||||
public IdentityHashMap<DownloadAction, FakeDownloader> downloaders;
|
||||
|
||||
public FakeDownloaderFactory() {
|
||||
downloaders = new IdentityHashMap<>();
|
||||
}
|
||||
|
||||
public void putFakeDownloader(DownloadAction action, FakeDownloader downloader) {
|
||||
downloaders.put(action, downloader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Downloader createDownloader(DownloadAction action) {
|
||||
return downloaders.get(action);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FakeDownloader implements Downloader {
|
||||
|
||||
private final com.google.android.exoplayer2.util.ConditionVariable blocker;
|
||||
private final boolean isRemoveAction;
|
||||
private final boolean isRemove;
|
||||
|
||||
private CountDownLatch started;
|
||||
private boolean ignoreInterrupts;
|
||||
private volatile boolean enableDownloadIOException;
|
||||
private volatile int downloadedBytes = C.LENGTH_UNSET;
|
||||
|
||||
private FakeDownloader(boolean isRemoveAction) {
|
||||
this.isRemoveAction = isRemoveAction;
|
||||
private FakeDownloader(boolean isRemove) {
|
||||
this.isRemove = isRemove;
|
||||
this.started = new CountDownLatch(1);
|
||||
this.blocker = new com.google.android.exoplayer2.util.ConditionVariable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void download() throws InterruptedException, IOException {
|
||||
assertThat(isRemoveAction).isFalse();
|
||||
assertThat(isRemove).isFalse();
|
||||
started.countDown();
|
||||
block();
|
||||
if (enableDownloadIOException) {
|
||||
|
|
@ -575,7 +588,7 @@ public class DownloadManagerTest {
|
|||
|
||||
@Override
|
||||
public void remove() throws InterruptedException {
|
||||
assertThat(isRemoveAction).isTrue();
|
||||
assertThat(isRemove).isTrue();
|
||||
started.countDown();
|
||||
block();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,21 +18,19 @@ package com.google.android.exoplayer2.offline;
|
|||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** Unit tests for {@link ProgressiveDownloadAction}. */
|
||||
// TODO: Merge into DownloadActionTest
|
||||
/** Unit tests for progressive {@link DownloadAction}s. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ProgressiveDownloadActionTest {
|
||||
|
||||
|
|
@ -57,15 +55,6 @@ public class ProgressiveDownloadActionTest {
|
|||
assertThat(action2.isRemoveAction).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDownloader() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
DownloadAction action = createDownloadAction(uri1, null);
|
||||
DownloaderConstructorHelper constructorHelper =
|
||||
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
||||
assertThat(action.createDownloader(constructorHelper)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameUriCacheKeyDifferentAction_IsSameMedia() throws Exception {
|
||||
DownloadAction action1 = createRemoveAction(uri1, null);
|
||||
|
|
@ -139,6 +128,13 @@ public class ProgressiveDownloadActionTest {
|
|||
doTestSerializationRoundTrip(createRemoveAction(uri2, "key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializerVersion0() throws Exception {
|
||||
doTestLegacySerializationRoundTrip(createDownloadAction(uri1, "key"));
|
||||
doTestLegacySerializationRoundTrip(createRemoveAction(uri1, "key"));
|
||||
doTestLegacySerializationRoundTrip(createDownloadAction(uri2, "key"));
|
||||
}
|
||||
|
||||
private void assertSameMedia(DownloadAction action1, DownloadAction action2) {
|
||||
assertThat(action1.isSameMedia(action2)).isTrue();
|
||||
assertThat(action2.isSameMedia(action1)).isTrue();
|
||||
|
|
@ -149,25 +145,61 @@ public class ProgressiveDownloadActionTest {
|
|||
assertThat(action2.isSameMedia(action1)).isFalse();
|
||||
}
|
||||
|
||||
private static void assertEqual(DownloadAction action1, DownloadAction action2) {
|
||||
assertThat(action1).isEqualTo(action2);
|
||||
assertThat(action2).isEqualTo(action1);
|
||||
}
|
||||
|
||||
private static void doTestSerializationRoundTrip(DownloadAction action) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream output = new DataOutputStream(out);
|
||||
DownloadAction.serializeToStream(action, output);
|
||||
action.serializeToStream(output);
|
||||
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
DataInputStream input = new DataInputStream(in);
|
||||
DownloadAction action2 =
|
||||
DownloadAction.deserializeFromStream(
|
||||
new DownloadAction.Deserializer[] {ProgressiveDownloadAction.DESERIALIZER}, input);
|
||||
DownloadAction action2 = DownloadAction.deserializeFromStream(input);
|
||||
|
||||
assertThat(action2).isEqualTo(action);
|
||||
}
|
||||
|
||||
private static void doTestLegacySerializationRoundTrip(DownloadAction action) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream output = new DataOutputStream(out);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(output);
|
||||
dataOutputStream.writeUTF(action.type);
|
||||
dataOutputStream.writeInt(/* version= */ 0);
|
||||
dataOutputStream.writeUTF(action.uri.toString());
|
||||
dataOutputStream.writeBoolean(action.isRemoveAction);
|
||||
dataOutputStream.writeInt(action.data.length);
|
||||
dataOutputStream.write(action.data);
|
||||
boolean customCacheKeySet = action.customCacheKey != null;
|
||||
output.writeBoolean(customCacheKeySet);
|
||||
if (customCacheKeySet) {
|
||||
output.writeUTF(action.customCacheKey);
|
||||
}
|
||||
dataOutputStream.flush();
|
||||
|
||||
assertEqual(action, deserializeActionFromStream(out));
|
||||
}
|
||||
|
||||
private static DownloadAction deserializeActionFromStream(ByteArrayOutputStream out)
|
||||
throws IOException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
DataInputStream input = new DataInputStream(in);
|
||||
return DownloadAction.deserializeFromStream(input);
|
||||
}
|
||||
|
||||
private static DownloadAction createDownloadAction(Uri uri1, String customCacheKey) {
|
||||
return ProgressiveDownloadAction.createDownloadAction(uri1, null, customCacheKey);
|
||||
return DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_PROGRESSIVE,
|
||||
uri1,
|
||||
/* keys= */ Collections.emptyList(),
|
||||
customCacheKey,
|
||||
/* data= */ null);
|
||||
}
|
||||
|
||||
private static DownloadAction createRemoveAction(Uri uri1, String customCacheKey) {
|
||||
return ProgressiveDownloadAction.createRemoveAction(uri1, null, customCacheKey);
|
||||
return DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_PROGRESSIVE, uri1, customCacheKey, /* data= */ null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.source.dash.offline;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
import com.google.android.exoplayer2.offline.SegmentDownloadAction;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** An action to download or remove downloaded DASH streams. */
|
||||
public final class DashDownloadAction extends SegmentDownloadAction {
|
||||
|
||||
private static final String TYPE = "dash";
|
||||
private static final int VERSION = 0;
|
||||
|
||||
public static final Deserializer DESERIALIZER =
|
||||
new SegmentDownloadActionDeserializer(TYPE, VERSION) {
|
||||
@Override
|
||||
protected DownloadAction createDownloadAction(
|
||||
Uri uri, boolean isRemoveAction, byte[] data, List<StreamKey> keys) {
|
||||
return new DashDownloadAction(uri, isRemoveAction, data, keys);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a DASH download action.
|
||||
*
|
||||
* @param uri The URI of the media to be downloaded.
|
||||
* @param data Optional custom data for this action. If {@code null} an empty array will be used.
|
||||
* @param keys Keys of tracks to be downloaded. If empty, all tracks will be downloaded.
|
||||
*/
|
||||
public static DashDownloadAction createDownloadAction(
|
||||
Uri uri, @Nullable byte[] data, List<StreamKey> keys) {
|
||||
return new DashDownloadAction(uri, /* isRemoveAction= */ false, data, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DASH remove action.
|
||||
*
|
||||
* @param uri The URI of the media to be removed.
|
||||
* @param data Optional custom data for this action. If {@code null} an empty array will be used.
|
||||
*/
|
||||
public static DashDownloadAction createRemoveAction(Uri uri, @Nullable byte[] data) {
|
||||
return new DashDownloadAction(uri, /* isRemoveAction= */ true, data, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The DASH manifest URI.
|
||||
* @param isRemoveAction Whether the data will be removed. If {@code false} it will be downloaded.
|
||||
* @param data Optional custom data for this action.
|
||||
* @param keys Keys of representations to be downloaded. If empty, all representations are
|
||||
* downloaded. If {@code removeAction} is true, {@code keys} must be empty.
|
||||
* @deprecated Use {@link #createDownloadAction(Uri, byte[], List)} or {@link
|
||||
* #createRemoveAction(Uri, byte[])}.
|
||||
*/
|
||||
@Deprecated
|
||||
public DashDownloadAction(
|
||||
Uri uri, boolean isRemoveAction, @Nullable byte[] data, List<StreamKey> keys) {
|
||||
super(TYPE, VERSION, uri, isRemoveAction, data, keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DashDownloader createDownloader(DownloaderConstructorHelper constructorHelper) {
|
||||
return new DashDownloader(uri, keys, constructorHelper);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import android.net.Uri;
|
|||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloadHelper;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.offline.TrackKey;
|
||||
|
|
@ -86,13 +87,15 @@ public final class DashDownloadHelper extends DownloadHelper {
|
|||
}
|
||||
|
||||
@Override
|
||||
public DashDownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
|
||||
return DashDownloadAction.createDownloadAction(uri, data, toStreamKeys(trackKeys));
|
||||
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
|
||||
return DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_DASH, uri, toStreamKeys(trackKeys), /* customCacheKey= */ null, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DashDownloadAction getRemoveAction(@Nullable byte[] data) {
|
||||
return DashDownloadAction.createRemoveAction(uri, data);
|
||||
public DownloadAction getRemoveAction(@Nullable byte[] data) {
|
||||
return DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_DASH, uri, /* customCacheKey= */ null, data);
|
||||
}
|
||||
|
||||
private static List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
|
|
@ -33,11 +30,10 @@ import java.util.Collections;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** Unit tests for {@link DashDownloadAction}. */
|
||||
// TODO: Merge into DownloadActionTest
|
||||
/** Unit tests for DASH {@link DownloadAction}s. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class DashDownloadActionTest {
|
||||
|
||||
|
|
@ -62,15 +58,6 @@ public class DashDownloadActionTest {
|
|||
assertThat(action2.isRemoveAction).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDownloader() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
DownloadAction action = createDownloadAction(uri1);
|
||||
DownloaderConstructorHelper constructorHelper =
|
||||
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
||||
assertThat(action.createDownloader(constructorHelper)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameUriDifferentAction_IsSameMedia() {
|
||||
DownloadAction action1 = createRemoveAction(uri1);
|
||||
|
|
@ -141,6 +128,14 @@ public class DashDownloadActionTest {
|
|||
createDownloadAction(uri2, new StreamKey(0, 0, 0), new StreamKey(1, 1, 1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializerVersion0() throws Exception {
|
||||
doTestLegacySerializationRoundTrip(createDownloadAction(uri1));
|
||||
doTestLegacySerializationRoundTrip(createRemoveAction(uri1));
|
||||
doTestLegacySerializationRoundTrip(
|
||||
createDownloadAction(uri2, new StreamKey(0, 0, 0), new StreamKey(1, 1, 1)));
|
||||
}
|
||||
|
||||
private static void assertNotEqual(DownloadAction action1, DownloadAction action2) {
|
||||
assertThat(action1).isNotEqualTo(action2);
|
||||
assertThat(action2).isNotEqualTo(action1);
|
||||
|
|
@ -154,24 +149,53 @@ public class DashDownloadActionTest {
|
|||
private static void doTestSerializationRoundTrip(DownloadAction action) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream output = new DataOutputStream(out);
|
||||
DownloadAction.serializeToStream(action, output);
|
||||
action.serializeToStream(output);
|
||||
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
DataInputStream input = new DataInputStream(in);
|
||||
DownloadAction action2 =
|
||||
DownloadAction.deserializeFromStream(
|
||||
new DownloadAction.Deserializer[] {DashDownloadAction.DESERIALIZER}, input);
|
||||
DownloadAction action2 = DownloadAction.deserializeFromStream(input);
|
||||
|
||||
assertThat(action).isEqualTo(action2);
|
||||
}
|
||||
|
||||
private static void doTestLegacySerializationRoundTrip(DownloadAction action) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream output = new DataOutputStream(out);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(output);
|
||||
dataOutputStream.writeUTF(action.type);
|
||||
dataOutputStream.writeInt(/* version= */ 0);
|
||||
dataOutputStream.writeUTF(action.uri.toString());
|
||||
dataOutputStream.writeBoolean(action.isRemoveAction);
|
||||
dataOutputStream.writeInt(action.data.length);
|
||||
dataOutputStream.write(action.data);
|
||||
dataOutputStream.writeInt(action.keys.size());
|
||||
for (int i = 0; i < action.keys.size(); i++) {
|
||||
StreamKey key = action.keys.get(i);
|
||||
dataOutputStream.writeInt(key.periodIndex);
|
||||
dataOutputStream.writeInt(key.groupIndex);
|
||||
dataOutputStream.writeInt(key.trackIndex);
|
||||
}
|
||||
dataOutputStream.flush();
|
||||
|
||||
assertEqual(action, deserializeActionFromStream(out));
|
||||
}
|
||||
|
||||
private static DownloadAction deserializeActionFromStream(ByteArrayOutputStream out)
|
||||
throws IOException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
DataInputStream input = new DataInputStream(in);
|
||||
return DownloadAction.deserializeFromStream(input);
|
||||
}
|
||||
|
||||
private static DownloadAction createDownloadAction(Uri uri, StreamKey... keys) {
|
||||
ArrayList<StreamKey> keysList = new ArrayList<>();
|
||||
Collections.addAll(keysList, keys);
|
||||
return DashDownloadAction.createDownloadAction(uri, null, keysList);
|
||||
return DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_DASH, uri, keysList, /* customCacheKey= */ null, /* data= */ null);
|
||||
}
|
||||
|
||||
private static DownloadAction createRemoveAction(Uri uri) {
|
||||
return DashDownloadAction.createRemoveAction(uri, null);
|
||||
return DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_DASH, uri, /* customCacheKey= */ null, /* data= */ null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,14 +25,21 @@ import static org.junit.Assert.fail;
|
|||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloadException;
|
||||
import com.google.android.exoplayer2.offline.Downloader;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
import com.google.android.exoplayer2.offline.DownloaderFactory;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.testutil.FakeDataSet;
|
||||
import com.google.android.exoplayer2.testutil.FakeDataSource;
|
||||
import com.google.android.exoplayer2.testutil.FakeDataSource.Factory;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
|
||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
|
@ -44,6 +51,7 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
|
@ -67,6 +75,23 @@ public class DashDownloaderTest {
|
|||
Util.recursiveDelete(tempFolder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithDefaultDownloaderFactory() throws Exception {
|
||||
DownloaderConstructorHelper constructorHelper =
|
||||
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
||||
DownloaderFactory factory = new DefaultDownloaderFactory(constructorHelper);
|
||||
|
||||
Downloader downloader =
|
||||
factory.createDownloader(
|
||||
DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_DASH,
|
||||
Uri.parse("https://www.test.com/download"),
|
||||
Collections.singletonList(new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0)),
|
||||
/* customCacheKey= */ null,
|
||||
/* data= */ null));
|
||||
assertThat(downloader).isInstanceOf(DashDownloader.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownloadRepresentation() throws Exception {
|
||||
FakeDataSet fakeDataSet =
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import android.content.Context;
|
|||
import android.net.Uri;
|
||||
import android.os.ConditionVariable;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloadManager;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
|
|
@ -238,11 +239,11 @@ public class DownloadManagerDashTest {
|
|||
Factory fakeDataSourceFactory = new FakeDataSource.Factory().setFakeDataSet(fakeDataSet);
|
||||
downloadManager =
|
||||
new DownloadManager(
|
||||
new DownloaderConstructorHelper(cache, fakeDataSourceFactory),
|
||||
/* maxSimultaneousDownloads= */ 1,
|
||||
/* minRetryCount= */ 3,
|
||||
actionFile,
|
||||
DashDownloadAction.DESERIALIZER);
|
||||
new DefaultDownloaderFactory(
|
||||
new DownloaderConstructorHelper(cache, fakeDataSourceFactory)),
|
||||
/* maxSimultaneousDownloads= */ 1,
|
||||
/* minRetryCount= */ 3);
|
||||
|
||||
downloadManagerListener =
|
||||
new TestDownloadManagerListener(downloadManager, dummyMainThread);
|
||||
|
|
@ -257,9 +258,13 @@ public class DownloadManagerDashTest {
|
|||
Collections.addAll(keysList, keys);
|
||||
DownloadAction result;
|
||||
if (isRemoveAction) {
|
||||
result = DashDownloadAction.createRemoveAction(uri, data);
|
||||
result =
|
||||
DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_DASH, uri, /* customCacheKey= */ null, data);
|
||||
} else {
|
||||
result = DashDownloadAction.createDownloadAction(uri, data, keysList);
|
||||
result =
|
||||
DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_DASH, uri, keysList, /* customCacheKey= */ null, data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloadManager;
|
||||
import com.google.android.exoplayer2.offline.DownloadService;
|
||||
|
|
@ -116,11 +117,11 @@ public class DownloadServiceDashTest {
|
|||
actionFile.delete();
|
||||
final DownloadManager dashDownloadManager =
|
||||
new DownloadManager(
|
||||
new DownloaderConstructorHelper(cache, fakeDataSourceFactory),
|
||||
1,
|
||||
3,
|
||||
actionFile,
|
||||
DashDownloadAction.DESERIALIZER);
|
||||
new DefaultDownloaderFactory(
|
||||
new DownloaderConstructorHelper(cache, fakeDataSourceFactory)),
|
||||
/* maxSimultaneousDownloads= */ 1,
|
||||
/* minRetryCount= */ 3);
|
||||
downloadManagerListener =
|
||||
new TestDownloadManagerListener(dashDownloadManager, dummyMainThread);
|
||||
dashDownloadManager.addListener(downloadManagerListener);
|
||||
|
|
@ -211,9 +212,13 @@ public class DownloadServiceDashTest {
|
|||
Collections.addAll(keysList, keys);
|
||||
DownloadAction result;
|
||||
if (isRemoveAction) {
|
||||
result = DashDownloadAction.createRemoveAction(uri, data);
|
||||
result =
|
||||
DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_DASH, uri, /* customCacheKey= */ null, data);
|
||||
} else {
|
||||
result = DashDownloadAction.createDownloadAction(uri, data, keysList);
|
||||
result =
|
||||
DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_DASH, uri, keysList, /* customCacheKey= */ null, data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.source.hls.offline;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
import com.google.android.exoplayer2.offline.SegmentDownloadAction;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** An action to download or remove downloaded HLS streams. */
|
||||
public final class HlsDownloadAction extends SegmentDownloadAction {
|
||||
|
||||
private static final String TYPE = "hls";
|
||||
private static final int VERSION = 1;
|
||||
|
||||
public static final Deserializer DESERIALIZER =
|
||||
new SegmentDownloadActionDeserializer(TYPE, VERSION) {
|
||||
|
||||
@Override
|
||||
protected StreamKey readKey(int version, DataInputStream input) throws IOException {
|
||||
if (version > 0) {
|
||||
return super.readKey(version, input);
|
||||
}
|
||||
int renditionGroup = input.readInt();
|
||||
int trackIndex = input.readInt();
|
||||
return new StreamKey(renditionGroup, trackIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DownloadAction createDownloadAction(
|
||||
Uri uri, boolean isRemoveAction, byte[] data, List<StreamKey> keys) {
|
||||
return new HlsDownloadAction(uri, isRemoveAction, data, keys);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a HLS download action.
|
||||
*
|
||||
* @param uri The URI of the media to be downloaded.
|
||||
* @param data Optional custom data for this action. If {@code null} an empty array will be used.
|
||||
* @param keys Keys of tracks to be downloaded. If empty, all tracks will be downloaded.
|
||||
*/
|
||||
public static HlsDownloadAction createDownloadAction(
|
||||
Uri uri, @Nullable byte[] data, List<StreamKey> keys) {
|
||||
return new HlsDownloadAction(uri, /* isRemoveAction= */ false, data, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a HLS remove action.
|
||||
*
|
||||
* @param uri The URI of the media to be removed.
|
||||
* @param data Optional custom data for this action. If {@code null} an empty array will be used.
|
||||
*/
|
||||
public static HlsDownloadAction createRemoveAction(Uri uri, @Nullable byte[] data) {
|
||||
return new HlsDownloadAction(uri, /* isRemoveAction= */ true, data, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The HLS playlist URI.
|
||||
* @param isRemoveAction Whether the data will be removed. If {@code false} it will be downloaded.
|
||||
* @param data Optional custom data for this action.
|
||||
* @param keys Keys of renditions to be downloaded. If empty, all renditions are downloaded. If
|
||||
* {@code removeAction} is true, {@code keys} must empty.
|
||||
* @deprecated Use {@link #createDownloadAction(Uri, byte[], List)} or {@link
|
||||
* #createRemoveAction(Uri, byte[])}.
|
||||
*/
|
||||
@Deprecated
|
||||
public HlsDownloadAction(
|
||||
Uri uri, boolean isRemoveAction, @Nullable byte[] data, List<StreamKey> keys) {
|
||||
super(TYPE, VERSION, uri, isRemoveAction, data, keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HlsDownloader createDownloader(DownloaderConstructorHelper constructorHelper) {
|
||||
return new HlsDownloader(uri, keys, constructorHelper);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import android.net.Uri;
|
|||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloadHelper;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.offline.TrackKey;
|
||||
|
|
@ -97,15 +98,20 @@ public final class HlsDownloadHelper extends DownloadHelper {
|
|||
}
|
||||
|
||||
@Override
|
||||
public HlsDownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
|
||||
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
|
||||
Assertions.checkNotNull(renditionGroups);
|
||||
return HlsDownloadAction.createDownloadAction(
|
||||
uri, data, toStreamKeys(trackKeys, renditionGroups));
|
||||
return DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_HLS,
|
||||
uri,
|
||||
toStreamKeys(trackKeys, renditionGroups),
|
||||
/* customCacheKey= */ null,
|
||||
data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HlsDownloadAction getRemoveAction(@Nullable byte[] data) {
|
||||
return HlsDownloadAction.createRemoveAction(uri, data);
|
||||
public DownloadAction getRemoveAction(@Nullable byte[] data) {
|
||||
return DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_HLS, uri, /* customCacheKey= */ null, data);
|
||||
}
|
||||
|
||||
private static Format[] toFormats(List<HlsMasterPlaylist.HlsUrl> hlsUrls) {
|
||||
|
|
|
|||
|
|
@ -19,10 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
|
|||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
|
|
@ -33,11 +31,10 @@ import java.util.Collections;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** Unit tests for {@link HlsDownloadAction}. */
|
||||
// TODO: Merge into DownloadActionTest
|
||||
/** Unit tests for HLS {@link DownloadAction}s. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class HlsDownloadActionTest {
|
||||
|
||||
|
|
@ -62,15 +59,6 @@ public class HlsDownloadActionTest {
|
|||
assertThat(action2.isRemoveAction).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDownloader() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
DownloadAction action = createDownloadAction(uri1);
|
||||
DownloaderConstructorHelper constructorHelper =
|
||||
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
||||
assertThat(action.createDownloader(constructorHelper)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameUriDifferentAction_IsSameMedia() {
|
||||
DownloadAction action1 = createRemoveAction(uri1);
|
||||
|
|
@ -140,10 +128,18 @@ public class HlsDownloadActionTest {
|
|||
|
||||
@Test
|
||||
public void testSerializerVersion0() throws Exception {
|
||||
doTestSerializationV0RoundTrip(createDownloadAction(uri1));
|
||||
doTestSerializationV0RoundTrip(createRemoveAction(uri1));
|
||||
doTestSerializationV0RoundTrip(
|
||||
createDownloadAction(uri2, new StreamKey(0, 0), new StreamKey(1, 1)));
|
||||
doTestLegacySerializationRoundTrip(createDownloadAction(uri1), /* version= */ 0);
|
||||
doTestLegacySerializationRoundTrip(createRemoveAction(uri1), /* version= */ 0);
|
||||
doTestLegacySerializationRoundTrip(
|
||||
createDownloadAction(uri2, new StreamKey(0, 0), new StreamKey(1, 1)), /* version= */ 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializerVersion1() throws Exception {
|
||||
doTestLegacySerializationRoundTrip(createDownloadAction(uri1), /* version= */ 1);
|
||||
doTestLegacySerializationRoundTrip(createRemoveAction(uri1), /* version= */ 1);
|
||||
doTestLegacySerializationRoundTrip(
|
||||
createDownloadAction(uri2, new StreamKey(0, 0), new StreamKey(1, 1)), /* version= */ 1);
|
||||
}
|
||||
|
||||
private static void assertNotEqual(DownloadAction action1, DownloadAction action2) {
|
||||
|
|
@ -159,17 +155,19 @@ public class HlsDownloadActionTest {
|
|||
private static void doTestSerializationRoundTrip(DownloadAction action) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream output = new DataOutputStream(out);
|
||||
DownloadAction.serializeToStream(action, output);
|
||||
action.serializeToStream(output);
|
||||
|
||||
assertEqual(action, deserializeActionFromStream(out));
|
||||
}
|
||||
|
||||
private static void doTestSerializationV0RoundTrip(HlsDownloadAction action) throws IOException {
|
||||
private static void doTestLegacySerializationRoundTrip(DownloadAction action, int version)
|
||||
throws IOException {
|
||||
Assertions.checkState(version == 0 || version == 1);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream output = new DataOutputStream(out);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(output);
|
||||
dataOutputStream.writeUTF(action.type);
|
||||
dataOutputStream.writeInt(/* version */ 0);
|
||||
dataOutputStream.writeInt(version);
|
||||
dataOutputStream.writeUTF(action.uri.toString());
|
||||
dataOutputStream.writeBoolean(action.isRemoveAction);
|
||||
dataOutputStream.writeInt(action.data.length);
|
||||
|
|
@ -177,6 +175,9 @@ public class HlsDownloadActionTest {
|
|||
dataOutputStream.writeInt(action.keys.size());
|
||||
for (int i = 0; i < action.keys.size(); i++) {
|
||||
StreamKey key = action.keys.get(i);
|
||||
if (version == 1) {
|
||||
dataOutputStream.writeInt(key.periodIndex);
|
||||
}
|
||||
dataOutputStream.writeInt(key.groupIndex);
|
||||
dataOutputStream.writeInt(key.trackIndex);
|
||||
}
|
||||
|
|
@ -189,17 +190,18 @@ public class HlsDownloadActionTest {
|
|||
throws IOException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
DataInputStream input = new DataInputStream(in);
|
||||
return DownloadAction.deserializeFromStream(
|
||||
new DownloadAction.Deserializer[] {HlsDownloadAction.DESERIALIZER}, input);
|
||||
return DownloadAction.deserializeFromStream(input);
|
||||
}
|
||||
|
||||
private static HlsDownloadAction createDownloadAction(Uri uri, StreamKey... keys) {
|
||||
private static DownloadAction createDownloadAction(Uri uri, StreamKey... keys) {
|
||||
ArrayList<StreamKey> keysList = new ArrayList<>();
|
||||
Collections.addAll(keysList, keys);
|
||||
return HlsDownloadAction.createDownloadAction(uri, null, keysList);
|
||||
return DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_HLS, uri, keysList, /* customCacheKey= */ null, /* data= */ null);
|
||||
}
|
||||
|
||||
private static HlsDownloadAction createRemoveAction(Uri uri) {
|
||||
return HlsDownloadAction.createRemoveAction(uri, null);
|
||||
private static DownloadAction createRemoveAction(Uri uri) {
|
||||
return DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_HLS, uri, /* customCacheKey= */ null, /* data= */ null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,21 +35,29 @@ import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedDa
|
|||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.Downloader;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
import com.google.android.exoplayer2.offline.DownloaderFactory;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
|
||||
import com.google.android.exoplayer2.testutil.FakeDataSet;
|
||||
import com.google.android.exoplayer2.testutil.FakeDataSource.Factory;
|
||||
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
|
||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
|
|
@ -84,6 +92,23 @@ public class HlsDownloaderTest {
|
|||
Util.recursiveDelete(tempFolder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithDefaultDownloaderFactory() throws Exception {
|
||||
DownloaderConstructorHelper constructorHelper =
|
||||
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
||||
DownloaderFactory factory = new DefaultDownloaderFactory(constructorHelper);
|
||||
|
||||
Downloader downloader =
|
||||
factory.createDownloader(
|
||||
DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_HLS,
|
||||
Uri.parse("https://www.test.com/download"),
|
||||
Collections.singletonList(new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0)),
|
||||
/* customCacheKey= */ null,
|
||||
/* data= */ null));
|
||||
assertThat(downloader).isInstanceOf(HlsDownloader.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCounterMethods() throws Exception {
|
||||
HlsDownloader downloader =
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.source.smoothstreaming.offline;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
import com.google.android.exoplayer2.offline.SegmentDownloadAction;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** An action to download or remove downloaded SmoothStreaming streams. */
|
||||
public final class SsDownloadAction extends SegmentDownloadAction {
|
||||
|
||||
private static final String TYPE = "ss";
|
||||
private static final int VERSION = 1;
|
||||
|
||||
public static final Deserializer DESERIALIZER =
|
||||
new SegmentDownloadActionDeserializer(TYPE, VERSION) {
|
||||
|
||||
@Override
|
||||
protected StreamKey readKey(int version, DataInputStream input) throws IOException {
|
||||
if (version > 0) {
|
||||
return super.readKey(version, input);
|
||||
}
|
||||
int groupIndex = input.readInt();
|
||||
int trackIndex = input.readInt();
|
||||
return new StreamKey(groupIndex, trackIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DownloadAction createDownloadAction(
|
||||
Uri uri, boolean isRemoveAction, byte[] data, List<StreamKey> keys) {
|
||||
return new SsDownloadAction(uri, isRemoveAction, data, keys);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a SmoothStreaming download action.
|
||||
*
|
||||
* @param uri The URI of the media to be downloaded.
|
||||
* @param data Optional custom data for this action. If {@code null} an empty array will be used.
|
||||
* @param keys Keys of tracks to be downloaded. If empty, all tracks will be downloaded.
|
||||
*/
|
||||
public static SsDownloadAction createDownloadAction(
|
||||
Uri uri, @Nullable byte[] data, List<StreamKey> keys) {
|
||||
return new SsDownloadAction(uri, /* isRemoveAction= */ false, data, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SmoothStreaming remove action.
|
||||
*
|
||||
* @param uri The URI of the media to be removed.
|
||||
* @param data Optional custom data for this action. If {@code null} an empty array will be used.
|
||||
*/
|
||||
public static SsDownloadAction createRemoveAction(Uri uri, @Nullable byte[] data) {
|
||||
return new SsDownloadAction(uri, /* isRemoveAction= */ true, data, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The SmoothStreaming manifest URI.
|
||||
* @param isRemoveAction Whether the data will be removed. If {@code false} it will be downloaded.
|
||||
* @param data Optional custom data for this action.
|
||||
* @param keys Keys of streams to be downloaded. If empty, all streams are downloaded. If {@code
|
||||
* removeAction} is true, {@code keys} must be empty.
|
||||
* @deprecated Use {@link #createDownloadAction(Uri, byte[], List)} or {@link
|
||||
* #createRemoveAction(Uri, byte[])}.
|
||||
*/
|
||||
@Deprecated
|
||||
public SsDownloadAction(
|
||||
Uri uri, boolean isRemoveAction, @Nullable byte[] data, List<StreamKey> keys) {
|
||||
super(TYPE, VERSION, uri, isRemoveAction, data, keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SsDownloader createDownloader(DownloaderConstructorHelper constructorHelper) {
|
||||
return new SsDownloader(uri, keys, constructorHelper);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.smoothstreaming.offline;
|
|||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloadHelper;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.offline.TrackKey;
|
||||
|
|
@ -76,13 +77,15 @@ public final class SsDownloadHelper extends DownloadHelper {
|
|||
}
|
||||
|
||||
@Override
|
||||
public SsDownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
|
||||
return SsDownloadAction.createDownloadAction(uri, data, toStreamKeys(trackKeys));
|
||||
public DownloadAction getDownloadAction(@Nullable byte[] data, List<TrackKey> trackKeys) {
|
||||
return DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_SS, uri, toStreamKeys(trackKeys), /* customCacheKey= */ null, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SsDownloadAction getRemoveAction(@Nullable byte[] data) {
|
||||
return SsDownloadAction.createRemoveAction(uri, data);
|
||||
public DownloadAction getRemoveAction(@Nullable byte[] data) {
|
||||
return DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_SS, uri, /* customCacheKey= */ null, data);
|
||||
}
|
||||
|
||||
private static List<StreamKey> toStreamKeys(List<TrackKey> trackKeys) {
|
||||
|
|
|
|||
|
|
@ -19,10 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
|
|||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
|
|
@ -33,11 +31,10 @@ import java.util.Collections;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** Unit tests for {@link SsDownloadAction}. */
|
||||
// TODO: Merge into DownloadActionTest
|
||||
/** Unit tests for SmoothStreaming {@link DownloadAction}s. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class SsDownloadActionTest {
|
||||
|
||||
|
|
@ -62,15 +59,6 @@ public class SsDownloadActionTest {
|
|||
assertThat(action2.isRemoveAction).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDownloader() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
DownloadAction action = createDownloadAction(uri1);
|
||||
DownloaderConstructorHelper constructorHelper =
|
||||
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
||||
assertThat(action.createDownloader(constructorHelper)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameUriDifferentAction_IsSameMedia() {
|
||||
DownloadAction action1 = createRemoveAction(uri1);
|
||||
|
|
@ -140,10 +128,18 @@ public class SsDownloadActionTest {
|
|||
|
||||
@Test
|
||||
public void testSerializerVersion0() throws Exception {
|
||||
doTestSerializationV0RoundTrip(createDownloadAction(uri1));
|
||||
doTestSerializationV0RoundTrip(createRemoveAction(uri1));
|
||||
doTestSerializationV0RoundTrip(
|
||||
createDownloadAction(uri2, new StreamKey(0, 0), new StreamKey(1, 1)));
|
||||
doTestLegacySerializationRoundTrip(createDownloadAction(uri1), /* version= */ 0);
|
||||
doTestLegacySerializationRoundTrip(createRemoveAction(uri1), /* version= */ 0);
|
||||
doTestLegacySerializationRoundTrip(
|
||||
createDownloadAction(uri2, new StreamKey(0, 0), new StreamKey(1, 1)), /* version= */ 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializerVersion1() throws Exception {
|
||||
doTestLegacySerializationRoundTrip(createDownloadAction(uri1), /* version= */ 1);
|
||||
doTestLegacySerializationRoundTrip(createRemoveAction(uri1), /* version= */ 1);
|
||||
doTestLegacySerializationRoundTrip(
|
||||
createDownloadAction(uri2, new StreamKey(0, 0), new StreamKey(1, 1)), /* version= */ 1);
|
||||
}
|
||||
|
||||
private static void assertNotEqual(DownloadAction action1, DownloadAction action2) {
|
||||
|
|
@ -159,17 +155,19 @@ public class SsDownloadActionTest {
|
|||
private static void doTestSerializationRoundTrip(DownloadAction action) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream output = new DataOutputStream(out);
|
||||
DownloadAction.serializeToStream(action, output);
|
||||
action.serializeToStream(output);
|
||||
|
||||
assertEqual(action, deserializeActionFromStream(out));
|
||||
}
|
||||
|
||||
private static void doTestSerializationV0RoundTrip(SsDownloadAction action) throws IOException {
|
||||
private static void doTestLegacySerializationRoundTrip(DownloadAction action, int version)
|
||||
throws IOException {
|
||||
Assertions.checkState(version == 0 || version == 1);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream output = new DataOutputStream(out);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(output);
|
||||
dataOutputStream.writeUTF(action.type);
|
||||
dataOutputStream.writeInt(/* version */ 0);
|
||||
dataOutputStream.writeInt(version);
|
||||
dataOutputStream.writeUTF(action.uri.toString());
|
||||
dataOutputStream.writeBoolean(action.isRemoveAction);
|
||||
dataOutputStream.writeInt(action.data.length);
|
||||
|
|
@ -177,6 +175,9 @@ public class SsDownloadActionTest {
|
|||
dataOutputStream.writeInt(action.keys.size());
|
||||
for (int i = 0; i < action.keys.size(); i++) {
|
||||
StreamKey key = action.keys.get(i);
|
||||
if (version == 1) {
|
||||
dataOutputStream.writeInt(key.periodIndex);
|
||||
}
|
||||
dataOutputStream.writeInt(key.groupIndex);
|
||||
dataOutputStream.writeInt(key.trackIndex);
|
||||
}
|
||||
|
|
@ -189,17 +190,18 @@ public class SsDownloadActionTest {
|
|||
throws IOException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
DataInputStream input = new DataInputStream(in);
|
||||
return DownloadAction.deserializeFromStream(
|
||||
new DownloadAction.Deserializer[] {SsDownloadAction.DESERIALIZER}, input);
|
||||
return DownloadAction.deserializeFromStream(input);
|
||||
}
|
||||
|
||||
private static SsDownloadAction createDownloadAction(Uri uri, StreamKey... keys) {
|
||||
private static DownloadAction createDownloadAction(Uri uri, StreamKey... keys) {
|
||||
ArrayList<StreamKey> keysList = new ArrayList<>();
|
||||
Collections.addAll(keysList, keys);
|
||||
return SsDownloadAction.createDownloadAction(uri, null, keysList);
|
||||
return DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_SS, uri, keysList, /* customCacheKey= */ null, /* data= */ null);
|
||||
}
|
||||
|
||||
private static SsDownloadAction createRemoveAction(Uri uri) {
|
||||
return SsDownloadAction.createRemoveAction(uri, null);
|
||||
private static DownloadAction createRemoveAction(Uri uri) {
|
||||
return DownloadAction.createRemoveAction(
|
||||
DownloadAction.TYPE_SS, uri, /* customCacheKey= */ null, /* data= */ null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.source.smoothstreaming.offline;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.Downloader;
|
||||
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
|
||||
import com.google.android.exoplayer2.offline.DownloaderFactory;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.Cache;
|
||||
import java.util.Collections;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** Unit tests for {@link SsDownloader}. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public final class SsDownloaderTest {
|
||||
|
||||
@Test
|
||||
public void createWithDefaultDownloaderFactory() throws Exception {
|
||||
DownloaderConstructorHelper constructorHelper =
|
||||
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
||||
DownloaderFactory factory = new DefaultDownloaderFactory(constructorHelper);
|
||||
|
||||
Downloader downloader =
|
||||
factory.createDownloader(
|
||||
DownloadAction.createDownloadAction(
|
||||
DownloadAction.TYPE_SS,
|
||||
Uri.parse("https://www.test.com/download"),
|
||||
Collections.singletonList(new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0)),
|
||||
/* customCacheKey= */ null,
|
||||
/* data= */ null));
|
||||
assertThat(downloader).isInstanceOf(SsDownloader.class);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue