Use parcelable keys for HLS downloads to remove special casing

This removes the need for separate String/Parcelable filter paths in
the demo app. Hopefully this is a temporary measure as we work
toward using track groups + consistent keys for all media types.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=194065499
This commit is contained in:
olly 2018-04-24 03:49:14 -07:00 committed by Oliver Woodman
parent 874dc202f8
commit 1f3adacac3
8 changed files with 143 additions and 79 deletions

View File

@ -41,6 +41,7 @@ import com.google.android.exoplayer2.source.dash.offline.DashDownloadAction;
import com.google.android.exoplayer2.source.dash.offline.DashDownloader;
import com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction;
import com.google.android.exoplayer2.source.hls.offline.HlsDownloader;
import com.google.android.exoplayer2.source.hls.playlist.RenditionKey;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.TrackKey;
import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction;
import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloader;
@ -63,7 +64,6 @@ public class DownloadActivity extends Activity {
private AsyncTask manifestDownloaderTask;
private DownloadUtilMethods downloadUtilMethods;
private ListView representationList;
private ArrayAdapter<RepresentationItem> arrayAdapter;
@ -162,15 +162,11 @@ public class DownloadActivity extends Activity {
ArrayList<Object> selectedRepresentationKeys = getSelectedRepresentationKeys();
if (selectedRepresentationKeys.isEmpty()) {
playerIntent.removeExtra(PlayerActivity.MANIFEST_FILTER_EXTRA);
} else if (selectedRepresentationKeys.get(0) instanceof Parcelable) {
} else {
Parcelable[] parcelables = new Parcelable[selectedRepresentationKeys.size()];
selectedRepresentationKeys.toArray(parcelables);
playerIntent.putExtra(
PlayerActivity.MANIFEST_FILTER_EXTRA, new ParcelableArray<>(parcelables));
} else {
String[] strings = new String[selectedRepresentationKeys.size()];
selectedRepresentationKeys.toArray(strings);
playerIntent.putExtra(PlayerActivity.MANIFEST_FILTER_EXTRA, strings);
}
startActivity(playerIntent);
}
@ -198,11 +194,11 @@ public class DownloadActivity extends Activity {
private static final class RepresentationItem {
public final Object key;
public final Parcelable key;
public final String title;
public final int percentDownloaded;
public RepresentationItem(Object key, String title, float percentDownloaded) {
public RepresentationItem(Parcelable key, String title, float percentDownloaded) {
this.key = key;
this.title = title;
this.percentDownloaded = (int) percentDownloaded;
@ -317,14 +313,14 @@ public class DownloadActivity extends Activity {
throws IOException, InterruptedException {
HlsDownloader downloader = new HlsDownloader(manifestUri, constructorHelper);
ArrayList<RepresentationItem> items = new ArrayList<>();
for (String key : downloader.getAllRepresentationKeys()) {
downloader.selectRepresentations(new String[] {key});
for (RenditionKey key : downloader.getAllRepresentationKeys()) {
downloader.selectRepresentations(new RenditionKey[] {key});
try {
downloader.init();
} catch (IOException e) {
continue;
}
items.add(new RepresentationItem(key, key, downloader.getDownloadPercentage()));
items.add(new RepresentationItem(key, key.url, downloader.getDownloadPercentage()));
}
return items;
}
@ -332,7 +328,7 @@ public class DownloadActivity extends Activity {
@Override
public DownloadAction getDownloadAction(
String sampleName, ArrayList<Object> representationKeys) {
String[] keys = representationKeys.toArray(new String[representationKeys.size()]);
RenditionKey[] keys = representationKeys.toArray(new RenditionKey[representationKeys.size()]);
return new HlsDownloadAction(manifestUri, false, sampleName, keys);
}

View File

@ -21,6 +21,7 @@ import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.KeyEvent;
@ -60,6 +61,7 @@ import com.google.android.exoplayer2.source.dash.manifest.FilteringDashManifestP
import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.hls.playlist.FilteringHlsPlaylistParser;
import com.google.android.exoplayer2.source.hls.playlist.RenditionKey;
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.FilteringSsManifestParser;
@ -83,7 +85,6 @@ import java.lang.reflect.Constructor;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@ -300,15 +301,11 @@ public class PlayerActivity extends Activity
String action = intent.getAction();
Uri[] uris;
String[] extensions;
Object[] manifestFilters;
Parcelable[] manifestFilters;
if (ACTION_VIEW.equals(action)) {
uris = new Uri[] {intent.getData()};
extensions = new String[] {intent.getStringExtra(EXTENSION_EXTRA)};
Object filter = intent.getParcelableExtra(MANIFEST_FILTER_EXTRA);
if (filter == null) {
filter = intent.getStringArrayExtra(MANIFEST_FILTER_EXTRA);
}
manifestFilters = new Object[] {filter};
manifestFilters = new Parcelable[] {intent.getParcelableExtra(MANIFEST_FILTER_EXTRA)};
} else if (ACTION_VIEW_LIST.equals(action)) {
String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA);
uris = new Uri[uriStrings.length];
@ -321,7 +318,7 @@ public class PlayerActivity extends Activity
}
manifestFilters = intent.getParcelableArrayExtra(MANIFEST_FILTER_LIST_EXTRA);
if (manifestFilters == null) {
manifestFilters = new Object[uriStrings.length];
manifestFilters = new Parcelable[uriStrings.length];
}
} else {
showToast(getString(R.string.unexpected_intent_action, action));
@ -414,15 +411,8 @@ public class PlayerActivity extends Activity
MediaSource[] mediaSources = new MediaSource[uris.length];
for (int i = 0; i < uris.length; i++) {
List<?> filter;
Object manifestFilter = manifestFilters[i];
if (manifestFilter instanceof ParcelableArray<?>) {
filter = ((ParcelableArray<?>) manifestFilter).asList();
} else if (manifestFilter instanceof String[]) {
filter = Arrays.asList((String[]) manifestFilter);
} else {
filter = null;
}
ParcelableArray<?> manifestFilter = (ParcelableArray<?>) manifestFilters[i];
List<?> filter = manifestFilter != null ? manifestFilter.asList() : null;
mediaSources[i] = buildMediaSource(uris[i], extensions[i], filter);
}
mediaSource =
@ -479,7 +469,7 @@ public class PlayerActivity extends Activity
.createMediaSource(uri);
case C.TYPE_HLS:
return new HlsMediaSource.Factory(mediaDataSourceFactory)
.setPlaylistParser(new FilteringHlsPlaylistParser((List<String>) manifestFilter))
.setPlaylistParser(new FilteringHlsPlaylistParser((List<RenditionKey>) manifestFilter))
.createMediaSource(uri);
case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri);

View File

@ -20,15 +20,16 @@ import android.support.annotation.Nullable;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.offline.SegmentDownloadAction;
import com.google.android.exoplayer2.source.hls.playlist.RenditionKey;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/** An action to download or remove downloaded HLS streams. */
public final class HlsDownloadAction extends SegmentDownloadAction<String> {
public final class HlsDownloadAction extends SegmentDownloadAction<RenditionKey> {
public static final Deserializer DESERIALIZER =
new SegmentDownloadActionDeserializer<String>() {
new SegmentDownloadActionDeserializer<RenditionKey>() {
@Override
public String getType() {
@ -36,18 +37,18 @@ public final class HlsDownloadAction extends SegmentDownloadAction<String> {
}
@Override
protected String readKey(DataInputStream input) throws IOException {
return input.readUTF();
protected RenditionKey readKey(DataInputStream input) throws IOException {
return new RenditionKey(input.readUTF());
}
@Override
protected String[] createKeyArray(int keyCount) {
return new String[keyCount];
protected RenditionKey[] createKeyArray(int keyCount) {
return new RenditionKey[keyCount];
}
@Override
protected DownloadAction createDownloadAction(
Uri manifestUri, boolean removeAction, String data, String[] keys) {
Uri manifestUri, boolean removeAction, String data, RenditionKey[] keys) {
return new HlsDownloadAction(manifestUri, removeAction, data, keys);
}
};
@ -56,7 +57,7 @@ public final class HlsDownloadAction extends SegmentDownloadAction<String> {
/** @see SegmentDownloadAction#SegmentDownloadAction(Uri, boolean, String, Object[]) */
public HlsDownloadAction(
Uri manifestUri, boolean removeAction, @Nullable String data, String... keys) {
Uri manifestUri, boolean removeAction, @Nullable String data, RenditionKey... keys) {
super(manifestUri, removeAction, data, keys);
}
@ -75,8 +76,8 @@ public final class HlsDownloadAction extends SegmentDownloadAction<String> {
}
@Override
protected void writeKey(DataOutputStream output, String key) throws IOException {
output.writeUTF(key);
protected void writeKey(DataOutputStream output, RenditionKey key) throws IOException {
output.writeUTF(key.url);
}
}

View File

@ -24,6 +24,7 @@ import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUr
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer2.source.hls.playlist.RenditionKey;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
@ -37,10 +38,9 @@ import java.util.List;
* Helper class to download HLS streams.
*
* <p>A subset of renditions can be downloaded by selecting them using {@link
* #selectRepresentations(Object[])}. As key, string form of the rendition's url is used. The urls
* can be absolute or relative to the master playlist url.
* #selectRepresentations(Object[])}.
*/
public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, String> {
public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, RenditionKey> {
/**
* @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper)
@ -50,13 +50,13 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, St
}
@Override
public String[] getAllRepresentationKeys() throws IOException {
ArrayList<String> urls = new ArrayList<>();
public RenditionKey[] getAllRepresentationKeys() throws IOException {
ArrayList<RenditionKey> renditionKeys = new ArrayList<>();
HlsMasterPlaylist manifest = getManifest();
extractUrls(manifest.variants, urls);
extractUrls(manifest.audios, urls);
extractUrls(manifest.subtitles, urls);
return urls.toArray(new String[urls.size()]);
extractUrls(manifest.variants, renditionKeys);
extractUrls(manifest.audios, renditionKeys);
extractUrls(manifest.subtitles, renditionKeys);
return renditionKeys.toArray(new RenditionKey[renditionKeys.size()]);
}
@Override
@ -70,13 +70,17 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, St
}
@Override
protected List<Segment> getSegments(DataSource dataSource, HlsMasterPlaylist manifest,
String[] keys, boolean allowIndexLoadErrors) throws InterruptedException, IOException {
protected List<Segment> getSegments(
DataSource dataSource,
HlsMasterPlaylist manifest,
RenditionKey[] keys,
boolean allowIndexLoadErrors)
throws InterruptedException, IOException {
HashSet<Uri> encryptionKeyUris = new HashSet<>();
ArrayList<Segment> segments = new ArrayList<>();
for (String playlistUrl : keys) {
for (RenditionKey renditionKey : keys) {
HlsMediaPlaylist mediaPlaylist = null;
Uri uri = UriUtil.resolveToUri(manifest.baseUri, playlistUrl);
Uri uri = UriUtil.resolveToUri(manifest.baseUri, renditionKey.url);
try {
mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, uri);
} catch (IOException e) {
@ -128,9 +132,9 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, St
new DataSpec(resolvedUri, hlsSegment.byterangeOffset, hlsSegment.byterangeLength, null)));
}
private static void extractUrls(List<HlsUrl> hlsUrls, ArrayList<String> urls) {
private static void extractUrls(List<HlsUrl> hlsUrls, ArrayList<RenditionKey> renditionKeys) {
for (int i = 0; i < hlsUrls.size(); i++) {
urls.add(hlsUrls.get(i).url);
renditionKeys.add(new RenditionKey(hlsUrls.get(i).url));
}
}

View File

@ -26,13 +26,13 @@ import java.util.List;
public final class FilteringHlsPlaylistParser implements Parser<HlsPlaylist> {
private final HlsPlaylistParser hlsPlaylistParser;
private final List<String> filter;
private final List<RenditionKey> filter;
/**
* @param filter The urls to renditions that should be retained in the parsed playlists. If null,
* all renditions are retained.
*/
public FilteringHlsPlaylistParser(@Nullable List<String> filter) {
public FilteringHlsPlaylistParser(@Nullable List<RenditionKey> filter) {
this.hlsPlaylistParser = new HlsPlaylistParser();
this.filter = filter;
}

View File

@ -111,16 +111,21 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
/**
* Returns a copy of this playlist which includes only the renditions identified by the given
* urls.
* keys.
*
* @param renditionUrls List of rendition urls.
* @param renditionKeys List of rendition keys.
* @return A copy of this playlist which includes only the renditions identified by the given
* urls.
*/
public HlsMasterPlaylist copy(List<String> renditionUrls) {
return new HlsMasterPlaylist(baseUri, tags, copyRenditionsList(variants, renditionUrls),
copyRenditionsList(audios, renditionUrls), copyRenditionsList(subtitles, renditionUrls),
muxedAudioFormat, muxedCaptionFormats);
public HlsMasterPlaylist copy(List<RenditionKey> renditionKeys) {
return new HlsMasterPlaylist(
baseUri,
tags,
copyRenditionsList(variants, renditionKeys),
copyRenditionsList(audios, renditionKeys),
copyRenditionsList(subtitles, renditionKeys),
muxedAudioFormat,
muxedCaptionFormats);
}
/**
@ -136,12 +141,17 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
emptyList, null, null);
}
private static List<HlsUrl> copyRenditionsList(List<HlsUrl> renditions, List<String> urls) {
List<HlsUrl> copiedRenditions = new ArrayList<>(urls.size());
private static List<HlsUrl> copyRenditionsList(
List<HlsUrl> renditions, List<RenditionKey> renditionKeys) {
List<HlsUrl> copiedRenditions = new ArrayList<>(renditionKeys.size());
for (int i = 0; i < renditions.size(); i++) {
HlsUrl rendition = renditions.get(i);
if (urls.contains(rendition.url)) {
for (int j = 0; j < renditionKeys.size(); j++) {
RenditionKey renditionKey = renditionKeys.get(j);
if (renditionKey.url.equals(rendition.url)) {
copiedRenditions.add(rendition);
break;
}
}
}
return copiedRenditions;

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.source.hls.playlist;
import android.os.Parcel;
import android.os.Parcelable;
/** Uniquely identifies a rendition in an {@link HlsMasterPlaylist}. */
public final class RenditionKey implements Parcelable {
public final String url;
public RenditionKey(String url) {
this.url = url;
}
// Parcelable implementation.
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(url);
}
public static final Creator<RenditionKey> CREATOR =
new Creator<RenditionKey>() {
@Override
public RenditionKey createFromParcel(Parcel in) {
return new RenditionKey(in.readString());
}
@Override
public RenditionKey[] newArray(int size) {
return new RenditionKey[size];
}
};
}

View File

@ -35,6 +35,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.RenditionKey;
import com.google.android.exoplayer2.testutil.FakeDataSet;
import com.google.android.exoplayer2.testutil.FakeDataSource.Factory;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
@ -91,8 +92,8 @@ public class HlsDownloaderTest {
@Test
public void testSelectRepresentationsClearsPreviousSelection() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_2_URI});
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_2_URI));
hlsDownloader.download(null);
assertCachedData(
@ -107,7 +108,7 @@ public class HlsDownloaderTest {
@Test
public void testCounterMethods() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
hlsDownloader.download(null);
assertThat(hlsDownloader.getTotalSegments()).isEqualTo(4);
@ -118,11 +119,11 @@ public class HlsDownloaderTest {
@Test
public void testInitStatus() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
hlsDownloader.download(null);
HlsDownloader newHlsDownloader = getHlsDownloader(MASTER_PLAYLIST_URI);
newHlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
newHlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
newHlsDownloader.init();
assertThat(newHlsDownloader.getTotalSegments()).isEqualTo(4);
@ -133,7 +134,7 @@ public class HlsDownloaderTest {
@Test
public void testDownloadRepresentation() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
hlsDownloader.download(null);
assertCachedData(
@ -148,7 +149,7 @@ public class HlsDownloaderTest {
@Test
public void testDownloadMultipleRepresentations() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI});
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI));
hlsDownloader.download(null);
assertCachedData(cache, fakeDataSet);
@ -174,9 +175,9 @@ public class HlsDownloaderTest {
hlsDownloader.remove();
// select something random
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
// clear selection
hlsDownloader.selectRepresentations(new String[0]);
hlsDownloader.selectRepresentations(getKeys());
hlsDownloader.download(null);
assertCachedData(cache, fakeDataSet);
hlsDownloader.remove();
@ -184,7 +185,7 @@ public class HlsDownloaderTest {
@Test
public void testRemoveAll() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI});
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI));
hlsDownloader.download(null);
hlsDownloader.remove();
@ -194,7 +195,7 @@ public class HlsDownloaderTest {
@Test
public void testDownloadMediaPlaylist() throws Exception {
hlsDownloader = getHlsDownloader(MEDIA_PLAYLIST_1_URI);
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.selectRepresentations(getKeys(MEDIA_PLAYLIST_1_URI));
hlsDownloader.download(null);
assertCachedData(
@ -217,7 +218,7 @@ public class HlsDownloaderTest {
.setRandomData("fileSequence1.ts", 11)
.setRandomData("fileSequence2.ts", 12);
hlsDownloader = getHlsDownloader(ENC_MEDIA_PLAYLIST_URI);
hlsDownloader.selectRepresentations(new String[] {ENC_MEDIA_PLAYLIST_URI});
hlsDownloader.selectRepresentations(getKeys(ENC_MEDIA_PLAYLIST_URI));
hlsDownloader.download(null);
assertCachedData(cache, fakeDataSet);
@ -228,4 +229,12 @@ public class HlsDownloaderTest {
return new HlsDownloader(
Uri.parse(mediaPlaylistUri), new DownloaderConstructorHelper(cache, factory));
}
private static RenditionKey[] getKeys(String... urls) {
RenditionKey[] renditionKeys = new RenditionKey[urls.length];
for (int i = 0; i < urls.length; i++) {
renditionKeys[i] = new RenditionKey(urls[i]);
}
return renditionKeys;
}
}