mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Offline DRM in main demo app
PiperOrigin-RevId: 325413035
This commit is contained in:
parent
c7a1151c2b
commit
a5e6e3054d
@ -168,6 +168,7 @@
|
||||
[#6725](https://github.com/google/ExoPlayer/issues/6725),
|
||||
[#7066](https://github.com/google/ExoPlayer/issues/7066)).
|
||||
* Downloads and caching:
|
||||
* Add support for offline DRM playbacks.
|
||||
* Add builder in `DownloadRequest`.
|
||||
* Support passing an `Executor` to `DefaultDownloaderFactory` on which
|
||||
data downloads are performed.
|
||||
|
@ -93,7 +93,7 @@ public final class DemoUtil {
|
||||
}
|
||||
|
||||
/** Returns a {@link DataSource.Factory}. */
|
||||
public static synchronized DataSource.Factory buildDataSourceFactory(Context context) {
|
||||
public static synchronized DataSource.Factory getDataSourceFactory(Context context) {
|
||||
if (dataSourceFactory == null) {
|
||||
context = context.getApplicationContext();
|
||||
DefaultDataSourceFactory upstreamFactory =
|
||||
@ -151,7 +151,7 @@ public final class DemoUtil {
|
||||
getHttpDataSourceFactory(context),
|
||||
Executors.newFixedThreadPool(/* nThreads= */ 6));
|
||||
downloadTracker =
|
||||
new DownloadTracker(context, buildDataSourceFactory(context), downloadManager);
|
||||
new DownloadTracker(context, getHttpDataSourceFactory(context), downloadManager);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,9 +23,15 @@ import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.RenderersFactory;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
import com.google.android.exoplayer2.drm.DrmSession;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||
import com.google.android.exoplayer2.drm.OfflineLicenseHelper;
|
||||
import com.google.android.exoplayer2.offline.Download;
|
||||
import com.google.android.exoplayer2.offline.DownloadCursor;
|
||||
import com.google.android.exoplayer2.offline.DownloadHelper;
|
||||
@ -33,9 +39,11 @@ import com.google.android.exoplayer2.offline.DownloadIndex;
|
||||
import com.google.android.exoplayer2.offline.DownloadManager;
|
||||
import com.google.android.exoplayer2.offline.DownloadRequest;
|
||||
import com.google.android.exoplayer2.offline.DownloadService;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
@ -55,7 +63,7 @@ public class DownloadTracker {
|
||||
private static final String TAG = "DownloadTracker";
|
||||
|
||||
private final Context context;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final HttpDataSource.Factory httpDataSourceFactory;
|
||||
private final CopyOnWriteArraySet<Listener> listeners;
|
||||
private final HashMap<Uri, Download> downloads;
|
||||
private final DownloadIndex downloadIndex;
|
||||
@ -64,9 +72,11 @@ public class DownloadTracker {
|
||||
@Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
|
||||
|
||||
public DownloadTracker(
|
||||
Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
|
||||
Context context,
|
||||
HttpDataSource.Factory httpDataSourceFactory,
|
||||
DownloadManager downloadManager) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.httpDataSourceFactory = httpDataSourceFactory;
|
||||
listeners = new CopyOnWriteArraySet<>();
|
||||
downloads = new HashMap<>();
|
||||
downloadIndex = downloadManager.getDownloadIndex();
|
||||
@ -89,6 +99,7 @@ public class DownloadTracker {
|
||||
return download != null && download.state != Download.STATE_FAILED;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DownloadRequest getDownloadRequest(Uri uri) {
|
||||
Download download = downloads.get(uri);
|
||||
return download != null && download.state != Download.STATE_FAILED ? download.request : null;
|
||||
@ -107,7 +118,8 @@ public class DownloadTracker {
|
||||
startDownloadDialogHelper =
|
||||
new StartDownloadDialogHelper(
|
||||
fragmentManager,
|
||||
DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, dataSourceFactory),
|
||||
DownloadHelper.forMediaItem(
|
||||
context, mediaItem, renderersFactory, httpDataSourceFactory),
|
||||
mediaItem);
|
||||
}
|
||||
}
|
||||
@ -157,6 +169,7 @@ public class DownloadTracker {
|
||||
|
||||
private TrackSelectionDialog trackSelectionDialog;
|
||||
private MappedTrackInfo mappedTrackInfo;
|
||||
@Nullable private byte[] keySetId;
|
||||
|
||||
public StartDownloadDialogHelper(
|
||||
FragmentManager fragmentManager, DownloadHelper downloadHelper, MediaItem mediaItem) {
|
||||
@ -177,12 +190,43 @@ public class DownloadTracker {
|
||||
|
||||
@Override
|
||||
public void onPrepared(@NonNull DownloadHelper helper) {
|
||||
@Nullable DrmInitData drmInitData = findDrmInitData(helper);
|
||||
if (drmInitData != null) {
|
||||
if (Util.SDK_INT < 18) {
|
||||
Toast.makeText(context, R.string.error_drm_unsupported_before_api_18, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
Log.e(TAG, "Downloading DRM protected content is not supported on API versions below 18");
|
||||
return;
|
||||
}
|
||||
// TODO(internal b/163107948): Support cases where DrmInitData are not in the manifest.
|
||||
if (!hasSchemaData(drmInitData)) {
|
||||
Toast.makeText(context, R.string.download_start_error_offline_license, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
Log.e(
|
||||
TAG,
|
||||
"Downloading content where DRM scheme data is not located in the manifest is not"
|
||||
+ " supported");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// TODO(internal b/163107948): Download the license on another thread to keep the UI
|
||||
// thread unblocked.
|
||||
fetchOfflineLicense(drmInitData);
|
||||
} catch (DrmSession.DrmSessionException e) {
|
||||
Toast.makeText(context, R.string.download_start_error_offline_license, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
Log.e(TAG, "Failed to fetch offline DRM license", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (helper.getPeriodCount() == 0) {
|
||||
Log.d(TAG, "No periods found. Downloading entire stream.");
|
||||
startDownload();
|
||||
downloadHelper.release();
|
||||
return;
|
||||
}
|
||||
|
||||
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
|
||||
if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) {
|
||||
Log.d(TAG, "No dialog content. Downloading entire stream.");
|
||||
@ -257,8 +301,59 @@ public class DownloadTracker {
|
||||
}
|
||||
|
||||
private DownloadRequest buildDownloadRequest() {
|
||||
return downloadHelper.getDownloadRequest(
|
||||
Util.getUtf8Bytes(checkNotNull(mediaItem.mediaMetadata.title)));
|
||||
return downloadHelper
|
||||
.getDownloadRequest(Util.getUtf8Bytes(checkNotNull(mediaItem.mediaMetadata.title)))
|
||||
.copyWithKeySetId(keySetId);
|
||||
}
|
||||
|
||||
@RequiresApi(18)
|
||||
private void fetchOfflineLicense(DrmInitData drmInitData)
|
||||
throws DrmSession.DrmSessionException {
|
||||
OfflineLicenseHelper offlineLicenseHelper =
|
||||
OfflineLicenseHelper.newWidevineInstance(
|
||||
mediaItem.playbackProperties.drmConfiguration.licenseUri.toString(),
|
||||
httpDataSourceFactory,
|
||||
new DrmSessionEventListener.EventDispatcher());
|
||||
keySetId = offlineLicenseHelper.downloadLicense(drmInitData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether any the {@link DrmInitData.SchemeData} contained in {@code drmInitData} has
|
||||
* non-null {@link DrmInitData.SchemeData#data}.
|
||||
*/
|
||||
private static boolean hasSchemaData(DrmInitData drmInitData) {
|
||||
for (int i = 0; i < drmInitData.schemeDataCount; i++) {
|
||||
if (drmInitData.get(i).hasData()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first non-null {@link DrmInitData} found in the content's tracks, or null if no
|
||||
* {@link DrmInitData} are found.
|
||||
*/
|
||||
@Nullable
|
||||
private DrmInitData findDrmInitData(DownloadHelper helper) {
|
||||
for (int periodIndex = 0; periodIndex < helper.getPeriodCount(); periodIndex++) {
|
||||
MappedTrackInfo mappedTrackInfo = helper.getMappedTrackInfo(periodIndex);
|
||||
for (int rendererIndex = 0;
|
||||
rendererIndex < mappedTrackInfo.getRendererCount();
|
||||
rendererIndex++) {
|
||||
TrackGroupArray trackGroups = mappedTrackInfo.getTrackGroups(rendererIndex);
|
||||
for (int trackGroupIndex = 0; trackGroupIndex < trackGroups.length; trackGroupIndex++) {
|
||||
TrackGroup trackGroup = trackGroups.get(trackGroupIndex);
|
||||
for (int formatIndex = 0; formatIndex < trackGroup.length; formatIndex++) {
|
||||
Format format = trackGroup.getFormat(formatIndex);
|
||||
if (format.drmInitData != null) {
|
||||
return format.drmInitData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Intent intent = getIntent();
|
||||
super.onCreate(savedInstanceState);
|
||||
dataSourceFactory = buildDataSourceFactory();
|
||||
dataSourceFactory = DemoUtil.getDataSourceFactory(/* context= */ this);
|
||||
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
|
||||
CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
|
||||
}
|
||||
@ -405,11 +405,6 @@ public class PlayerActivity extends AppCompatActivity
|
||||
startPosition = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
/** Returns a new DataSource factory. */
|
||||
protected DataSource.Factory buildDataSourceFactory() {
|
||||
return DemoUtil.buildDataSourceFactory(/* context= */ this);
|
||||
}
|
||||
|
||||
// User controls
|
||||
|
||||
private void updateButtonVisibility() {
|
||||
|
@ -253,9 +253,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||
}
|
||||
MediaItem.PlaybackProperties playbackProperties =
|
||||
checkNotNull(playlistHolder.mediaItems.get(0).playbackProperties);
|
||||
if (playbackProperties.drmConfiguration != null) {
|
||||
return R.string.download_drm_unsupported;
|
||||
}
|
||||
if (((IntentUtil.Tag) checkNotNull(playbackProperties.tag)).isLive) {
|
||||
return R.string.download_live_unsupported;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
<string name="error_unrecognized_stereo_mode">Unrecognized stereo mode</string>
|
||||
|
||||
<string name="error_drm_unsupported_before_api_18">Protected content not supported on API levels below 18</string>
|
||||
<string name="error_drm_unsupported_before_api_18">DRM content not supported on API levels below 18</string>
|
||||
|
||||
<string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string>
|
||||
|
||||
@ -55,9 +55,9 @@
|
||||
|
||||
<string name="download_start_error">Failed to start download</string>
|
||||
|
||||
<string name="download_playlist_unsupported">This demo app does not support downloading playlists</string>
|
||||
<string name="download_start_error_offline_license">Failed to obtain offline license</string>
|
||||
|
||||
<string name="download_drm_unsupported">This demo app does not support downloading protected content</string>
|
||||
<string name="download_playlist_unsupported">This demo app does not support downloading playlists</string>
|
||||
|
||||
<string name="download_scheme_unsupported">This demo app only supports downloading http streams</string>
|
||||
|
||||
|
@ -743,13 +743,17 @@ public final class DownloadHelper {
|
||||
* @return The built {@link DownloadRequest}.
|
||||
*/
|
||||
public DownloadRequest getDownloadRequest(String id, @Nullable byte[] data) {
|
||||
if (mediaSource == null) {
|
||||
// TODO: add support for DRM (keySetId) [Internal ref: b/158980798]
|
||||
return new DownloadRequest.Builder(id, playbackProperties.uri)
|
||||
DownloadRequest.Builder requestBuilder =
|
||||
new DownloadRequest.Builder(id, playbackProperties.uri)
|
||||
.setMimeType(playbackProperties.mimeType)
|
||||
.setKeySetId(
|
||||
playbackProperties.drmConfiguration != null
|
||||
? playbackProperties.drmConfiguration.getKeySetId()
|
||||
: null)
|
||||
.setCustomCacheKey(playbackProperties.customCacheKey)
|
||||
.setData(data)
|
||||
.build();
|
||||
.setData(data);
|
||||
if (mediaSource == null) {
|
||||
return requestBuilder.build();
|
||||
}
|
||||
assertPreparedWithMedia();
|
||||
List<StreamKey> streamKeys = new ArrayList<>();
|
||||
@ -763,13 +767,7 @@ public final class DownloadHelper {
|
||||
}
|
||||
streamKeys.addAll(mediaPreparer.mediaPeriods[periodIndex].getStreamKeys(allSelections));
|
||||
}
|
||||
// TODO: add support for DRM (keySetId) [Internal ref: b/158980798]
|
||||
return new DownloadRequest.Builder(id, playbackProperties.uri)
|
||||
.setMimeType(playbackProperties.mimeType)
|
||||
.setStreamKeys(streamKeys)
|
||||
.setCustomCacheKey(playbackProperties.customCacheKey)
|
||||
.setData(data)
|
||||
.build();
|
||||
return requestBuilder.setStreamKeys(streamKeys).build();
|
||||
}
|
||||
|
||||
// Initialization of array of Lists.
|
||||
|
@ -174,6 +174,16 @@ public final class DownloadRequest implements Parcelable {
|
||||
return new DownloadRequest(id, uri, mimeType, streamKeys, keySetId, customCacheKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with the specified key set ID.
|
||||
*
|
||||
* @param keySetId The key set ID of the copy.
|
||||
* @return The copy with the specified key set ID.
|
||||
*/
|
||||
public DownloadRequest copyWithKeySetId(@Nullable byte[] keySetId) {
|
||||
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}.
|
||||
|
Loading…
x
Reference in New Issue
Block a user