Give actions per-type versioning

A single master version prevents app developers from providing
custom download functionality (because they can't increment the
version). It's also error prone to expect someone modifying the
DASH action to remember to update the ActionFile master version.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=194981886
This commit is contained in:
olly 2018-05-01 12:59:08 -07:00 committed by Oliver Woodman
parent 3f003c517c
commit d38e4dc8e0
11 changed files with 122 additions and 89 deletions

View File

@ -29,6 +29,8 @@ import java.io.InputStream;
*/ */
public final class ActionFile { public final class ActionFile {
/* package */ static final int VERSION = 0;
private final AtomicFile atomicFile; private final AtomicFile atomicFile;
private final File actionFile; private final File actionFile;
@ -56,13 +58,13 @@ public final class ActionFile {
inputStream = atomicFile.openRead(); inputStream = atomicFile.openRead();
DataInputStream dataInputStream = new DataInputStream(inputStream); DataInputStream dataInputStream = new DataInputStream(inputStream);
int version = dataInputStream.readInt(); int version = dataInputStream.readInt();
if (version > DownloadAction.MASTER_VERSION) { if (version > VERSION) {
throw new IOException("Not supported action file version: " + version); throw new IOException("Unsupported action file version: " + version);
} }
int actionCount = dataInputStream.readInt(); int actionCount = dataInputStream.readInt();
DownloadAction[] actions = new DownloadAction[actionCount]; DownloadAction[] actions = new DownloadAction[actionCount];
for (int i = 0; i < actionCount; i++) { for (int i = 0; i < actionCount; i++) {
actions[i] = DownloadAction.deserializeFromStream(deserializers, dataInputStream, version); actions[i] = DownloadAction.deserializeFromStream(deserializers, dataInputStream);
} }
return actions; return actions;
} finally { } finally {
@ -80,7 +82,7 @@ public final class ActionFile {
DataOutputStream output = null; DataOutputStream output = null;
try { try {
output = new DataOutputStream(atomicFile.startWrite()); output = new DataOutputStream(atomicFile.startWrite());
output.writeInt(DownloadAction.MASTER_VERSION); output.writeInt(VERSION);
output.writeInt(downloadActions.length); output.writeInt(downloadActions.length);
for (DownloadAction action : downloadActions) { for (DownloadAction action : downloadActions) {
DownloadAction.serializeToStream(action, output); DownloadAction.serializeToStream(action, output);

View File

@ -26,29 +26,23 @@ import java.io.OutputStream;
/** Contains the necessary parameters for a download or remove action. */ /** Contains the necessary parameters for a download or remove action. */
public abstract class DownloadAction { public abstract class DownloadAction {
/**
* Master version for all {@link DownloadAction} serialization/deserialization implementations. On
* each change on any {@link DownloadAction} serialization format this version needs to be
* increased.
*/
public static final int MASTER_VERSION = 0;
/** Used to deserialize {@link DownloadAction}s. */ /** Used to deserialize {@link DownloadAction}s. */
public abstract static class Deserializer { public abstract static class Deserializer {
public String type; public final String type;
public final int version;
public Deserializer(String type) { public Deserializer(String type, int version) {
this.type = type; this.type = type;
this.version = version;
} }
/** /**
* Deserializes an action from the {@code input}. * Deserializes an action from the {@code input}.
* *
* @param version Version of the data. * @param version The version of the serialized action.
* @param input DataInputStream to read data from. * @param input The stream from which to read the action.
* @see DownloadAction#writeToStream(DataOutputStream) * @see DownloadAction#writeToStream(DataOutputStream)
* @see DownloadAction#MASTER_VERSION
*/ */
public abstract DownloadAction readFromStream(int version, DataInputStream input) public abstract DownloadAction readFromStream(int version, DataInputStream input)
throws IOException; throws IOException;
@ -62,42 +56,23 @@ public abstract class DownloadAction {
* <p>The caller is responsible for closing the given {@link InputStream}. * <p>The caller is responsible for closing the given {@link InputStream}.
* *
* @param deserializers {@link Deserializer}s for supported actions. * @param deserializers {@link Deserializer}s for supported actions.
* @param input Input stream to read serialized data. * @param input The stream from which to read the action.
* @return The deserialized action. * @return The deserialized action.
* @throws IOException If there is an IO error reading from {@code input}, or if the action type * @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}. * isn't supported by any of the {@code deserializers}.
*/ */
public static DownloadAction deserializeFromStream( public static DownloadAction deserializeFromStream(
Deserializer[] deserializers, InputStream input) throws IOException { Deserializer[] deserializers, InputStream input) throws IOException {
return deserializeFromStream(deserializers, input, MASTER_VERSION);
}
/**
* Deserializes one {@code action} which was serialized by {@link
* #serializeToStream(DownloadAction, OutputStream)} from the {@code input} using one of the
* {@link Deserializer}s which supports the type of the action.
*
* <p>The caller is responsible for closing the given {@link InputStream}.
*
* @param deserializers {@link Deserializer}s for supported actions.
* @param input Input stream to read serialized data.
* @param version Master version of the serialization. See {@link DownloadAction#MASTER_VERSION}.
* @return The deserialized action.
* @throws IOException If there is an IO error from {@code input}.
* @throws DownloadException If the action type isn't supported by any of the {@code
* deserializers}.
*/
public static DownloadAction deserializeFromStream(
Deserializer[] deserializers, InputStream input, int version) throws IOException {
// Don't close the stream as it closes the underlying stream too. // Don't close the stream as it closes the underlying stream too.
DataInputStream dataInputStream = new DataInputStream(input); DataInputStream dataInputStream = new DataInputStream(input);
String type = dataInputStream.readUTF(); String type = dataInputStream.readUTF();
int version = dataInputStream.readInt();
for (Deserializer deserializer : deserializers) { for (Deserializer deserializer : deserializers) {
if (type.equals(deserializer.type)) { if (type.equals(deserializer.type) && deserializer.version >= version) {
return deserializer.readFromStream(version, dataInputStream); return deserializer.readFromStream(version, dataInputStream);
} }
} }
throw new DownloadException("No Deserializer can be found to parse the data."); throw new DownloadException("No deserializer found for:" + type + ", " + version);
} }
/** Serializes {@code action} type and data into the {@code output}. */ /** Serializes {@code action} type and data into the {@code output}. */
@ -106,12 +81,15 @@ public abstract class DownloadAction {
// Don't close the stream as it closes the underlying stream too. // Don't close the stream as it closes the underlying stream too.
DataOutputStream dataOutputStream = new DataOutputStream(output); DataOutputStream dataOutputStream = new DataOutputStream(output);
dataOutputStream.writeUTF(action.type); dataOutputStream.writeUTF(action.type);
dataOutputStream.writeInt(action.version);
action.writeToStream(dataOutputStream); action.writeToStream(dataOutputStream);
dataOutputStream.flush(); dataOutputStream.flush();
} }
/** The type of the action. */ /** The type of the action. */
public final String type; public final String type;
/** The action version. */
public final int version;
/** Whether this is a remove action. If false, this is a download action. */ /** Whether this is a remove action. If false, this is a download action. */
public final boolean isRemoveAction; public final boolean isRemoveAction;
/** Custom data for this action. May be the empty string if no custom data was specified. */ /** Custom data for this action. May be the empty string if no custom data was specified. */
@ -119,11 +97,14 @@ public abstract class DownloadAction {
/** /**
* @param type The type of the action. * @param type The type of the action.
* @param version The action version.
* @param isRemoveAction Whether this is a remove action. If false, this is a download action. * @param isRemoveAction Whether this is a remove action. If false, this is a download action.
* @param data Optional custom data for this action. If null, an empty string is used. * @param data Optional custom data for this action. If null, an empty string is used.
*/ */
protected DownloadAction(String type, boolean isRemoveAction, @Nullable String data) { protected DownloadAction(
String type, int version, boolean isRemoveAction, @Nullable String data) {
this.type = type; this.type = type;
this.version = version;
this.isRemoveAction = isRemoveAction; this.isRemoveAction = isRemoveAction;
this.data = data != null ? data : ""; this.data = data != null ? data : "";
} }

View File

@ -28,9 +28,10 @@ import java.io.IOException;
public final class ProgressiveDownloadAction extends DownloadAction { public final class ProgressiveDownloadAction extends DownloadAction {
private static final String TYPE = "ProgressiveDownloadAction"; private static final String TYPE = "ProgressiveDownloadAction";
private static final int VERSION = 0;
public static final Deserializer DESERIALIZER = public static final Deserializer DESERIALIZER =
new Deserializer(TYPE) { new Deserializer(TYPE, VERSION) {
@Override @Override
public ProgressiveDownloadAction readFromStream(int version, DataInputStream input) public ProgressiveDownloadAction readFromStream(int version, DataInputStream input)
throws IOException { throws IOException {
@ -54,7 +55,7 @@ public final class ProgressiveDownloadAction extends DownloadAction {
*/ */
public ProgressiveDownloadAction( public ProgressiveDownloadAction(
boolean isRemoveAction, @Nullable String data, Uri uri, @Nullable String customCacheKey) { boolean isRemoveAction, @Nullable String data, Uri uri, @Nullable String customCacheKey) {
super(TYPE, isRemoveAction, data); super(TYPE, VERSION, isRemoveAction, data);
this.uri = Assertions.checkNotNull(uri); this.uri = Assertions.checkNotNull(uri);
this.customCacheKey = customCacheKey; this.customCacheKey = customCacheKey;
} }

View File

@ -37,8 +37,8 @@ public abstract class SegmentDownloadAction<K extends Comparable> extends Downlo
*/ */
protected abstract static class SegmentDownloadActionDeserializer<K> extends Deserializer { protected abstract static class SegmentDownloadActionDeserializer<K> extends Deserializer {
public SegmentDownloadActionDeserializer(String type) { public SegmentDownloadActionDeserializer(String type, int version) {
super(type); super(type, version);
} }
@Override @Override
@ -71,15 +71,21 @@ public abstract class SegmentDownloadAction<K extends Comparable> extends Downlo
/** /**
* @param type The type of the action. * @param type The type of the action.
* @param data Optional custom data for this action. If null, an empty string is used. * @param version The action version.
* @param isRemoveAction Whether the data will be removed. If {@code false} it will be 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 null, an empty string is used.
* @param manifestUri The {@link Uri} of the manifest to be downloaded. * @param manifestUri The {@link Uri} of the manifest to be downloaded.
* @param keys Keys of representations to be downloaded. If empty, all representations are * @param keys Keys of representations to be downloaded. If empty, all representations are
* downloaded. If {@code removeAction} is true, {@code keys} must be an empty array. * downloaded. If {@code removeAction} is true, {@code keys} must be an empty array.
*/ */
protected SegmentDownloadAction( protected SegmentDownloadAction(
String type, boolean isRemoveAction, @Nullable String data, Uri manifestUri, K[] keys) { String type,
super(type, isRemoveAction, data); int version,
boolean isRemoveAction,
@Nullable String data,
Uri manifestUri,
K[] keys) {
super(type, version, isRemoveAction, data);
this.manifestUri = manifestUri; this.manifestUri = manifestUri;
if (isRemoveAction) { if (isRemoveAction) {
Assertions.checkArgument(keys.length == 0); Assertions.checkArgument(keys.length == 0);

View File

@ -63,7 +63,7 @@ public class ActionFileTest {
@Test @Test
public void testLoadIncompleteHeaderThrowsIOException() throws Exception { public void testLoadIncompleteHeaderThrowsIOException() throws Exception {
try { try {
loadActions(new Object[] {DownloadAction.MASTER_VERSION}); loadActions(new Object[] {ActionFile.VERSION});
Assert.fail(); Assert.fail();
} catch (IOException e) { } catch (IOException e) {
// Expected exception. // Expected exception.
@ -72,8 +72,7 @@ public class ActionFileTest {
@Test @Test
public void testLoadCompleteHeaderZeroAction() throws Exception { public void testLoadCompleteHeaderZeroAction() throws Exception {
DownloadAction[] actions = DownloadAction[] actions = loadActions(new Object[] {ActionFile.VERSION, 0});
loadActions(new Object[] {DownloadAction.MASTER_VERSION, /*action count*/0});
assertThat(actions).isNotNull(); assertThat(actions).isNotNull();
assertThat(actions).hasLength(0); assertThat(actions).hasLength(0);
} }
@ -83,12 +82,16 @@ public class ActionFileTest {
DownloadAction[] actions = DownloadAction[] actions =
loadActions( loadActions(
new Object[] { new Object[] {
DownloadAction.MASTER_VERSION, /*action count*/ 1, /*action 1*/ "type2", "321" ActionFile.VERSION,
1, // Action count
"type2", // Action 1
FakeDownloadAction.VERSION,
"321"
}, },
new FakeDeserializer("type2")); new FakeDeserializer("type2"));
assertThat(actions).isNotNull(); assertThat(actions).isNotNull();
assertThat(actions).hasLength(1); assertThat(actions).hasLength(1);
assertAction(actions[0], "type2", DownloadAction.MASTER_VERSION, "321"); assertAction(actions[0], "type2", FakeDownloadAction.VERSION, "321");
} }
@Test @Test
@ -96,26 +99,53 @@ public class ActionFileTest {
DownloadAction[] actions = DownloadAction[] actions =
loadActions( loadActions(
new Object[] { new Object[] {
DownloadAction.MASTER_VERSION, /*action count*/ ActionFile.VERSION,
2, /*action 1*/ 2, // Action count
"type1", "type1", // Action 1
FakeDownloadAction.VERSION,
"123", "123",
/*action 2*/ "type2", "type2", // Action 2
FakeDownloadAction.VERSION,
"321" "321"
}, // Action 2 },
new FakeDeserializer("type1"), new FakeDeserializer("type1"),
new FakeDeserializer("type2")); new FakeDeserializer("type2"));
assertThat(actions).isNotNull(); assertThat(actions).isNotNull();
assertThat(actions).hasLength(2); assertThat(actions).hasLength(2);
assertAction(actions[0], "type1", DownloadAction.MASTER_VERSION, "123"); assertAction(actions[0], "type1", FakeDownloadAction.VERSION, "123");
assertAction(actions[1], "type2", DownloadAction.MASTER_VERSION, "321"); assertAction(actions[1], "type2", FakeDownloadAction.VERSION, "321");
} }
@Test @Test
public void testLoadNotSupportedVersion() throws Exception { public void testLoadNotSupportedVersion() throws Exception {
try { try {
loadActions(new Object[] {DownloadAction.MASTER_VERSION + 1, /*action count*/1, loadActions(
/*action 1*/"type2", 321}, new FakeDeserializer("type2")); new Object[] {
ActionFile.VERSION + 1,
1, // Action count
"type2", // Action 1
FakeDownloadAction.VERSION,
321
},
new FakeDeserializer("type2"));
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,
321
},
new FakeDeserializer("type2"));
Assert.fail(); Assert.fail();
} catch (IOException e) { } catch (IOException e) {
// Expected exception. // Expected exception.
@ -125,8 +155,15 @@ public class ActionFileTest {
@Test @Test
public void testLoadNotSupportedType() throws Exception { public void testLoadNotSupportedType() throws Exception {
try { try {
loadActions(new Object[] {DownloadAction.MASTER_VERSION, /*action count*/1, loadActions(
/*action 1*/"type2", 321}, new FakeDeserializer("type1")); new Object[] {
ActionFile.VERSION,
1, // Action count
"type2", // Action 1
FakeDownloadAction.VERSION,
321
},
new FakeDeserializer("type1"));
Assert.fail(); Assert.fail();
} catch (DownloadException e) { } catch (DownloadException e) {
// Expected exception. // Expected exception.
@ -142,8 +179,7 @@ public class ActionFileTest {
public void testStoreAndLoadActions() throws Exception { public void testStoreAndLoadActions() throws Exception {
doTestSerializationRoundTrip( doTestSerializationRoundTrip(
new DownloadAction[] { new DownloadAction[] {
new FakeDownloadAction("type1", DownloadAction.MASTER_VERSION, "123"), new FakeDownloadAction("type1", "123"), new FakeDownloadAction("type2", "321"),
new FakeDownloadAction("type2", DownloadAction.MASTER_VERSION, "321"),
}, },
new FakeDeserializer("type1"), new FakeDeserializer("type1"),
new FakeDeserializer("type2")); new FakeDeserializer("type2"));
@ -186,22 +222,21 @@ public class ActionFileTest {
private static class FakeDeserializer extends Deserializer { private static class FakeDeserializer extends Deserializer {
FakeDeserializer(String type) { FakeDeserializer(String type) {
super(type); super(type, FakeDownloadAction.VERSION);
} }
@Override @Override
public DownloadAction readFromStream(int version, DataInputStream input) throws IOException { public DownloadAction readFromStream(int version, DataInputStream input) throws IOException {
return new FakeDownloadAction(type, version, input.readUTF()); return new FakeDownloadAction(type, input.readUTF());
} }
} }
private static class FakeDownloadAction extends DownloadAction { private static class FakeDownloadAction extends DownloadAction {
public final int version; public static final int VERSION = 0;
private FakeDownloadAction(String type, int version, String data) { private FakeDownloadAction(String type, String data) {
super(type, false, data); super(type, VERSION, /* isRemoveAction= */ false, data);
this.version = version;
} }
@Override @Override

View File

@ -535,7 +535,7 @@ public class DownloadManagerTest {
private final BlockingQueue<Integer> states; private final BlockingQueue<Integer> states;
private FakeDownloadAction(boolean isRemoveAction, @Nullable String mediaId) { private FakeDownloadAction(boolean isRemoveAction, @Nullable String mediaId) {
super("FakeDownloadAction", isRemoveAction, mediaId); super("FakeDownloadAction", /* version= */ 0, isRemoveAction, mediaId);
this.mediaId = mediaId; this.mediaId = mediaId;
this.downloader = new FakeDownloader(isRemoveAction); this.downloader = new FakeDownloader(isRemoveAction);
this.states = new ArrayBlockingQueue<>(10); this.states = new ArrayBlockingQueue<>(10);

View File

@ -153,18 +153,19 @@ public class ProgressiveDownloadActionTest {
assertThat(action2.isSameMedia(action1)).isFalse(); assertThat(action2.isSameMedia(action1)).isFalse();
} }
private static void doTestSerializationRoundTrip(ProgressiveDownloadAction action1) private static void doTestSerializationRoundTrip(ProgressiveDownloadAction action)
throws IOException { throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(out); DataOutputStream output = new DataOutputStream(out);
action1.writeToStream(output); DownloadAction.serializeToStream(action, output);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
DataInputStream input = new DataInputStream(in); DataInputStream input = new DataInputStream(in);
DownloadAction action2 = DownloadAction action2 =
ProgressiveDownloadAction.DESERIALIZER.readFromStream(DownloadAction.MASTER_VERSION, input); DownloadAction.deserializeFromStream(
new DownloadAction.Deserializer[] {ProgressiveDownloadAction.DESERIALIZER}, input);
assertThat(action2).isEqualTo(action1); assertThat(action2).isEqualTo(action);
} }
} }

View File

@ -29,9 +29,10 @@ import java.io.IOException;
public final class DashDownloadAction extends SegmentDownloadAction<RepresentationKey> { public final class DashDownloadAction extends SegmentDownloadAction<RepresentationKey> {
private static final String TYPE = "DashDownloadAction"; private static final String TYPE = "DashDownloadAction";
private static final int VERSION = 0;
public static final Deserializer DESERIALIZER = public static final Deserializer DESERIALIZER =
new SegmentDownloadActionDeserializer<RepresentationKey>(TYPE) { new SegmentDownloadActionDeserializer<RepresentationKey>(TYPE, VERSION) {
@Override @Override
protected RepresentationKey readKey(DataInputStream input) throws IOException { protected RepresentationKey readKey(DataInputStream input) throws IOException {
@ -51,11 +52,12 @@ public final class DashDownloadAction extends SegmentDownloadAction<Representati
}; };
/** /**
* @see SegmentDownloadAction#SegmentDownloadAction(String, boolean, String, Uri, Comparable[]) * @see SegmentDownloadAction#SegmentDownloadAction(String, int, boolean, String, Uri,
* Comparable[])
*/ */
public DashDownloadAction( public DashDownloadAction(
boolean isRemoveAction, @Nullable String data, Uri manifestUri, RepresentationKey... keys) { boolean isRemoveAction, @Nullable String data, Uri manifestUri, RepresentationKey... keys) {
super(TYPE, isRemoveAction, data, manifestUri, keys); super(TYPE, VERSION, isRemoveAction, data, manifestUri, keys);
} }
@Override @Override

View File

@ -200,17 +200,18 @@ public class DashDownloadActionTest {
assertThat(action2).isEqualTo(action1); assertThat(action2).isEqualTo(action1);
} }
private static void doTestSerializationRoundTrip(DashDownloadAction action1) throws IOException { private static void doTestSerializationRoundTrip(DashDownloadAction action) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(out); DataOutputStream output = new DataOutputStream(out);
action1.writeToStream(output); DownloadAction.serializeToStream(action, output);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
DataInputStream input = new DataInputStream(in); DataInputStream input = new DataInputStream(in);
DownloadAction action2 = DownloadAction action2 =
DashDownloadAction.DESERIALIZER.readFromStream(DownloadAction.MASTER_VERSION, input); DownloadAction.deserializeFromStream(
new DownloadAction.Deserializer[] {DashDownloadAction.DESERIALIZER}, input);
assertThat(action1).isEqualTo(action2); assertThat(action).isEqualTo(action2);
} }
} }

View File

@ -29,9 +29,10 @@ import java.io.IOException;
public final class HlsDownloadAction extends SegmentDownloadAction<RenditionKey> { public final class HlsDownloadAction extends SegmentDownloadAction<RenditionKey> {
private static final String TYPE = "HlsDownloadAction"; private static final String TYPE = "HlsDownloadAction";
private static final int VERSION = 0;
public static final Deserializer DESERIALIZER = public static final Deserializer DESERIALIZER =
new SegmentDownloadActionDeserializer<RenditionKey>(TYPE) { new SegmentDownloadActionDeserializer<RenditionKey>(TYPE, VERSION) {
@Override @Override
protected RenditionKey readKey(DataInputStream input) throws IOException { protected RenditionKey readKey(DataInputStream input) throws IOException {
@ -51,11 +52,12 @@ public final class HlsDownloadAction extends SegmentDownloadAction<RenditionKey>
}; };
/** /**
* @see SegmentDownloadAction#SegmentDownloadAction(String, boolean, String, Uri, Comparable[]) * @see SegmentDownloadAction#SegmentDownloadAction(String, int, boolean, String, Uri,
* Comparable[])
*/ */
public HlsDownloadAction( public HlsDownloadAction(
boolean isRemoveAction, @Nullable String data, Uri manifestUri, RenditionKey... keys) { boolean isRemoveAction, @Nullable String data, Uri manifestUri, RenditionKey... keys) {
super(TYPE, isRemoveAction, data, manifestUri, keys); super(TYPE, VERSION, isRemoveAction, data, manifestUri, keys);
} }
@Override @Override

View File

@ -29,9 +29,10 @@ import java.io.IOException;
public final class SsDownloadAction extends SegmentDownloadAction<TrackKey> { public final class SsDownloadAction extends SegmentDownloadAction<TrackKey> {
private static final String TYPE = "SsDownloadAction"; private static final String TYPE = "SsDownloadAction";
private static final int VERSION = 0;
public static final Deserializer DESERIALIZER = public static final Deserializer DESERIALIZER =
new SegmentDownloadActionDeserializer<TrackKey>(TYPE) { new SegmentDownloadActionDeserializer<TrackKey>(TYPE, VERSION) {
@Override @Override
protected TrackKey readKey(DataInputStream input) throws IOException { protected TrackKey readKey(DataInputStream input) throws IOException {
@ -51,11 +52,12 @@ public final class SsDownloadAction extends SegmentDownloadAction<TrackKey> {
}; };
/** /**
* @see SegmentDownloadAction#SegmentDownloadAction(String, boolean, String, Uri, Comparable[]) * @see SegmentDownloadAction#SegmentDownloadAction(String, int, boolean, String, Uri,
* Comparable[])
*/ */
public SsDownloadAction( public SsDownloadAction(
boolean isRemoveAction, @Nullable String data, Uri manifestUri, TrackKey... keys) { boolean isRemoveAction, @Nullable String data, Uri manifestUri, TrackKey... keys) {
super(TYPE, isRemoveAction, data, manifestUri, keys); super(TYPE, VERSION, isRemoveAction, data, manifestUri, keys);
} }
@Override @Override