Commit 0a8ae742 by tonihei Committed by Oliver Woodman

Update DownloadHelper to use MediaSource and MediaPeriod directly.

This requires to prepare the media source and the periods in a small helper similar
to the metadata retriever. It also gets rid of the need to have abstract protected
methods to load the manifest, to extract the track groups and to convert to stream keys,
as this can now be handled by the media period.

PiperOrigin-RevId: 231385590
parent 32b40502
......@@ -210,7 +210,7 @@ public class DownloadTracker implements DownloadManager.Listener {
DownloadService.startWithAction(context, DemoDownloadService.class, action, false);
}
private DownloadHelper<?> getDownloadHelper(
private DownloadHelper getDownloadHelper(
Uri uri, String extension, RenderersFactory renderersFactory) {
int type = Util.inferContentType(uri, extension);
switch (type) {
......@@ -231,10 +231,11 @@ public class DownloadTracker implements DownloadManager.Listener {
private final class StartDownloadDialogHelper
implements DownloadHelper.Callback,
DialogInterface.OnClickListener,
DialogInterface.OnDismissListener,
View.OnClickListener,
TrackSelectionView.DialogCallback {
private final DownloadHelper<?> downloadHelper;
private final DownloadHelper downloadHelper;
private final String name;
private final LayoutInflater dialogInflater;
private final AlertDialog dialog;
......@@ -244,20 +245,21 @@ public class DownloadTracker implements DownloadManager.Listener {
private DefaultTrackSelector.Parameters parameters;
private StartDownloadDialogHelper(
Activity activity, DownloadHelper<?> downloadHelper, String name) {
Activity activity, DownloadHelper downloadHelper, String name) {
this.downloadHelper = downloadHelper;
this.name = name;
AlertDialog.Builder builder =
new AlertDialog.Builder(activity)
.setTitle(R.string.download_preparing)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, null);
.setPositiveButton(android.R.string.ok, /* listener= */ this)
.setNegativeButton(android.R.string.cancel, /* listener= */ null);
// Inflate with the builder's context to ensure the correct style is used.
dialogInflater = LayoutInflater.from(builder.getContext());
selectionList = (LinearLayout) dialogInflater.inflate(R.layout.start_download_dialog, null);
builder.setView(selectionList);
dialog = builder.create();
dialog.setOnDismissListener(/* listener= */ this);
dialog.show();
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
......@@ -268,19 +270,17 @@ public class DownloadTracker implements DownloadManager.Listener {
// DownloadHelper.Callback implementation.
@Override
public void onPrepared(DownloadHelper<?> helper) {
if (helper.getPeriodCount() < 1) {
onPrepareError(downloadHelper, new IOException("Content is empty."));
return;
public void onPrepared(DownloadHelper helper) {
if (helper.getPeriodCount() > 0) {
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
updateSelectionList();
}
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
updateSelectionList();
dialog.setTitle(R.string.exo_download_description);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
}
@Override
public void onPrepareError(DownloadHelper<?> helper, IOException e) {
public void onPrepareError(DownloadHelper helper, IOException e) {
Toast.makeText(
context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG)
.show();
......@@ -326,6 +326,13 @@ public class DownloadTracker implements DownloadManager.Listener {
startDownload(downloadAction);
}
// DialogInterface.OnDismissListener implementation.
@Override
public void onDismiss(DialogInterface dialog) {
downloadHelper.release();
}
// Internal methods.
private void updateSelectionList() {
......
......@@ -17,11 +17,9 @@ package com.google.android.exoplayer2.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.source.TrackGroupArray;
/** A {@link DownloadHelper} for progressive streams. */
public final class ProgressiveDownloadHelper extends DownloadHelper<Void> {
public final class ProgressiveDownloadHelper extends DownloadHelper {
/**
* Creates download helper for progressive streams.
......@@ -43,24 +41,9 @@ public final class ProgressiveDownloadHelper extends DownloadHelper<Void> {
DownloadAction.TYPE_PROGRESSIVE,
uri,
cacheKey,
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
(handler, videoListener, audioListener, metadata, text, drm) -> new Renderer[0],
/* mediaSource= */ null,
/* trackSelectorParameters= */ DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
/* renderersFactory= */ null,
/* drmSessionManager= */ null);
}
@Override
protected Void loadManifest(Uri uri) {
return null;
}
@Override
protected TrackGroupArray[] getTrackGroupArrays(Void manifest) {
return new TrackGroupArray[] {TrackGroupArray.EMPTY};
}
@Override
protected StreamKey toStreamKey(
int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
return new StreamKey(periodIndex, trackGroupIndex, trackIndexInTrackGroup);
}
}
......@@ -22,17 +22,28 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.offline.DownloadHelper.Callback;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.testutil.FakeMediaPeriod;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeRenderer;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.ParametersBuilder;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
......@@ -40,15 +51,19 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
/** Unit tests for {@link DownloadHelper}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class})
public class DownloadHelperTest {
private static final String TEST_DOWNLOAD_TYPE = "downloadType";
private static final String TEST_CACHE_KEY = "cacheKey";
private static final ManifestType TEST_MANIFEST = new ManifestType();
private static final Timeline TEST_TIMELINE =
new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ new Object()));
private static final Object TEST_MANIFEST = new Object();
private static final Format VIDEO_FORMAT_LOW = createVideoFormat(/* bitrate= */ 200_000);
private static final Format VIDEO_FORMAT_HIGH = createVideoFormat(/* bitrate= */ 800_000);
......@@ -98,7 +113,7 @@ public class DownloadHelperTest {
public void getManifest_returnsManifest() throws Exception {
prepareDownloadHelper(downloadHelper);
ManifestType manifest = downloadHelper.getManifest();
Object manifest = downloadHelper.getManifest();
assertThat(manifest).isEqualTo(TEST_MANIFEST);
}
......@@ -337,12 +352,12 @@ public class DownloadHelperTest {
downloadHelper.prepare(
new Callback() {
@Override
public void onPrepared(DownloadHelper<?> helper) {
public void onPrepared(DownloadHelper helper) {
preparedCondition.open();
}
@Override
public void onPrepareError(DownloadHelper<?> helper, IOException e) {
public void onPrepareError(DownloadHelper helper, IOException e) {
prepareException.set(e);
preparedCondition.open();
}
......@@ -411,35 +426,52 @@ public class DownloadHelperTest {
assertThat(selectedTracksInGroup).isEqualTo(tracks);
}
private static final class ManifestType {}
private static final class FakeDownloadHelper extends DownloadHelper<ManifestType> {
private static final class FakeDownloadHelper extends DownloadHelper {
public FakeDownloadHelper(Uri testUri, RenderersFactory renderersFactory) {
super(
TEST_DOWNLOAD_TYPE,
testUri,
TEST_CACHE_KEY,
new TestMediaSource(),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
renderersFactory,
/* drmSessionManager= */ null);
}
}
@Override
protected ManifestType loadManifest(Uri uri) throws IOException {
return TEST_MANIFEST;
private static final class TestMediaSource extends FakeMediaSource {
public TestMediaSource() {
super(TEST_TIMELINE, TEST_MANIFEST);
}
@Override
protected TrackGroupArray[] getTrackGroupArrays(ManifestType manifest) {
assertThat(manifest).isEqualTo(TEST_MANIFEST);
return TRACK_GROUP_ARRAYS;
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
int periodIndex = TEST_TIMELINE.getIndexOfPeriod(id.periodUid);
return new FakeMediaPeriod(
TRACK_GROUP_ARRAYS[periodIndex],
new EventDispatcher()
.withParameters(/* windowIndex= */ 0, id, /* mediaTimeOffsetMs= */ 0)) {
@Override
public List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) {
List<StreamKey> result = new ArrayList<>();
for (TrackSelection trackSelection : trackSelections) {
int groupIndex =
TRACK_GROUP_ARRAYS[periodIndex].indexOf(trackSelection.getTrackGroup());
for (int i = 0; i < trackSelection.length(); i++) {
result.add(
new StreamKey(periodIndex, groupIndex, trackSelection.getIndexInTrackGroup(i)));
}
}
return result;
}
};
}
@Override
protected StreamKey toStreamKey(
int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
return new StreamKey(periodIndex, trackGroupIndex, trackIndexInTrackGroup);
public void releasePeriod(MediaPeriod mediaPeriod) {
// Do nothing.
}
}
}
......@@ -17,30 +17,17 @@ package com.google.android.exoplayer2.source.dash.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import java.io.IOException;
import java.util.List;
/** A {@link DownloadHelper} for DASH streams. */
public final class DashDownloadHelper extends DownloadHelper<DashManifest> {
private final DataSource.Factory manifestDataSourceFactory;
public final class DashDownloadHelper extends DownloadHelper {
/**
* Creates a DASH download helper.
......@@ -85,42 +72,9 @@ public final class DashDownloadHelper extends DownloadHelper<DashManifest> {
DownloadAction.TYPE_DASH,
uri,
/* cacheKey= */ null,
new DashMediaSource.Factory(manifestDataSourceFactory).createMediaSource(uri),
trackSelectorParameters,
renderersFactory,
drmSessionManager);
this.manifestDataSourceFactory = manifestDataSourceFactory;
}
@Override
protected DashManifest loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource();
return ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_MANIFEST);
}
@Override
public TrackGroupArray[] getTrackGroupArrays(DashManifest manifest) {
int periodCount = manifest.getPeriodCount();
TrackGroupArray[] trackGroupArrays = new TrackGroupArray[periodCount];
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()];
for (int i = 0; i < trackGroups.length; i++) {
List<Representation> representations = adaptationSets.get(i).representations;
Format[] formats = new Format[representations.size()];
int representationsCount = representations.size();
for (int j = 0; j < representationsCount; j++) {
formats[j] = representations.get(j).format;
}
trackGroups[i] = new TrackGroup(formats);
}
trackGroupArrays[periodIndex] = new TrackGroupArray(trackGroups);
}
return trackGroupArrays;
}
@Override
protected StreamKey toStreamKey(
int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
return new StreamKey(periodIndex, trackGroupIndex, trackIndexInTrackGroup);
}
}
......@@ -17,34 +17,17 @@ package com.google.android.exoplayer2.source.hls.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
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.HlsMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/** A {@link DownloadHelper} for HLS streams. */
public final class HlsDownloadHelper extends DownloadHelper<HlsPlaylist> {
private final DataSource.Factory manifestDataSourceFactory;
private int[] renditionGroups;
public final class HlsDownloadHelper extends DownloadHelper {
/**
* Creates a HLS download helper.
......@@ -89,56 +72,11 @@ public final class HlsDownloadHelper extends DownloadHelper<HlsPlaylist> {
DownloadAction.TYPE_HLS,
uri,
/* cacheKey= */ null,
new HlsMediaSource.Factory(manifestDataSourceFactory)
.setAllowChunklessPreparation(true)
.createMediaSource(uri),
trackSelectorParameters,
renderersFactory,
drmSessionManager);
this.manifestDataSourceFactory = manifestDataSourceFactory;
}
@Override
protected HlsPlaylist loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource();
return ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST);
}
@Override
protected TrackGroupArray[] getTrackGroupArrays(HlsPlaylist playlist) {
Assertions.checkNotNull(playlist);
if (playlist instanceof HlsMediaPlaylist) {
renditionGroups = new int[0];
return new TrackGroupArray[] {TrackGroupArray.EMPTY};
}
// TODO: Generate track groups as in playback. Reverse the mapping in toStreamKey.
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
TrackGroup[] trackGroups = new TrackGroup[3];
renditionGroups = new int[3];
int trackGroupIndex = 0;
if (!masterPlaylist.variants.isEmpty()) {
renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_VARIANT;
trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.variants));
}
if (!masterPlaylist.audios.isEmpty()) {
renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_AUDIO;
trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.audios));
}
if (!masterPlaylist.subtitles.isEmpty()) {
renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_SUBTITLE;
trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.subtitles));
}
return new TrackGroupArray[] {new TrackGroupArray(Arrays.copyOf(trackGroups, trackGroupIndex))};
}
@Override
protected StreamKey toStreamKey(
int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
return new StreamKey(renditionGroups[trackGroupIndex], trackIndexInTrackGroup);
}
private static Format[] toFormats(List<HlsMasterPlaylist.HlsUrl> hlsUrls) {
Format[] formats = new Format[hlsUrls.size()];
for (int i = 0; i < hlsUrls.size(); i++) {
formats[i] = hlsUrls.get(i).format;
}
return formats;
}
}
......@@ -17,27 +17,17 @@ package com.google.android.exoplayer2.source.smoothstreaming.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import java.io.IOException;
/** A {@link DownloadHelper} for SmoothStreaming streams. */
public final class SsDownloadHelper extends DownloadHelper<SsManifest> {
private final DataSource.Factory manifestDataSourceFactory;
public final class SsDownloadHelper extends DownloadHelper {
/**
* Creates a SmoothStreaming download helper.
......@@ -82,32 +72,9 @@ public final class SsDownloadHelper extends DownloadHelper<SsManifest> {
DownloadAction.TYPE_SS,
uri,
/* cacheKey= */ null,
new SsMediaSource.Factory(manifestDataSourceFactory).createMediaSource(uri),
trackSelectorParameters,
renderersFactory,
drmSessionManager);
this.manifestDataSourceFactory = manifestDataSourceFactory;
}
@Override
protected SsManifest loadManifest(Uri uri) throws IOException {
DataSource dataSource = manifestDataSourceFactory.createDataSource();
Uri fixedUri = SsUtil.fixManifestUri(uri);
return ParsingLoadable.load(dataSource, new SsManifestParser(), fixedUri, C.DATA_TYPE_MANIFEST);
}
@Override
protected TrackGroupArray[] getTrackGroupArrays(SsManifest manifest) {
SsManifest.StreamElement[] streamElements = manifest.streamElements;
TrackGroup[] trackGroups = new TrackGroup[streamElements.length];
for (int i = 0; i < streamElements.length; i++) {
trackGroups[i] = new TrackGroup(streamElements[i].formats);
}
return new TrackGroupArray[] {new TrackGroupArray(trackGroups)};
}
@Override
protected StreamKey toStreamKey(
int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
return new StreamKey(trackGroupIndex, trackIndexInTrackGroup);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment