diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index abd71278cd..1a5cec63d5 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -30,6 +30,8 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; import android.graphics.Point; import android.media.AudioFormat; import android.net.ConnectivityManager; @@ -2227,6 +2229,14 @@ public final class Util { items.addAll(Math.min(newFromIndex, items.size()), removedItems); } + /** Returns whether the table exists in the database. */ + public static boolean tableExists(SQLiteDatabase database, String tableName) { + long count = + DatabaseUtils.queryNumEntries( + database, "sqlite_master", "tbl_name = ?", new String[] {tableName}); + return count > 0; + } + @Nullable private static String getSystemProperty(String name) { try { diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java index d3294997da..b6e3ae0f41 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.util; +import static com.google.android.exoplayer2.testutil.TestUtil.getInMemoryDatabaseProvider; import static com.google.android.exoplayer2.util.Util.binarySearchCeil; import static com.google.android.exoplayer2.util.Util.binarySearchFloor; import static com.google.android.exoplayer2.util.Util.escapeFileName; @@ -24,6 +25,7 @@ import static com.google.android.exoplayer2.util.Util.parseXsDuration; import static com.google.android.exoplayer2.util.Util.unescapeFileName; import static com.google.common.truth.Truth.assertThat; +import android.database.sqlite.SQLiteDatabase; import android.text.SpannableString; import android.text.Spanned; import android.text.style.StrikethroughSpan; @@ -997,6 +999,21 @@ public class UtilTest { assertThat(Util.normalizeLanguageCode("hsn")).isEqualTo("zh-hsn"); } + @Test + public void tableExists_withExistingTable() { + SQLiteDatabase database = getInMemoryDatabaseProvider().getWritableDatabase(); + database.execSQL("CREATE TABLE TestTable (ID INTEGER NOT NULL)"); + + assertThat(Util.tableExists(database, "TestTable")).isTrue(); + } + + @Test + public void tableExists_withNonExistingTable() { + SQLiteDatabase database = getInMemoryDatabaseProvider().getReadableDatabase(); + + assertThat(Util.tableExists(database, "table")).isFalse(); + } + private static void assertEscapeUnescapeFileName(String fileName, String escapedFileName) { assertThat(escapeFileName(fileName)).isEqualTo(escapedFileName); assertThat(unescapeFileName(escapedFileName)).isEqualTo(fileName); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/database/VersionTable.java b/library/core/src/main/java/com/google/android/exoplayer2/database/VersionTable.java index f1d269ddbf..e69b9576dd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/database/VersionTable.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/database/VersionTable.java @@ -17,11 +17,10 @@ package com.google.android.exoplayer2.database; import android.content.ContentValues; import android.database.Cursor; -import android.database.DatabaseUtils; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import androidx.annotation.IntDef; -import androidx.annotation.VisibleForTesting; +import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -115,7 +114,7 @@ public final class VersionTable { SQLiteDatabase writableDatabase, @Feature int feature, String instanceUid) throws DatabaseIOException { try { - if (!tableExists(writableDatabase, TABLE_NAME)) { + if (!Util.tableExists(writableDatabase, TABLE_NAME)) { return; } writableDatabase.delete( @@ -140,7 +139,7 @@ public final class VersionTable { public static int getVersion(SQLiteDatabase database, @Feature int feature, String instanceUid) throws DatabaseIOException { try { - if (!tableExists(database, TABLE_NAME)) { + if (!Util.tableExists(database, TABLE_NAME)) { return VERSION_UNSET; } try (Cursor cursor = @@ -163,14 +162,6 @@ public final class VersionTable { } } - @VisibleForTesting - /* package */ static boolean tableExists(SQLiteDatabase readableDatabase, String tableName) { - long count = - DatabaseUtils.queryNumEntries( - readableDatabase, "sqlite_master", "tbl_name = ?", new String[] {tableName}); - return count > 0; - } - private static String[] featureAndInstanceUidArguments(int feature, String instance) { return new String[] {Integer.toString(feature), instance}; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java index c69908c746..0807241b1e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ActionFile.java @@ -19,6 +19,7 @@ import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.offline.DownloadRequest.UnsupportedRequestException; import com.google.android.exoplayer2.util.AtomicFile; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.DataInputStream; import java.io.File; @@ -37,6 +38,10 @@ import java.util.List; /* package */ final class ActionFile { private static final int VERSION = 0; + private static final String DOWNLOAD_TYPE_PROGRESSIVE = "progressive"; + private static final String DOWNLOAD_TYPE_DASH = "dash"; + private static final String DOWNLOAD_TYPE_HLS = "hls"; + private static final String DOWNLOAD_TYPE_SS = "ss"; private final AtomicFile atomicFile; @@ -92,7 +97,7 @@ import java.util.List; } private static DownloadRequest readDownloadRequest(DataInputStream input) throws IOException { - String type = input.readUTF(); + String downloadType = input.readUTF(); int version = input.readInt(); Uri uri = Uri.parse(input.readUTF()); @@ -108,21 +113,21 @@ import java.util.List; } // Serialized version 0 progressive actions did not contain keys. - boolean isLegacyProgressive = version == 0 && DownloadRequest.TYPE_PROGRESSIVE.equals(type); + boolean isLegacyProgressive = version == 0 && DOWNLOAD_TYPE_PROGRESSIVE.equals(downloadType); List keys = new ArrayList<>(); if (!isLegacyProgressive) { int keyCount = input.readInt(); for (int i = 0; i < keyCount; i++) { - keys.add(readKey(type, version, input)); + keys.add(readKey(downloadType, version, input)); } } // Serialized version 0 and 1 DASH/HLS/SS actions did not contain a custom cache key. boolean isLegacySegmented = version < 2 - && (DownloadRequest.TYPE_DASH.equals(type) - || DownloadRequest.TYPE_HLS.equals(type) - || DownloadRequest.TYPE_SS.equals(type)); + && (DOWNLOAD_TYPE_DASH.equals(downloadType) + || DOWNLOAD_TYPE_HLS.equals(downloadType) + || DOWNLOAD_TYPE_SS.equals(downloadType)); @Nullable String customCacheKey = null; if (!isLegacySegmented) { customCacheKey = input.readBoolean() ? input.readUTF() : null; @@ -135,7 +140,10 @@ import java.util.List; // Remove actions are not supported anymore. throw new UnsupportedRequestException(); } - return new DownloadRequest(id, type, uri, keys, customCacheKey, data); + // keySetId and mimeType were not supported. Set keySetId to null and try to infer the mime + // type from the download type. + return new DownloadRequest( + id, uri, inferMimeType(downloadType), keys, /* keySetId= */ null, customCacheKey, data); } private static StreamKey readKey(String type, int version, DataInputStream input) @@ -145,8 +153,7 @@ import java.util.List; int trackIndex; // Serialized version 0 HLS/SS actions did not contain a period index. - if ((DownloadRequest.TYPE_HLS.equals(type) || DownloadRequest.TYPE_SS.equals(type)) - && version == 0) { + if ((DOWNLOAD_TYPE_HLS.equals(type) || DOWNLOAD_TYPE_SS.equals(type)) && version == 0) { periodIndex = 0; groupIndex = input.readInt(); trackIndex = input.readInt(); @@ -158,6 +165,20 @@ import java.util.List; return new StreamKey(periodIndex, groupIndex, trackIndex); } + private static String inferMimeType(String downloadType) { + switch (downloadType) { + case DOWNLOAD_TYPE_DASH: + return MimeTypes.APPLICATION_MPD; + case DOWNLOAD_TYPE_HLS: + return MimeTypes.APPLICATION_M3U8; + case DOWNLOAD_TYPE_SS: + return MimeTypes.APPLICATION_SS; + case DOWNLOAD_TYPE_PROGRESSIVE: + default: + return MimeTypes.VIDEO_UNKNOWN; + } + } + private static String generateDownloadId(Uri uri, @Nullable String customCacheKey) { return customCacheKey != null ? customCacheKey : uri.toString(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloadIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloadIndex.java index 4437fccd16..90bf57cd47 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloadIndex.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloadIndex.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.database.VersionTable; import com.google.android.exoplayer2.offline.Download.FailureReason; import com.google.android.exoplayer2.offline.Download.State; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; import java.util.List; @@ -38,10 +39,10 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { private static final String TABLE_PREFIX = DatabaseProvider.TABLE_PREFIX + "Downloads"; - @VisibleForTesting /* package */ static final int TABLE_VERSION = 2; + @VisibleForTesting /* package */ static final int TABLE_VERSION = 3; private static final String COLUMN_ID = "id"; - private static final String COLUMN_TYPE = "title"; + private static final String COLUMN_MIME_TYPE = "mime_type"; private static final String COLUMN_URI = "uri"; private static final String COLUMN_STREAM_KEYS = "stream_keys"; private static final String COLUMN_CUSTOM_CACHE_KEY = "custom_cache_key"; @@ -54,9 +55,10 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { private static final String COLUMN_FAILURE_REASON = "failure_reason"; private static final String COLUMN_PERCENT_DOWNLOADED = "percent_downloaded"; private static final String COLUMN_BYTES_DOWNLOADED = "bytes_downloaded"; + private static final String COLUMN_KEY_SET_ID = "key_set_id"; private static final int COLUMN_INDEX_ID = 0; - private static final int COLUMN_INDEX_TYPE = 1; + private static final int COLUMN_INDEX_MIME_TYPE = 1; private static final int COLUMN_INDEX_URI = 2; private static final int COLUMN_INDEX_STREAM_KEYS = 3; private static final int COLUMN_INDEX_CUSTOM_CACHE_KEY = 4; @@ -69,6 +71,7 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { private static final int COLUMN_INDEX_FAILURE_REASON = 11; private static final int COLUMN_INDEX_PERCENT_DOWNLOADED = 12; private static final int COLUMN_INDEX_BYTES_DOWNLOADED = 13; + private static final int COLUMN_INDEX_KEY_SET_ID = 14; private static final String WHERE_ID_EQUALS = COLUMN_ID + " = ?"; private static final String WHERE_STATE_IS_DOWNLOADING = @@ -79,7 +82,7 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { private static final String[] COLUMNS = new String[] { COLUMN_ID, - COLUMN_TYPE, + COLUMN_MIME_TYPE, COLUMN_URI, COLUMN_STREAM_KEYS, COLUMN_CUSTOM_CACHE_KEY, @@ -92,14 +95,15 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { COLUMN_FAILURE_REASON, COLUMN_PERCENT_DOWNLOADED, COLUMN_BYTES_DOWNLOADED, + COLUMN_KEY_SET_ID }; private static final String TABLE_SCHEMA = "(" + COLUMN_ID + " TEXT PRIMARY KEY NOT NULL," - + COLUMN_TYPE - + " TEXT NOT NULL," + + COLUMN_MIME_TYPE + + " TEXT," + COLUMN_URI + " TEXT NOT NULL," + COLUMN_STREAM_KEYS @@ -123,7 +127,9 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { + COLUMN_PERCENT_DOWNLOADED + " REAL NOT NULL," + COLUMN_BYTES_DOWNLOADED - + " INTEGER NOT NULL)"; + + " INTEGER NOT NULL," + + COLUMN_KEY_SET_ID + + " BLOB NOT NULL)"; private static final String TRUE = "1"; @@ -189,24 +195,9 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { @Override public void putDownload(Download download) throws DatabaseIOException { ensureInitialized(); - ContentValues values = new ContentValues(); - values.put(COLUMN_ID, download.request.id); - values.put(COLUMN_TYPE, download.request.type); - values.put(COLUMN_URI, download.request.uri.toString()); - values.put(COLUMN_STREAM_KEYS, encodeStreamKeys(download.request.streamKeys)); - values.put(COLUMN_CUSTOM_CACHE_KEY, download.request.customCacheKey); - values.put(COLUMN_DATA, download.request.data); - values.put(COLUMN_STATE, download.state); - values.put(COLUMN_START_TIME_MS, download.startTimeMs); - values.put(COLUMN_UPDATE_TIME_MS, download.updateTimeMs); - values.put(COLUMN_CONTENT_LENGTH, download.contentLength); - values.put(COLUMN_STOP_REASON, download.stopReason); - values.put(COLUMN_FAILURE_REASON, download.failureReason); - values.put(COLUMN_PERCENT_DOWNLOADED, download.getPercentDownloaded()); - values.put(COLUMN_BYTES_DOWNLOADED, download.getBytesDownloaded()); try { SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase(); - writableDatabase.replaceOrThrow(tableName, /* nullColumnHack= */ null, values); + putDownloadInternal(download, writableDatabase); } catch (SQLiteException e) { throw new DatabaseIOException(e); } @@ -294,8 +285,13 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { try { VersionTable.setVersion( writableDatabase, VersionTable.FEATURE_OFFLINE, name, TABLE_VERSION); + List upgradedDownloads = + version == 2 ? loadDownloadsFromVersion2(writableDatabase) : new ArrayList<>(); writableDatabase.execSQL("DROP TABLE IF EXISTS " + tableName); writableDatabase.execSQL("CREATE TABLE " + tableName + " " + TABLE_SCHEMA); + for (Download download : upgradedDownloads) { + putDownloadInternal(download, writableDatabase); + } writableDatabase.setTransactionSuccessful(); } finally { writableDatabase.endTransaction(); @@ -307,6 +303,78 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { } } + private void putDownloadInternal(Download download, SQLiteDatabase database) { + ContentValues values = new ContentValues(); + values.put(COLUMN_ID, download.request.id); + values.put(COLUMN_MIME_TYPE, download.request.mimeType); + values.put(COLUMN_URI, download.request.uri.toString()); + values.put(COLUMN_STREAM_KEYS, encodeStreamKeys(download.request.streamKeys)); + values.put(COLUMN_CUSTOM_CACHE_KEY, download.request.customCacheKey); + values.put(COLUMN_DATA, download.request.data); + values.put(COLUMN_STATE, download.state); + values.put(COLUMN_START_TIME_MS, download.startTimeMs); + values.put(COLUMN_UPDATE_TIME_MS, download.updateTimeMs); + values.put(COLUMN_CONTENT_LENGTH, download.contentLength); + values.put(COLUMN_STOP_REASON, download.stopReason); + values.put(COLUMN_FAILURE_REASON, download.failureReason); + values.put(COLUMN_PERCENT_DOWNLOADED, download.getPercentDownloaded()); + values.put(COLUMN_BYTES_DOWNLOADED, download.getBytesDownloaded()); + values.put(COLUMN_KEY_SET_ID, download.request.keySetId); + database.replaceOrThrow(tableName, /* nullColumnHack= */ null, values); + } + + private List loadDownloadsFromVersion2(SQLiteDatabase database) { + List downloads = new ArrayList<>(); + if (!Util.tableExists(database, tableName)) { + return downloads; + } + + String[] columnsV2 = + new String[] { + "id", + "title", + "uri", + "stream_keys", + "custom_cache_key", + "data", + "state", + "start_time_ms", + "update_time_ms", + "content_length", + "stop_reason", + "failure_reason", + "percent_downloaded", + "bytes_downloaded" + }; + try (Cursor cursor = + database.query( + tableName, + columnsV2, + /* selection= */ null, + /* selectionArgs= */ null, + /* groupBy= */ null, + /* having= */ null, + /* orderBy= */ null); ) { + while (cursor.moveToNext()) { + downloads.add(getDownloadForCurrentRowV2(cursor)); + } + return downloads; + } + } + + /** Infers the MIME type from a v2 table row. */ + private static String inferMimeType(String downloadType) { + if ("dash".equals(downloadType)) { + return MimeTypes.APPLICATION_MPD; + } else if ("hls".equals(downloadType)) { + return MimeTypes.APPLICATION_M3U8; + } else if ("ss".equals(downloadType)) { + return MimeTypes.APPLICATION_SS; + } else { + return MimeTypes.VIDEO_UNKNOWN; + } + } + private Cursor getCursor(String selection, @Nullable String[] selectionArgs) throws DatabaseIOException { try { @@ -326,6 +394,25 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { } } + @VisibleForTesting + /* package*/ static String encodeStreamKeys(List streamKeys) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < streamKeys.size(); i++) { + StreamKey streamKey = streamKeys.get(i); + stringBuilder + .append(streamKey.periodIndex) + .append('.') + .append(streamKey.groupIndex) + .append('.') + .append(streamKey.trackIndex) + .append(','); + } + if (stringBuilder.length() > 0) { + stringBuilder.setLength(stringBuilder.length() - 1); + } + return stringBuilder.toString(); + } + private static String getStateQuery(@Download.State int... states) { if (states.length == 0) { return TRUE; @@ -346,9 +433,10 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { DownloadRequest request = new DownloadRequest( /* id= */ cursor.getString(COLUMN_INDEX_ID), - /* type= */ cursor.getString(COLUMN_INDEX_TYPE), /* uri= */ Uri.parse(cursor.getString(COLUMN_INDEX_URI)), + /* mimeType= */ cursor.getString(COLUMN_INDEX_MIME_TYPE), /* streamKeys= */ decodeStreamKeys(cursor.getString(COLUMN_INDEX_STREAM_KEYS)), + /* keySetId= */ cursor.getBlob(COLUMN_INDEX_KEY_SET_ID), /* customCacheKey= */ cursor.getString(COLUMN_INDEX_CUSTOM_CACHE_KEY), /* data= */ cursor.getBlob(COLUMN_INDEX_DATA)); DownloadProgress downloadProgress = new DownloadProgress(); @@ -373,22 +461,53 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { downloadProgress); } - private static String encodeStreamKeys(List streamKeys) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < streamKeys.size(); i++) { - StreamKey streamKey = streamKeys.get(i); - stringBuilder - .append(streamKey.periodIndex) - .append('.') - .append(streamKey.groupIndex) - .append('.') - .append(streamKey.trackIndex) - .append(','); - } - if (stringBuilder.length() > 0) { - stringBuilder.setLength(stringBuilder.length() - 1); - } - return stringBuilder.toString(); + /** Read a {@link Download} from a table row of version 2. */ + private static Download getDownloadForCurrentRowV2(Cursor cursor) { + /* + * Version 2 schema + * Index Column Type + * 0 id string + * 1 type string + * 2 uri string + * 3 stream_keys string + * 4 custom_cache_key string + * 5 data blob + * 6 state integer + * 7 start_time_ms integer + * 8 update_time_ms integer + * 9 content_length integer + * 10 stop_reason integer + * 11 failure_reason integer + * 12 percent_downloaded real + * 13 bytes_downloaded integer + */ + DownloadRequest request = + new DownloadRequest( + /* id= */ cursor.getString(0), + /* uri= */ Uri.parse(cursor.getString(2)), + /* mimeType= */ inferMimeType(cursor.getString(1)), + /* streamKeys= */ decodeStreamKeys(cursor.getString(3)), + /* keySetId= */ null, + /* customCacheKey= */ cursor.getString(4), + /* data= */ cursor.getBlob(5)); + DownloadProgress downloadProgress = new DownloadProgress(); + downloadProgress.bytesDownloaded = cursor.getLong(13); + downloadProgress.percentDownloaded = cursor.getFloat(12); + @State int state = cursor.getInt(6); + // It's possible the database contains failure reasons for non-failed downloads, which is + // invalid. Clear them here. See https://github.com/google/ExoPlayer/issues/6785. + @FailureReason + int failureReason = + state == Download.STATE_FAILED ? cursor.getInt(11) : Download.FAILURE_REASON_NONE; + return new Download( + request, + state, + /* startTimeMs= */ cursor.getLong(7), + /* updateTimeMs= */ cursor.getLong(8), + /* contentLength= */ cursor.getLong(9), + /* stopReason= */ cursor.getInt(10), + failureReason, + downloadProgress); } private static List decodeStreamKeys(String encodedStreamKeys) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactory.java index 183d214759..786d60b545 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactory.java @@ -16,10 +16,13 @@ package com.google.android.exoplayer2.offline; import android.net.Uri; +import android.util.SparseArray; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import java.lang.reflect.Constructor; import java.util.List; import java.util.concurrent.Executor; @@ -31,46 +34,8 @@ import java.util.concurrent.Executor; */ public class DefaultDownloaderFactory implements DownloaderFactory { - @Nullable private static final Constructor DASH_DOWNLOADER_CONSTRUCTOR; - @Nullable private static final Constructor HLS_DOWNLOADER_CONSTRUCTOR; - @Nullable private static final Constructor SS_DOWNLOADER_CONSTRUCTOR; - - static { - @Nullable Constructor 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; - @Nullable Constructor 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; - @Nullable Constructor 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 static final SparseArray> CONSTRUCTORS = + createDownloaderConstructors(); private final CacheDataSource.Factory cacheDataSourceFactory; private final Executor executor; @@ -105,8 +70,14 @@ public class DefaultDownloaderFactory implements DownloaderFactory { @Override public Downloader createDownloader(DownloadRequest request) { - switch (request.type) { - case DownloadRequest.TYPE_PROGRESSIVE: + @C.ContentType + int contentType = Util.inferContentTypeWithMimeType(request.uri, request.mimeType); + switch (contentType) { + case C.TYPE_DASH: + case C.TYPE_HLS: + case C.TYPE_SS: + return createDownloader(request, contentType); + case C.TYPE_OTHER: return new ProgressiveDownloader( new MediaItem.Builder() .setUri(request.uri) @@ -114,31 +85,57 @@ public class DefaultDownloaderFactory implements DownloaderFactory { .build(), cacheDataSourceFactory, executor); - case DownloadRequest.TYPE_DASH: - return createDownloader(request, DASH_DOWNLOADER_CONSTRUCTOR); - case DownloadRequest.TYPE_HLS: - return createDownloader(request, HLS_DOWNLOADER_CONSTRUCTOR); - case DownloadRequest.TYPE_SS: - return createDownloader(request, SS_DOWNLOADER_CONSTRUCTOR); default: - throw new IllegalArgumentException("Unsupported type: " + request.type); + throw new IllegalArgumentException("Unsupported type: " + contentType); } } - private Downloader createDownloader( - DownloadRequest request, @Nullable Constructor constructor) { + private Downloader createDownloader(DownloadRequest request, @C.ContentType int contentType) { + @Nullable Constructor constructor = CONSTRUCTORS.get(contentType); if (constructor == null) { - throw new IllegalStateException("Module missing for: " + request.type); + throw new IllegalStateException("Module missing for content type " + contentType); } try { return constructor.newInstance( request.uri, request.streamKeys, cacheDataSourceFactory, executor); } catch (Exception e) { - throw new RuntimeException("Failed to instantiate downloader for: " + request.type, e); + throw new IllegalStateException( + "Failed to instantiate downloader for content type " + contentType); } } // LINT.IfChange + private static SparseArray> createDownloaderConstructors() { + SparseArray> array = new SparseArray<>(); + try { + array.put( + C.TYPE_DASH, + getDownloaderConstructor( + Class.forName("com.google.android.exoplayer2.source.dash.offline.DashDownloader"))); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the DASH module. + } + + try { + array.put( + C.TYPE_HLS, + getDownloaderConstructor( + Class.forName("com.google.android.exoplayer2.source.hls.offline.HlsDownloader"))); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the HLS module. + } + try { + array.put( + C.TYPE_SS, + getDownloaderConstructor( + Class.forName( + "com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloader"))); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the SmoothStreaming module. + } + return array; + } + private static Constructor getDownloaderConstructor(Class clazz) { try { return clazz @@ -146,7 +143,7 @@ public class DefaultDownloaderFactory implements DownloaderFactory { .getConstructor(Uri.class, List.class, CacheDataSource.Factory.class, Executor.class); } catch (NoSuchMethodException e) { // The downloader is present, but the expected constructor is missing. - throw new RuntimeException("Downloader constructor missing", e); + throw new IllegalStateException("Downloader constructor missing", e); } } // LINT.ThenChange(../../../../../../../../proguard-rules.txt) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java index d9d57b18f7..cda674a7bf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java @@ -320,9 +320,7 @@ public final class DownloadHelper { * @throws IllegalStateException If the media item is of type DASH, HLS or SmoothStreaming. */ public static DownloadHelper forMediaItem(Context context, MediaItem mediaItem) { - Assertions.checkArgument( - DownloadRequest.TYPE_PROGRESSIVE.equals( - getDownloadType(checkNotNull(mediaItem.playbackProperties)))); + Assertions.checkArgument(isProgressive(checkNotNull(mediaItem.playbackProperties))); return forMediaItem( mediaItem, getDefaultTrackSelectorParameters(context), @@ -412,9 +410,7 @@ public final class DownloadHelper { @Nullable RenderersFactory renderersFactory, @Nullable DataSource.Factory dataSourceFactory, @Nullable DrmSessionManager drmSessionManager) { - boolean isProgressive = - DownloadRequest.TYPE_PROGRESSIVE.equals( - getDownloadType(checkNotNull(mediaItem.playbackProperties))); + boolean isProgressive = isProgressive(checkNotNull(mediaItem.playbackProperties)); Assertions.checkArgument(isProgressive || dataSourceFactory != null); return new DownloadHelper( mediaItem, @@ -455,7 +451,7 @@ public final class DownloadHelper { new MediaItem.Builder() .setUri(downloadRequest.uri) .setCustomCacheKey(downloadRequest.customCacheKey) - .setMimeType(getMimeType(downloadRequest.type)) + .setMimeType(downloadRequest.mimeType) .setStreamKeys(downloadRequest.streamKeys) .build(), dataSourceFactory, @@ -747,13 +743,14 @@ public final class DownloadHelper { * @return The built {@link DownloadRequest}. */ public DownloadRequest getDownloadRequest(String id, @Nullable byte[] data) { - String downloadType = getDownloadType(playbackProperties); if (mediaSource == null) { + // TODO: add support for DRM (keySetId) [Internal ref: b/158980798] return new DownloadRequest( id, - downloadType, playbackProperties.uri, + playbackProperties.mimeType, /* streamKeys= */ Collections.emptyList(), + /* keySetId= */ null, playbackProperties.customCacheKey, data); } @@ -769,11 +766,13 @@ public final class DownloadHelper { } streamKeys.addAll(mediaPreparer.mediaPeriods[periodIndex].getStreamKeys(allSelections)); } + // TODO: add support for DRM (keySetId) [Internal ref: b/158980798] return new DownloadRequest( id, - downloadType, playbackProperties.uri, + playbackProperties.mimeType, streamKeys, + /* keySetId= */ null, playbackProperties.customCacheKey, data); } @@ -909,36 +908,9 @@ public final class DownloadHelper { .createMediaSource(mediaItem); } - private static String getDownloadType(MediaItem.PlaybackProperties playbackProperties) { - int contentType = - Util.inferContentTypeWithMimeType(playbackProperties.uri, playbackProperties.mimeType); - switch (contentType) { - case C.TYPE_DASH: - return DownloadRequest.TYPE_DASH; - case C.TYPE_HLS: - return DownloadRequest.TYPE_HLS; - case C.TYPE_SS: - return DownloadRequest.TYPE_SS; - case C.TYPE_OTHER: - default: - return DownloadRequest.TYPE_PROGRESSIVE; - } - } - - @Nullable - private static String getMimeType(String downloadType) { - switch (downloadType) { - case DownloadRequest.TYPE_DASH: - return MimeTypes.APPLICATION_MPD; - case DownloadRequest.TYPE_HLS: - return MimeTypes.APPLICATION_M3U8; - case DownloadRequest.TYPE_SS: - return MimeTypes.APPLICATION_SS; - case DownloadRequest.TYPE_PROGRESSIVE: - return null; - default: - throw new IllegalArgumentException(); - } + private static boolean isProgressive(MediaItem.PlaybackProperties playbackProperties) { + return Util.inferContentTypeWithMimeType(playbackProperties.uri, playbackProperties.mimeType) + == C.TYPE_OTHER; } private static final class MediaPreparer diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadRequest.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadRequest.java index 988b908140..cecb76983b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadRequest.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadRequest.java @@ -21,6 +21,7 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.IOException; @@ -35,23 +36,23 @@ public final class DownloadRequest implements Parcelable { /** Thrown when the encoded request data belongs to an unsupported request type. */ public static class UnsupportedRequestException extends IOException {} - /** Type for progressive downloads. */ - public static final String TYPE_PROGRESSIVE = "progressive"; - /** Type for DASH downloads. */ - public static final String TYPE_DASH = "dash"; - /** Type for HLS downloads. */ - public static final String TYPE_HLS = "hls"; - /** Type for SmoothStreaming downloads. */ - public static final String TYPE_SS = "ss"; - /** The unique content id. */ public final String id; - /** The type of the request. */ - public final String type; /** The uri being downloaded. */ public final Uri uri; + /** + * The MIME type of this content. Used as a hint to infer the content's type (DASH, HLS, + * SmoothStreaming). If null, a {@link DownloadService} will infer the content type from the + * {@link #uri}. + */ + @Nullable public final String mimeType; /** Stream keys to be downloaded. If empty, all streams will be downloaded. */ public final List streamKeys; + /** + * The key set id of the offline licence if the content is protected with DRM, or empty if no + * license is needed. + */ + public final byte[] keySetId; /** * Custom key for cache indexing, or null. Must be null for DASH, HLS and SmoothStreaming * downloads. @@ -62,43 +63,48 @@ public final class DownloadRequest implements Parcelable { /** * @param id See {@link #id}. - * @param type See {@link #type}. * @param uri See {@link #uri}. + * @param mimeType See {@link #mimeType} * @param streamKeys See {@link #streamKeys}. * @param customCacheKey See {@link #customCacheKey}. * @param data See {@link #data}. */ public DownloadRequest( String id, - String type, Uri uri, + @Nullable String mimeType, List streamKeys, + @Nullable byte[] keySetId, @Nullable String customCacheKey, @Nullable byte[] data) { - if (TYPE_DASH.equals(type) || TYPE_HLS.equals(type) || TYPE_SS.equals(type)) { + @C.ContentType int contentType = Util.inferContentTypeWithMimeType(uri, mimeType); + if (contentType == C.TYPE_DASH || contentType == C.TYPE_HLS || contentType == C.TYPE_SS) { Assertions.checkArgument( - customCacheKey == null, "customCacheKey must be null for type: " + type); + customCacheKey == null, "customCacheKey must be null for type: " + contentType); } this.id = id; - this.type = type; this.uri = uri; + this.mimeType = mimeType; ArrayList mutableKeys = new ArrayList<>(streamKeys); Collections.sort(mutableKeys); this.streamKeys = Collections.unmodifiableList(mutableKeys); + this.keySetId = + keySetId != null ? Arrays.copyOf(keySetId, keySetId.length) : Util.EMPTY_BYTE_ARRAY; this.customCacheKey = customCacheKey; this.data = data != null ? Arrays.copyOf(data, data.length) : Util.EMPTY_BYTE_ARRAY; } /* package */ DownloadRequest(Parcel in) { id = castNonNull(in.readString()); - type = castNonNull(in.readString()); uri = Uri.parse(castNonNull(in.readString())); + mimeType = in.readString(); int streamKeyCount = in.readInt(); ArrayList mutableStreamKeys = new ArrayList<>(streamKeyCount); for (int i = 0; i < streamKeyCount; i++) { mutableStreamKeys.add(in.readParcelable(StreamKey.class.getClassLoader())); } streamKeys = Collections.unmodifiableList(mutableStreamKeys); + keySetId = castNonNull(in.createByteArray()); customCacheKey = in.readString(); data = castNonNull(in.createByteArray()); } @@ -110,24 +116,22 @@ public final class DownloadRequest implements Parcelable { * @return The copy with the specified ID. */ public DownloadRequest copyWithId(String id) { - return new DownloadRequest(id, type, uri, streamKeys, customCacheKey, data); + return new DownloadRequest(id, uri, mimeType, streamKeys, keySetId, customCacheKey, data); } /** * Returns the result of merging {@code newRequest} into this request. The requests must have the - * same {@link #id} and {@link #type}. + * same {@link #id}. * - *

If the requests have different {@link #uri}, {@link #customCacheKey} and {@link #data} - * values, then those from the request being merged are included in the result. + *

The resulting request contains the stream keys from both requests. For all other member + * variables, those in {@code newRequest} are preferred. * * @param newRequest The request being merged. * @return The merged result. - * @throws IllegalArgumentException If the requests do not have the same {@link #id} and {@link - * #type}. + * @throws IllegalArgumentException If the requests do not have the same {@link #id}. */ public DownloadRequest copyWithMergedRequest(DownloadRequest newRequest) { Assertions.checkArgument(id.equals(newRequest.id)); - Assertions.checkArgument(type.equals(newRequest.type)); List mergedKeys; if (streamKeys.isEmpty() || newRequest.streamKeys.isEmpty()) { // If either streamKeys is empty then all streams should be downloaded. @@ -142,12 +146,18 @@ public final class DownloadRequest implements Parcelable { } } return new DownloadRequest( - id, type, newRequest.uri, mergedKeys, newRequest.customCacheKey, newRequest.data); + id, + newRequest.uri, + newRequest.mimeType, + mergedKeys, + newRequest.keySetId, + newRequest.customCacheKey, + newRequest.data); } @Override public String toString() { - return type + ":" + id; + return mimeType + ":" + id; } @Override @@ -157,20 +167,21 @@ public final class DownloadRequest implements Parcelable { } DownloadRequest that = (DownloadRequest) o; return id.equals(that.id) - && type.equals(that.type) && uri.equals(that.uri) + && Util.areEqual(mimeType, that.mimeType) && streamKeys.equals(that.streamKeys) + && Arrays.equals(keySetId, that.keySetId) && Util.areEqual(customCacheKey, that.customCacheKey) && Arrays.equals(data, that.data); } @Override public final int hashCode() { - int result = type.hashCode(); - result = 31 * result + id.hashCode(); - result = 31 * result + type.hashCode(); + int result = 31 * id.hashCode(); result = 31 * result + uri.hashCode(); + result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0); result = 31 * result + streamKeys.hashCode(); + result = 31 * result + Arrays.hashCode(keySetId); result = 31 * result + (customCacheKey != null ? customCacheKey.hashCode() : 0); result = 31 * result + Arrays.hashCode(data); return result; @@ -186,12 +197,13 @@ public final class DownloadRequest implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(id); - dest.writeString(type); dest.writeString(uri.toString()); + dest.writeString(mimeType); dest.writeInt(streamKeys.size()); for (int i = 0; i < streamKeys.size(); i++) { dest.writeParcelable(streamKeys.get(i), /* parcelableFlags= */ 0); } + dest.writeByteArray(keySetId); dest.writeString(customCacheKey); dest.writeByteArray(data); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/database/VersionTableTest.java b/library/core/src/test/java/com/google/android/exoplayer2/database/VersionTableTest.java index 3d07bdcb5a..f74b0ada91 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/database/VersionTableTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/database/VersionTableTest.java @@ -85,16 +85,4 @@ public class VersionTableTest { .isEqualTo(VersionTable.VERSION_UNSET); assertThat(VersionTable.getVersion(database, FEATURE_1, INSTANCE_2)).isEqualTo(2); } - - @Test - public void doesTableExist_nonExistingTable_returnsFalse() { - assertThat(VersionTable.tableExists(database, "NonExistingTable")).isFalse(); - } - - @Test - public void doesTableExist_existingTable_returnsTrue() { - String table = "TestTable"; - databaseProvider.getWritableDatabase().execSQL("CREATE TABLE " + table + " (test INTEGER)"); - assertThat(VersionTable.tableExists(database, table)).isTrue(); - } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java index cec0d07688..ffc34cac30 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileTest.java @@ -21,6 +21,7 @@ import android.net.Uri; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.File; import java.io.FileOutputStream; @@ -127,9 +128,10 @@ public class ActionFileTest { private static DownloadRequest buildExpectedRequest(Uri uri, byte[] data) { return new DownloadRequest( /* id= */ uri.toString(), - DownloadRequest.TYPE_PROGRESSIVE, uri, + /* mimeType= */ MimeTypes.VIDEO_UNKNOWN, /* streamKeys= */ Collections.emptyList(), + /* keySetId= */ null, /* customCacheKey= */ null, data); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileUpgradeUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileUpgradeUtilTest.java index 17c1b57f37..c77dca90ee 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileUpgradeUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/ActionFileUpgradeUtilTest.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.offline; -import static com.google.android.exoplayer2.offline.DownloadRequest.TYPE_PROGRESSIVE; import static com.google.common.truth.Truth.assertThat; import android.net.Uri; @@ -23,6 +22,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.File; import java.io.FileOutputStream; @@ -74,18 +74,20 @@ public class ActionFileUpgradeUtilTest { new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* trackIndex= */ 2); DownloadRequest expectedRequest1 = new DownloadRequest( - "key123", - /* type= */ "test", + /* id= */ "key123", Uri.parse("https://www.test.com/download1"), + /* mimeType= */ MimeTypes.VIDEO_UNKNOWN, asList(expectedStreamKey1), + /* keySetId= */ null, /* customCacheKey= */ "key123", - new byte[] {1, 2, 3, 4}); + /* data= */ new byte[] {1, 2, 3, 4}); DownloadRequest expectedRequest2 = new DownloadRequest( - "key234", - /* type= */ "test", + /* id= */ "key234", Uri.parse("https://www.test.com/download2"), + /* mimeType= */ MimeTypes.VIDEO_UNKNOWN, asList(expectedStreamKey2), + /* keySetId= */ null, /* customCacheKey= */ "key234", new byte[] {5, 4, 3, 2, 1}); @@ -102,17 +104,17 @@ public class ActionFileUpgradeUtilTest { @Test public void mergeRequest_nonExistingDownload_createsNewDownload() throws IOException { - byte[] data = new byte[] {1, 2, 3, 4}; DownloadRequest request = new DownloadRequest( - "id", - TYPE_PROGRESSIVE, + /* id= */ "id", Uri.parse("https://www.test.com/download"), + /* mimeType= */ null, asList( new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* trackIndex= */ 2), new StreamKey(/* periodIndex= */ 3, /* groupIndex= */ 4, /* trackIndex= */ 5)), + /* keySetId= */ new byte[] {1, 2, 3, 4}, /* customCacheKey= */ "key123", - data); + /* data= */ new byte[] {1, 2, 3, 4}); ActionFileUpgradeUtil.mergeRequest( request, downloadIndex, /* addNewDownloadAsCompleted= */ false, NOW_MS); @@ -128,32 +130,36 @@ public class ActionFileUpgradeUtilTest { new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* trackIndex= */ 2); DownloadRequest request1 = new DownloadRequest( - "id", - TYPE_PROGRESSIVE, + /* id= */ "id", Uri.parse("https://www.test.com/download1"), + /* mimeType= */ null, asList(streamKey1), + /* keySetId= */ new byte[] {1, 2, 3, 4}, /* customCacheKey= */ "key123", - new byte[] {1, 2, 3, 4}); + /* data= */ new byte[] {1, 2, 3, 4}); DownloadRequest request2 = new DownloadRequest( - "id", - TYPE_PROGRESSIVE, + /* id= */ "id", Uri.parse("https://www.test.com/download2"), + /* mimeType= */ MimeTypes.APPLICATION_MP4, asList(streamKey2), - /* customCacheKey= */ "key123", - new byte[] {5, 4, 3, 2, 1}); + /* keySetId= */ new byte[] {5, 4, 3, 2, 1}, + /* customCacheKey= */ "key345", + /* data= */ new byte[] {5, 4, 3, 2, 1}); + ActionFileUpgradeUtil.mergeRequest( request1, downloadIndex, /* addNewDownloadAsCompleted= */ false, NOW_MS); ActionFileUpgradeUtil.mergeRequest( request2, downloadIndex, /* addNewDownloadAsCompleted= */ false, NOW_MS); - Download download = downloadIndex.getDownload(request2.id); + assertThat(download).isNotNull(); - assertThat(download.request.type).isEqualTo(request2.type); + assertThat(download.request.mimeType).isEqualTo(MimeTypes.APPLICATION_MP4); assertThat(download.request.customCacheKey).isEqualTo(request2.customCacheKey); assertThat(download.request.data).isEqualTo(request2.data); assertThat(download.request.uri).isEqualTo(request2.uri); assertThat(download.request.streamKeys).containsExactly(streamKey1, streamKey2); + assertThat(download.request.keySetId).isEqualTo(request2.keySetId); assertThat(download.state).isEqualTo(Download.STATE_QUEUED); } @@ -165,20 +171,22 @@ public class ActionFileUpgradeUtilTest { new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* trackIndex= */ 2); DownloadRequest request1 = new DownloadRequest( - "id1", - TYPE_PROGRESSIVE, + /* id= */ "id1", Uri.parse("https://www.test.com/download1"), + /* mimeType= */ null, asList(streamKey1), + /* keySetId= */ new byte[] {1, 2, 3, 4}, /* customCacheKey= */ "key123", - new byte[] {1, 2, 3, 4}); + /* data= */ new byte[] {1, 2, 3, 4}); DownloadRequest request2 = new DownloadRequest( - "id2", - TYPE_PROGRESSIVE, + /* id= */ "id2", Uri.parse("https://www.test.com/download2"), + /* mimeType= */ null, asList(streamKey2), + /* keySetId= */ new byte[] {5, 4, 3, 2, 1}, /* customCacheKey= */ "key123", - new byte[] {5, 4, 3, 2, 1}); + /* data= */ new byte[] {5, 4, 3, 2, 1}); ActionFileUpgradeUtil.mergeRequest( request1, downloadIndex, /* addNewDownloadAsCompleted= */ false, NOW_MS); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloadIndexTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloadIndexTest.java index cc1ae4b71b..313ee86413 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloadIndexTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloadIndexTest.java @@ -15,15 +15,31 @@ */ package com.google.android.exoplayer2.offline; +import static com.google.android.exoplayer2.offline.Download.FAILURE_REASON_NONE; +import static com.google.android.exoplayer2.offline.Download.FAILURE_REASON_UNKNOWN; +import static com.google.android.exoplayer2.offline.Download.STATE_DOWNLOADING; +import static com.google.android.exoplayer2.offline.Download.STATE_STOPPED; +import static com.google.android.exoplayer2.offline.Download.STOP_REASON_NONE; import static com.google.common.truth.Truth.assertThat; +import android.content.Context; import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.database.DatabaseIOException; import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.database.VersionTable; import com.google.android.exoplayer2.testutil.DownloadBuilder; +import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.common.collect.ImmutableList; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -73,14 +89,14 @@ public class DefaultDownloadIndexTest { Download download = downloadBuilder - .setType("different type") .setUri("different uri") + .setMimeType(MimeTypes.APPLICATION_MP4) .setCacheKey("different cacheKey") .setState(Download.STATE_FAILED) .setPercentDownloaded(50) .setBytesDownloaded(200) .setContentLength(400) - .setFailureReason(Download.FAILURE_REASON_UNKNOWN) + .setFailureReason(FAILURE_REASON_UNKNOWN) .setStopReason(0x12345678) .setStartTimeMs(10) .setUpdateTimeMs(20) @@ -88,6 +104,7 @@ public class DefaultDownloadIndexTest { new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* trackIndex= */ 2), new StreamKey(/* periodIndex= */ 3, /* groupIndex= */ 4, /* trackIndex= */ 5)) .setCustomMetadata(new byte[] {0, 1, 2, 3, 7, 8, 9, 10}) + .setKeySetId(new byte[] {0, 1, 2, 3}) .build(); downloadIndex.putDownload(download); Download readDownload = downloadIndex.getDownload(id); @@ -153,7 +170,7 @@ public class DefaultDownloadIndexTest { new DownloadBuilder("id1").setStartTimeMs(0).setState(Download.STATE_REMOVING).build(); downloadIndex.putDownload(download1); Download download2 = - new DownloadBuilder("id2").setStartTimeMs(1).setState(Download.STATE_STOPPED).build(); + new DownloadBuilder("id2").setStartTimeMs(1).setState(STATE_STOPPED).build(); downloadIndex.putDownload(download2); Download download3 = new DownloadBuilder("id3").setStartTimeMs(2).setState(Download.STATE_COMPLETED).build(); @@ -202,6 +219,47 @@ public class DefaultDownloadIndexTest { .isEqualTo(DefaultDownloadIndex.TABLE_VERSION); } + @Test + public void downloadIndex_upgradesFromVersion2() throws IOException { + Context context = ApplicationProvider.getApplicationContext(); + File databaseFile = context.getDatabasePath(ExoDatabaseProvider.DATABASE_NAME); + try (FileOutputStream output = new FileOutputStream(databaseFile)) { + output.write(TestUtil.getByteArray(context, "offline/exoplayer_internal_v2.db")); + } + Download dashDownload = + createDownload( + /* uri= */ "http://www.test.com/manifest.mpd", + /* mimeType= */ MimeTypes.APPLICATION_MPD, + ImmutableList.of(), + /* customCacheKey= */ null); + Download hlsDownload = + createDownload( + /* uri= */ "http://www.test.com/manifest.m3u8", + /* mimeType= */ MimeTypes.APPLICATION_M3U8, + ImmutableList.of(), + /* customCacheKey= */ null); + Download ssDownload = + createDownload( + /* uri= */ "http://www.test.com/video.ism/manifest", + /* mimeType= */ MimeTypes.APPLICATION_SS, + Arrays.asList(new StreamKey(0, 0), new StreamKey(1, 1)), + /* customCacheKey= */ null); + Download progressiveDownload = + createDownload( + /* uri= */ "http://www.test.com/video.mp4", + /* mimeType= */ MimeTypes.VIDEO_UNKNOWN, + ImmutableList.of(), + /* customCacheKey= */ "customCacheKey"); + + databaseProvider = new ExoDatabaseProvider(context); + downloadIndex = new DefaultDownloadIndex(databaseProvider); + + assertEqual(downloadIndex.getDownload("http://www.test.com/manifest.mpd"), dashDownload); + assertEqual(downloadIndex.getDownload("http://www.test.com/manifest.m3u8"), hlsDownload); + assertEqual(downloadIndex.getDownload("http://www.test.com/video.ism/manifest"), ssDownload); + assertEqual(downloadIndex.getDownload("http://www.test.com/video.mp4"), progressiveDownload); + } + @Test public void setStopReason_setReasonToNone() throws Exception { String id = "id"; @@ -210,10 +268,10 @@ public class DefaultDownloadIndexTest { Download download = downloadBuilder.build(); downloadIndex.putDownload(download); - downloadIndex.setStopReason(Download.STOP_REASON_NONE); + downloadIndex.setStopReason(STOP_REASON_NONE); Download readDownload = downloadIndex.getDownload(id); - Download expectedDownload = downloadBuilder.setStopReason(Download.STOP_REASON_NONE).build(); + Download expectedDownload = downloadBuilder.setStopReason(STOP_REASON_NONE).build(); assertEqual(readDownload, expectedDownload); } @@ -223,7 +281,7 @@ public class DefaultDownloadIndexTest { DownloadBuilder downloadBuilder = new DownloadBuilder(id) .setState(Download.STATE_FAILED) - .setFailureReason(Download.FAILURE_REASON_UNKNOWN); + .setFailureReason(FAILURE_REASON_UNKNOWN); Download download = downloadBuilder.build(); downloadIndex.putDownload(download); int stopReason = 0x12345678; @@ -238,7 +296,7 @@ public class DefaultDownloadIndexTest { @Test public void setStopReason_notTerminalState_doesNotSetStopReason() throws Exception { String id = "id"; - DownloadBuilder downloadBuilder = new DownloadBuilder(id).setState(Download.STATE_DOWNLOADING); + DownloadBuilder downloadBuilder = new DownloadBuilder(id).setState(STATE_DOWNLOADING); Download download = downloadBuilder.build(); downloadIndex.putDownload(download); int notMetRequirements = 0x12345678; @@ -255,7 +313,7 @@ public class DefaultDownloadIndexTest { DownloadBuilder downloadBuilder = new DownloadBuilder(id) .setState(Download.STATE_FAILED) - .setFailureReason(Download.FAILURE_REASON_UNKNOWN); + .setFailureReason(FAILURE_REASON_UNKNOWN); Download download = downloadBuilder.build(); downloadIndex.putDownload(download); @@ -263,7 +321,7 @@ public class DefaultDownloadIndexTest { download = downloadIndex.getDownload(id); assertThat(download.state).isEqualTo(Download.STATE_REMOVING); - assertThat(download.failureReason).isEqualTo(Download.FAILURE_REASON_NONE); + assertThat(download.failureReason).isEqualTo(FAILURE_REASON_NONE); } @Test @@ -274,10 +332,10 @@ public class DefaultDownloadIndexTest { Download download = downloadBuilder.build(); downloadIndex.putDownload(download); - downloadIndex.setStopReason(id, Download.STOP_REASON_NONE); + downloadIndex.setStopReason(id, STOP_REASON_NONE); Download readDownload = downloadIndex.getDownload(id); - Download expectedDownload = downloadBuilder.setStopReason(Download.STOP_REASON_NONE).build(); + Download expectedDownload = downloadBuilder.setStopReason(STOP_REASON_NONE).build(); assertEqual(readDownload, expectedDownload); } @@ -287,7 +345,7 @@ public class DefaultDownloadIndexTest { DownloadBuilder downloadBuilder = new DownloadBuilder(id) .setState(Download.STATE_FAILED) - .setFailureReason(Download.FAILURE_REASON_UNKNOWN); + .setFailureReason(FAILURE_REASON_UNKNOWN); Download download = downloadBuilder.build(); downloadIndex.putDownload(download); int stopReason = 0x12345678; @@ -302,7 +360,7 @@ public class DefaultDownloadIndexTest { @Test public void setSingleDownloadStopReason_notTerminalState_doesNotSetStopReason() throws Exception { String id = "id"; - DownloadBuilder downloadBuilder = new DownloadBuilder(id).setState(Download.STATE_DOWNLOADING); + DownloadBuilder downloadBuilder = new DownloadBuilder(id).setState(STATE_DOWNLOADING); Download download = downloadBuilder.build(); downloadIndex.putDownload(download); int notMetRequirements = 0x12345678; @@ -324,4 +382,23 @@ public class DefaultDownloadIndexTest { assertThat(download.getPercentDownloaded()).isEqualTo(that.getPercentDownloaded()); assertThat(download.getBytesDownloaded()).isEqualTo(that.getBytesDownloaded()); } + + private static Download createDownload( + String uri, String mimeType, List streamKeys, @Nullable String customCacheKey) { + return new Download( + new DownloadRequest( + uri, + Uri.parse(uri), + mimeType, + streamKeys, + /* keySetId= */ null, + customCacheKey, + /* data= */ new byte[] {0, 1, 2, 3}), + /* state= */ STATE_STOPPED, + /* startTimeMs= */ 1, + /* updateTimeMs= */ 2, + /* contentLength= */ 3, + /* stopReason= */ 4, + /* failureReason= */ FAILURE_REASON_NONE); + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactoryTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactoryTest.java index bf762f0da9..d3789c530e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactoryTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactoryTest.java @@ -43,10 +43,11 @@ public final class DefaultDownloaderFactoryTest { Downloader downloader = factory.createDownloader( new DownloadRequest( - "id", - DownloadRequest.TYPE_PROGRESSIVE, + /* id= */ "id", Uri.parse("https://www.test.com/download"), + /* mimeType= */ null, /* streamKeys= */ Collections.emptyList(), + /* keySetId= */ null, /* customCacheKey= */ null, /* data= */ null)); assertThat(downloader).isInstanceOf(ProgressiveDownloader.class); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java index bada7fc15c..73917606c5 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadHelperTest.java @@ -403,8 +403,8 @@ public class DownloadHelperTest { DownloadRequest downloadRequest = downloadHelper.getDownloadRequest(data); - assertThat(downloadRequest.type).isEqualTo(DownloadRequest.TYPE_PROGRESSIVE); assertThat(downloadRequest.uri).isEqualTo(testMediaItem.playbackProperties.uri); + assertThat(downloadRequest.mimeType).isEqualTo(testMediaItem.playbackProperties.mimeType); assertThat(downloadRequest.customCacheKey) .isEqualTo(testMediaItem.playbackProperties.customCacheKey); assertThat(downloadRequest.data).isEqualTo(data); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java index 6ec93cbc29..a9a6d3888c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java @@ -790,9 +790,10 @@ public class DownloadManagerTest { private static DownloadRequest createDownloadRequest(String id, StreamKey... keys) { return new DownloadRequest( id, - DownloadRequest.TYPE_DASH, Uri.parse("http://abc.com/ " + id), + /* mimeType= */ null, Arrays.asList(keys), + /* keySetId= */ null, /* customCacheKey= */ null, /* data= */ null); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadRequestTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadRequestTest.java index c5b00b02d6..0256215809 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadRequestTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadRequestTest.java @@ -15,9 +15,6 @@ */ package com.google.android.exoplayer2.offline; -import static com.google.android.exoplayer2.offline.DownloadRequest.TYPE_DASH; -import static com.google.android.exoplayer2.offline.DownloadRequest.TYPE_HLS; -import static com.google.android.exoplayer2.offline.DownloadRequest.TYPE_PROGRESSIVE; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; @@ -48,44 +45,20 @@ public class DownloadRequestTest { public void mergeRequests_withDifferentIds_fails() { DownloadRequest request1 = new DownloadRequest( - "id1", - TYPE_DASH, + /* id= */ "id1", uri1, + /* mimeType= */ null, /* streamKeys= */ Collections.emptyList(), + /* keySetId= */ null, /* customCacheKey= */ null, /* data= */ null); DownloadRequest request2 = new DownloadRequest( - "id2", - TYPE_DASH, + /* id= */ "id2", uri2, + /* mimeType= */ null, /* streamKeys= */ Collections.emptyList(), - /* customCacheKey= */ null, - /* data= */ null); - try { - request1.copyWithMergedRequest(request2); - fail(); - } catch (IllegalArgumentException e) { - // Expected. - } - } - - @Test - public void mergeRequests_withDifferentTypes_fails() { - DownloadRequest request1 = - new DownloadRequest( - "id1", - TYPE_DASH, - uri1, - /* streamKeys= */ Collections.emptyList(), - /* customCacheKey= */ null, - /* data= */ null); - DownloadRequest request2 = - new DownloadRequest( - "id1", - TYPE_HLS, - uri1, - /* streamKeys= */ Collections.emptyList(), + /* keySetId= */ null, /* customCacheKey= */ null, /* data= */ null); try { @@ -135,33 +108,40 @@ public class DownloadRequestTest { @Test public void mergeRequests_withDifferentFields() { - byte[] data1 = new byte[] {0, 1, 2}; - byte[] data2 = new byte[] {3, 4, 5}; + byte[] keySetId1 = new byte[] {0, 1, 2}; + byte[] keySetId2 = new byte[] {3, 4, 5}; + byte[] data1 = new byte[] {6, 7, 8}; + byte[] data2 = new byte[] {9, 10, 11}; + DownloadRequest request1 = new DownloadRequest( - "id1", - TYPE_PROGRESSIVE, + /* id= */ "id1", uri1, + /* mimeType= */ null, /* streamKeys= */ Collections.emptyList(), - "key1", - /* data= */ data1); + keySetId1, + /* customCacheKey= */ "key1", + data1); DownloadRequest request2 = new DownloadRequest( - "id1", - TYPE_PROGRESSIVE, + /* id= */ "id1", uri2, + /* mimeType= */ null, /* streamKeys= */ Collections.emptyList(), - "key2", - /* data= */ data2); + keySetId2, + /* customCacheKey= */ "key2", + data2); - // uri, customCacheKey and data should be from the request being merged. + // uri, keySetId, customCacheKey and data should be from the request being merged. DownloadRequest mergedRequest = request1.copyWithMergedRequest(request2); assertThat(mergedRequest.uri).isEqualTo(uri2); + assertThat(mergedRequest.keySetId).isEqualTo(keySetId2); assertThat(mergedRequest.customCacheKey).isEqualTo("key2"); assertThat(mergedRequest.data).isEqualTo(data2); mergedRequest = request2.copyWithMergedRequest(request1); assertThat(mergedRequest.uri).isEqualTo(uri1); + assertThat(mergedRequest.keySetId).isEqualTo(keySetId1); assertThat(mergedRequest.customCacheKey).isEqualTo("key1"); assertThat(mergedRequest.data).isEqualTo(data1); } @@ -173,12 +153,13 @@ public class DownloadRequestTest { streamKeys.add(new StreamKey(4, 5, 6)); DownloadRequest requestToParcel = new DownloadRequest( - "id", - "type", + /* id= */ "id", Uri.parse("https://abc.def/ghi"), + /* mimeType= */ null, streamKeys, - "key", - new byte[] {1, 2, 3, 4, 5}); + /* keySetId= */ new byte[] {1, 2, 3, 4, 5}, + /* customCacheKey= */ "key", + /* data= */ new byte[] {1, 2, 3, 4, 5}); Parcel parcel = Parcel.obtain(); requestToParcel.writeToParcel(parcel, 0); parcel.setDataPosition(0); @@ -232,11 +213,18 @@ public class DownloadRequestTest { private static void assertEqual(DownloadRequest request1, DownloadRequest request2) { assertThat(request1).isEqualTo(request2); assertThat(request2).isEqualTo(request1); + assertThat(request1.hashCode()).isEqualTo(request2.hashCode()); } private static DownloadRequest createRequest(Uri uri, StreamKey... keys) { return new DownloadRequest( - uri.toString(), TYPE_DASH, uri, toList(keys), /* customCacheKey= */ null, /* data= */ null); + uri.toString(), + uri, + /* mimeType= */ null, + toList(keys), + /* keySetId= */ null, + /* customCacheKey= */ null, + /* data= */ null); } private static List toList(StreamKey... keys) { diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java index 17c6da74dc..b5a2bf3057 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.upstream.cache.Cache; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.File; import java.io.IOException; @@ -93,9 +94,10 @@ public class DashDownloaderTest { factory.createDownloader( new DownloadRequest( "id", - DownloadRequest.TYPE_DASH, Uri.parse("https://www.test.com/download"), + MimeTypes.APPLICATION_MPD, Collections.singletonList(new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0)), + /* keySetId= */ null, /* customCacheKey= */ null, /* data= */ null)); assertThat(downloader).isInstanceOf(DashDownloader.class); diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java index d2756780f8..cdee3becdb 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java @@ -42,6 +42,7 @@ import com.google.android.exoplayer2.upstream.DataSource.Factory; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.File; import java.util.ArrayList; @@ -224,9 +225,10 @@ public class DownloadManagerDashTest { Collections.addAll(keysList, keys); return new DownloadRequest( TEST_ID, - DownloadRequest.TYPE_DASH, TEST_MPD_URI, + MimeTypes.APPLICATION_MPD, keysList, + /* keySetId= */ null, /* customCacheKey= */ null, null); } diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java index 1257a70a07..8dc88bffe5 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.util.ConditionVariable; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.File; import java.io.IOException; @@ -209,9 +210,10 @@ public class DownloadServiceDashTest { DownloadRequest action = new DownloadRequest( TEST_ID, - DownloadRequest.TYPE_DASH, TEST_MPD_URI, + MimeTypes.APPLICATION_MPD, keysList, + /* keySetId= */ null, /* customCacheKey= */ null, null); testThread.runOnMainThread( diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java index 1fc51ab498..d968af51bc 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloaderTest.java @@ -53,6 +53,7 @@ import com.google.android.exoplayer2.upstream.cache.Cache; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.File; import java.util.ArrayList; @@ -111,9 +112,10 @@ public class HlsDownloaderTest { factory.createDownloader( new DownloadRequest( "id", - DownloadRequest.TYPE_HLS, Uri.parse("https://www.test.com/download"), + MimeTypes.APPLICATION_M3U8, Collections.singletonList(new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0)), + /* keySetId= */ null, /* customCacheKey= */ null, /* data= */ null)); assertThat(downloader).isInstanceOf(HlsDownloader.class); diff --git a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloaderTest.java b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloaderTest.java index 1e6ea37365..6d1bb28009 100644 --- a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloaderTest.java +++ b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloaderTest.java @@ -27,6 +27,7 @@ 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.upstream.cache.CacheDataSource; +import com.google.android.exoplayer2.util.MimeTypes; import java.util.Collections; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,9 +50,10 @@ public final class SsDownloaderTest { factory.createDownloader( new DownloadRequest( "id", - DownloadRequest.TYPE_SS, Uri.parse("https://www.test.com/download"), + MimeTypes.APPLICATION_SS, Collections.singletonList(new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0)), + /* keySetId= */ null, /* customCacheKey= */ null, /* data= */ null)); assertThat(downloader).isInstanceOf(SsDownloader.class); diff --git a/testdata/src/test/assets/offline/exoplayer_internal_v2.db b/testdata/src/test/assets/offline/exoplayer_internal_v2.db new file mode 100644 index 0000000000..63f14052bf Binary files /dev/null and b/testdata/src/test/assets/offline/exoplayer_internal_v2.db differ diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DownloadBuilder.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DownloadBuilder.java index 1b707b3d3a..cc5565a69c 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DownloadBuilder.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DownloadBuilder.java @@ -38,9 +38,10 @@ public final class DownloadBuilder { private final DownloadProgress progress; private String id; - private String type; private Uri uri; + @Nullable private String mimeType; private List streamKeys; + private byte[] keySetId; @Nullable private String cacheKey; private byte[] customMetadata; @@ -52,18 +53,19 @@ public final class DownloadBuilder { private int failureReason; /** - * Creates a download builder for "uri" with type "type" and no stream keys. + * Creates a download builder for "uri" and no stream keys. * * @param id The unique content identifier for the download. */ public DownloadBuilder(String id) { this( id, - "type", Uri.parse("uri"), + /* mimeType= */ null, /* streamKeys= */ Collections.emptyList(), + /* keySetId= */ new byte[0], /* cacheKey= */ null, - new byte[0]); + /* customMetadata= */ new byte[0]); } /** @@ -74,9 +76,10 @@ public final class DownloadBuilder { public DownloadBuilder(DownloadRequest request) { this( request.id, - request.type, request.uri, + request.mimeType, request.streamKeys, + request.keySetId, request.customCacheKey, request.data); } @@ -84,15 +87,17 @@ public final class DownloadBuilder { /** Creates a download builder. */ private DownloadBuilder( String id, - String type, Uri uri, + @Nullable String mimeType, List streamKeys, + byte[] keySetId, @Nullable String cacheKey, byte[] customMetadata) { this.id = id; - this.type = type; this.uri = uri; + this.mimeType = mimeType; this.streamKeys = streamKeys; + this.keySetId = keySetId; this.cacheKey = cacheKey; this.customMetadata = customMetadata; this.state = Download.STATE_QUEUED; @@ -101,12 +106,6 @@ public final class DownloadBuilder { this.progress = new DownloadProgress(); } - /** @see DownloadRequest#type */ - public DownloadBuilder setType(String type) { - this.type = type; - return this; - } - /** @see DownloadRequest#uri */ public DownloadBuilder setUri(String uri) { this.uri = Uri.parse(uri); @@ -119,6 +118,18 @@ public final class DownloadBuilder { return this; } + /** @see DownloadRequest#mimeType */ + public DownloadBuilder setMimeType(String mimeType) { + this.mimeType = mimeType; + return this; + } + + /** @see DownloadRequest#keySetId */ + public DownloadBuilder setKeySetId(byte[] keySetId) { + this.keySetId = keySetId; + return this; + } + /** @see DownloadRequest#customCacheKey */ public DownloadBuilder setCacheKey(@Nullable String cacheKey) { this.cacheKey = cacheKey; @@ -187,7 +198,7 @@ public final class DownloadBuilder { public Download build() { DownloadRequest request = - new DownloadRequest(id, type, uri, streamKeys, cacheKey, customMetadata); + new DownloadRequest(id, uri, mimeType, streamKeys, keySetId, cacheKey, customMetadata); return new Download( request, state,