Skip to content

Commit

Permalink
Provides thread-safe usage of LoadEventInfo in HttpMediaDrmCallback
Browse files Browse the repository at this point in the history
1. Restore creation of `LoadEventInfo` to the code that was migrated to `DrmUtil.executePost()`
   that was removed in the rebase.
2. Refactor change signature of `MediaDrmCallback` to return both the byte[] response and an
   assoicated `LoadEventInfo`
  • Loading branch information
stevemayhew committed Oct 4, 2024
1 parent d7d54f4 commit 4a00318
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import androidx.media3.exoplayer.drm.ExoMediaDrm.KeyRequest;
import androidx.media3.exoplayer.drm.ExoMediaDrm.ProvisionRequest;
import androidx.media3.exoplayer.drm.KeyRequestInfo.Builder;
import androidx.media3.exoplayer.drm.MediaDrmCallback.KeyResponse;
import androidx.media3.exoplayer.source.LoadEventInfo;
import androidx.media3.exoplayer.source.MediaLoadData;
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
Expand Down Expand Up @@ -668,14 +669,16 @@ public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_PROVISION:
response =
callback.executeProvisionRequest(uuid, (ProvisionRequest) requestTask.request);
callback.executeProvisionRequest(uuid, (ProvisionRequest) requestTask.request)
.responseData;
break;
case MSG_KEYS:
response = callback.executeKeyRequest(uuid, (KeyRequest) requestTask.request);
KeyResponse keyResponse =
callback.executeKeyRequest(uuid, (KeyRequest) requestTask.request);
response = keyResponse.responseData;
if (currentKeyRequestInfo != null) {
LoadEventInfo loadEventInfo = callback.getLastLoadEventInfo();
loadEventInfo =
loadEventInfo != null ? loadEventInfo.copyWithTaskId(requestTask.taskId) : null;
LoadEventInfo loadEventInfo =
keyResponse.loadEventInfo.copyWithTaskId(requestTask.taskId);
currentKeyRequestInfo.setMainLoadRequest(loadEventInfo);
}
break;
Expand Down Expand Up @@ -733,7 +736,9 @@ private boolean maybeRetryRequest(Message originalMsg, MediaDrmCallbackException
// The error is fatal.
return false;
}
currentKeyRequestInfo.addRetryLoadRequest(loadEventInfo);
if (currentKeyRequestInfo != null) {
currentKeyRequestInfo.addRetryLoadRequest(loadEventInfo);
}
synchronized (this) {
if (!isReleased) {
sendMessageDelayed(Message.obtain(originalMsg), retryDelayMs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import android.media.MediaDrmResetException;
import android.media.NotProvisionedException;
import android.media.ResourceBusyException;
import android.os.SystemClock;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
Expand All @@ -38,6 +39,8 @@
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.StatsDataSource;
import androidx.media3.exoplayer.drm.MediaDrmCallback.KeyResponse;
import androidx.media3.exoplayer.source.LoadEventInfo;
import com.google.common.io.ByteStreams;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
Expand Down Expand Up @@ -150,10 +153,10 @@ public static boolean isFailureToConstructResourceBusyException(@Nullable Throwa
* @param url The requested URL.
* @param httpBody The HTTP request payload.
* @param requestProperties A keyed map of HTTP header request properties.
* @return A byte array that holds the response payload.
* @return A {@link KeyResponse} that holds the response payload, and LoadEventInfo
* @throws MediaDrmCallbackException if an exception was encountered during the download.
*/
public static byte[] executePost(
public static KeyResponse executePost(
DataSource dataSource,
String url,
@Nullable byte[] httpBody,
Expand All @@ -170,11 +173,22 @@ public static byte[] executePost(
.setFlags(DataSpec.FLAG_ALLOW_GZIP)
.build();
DataSpec originalDataSpec = dataSpec;
long startTimeMs = SystemClock.elapsedRealtime();
try {
while (true) {
DataSourceInputStream inputStream = new DataSourceInputStream(statsDataSource, dataSpec);
try {
return ByteStreams.toByteArray(inputStream);
byte[] response = ByteStreams.toByteArray(inputStream);
LoadEventInfo loadEventInfo =
new LoadEventInfo(
-1, // note this is replaced with the actual taskId from the request
originalDataSpec,
statsDataSource.getLastOpenedUri(),
statsDataSource.getLastResponseHeaders(),
SystemClock.elapsedRealtime(),
/* loadDurationMs= */ SystemClock.elapsedRealtime() - startTimeMs,
((byte[]) response).length);
return new KeyResponse(response, loadEventInfo);
} catch (HttpDataSource.InvalidResponseCodeException e) {
@Nullable String redirectUrl = getRedirectUrl(e, manualRedirectCount);
if (redirectUrl == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void clearAllKeyRequestProperties() {
}

@Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request)
public KeyResponse executeProvisionRequest(UUID uuid, ProvisionRequest request)
throws MediaDrmCallbackException {
String url =
request.getDefaultUrl() + "&signedRequest=" + Util.fromUtf8Bytes(request.getData());
Expand All @@ -125,7 +125,8 @@ public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request)
}

@Override
public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws MediaDrmCallbackException {
public KeyResponse executeKeyRequest(UUID uuid, KeyRequest request)
throws MediaDrmCallbackException {
String url = request.getLicenseServerUrl();
if (forceDefaultLicenseUrl || TextUtils.isEmpty(url)) {
url = defaultLicenseUrl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@

import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DataSpec;
import androidx.media3.exoplayer.drm.ExoMediaDrm.KeyRequest;
import androidx.media3.exoplayer.drm.ExoMediaDrm.ProvisionRequest;
import androidx.media3.exoplayer.source.LoadEventInfo;
import java.util.UUID;

/**
Expand All @@ -40,12 +42,12 @@ public LocalMediaDrmCallback(byte[] keyResponse) {
}

@Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) {
public KeyResponse executeProvisionRequest(UUID uuid, ProvisionRequest request) {
throw new UnsupportedOperationException();
}

@Override
public byte[] executeKeyRequest(UUID uuid, KeyRequest request) {
return keyResponse;
public KeyResponse executeKeyRequest(UUID uuid, KeyRequest request) {
return new KeyResponse(keyResponse, new LoadEventInfo(-1, new DataSpec.Builder().build(), 0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package androidx.media3.exoplayer.drm;

import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.drm.ExoMediaDrm.KeyRequest;
import androidx.media3.exoplayer.drm.ExoMediaDrm.ProvisionRequest;
Expand All @@ -26,39 +25,41 @@
@UnstableApi
public interface MediaDrmCallback {

/**
* Response data from the {@link MediaDrmCallback} requests.
*
* <p>Encapsulates the license server response data {@link #responseData} along with information
* ({@link #loadEventInfo} about the network transfer (if any) that was issued to gather the
* response.
*/
public class KeyResponse {
public final byte[] responseData;
public final LoadEventInfo loadEventInfo;

public KeyResponse(byte[] responseData, LoadEventInfo loadEventInfo) {
this.responseData = responseData;
this.loadEventInfo = loadEventInfo;
}
}

/**
* Executes a provisioning request.
*
* @param uuid The UUID of the content protection scheme.
* @param request The request.
* @return The response data.
* @return A {@link KeyResponse} that holds the response payload, and LoadEventInfo
* @throws MediaDrmCallbackException If an error occurred executing the request.
*/
byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request)
KeyResponse executeProvisionRequest(UUID uuid, ProvisionRequest request)
throws MediaDrmCallbackException;

/**
* Executes a key request.
*
* @param uuid The UUID of the content protection scheme.
* @param request The request.
* @return The response data.
* @return A {@link KeyResponse} that holds the response payload, and LoadEventInfo
* @throws MediaDrmCallbackException If an error occurred executing the request.
*/
byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws MediaDrmCallbackException;

/**
* Get the {@link LoadEventInfo} for the last executed request.
*
* <p>Valid after a call to {@link #executeKeyRequest(UUID, KeyRequest)} or {@link
* #executeProvisionRequest(UUID, ProvisionRequest)}, either contains the load event info for that
* request or null if no load was performed or this implementation does not support reporting load
* info
*
* @return the {@link LoadEventInfo} or null if no load was performed
*/
@Nullable
default LoadEventInfo getLastLoadEventInfo() {
return null;
}
KeyResponse executeKeyRequest(UUID uuid, KeyRequest request) throws MediaDrmCallbackException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import static org.robolectric.shadows.ShadowLooper.runMainLooperToNextTask;

import android.graphics.SurfaceTexture;
import android.net.Uri;
import android.os.Looper;
import android.util.SparseArray;
import android.view.Surface;
Expand All @@ -96,6 +97,7 @@
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSpec;
import androidx.media3.exoplayer.DecoderCounters;
import androidx.media3.exoplayer.DecoderReuseEvaluation;
import androidx.media3.exoplayer.ExoPlaybackException;
Expand All @@ -106,6 +108,8 @@
import androidx.media3.exoplayer.drm.DrmSession;
import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.drm.ExoMediaDrm;
import androidx.media3.exoplayer.drm.ExoMediaDrm.KeyRequest;
import androidx.media3.exoplayer.drm.ExoMediaDrm.ProvisionRequest;
import androidx.media3.exoplayer.drm.MediaDrmCallback;
import androidx.media3.exoplayer.drm.MediaDrmCallbackException;
import androidx.media3.exoplayer.source.LoadEventInfo;
Expand All @@ -131,6 +135,7 @@
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
Expand Down Expand Up @@ -190,6 +195,15 @@ public final class DefaultAnalyticsCollectorTest {
private EventWindowAndPeriodId period1Seq2;
private EventWindowAndPeriodId window0Period1Seq0;
private EventWindowAndPeriodId window1Period0Seq1;
private static final LoadEventInfo FAKE_LOAD_EVENT_INFO =
new LoadEventInfo(
1,
new DataSpec.Builder().setUri(Uri.EMPTY).build(),
Uri.EMPTY,
Collections.emptyMap(),
1000,
2000,
8192);

/**
* Verify that {@link DefaultAnalyticsCollector} explicitly overrides all {@link Player.Listener}
Expand Down Expand Up @@ -2402,13 +2416,13 @@ public String toString() {
*/
private static final class EmptyDrmCallback implements MediaDrmCallback {
@Override
public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request) {
return new byte[0];
public KeyResponse executeProvisionRequest(UUID uuid, ProvisionRequest request) {
return new KeyResponse(new byte[0], FAKE_LOAD_EVENT_INFO);
}

@Override
public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request) {
return new byte[0];
public KeyResponse executeKeyRequest(UUID uuid, KeyRequest request) {
return new KeyResponse(new byte[0], FAKE_LOAD_EVENT_INFO);
}
}

Expand Down Expand Up @@ -2442,26 +2456,26 @@ public static BlockingDrmCallback alwaysFailing() {
}

@Override
public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request)
public KeyResponse executeProvisionRequest(UUID uuid, ProvisionRequest request)
throws MediaDrmCallbackException {
provisionCondition.blockUninterruptible();
provisionCondition.close();
if (alwaysFail) {
throw new RuntimeException("executeProvisionRequest failed");
} else {
return new byte[0];
return new KeyResponse(new byte[0], FAKE_LOAD_EVENT_INFO);
}
}

@Override
public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request)
public KeyResponse executeKeyRequest(UUID uuid, KeyRequest request)
throws MediaDrmCallbackException {
keyCondition.blockUninterruptible();
keyCondition.close();
if (alwaysFail) {
throw new RuntimeException("executeKeyRequest failed");
} else {
return new byte[0];
return new KeyResponse(new byte[0], FAKE_LOAD_EVENT_INFO);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package androidx.media3.exoplayer.drm;

import static androidx.media3.test.utils.FakeExoMediaDrm.FAKE_LOAD_EVENT_INFO;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
Expand All @@ -27,6 +28,7 @@
import androidx.media3.common.DrmInitData.SchemeData;
import androidx.media3.common.Format;
import androidx.media3.exoplayer.drm.ExoMediaDrm.KeyRequest;
import androidx.media3.exoplayer.drm.MediaDrmCallback.KeyResponse;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.HashMap;
import org.junit.After;
Expand Down Expand Up @@ -61,6 +63,8 @@ public void setUp() throws Exception {
C.WIDEVINE_UUID, new ExoMediaDrm.AppManagedProvider(mediaDrm))
.build(mediaDrmCallback),
new DrmSessionEventListener.EventDispatcher());
when(mediaDrmCallback.executeKeyRequest(any(), any()))
.thenReturn(new KeyResponse(new byte[0], FAKE_LOAD_EVENT_INFO));
}

@After
Expand Down
Loading

0 comments on commit 4a00318

Please sign in to comment.