diff --git a/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java index 67f622d89f..7875477df3 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -17,11 +17,13 @@ package com.google.android.exoplayer2.text; import android.graphics.Bitmap; import android.graphics.Color; +import android.os.Bundle; import android.text.Layout; import android.text.Layout.Alignment; import androidx.annotation.ColorInt; import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.util.Assertions; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -31,7 +33,7 @@ import java.lang.annotation.RetentionPolicy; // This class shouldn't be sub-classed. If a subtitle format needs additional fields, either they // should be generic enough to be added here, or the format-specific decoder should pass the // information around in a sidecar object. -public final class Cue { +public final class Cue implements Bundleable { /** The empty cue. */ public static final Cue EMPTY = new Cue.Builder().setText("").build(); @@ -859,4 +861,139 @@ public final class Cue { shearDegrees); } } + + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_TEXT, + FIELD_TEXT_ALIGNMENT, + FIELD_MULTI_ROW_ALIGNMENT, + FIELD_BITMAP, + FIELD_LINE, + FIELD_LINE_TYPE, + FIELD_LINE_ANCHOR, + FIELD_POSITION, + FIELD_POSITION_ANCHOR, + FIELD_TEXT_SIZE_TYPE, + FIELD_TEXT_SIZE, + FIELD_SIZE, + FIELD_BITMAP_HEIGHT, + FIELD_WINDOW_COLOR, + FIELD_WINDOW_COLOR_SET, + FIELD_VERTICAL_TYPE, + FIELD_SHEAR_DEGREES + }) + private @interface FieldNumber {} + + private static final int FIELD_TEXT = 0; + private static final int FIELD_TEXT_ALIGNMENT = 1; + private static final int FIELD_MULTI_ROW_ALIGNMENT = 2; + private static final int FIELD_BITMAP = 3; + private static final int FIELD_LINE = 4; + private static final int FIELD_LINE_TYPE = 5; + private static final int FIELD_LINE_ANCHOR = 6; + private static final int FIELD_POSITION = 7; + private static final int FIELD_POSITION_ANCHOR = 8; + private static final int FIELD_TEXT_SIZE_TYPE = 9; + private static final int FIELD_TEXT_SIZE = 10; + private static final int FIELD_SIZE = 11; + private static final int FIELD_BITMAP_HEIGHT = 12; + private static final int FIELD_WINDOW_COLOR = 13; + private static final int FIELD_WINDOW_COLOR_SET = 14; + private static final int FIELD_VERTICAL_TYPE = 15; + private static final int FIELD_SHEAR_DEGREES = 16; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putCharSequence(keyForField(FIELD_TEXT), text); + bundle.putSerializable(keyForField(FIELD_TEXT_ALIGNMENT), textAlignment); + bundle.putSerializable(keyForField(FIELD_MULTI_ROW_ALIGNMENT), multiRowAlignment); + // TODO(b/187804381): Use Util.scaleDownBitmap when it's added + bundle.putParcelable(keyForField(FIELD_BITMAP), bitmap); + bundle.putFloat(keyForField(FIELD_LINE), line); + bundle.putInt(keyForField(FIELD_LINE_TYPE), lineType); + bundle.putInt(keyForField(FIELD_LINE_ANCHOR), lineAnchor); + bundle.putFloat(keyForField(FIELD_POSITION), position); + bundle.putInt(keyForField(FIELD_POSITION_ANCHOR), positionAnchor); + bundle.putInt(keyForField(FIELD_TEXT_SIZE_TYPE), textSizeType); + bundle.putFloat(keyForField(FIELD_TEXT_SIZE), textSize); + bundle.putFloat(keyForField(FIELD_SIZE), size); + bundle.putFloat(keyForField(FIELD_BITMAP_HEIGHT), bitmapHeight); + bundle.putBoolean(keyForField(FIELD_WINDOW_COLOR_SET), windowColorSet); + bundle.putInt(keyForField(FIELD_WINDOW_COLOR), windowColor); + bundle.putInt(keyForField(FIELD_VERTICAL_TYPE), verticalType); + bundle.putFloat(keyForField(FIELD_SHEAR_DEGREES), shearDegrees); + return bundle; + } + + public static final Creator CREATOR = Cue::fromBundle; + + private static final Cue fromBundle(Bundle bundle) { + Builder builder = new Builder(); + @Nullable CharSequence text = bundle.getCharSequence(keyForField(FIELD_TEXT)); + if (text != null) { + builder.setText(text); + } + @Nullable + Alignment textAlignment = (Alignment) bundle.getSerializable(keyForField(FIELD_TEXT_ALIGNMENT)); + if (textAlignment != null) { + builder.setTextAlignment(textAlignment); + } + @Nullable + Alignment multiRowAlignment = + (Alignment) bundle.getSerializable(keyForField(FIELD_MULTI_ROW_ALIGNMENT)); + if (multiRowAlignment != null) { + builder.setMultiRowAlignment(multiRowAlignment); + } + @Nullable Bitmap bitmap = bundle.getParcelable(keyForField(FIELD_BITMAP)); + if (bitmap != null) { + builder.setBitmap(bitmap); + } + if (bundle.containsKey(keyForField(FIELD_LINE)) + && bundle.containsKey(keyForField(FIELD_LINE_TYPE))) { + builder.setLine( + bundle.getFloat(keyForField(FIELD_LINE)), bundle.getInt(keyForField(FIELD_LINE_TYPE))); + } + if (bundle.containsKey(keyForField(FIELD_LINE_ANCHOR))) { + builder.setLineAnchor(bundle.getInt(keyForField(FIELD_LINE_ANCHOR))); + } + if (bundle.containsKey(keyForField(FIELD_POSITION))) { + builder.setPosition(bundle.getFloat(keyForField(FIELD_POSITION))); + } + if (bundle.containsKey(keyForField(FIELD_POSITION_ANCHOR))) { + builder.setPositionAnchor(bundle.getInt(keyForField(FIELD_POSITION_ANCHOR))); + } + if (bundle.containsKey(keyForField(FIELD_TEXT_SIZE)) + && bundle.containsKey(keyForField(FIELD_TEXT_SIZE_TYPE))) { + builder.setTextSize( + bundle.getFloat(keyForField(FIELD_TEXT_SIZE)), + bundle.getInt(keyForField(FIELD_TEXT_SIZE_TYPE))); + } + if (bundle.containsKey(keyForField(FIELD_SIZE))) { + builder.setSize(bundle.getFloat(keyForField(FIELD_SIZE))); + } + if (bundle.containsKey(keyForField(FIELD_BITMAP_HEIGHT))) { + builder.setBitmapHeight(bundle.getFloat(keyForField(FIELD_BITMAP_HEIGHT))); + } + if (bundle.containsKey(keyForField(FIELD_WINDOW_COLOR))) { + builder.setWindowColor(bundle.getInt(keyForField(FIELD_WINDOW_COLOR))); + } + if (!bundle.getBoolean(keyForField(FIELD_WINDOW_COLOR_SET), /* defaultValue= */ false)) { + builder.clearWindowColor(); + } + if (bundle.containsKey(keyForField(FIELD_VERTICAL_TYPE))) { + builder.setVerticalType(bundle.getInt(keyForField(FIELD_VERTICAL_TYPE))); + } + if (bundle.containsKey(keyForField(FIELD_SHEAR_DEGREES))) { + builder.setShearDegrees(bundle.getFloat(keyForField(FIELD_SHEAR_DEGREES))); + } + return builder.build(); + } + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/CueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/CueTest.java index 1d33c2834e..843e8555c9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/CueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/CueTest.java @@ -21,8 +21,11 @@ import static org.junit.Assert.assertThrows; import android.graphics.Bitmap; import android.graphics.Color; +import android.os.Bundle; +import android.os.Parcel; import android.text.Layout; import android.text.SpannedString; +import android.text.TextUtils; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -105,4 +108,64 @@ public class CueTest { .setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) .build()); } + + @Test + public void roundTripViaBundle_yieldsEqualInstance() { + Cue cue = + new Cue.Builder() + .setText(SpannedString.valueOf("text")) + .setTextAlignment(Layout.Alignment.ALIGN_CENTER) + .setMultiRowAlignment(Layout.Alignment.ALIGN_NORMAL) + .setLine(5, Cue.LINE_TYPE_NUMBER) + .setLineAnchor(Cue.ANCHOR_TYPE_END) + .setPosition(0.4f) + .setPositionAnchor(Cue.ANCHOR_TYPE_MIDDLE) + .setTextSize(0.2f, Cue.TEXT_SIZE_TYPE_FRACTIONAL) + .setSize(0.8f) + .setWindowColor(Color.CYAN) + .setVerticalType(Cue.VERTICAL_TYPE_RL) + .setShearDegrees(-15f) + .build(); + Cue modifiedCue = parcelAndUnParcelCue(cue); + + assertThat(TextUtils.equals(modifiedCue.text, cue.text)).isTrue(); + assertThat(modifiedCue.textAlignment).isEqualTo(cue.textAlignment); + assertThat(modifiedCue.multiRowAlignment).isEqualTo(cue.multiRowAlignment); + assertThat(modifiedCue.bitmap).isNull(); + assertThat(modifiedCue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET); + assertThat(modifiedCue.line).isEqualTo(cue.line); + assertThat(modifiedCue.lineType).isEqualTo(cue.lineType); + assertThat(modifiedCue.position).isEqualTo(cue.position); + assertThat(modifiedCue.positionAnchor).isEqualTo(cue.positionAnchor); + assertThat(modifiedCue.textSize).isEqualTo(cue.textSize); + assertThat(modifiedCue.textSizeType).isEqualTo(cue.textSizeType); + assertThat(modifiedCue.size).isEqualTo(cue.size); + assertThat(modifiedCue.windowColor).isEqualTo(cue.windowColor); + assertThat(modifiedCue.windowColorSet).isEqualTo(cue.windowColorSet); + assertThat(modifiedCue.verticalType).isEqualTo(cue.verticalType); + assertThat(modifiedCue.shearDegrees).isEqualTo(cue.shearDegrees); + } + + @Test + public void roundTripViaBundle_withBitmap_yieldsEqualInstance() { + Cue cue = + new Cue.Builder().setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)).build(); + Cue modifiedCue = parcelAndUnParcelCue(cue); + + assertThat(modifiedCue.bitmap.sameAs(cue.bitmap)).isTrue(); + assertThat(modifiedCue.bitmapHeight).isEqualTo(cue.bitmapHeight); + } + + private static Cue parcelAndUnParcelCue(Cue cue) { + Parcel parcel = Parcel.obtain(); + try { + parcel.writeBundle(cue.toBundle()); + parcel.setDataPosition(0); + + Bundle bundle = parcel.readBundle(); + return Cue.CREATOR.fromBundle(bundle); + } finally { + parcel.recycle(); + } + } }