mirror of
https://github.com/androidx/media.git
synced 2025-05-08 08:00:49 +08:00
Make ExoPlaybackException Bundleable
PiperOrigin-RevId: 366938045
This commit is contained in:
parent
9609af3c23
commit
9510ba4d91
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2;
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.RemoteException;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import androidx.annotation.CheckResult;
|
import androidx.annotation.CheckResult;
|
||||||
@ -29,7 +31,7 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
/** Thrown when a non locally recoverable playback failure occurs. */
|
/** Thrown when a non locally recoverable playback failure occurs. */
|
||||||
public final class ExoPlaybackException extends Exception {
|
public final class ExoPlaybackException extends Exception implements Bundleable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of source that produced the error. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER}
|
* The type of source that produced the error. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER}
|
||||||
@ -378,4 +380,136 @@ public final class ExoPlaybackException extends Exception {
|
|||||||
}
|
}
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bundleable implementation.
|
||||||
|
// TODO(b/145954241): Revisit bundling fields when this class is split for Player and ExoPlayer.
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({
|
||||||
|
FIELD_MESSAGE,
|
||||||
|
FIELD_TYPE,
|
||||||
|
FIELD_RENDERER_NAME,
|
||||||
|
FIELD_RENDERER_INDEX,
|
||||||
|
FIELD_RENDERER_FORMAT,
|
||||||
|
FIELD_RENDERER_FORMAT_SUPPORT,
|
||||||
|
FIELD_TIME_STAMP_MS,
|
||||||
|
FIELD_IS_RECOVERABLE,
|
||||||
|
FIELD_CAUSE_CLASS_NAME,
|
||||||
|
FIELD_CAUSE_MESSAGE
|
||||||
|
})
|
||||||
|
private @interface FieldNumber {}
|
||||||
|
|
||||||
|
private static final int FIELD_MESSAGE = 0;
|
||||||
|
private static final int FIELD_TYPE = 1;
|
||||||
|
private static final int FIELD_RENDERER_NAME = 2;
|
||||||
|
private static final int FIELD_RENDERER_INDEX = 3;
|
||||||
|
private static final int FIELD_RENDERER_FORMAT = 4;
|
||||||
|
private static final int FIELD_RENDERER_FORMAT_SUPPORT = 5;
|
||||||
|
private static final int FIELD_TIME_STAMP_MS = 6;
|
||||||
|
private static final int FIELD_IS_RECOVERABLE = 7;
|
||||||
|
private static final int FIELD_CAUSE_CLASS_NAME = 8;
|
||||||
|
private static final int FIELD_CAUSE_MESSAGE = 9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>It omits the {@link #mediaPeriodId} field. The {@link #mediaPeriodId} of an instance
|
||||||
|
* restored by {@link #CREATOR} will always be {@code null}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Bundle toBundle() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString(keyForField(FIELD_MESSAGE), getMessage());
|
||||||
|
bundle.putInt(keyForField(FIELD_TYPE), type);
|
||||||
|
bundle.putString(keyForField(FIELD_RENDERER_NAME), rendererName);
|
||||||
|
bundle.putInt(keyForField(FIELD_RENDERER_INDEX), rendererIndex);
|
||||||
|
bundle.putParcelable(keyForField(FIELD_RENDERER_FORMAT), rendererFormat);
|
||||||
|
bundle.putInt(keyForField(FIELD_RENDERER_FORMAT_SUPPORT), rendererFormatSupport);
|
||||||
|
bundle.putLong(keyForField(FIELD_TIME_STAMP_MS), timestampMs);
|
||||||
|
bundle.putBoolean(keyForField(FIELD_IS_RECOVERABLE), isRecoverable);
|
||||||
|
if (cause != null) {
|
||||||
|
bundle.putString(keyForField(FIELD_CAUSE_CLASS_NAME), cause.getClass().getName());
|
||||||
|
bundle.putString(keyForField(FIELD_CAUSE_MESSAGE), cause.getMessage());
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Object that can restore {@link ExoPlaybackException} from a {@link Bundle}. */
|
||||||
|
public static final Creator<ExoPlaybackException> CREATOR = ExoPlaybackException::fromBundle;
|
||||||
|
|
||||||
|
private static ExoPlaybackException fromBundle(Bundle bundle) {
|
||||||
|
int type = bundle.getInt(keyForField(FIELD_TYPE), /* defaultValue= */ TYPE_UNEXPECTED);
|
||||||
|
@Nullable String rendererName = bundle.getString(keyForField(FIELD_RENDERER_NAME));
|
||||||
|
int rendererIndex =
|
||||||
|
bundle.getInt(keyForField(FIELD_RENDERER_INDEX), /* defaultValue= */ C.INDEX_UNSET);
|
||||||
|
@Nullable Format rendererFormat = bundle.getParcelable(keyForField(FIELD_RENDERER_FORMAT));
|
||||||
|
int rendererFormatSupport =
|
||||||
|
bundle.getInt(
|
||||||
|
keyForField(FIELD_RENDERER_FORMAT_SUPPORT), /* defaultValue= */ C.FORMAT_HANDLED);
|
||||||
|
long timestampMs =
|
||||||
|
bundle.getLong(
|
||||||
|
keyForField(FIELD_TIME_STAMP_MS), /* defaultValue= */ SystemClock.elapsedRealtime());
|
||||||
|
boolean isRecoverable =
|
||||||
|
bundle.getBoolean(keyForField(FIELD_IS_RECOVERABLE), /* defaultValue= */ false);
|
||||||
|
@Nullable String message = bundle.getString(keyForField(FIELD_MESSAGE));
|
||||||
|
if (message == null) {
|
||||||
|
message =
|
||||||
|
deriveMessage(
|
||||||
|
type,
|
||||||
|
/* customMessage= */ null,
|
||||||
|
rendererName,
|
||||||
|
rendererIndex,
|
||||||
|
rendererFormat,
|
||||||
|
rendererFormatSupport);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable String causeClassName = bundle.getString(keyForField(FIELD_CAUSE_CLASS_NAME));
|
||||||
|
@Nullable String causeMessage = bundle.getString(keyForField(FIELD_CAUSE_MESSAGE));
|
||||||
|
@Nullable Throwable cause = null;
|
||||||
|
if (!TextUtils.isEmpty(causeClassName)) {
|
||||||
|
final Class<?> clazz;
|
||||||
|
try {
|
||||||
|
clazz =
|
||||||
|
Class.forName(
|
||||||
|
causeClassName,
|
||||||
|
/* initialize= */ true,
|
||||||
|
ExoPlaybackException.class.getClassLoader());
|
||||||
|
if (Throwable.class.isAssignableFrom(clazz)) {
|
||||||
|
cause = createThrowable(clazz, causeMessage);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// Intentionally catch Throwable to catch both Exception and Error.
|
||||||
|
cause = createRemoteException(causeMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ExoPlaybackException(
|
||||||
|
message,
|
||||||
|
cause,
|
||||||
|
type,
|
||||||
|
rendererName,
|
||||||
|
rendererIndex,
|
||||||
|
rendererFormat,
|
||||||
|
rendererFormatSupport,
|
||||||
|
/* mediaPeriodId= */ null,
|
||||||
|
timestampMs,
|
||||||
|
isRecoverable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new {@link Throwable} with possibly @{code null} message.
|
||||||
|
@SuppressWarnings("nullness:argument.type.incompatible")
|
||||||
|
private static Throwable createThrowable(Class<?> throwableClazz, @Nullable String message)
|
||||||
|
throws Exception {
|
||||||
|
return (Throwable) throwableClazz.getConstructor(String.class).newInstance(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new {@link RemoteException} with possibly {@code null} message.
|
||||||
|
@SuppressWarnings("nullness:argument.type.incompatible")
|
||||||
|
private static RemoteException createRemoteException(@Nullable String message) {
|
||||||
|
return new RemoteException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String keyForField(@FieldNumber int field) {
|
||||||
|
return Integer.toString(field, Character.MAX_RADIX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit tests for {@link ExoPlaybackException}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExoPlaybackExceptionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundtripViaBundle_ofExoPlaybackExceptionTypeRemote_yieldsEqualInstance() {
|
||||||
|
ExoPlaybackException before = ExoPlaybackException.createForRemote(/* message= */ "test");
|
||||||
|
ExoPlaybackException after = ExoPlaybackException.CREATOR.fromBundle(before.toBundle());
|
||||||
|
assertThat(areEqual(before, after)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundtripViaBundle_ofExoPlaybackExceptionTypeRenderer_yieldsEqualInstance() {
|
||||||
|
ExoPlaybackException before =
|
||||||
|
ExoPlaybackException.createForRenderer(
|
||||||
|
new IllegalStateException("ExoPlaybackExceptionTest"),
|
||||||
|
/* rendererName= */ "rendererName",
|
||||||
|
/* rendererIndex= */ 123,
|
||||||
|
/* rendererFormat= */ new Format.Builder().setCodecs("anyCodec").build(),
|
||||||
|
/* rendererFormatSupport= */ C.FORMAT_UNSUPPORTED_SUBTYPE,
|
||||||
|
/* isRecoverable= */ true);
|
||||||
|
|
||||||
|
ExoPlaybackException after = ExoPlaybackException.CREATOR.fromBundle(before.toBundle());
|
||||||
|
assertThat(areEqual(before, after)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
roundtripViaBundle_ofExoPlaybackExceptionTypeRendererWithPrivateCause_yieldsRemoteExceptionWithSameMessage() {
|
||||||
|
ExoPlaybackException before =
|
||||||
|
ExoPlaybackException.createForRenderer(
|
||||||
|
new Exception(/* message= */ "anonymous exception that class loader cannot know") {});
|
||||||
|
ExoPlaybackException after = ExoPlaybackException.CREATOR.fromBundle(before.toBundle());
|
||||||
|
|
||||||
|
assertThat(after.getCause()).isInstanceOf(RemoteException.class);
|
||||||
|
assertThat(after.getCause()).hasMessageThat().isEqualTo(before.getCause().getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean areEqual(ExoPlaybackException a, ExoPlaybackException b) {
|
||||||
|
if (a == null || b == null) {
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
return Util.areEqual(a.getMessage(), b.getMessage())
|
||||||
|
&& a.type == b.type
|
||||||
|
&& Util.areEqual(a.rendererName, b.rendererName)
|
||||||
|
&& a.rendererIndex == b.rendererIndex
|
||||||
|
&& Util.areEqual(a.rendererFormat, b.rendererFormat)
|
||||||
|
&& a.rendererFormatSupport == b.rendererFormatSupport
|
||||||
|
&& a.timestampMs == b.timestampMs
|
||||||
|
&& a.isRecoverable == b.isRecoverable
|
||||||
|
&& areEqual(a.getCause(), b.getCause());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean areEqual(Throwable a, Throwable b) {
|
||||||
|
if (a == null || b == null) {
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
return a.getClass() == b.getClass() && Util.areEqual(a.getMessage(), b.getMessage());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user