Commit 6ebb6124 by olly Committed by Oliver Woodman

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
parent 527f2cf7
Showing with 600 additions and 885 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.
......
/*
* 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)
}
......@@ -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;
......@@ -103,70 +99,33 @@ public final class DownloadManager {
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 {
......
/*
* 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);
}
/*
* 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();
......
/*
* 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,11 +125,8 @@ 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.
......@@ -137,78 +134,30 @@ public class ActionFileTest {
}
@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 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 DownloadAction buildAction(String type, Uri uri, byte[] data) {
return DownloadAction.createDownloadAction(
type, uri, /* keys= */ Collections.emptyList(), /* customCacheKey= */ null, 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;
}
}
}
/*
* 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);
}
}
......@@ -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 {
......@@ -58,15 +56,6 @@ public class ProgressiveDownloadActionTest {
}
@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);
DownloadAction action2 = createDownloadAction(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);
}
}
/*
* 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 {
......@@ -63,15 +59,6 @@ public class DashDownloadActionTest {
}
@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);
DownloadAction action2 = createDownloadAction(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;
......@@ -68,6 +76,23 @@ public class DashDownloaderTest {
}
@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 =
new 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;
}
......
/*
* 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 {
......@@ -63,15 +60,6 @@ public class HlsDownloadActionTest {
}
@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);
DownloadAction action2 = createDownloadAction(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;
......@@ -85,6 +93,23 @@ public class HlsDownloaderTest {
}
@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 =
getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX));
......
/*
* 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 {
......@@ -63,15 +60,6 @@ public class SsDownloadActionTest {
}
@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);
DownloadAction action2 = createDownloadAction(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);
}
}
/*
* 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);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment