Create new class to store cues and timestamp.

We need to pass timestamp for the list of cues so we are defining a new class CueGroup which will store both cues and timestamp.

PiperOrigin-RevId: 449212054
This commit is contained in:
rohks 2022-05-17 15:10:15 +01:00 committed by Ian Baker
parent 0e2bb8c8bd
commit c728647290
29 changed files with 329 additions and 136 deletions

View File

@ -28,6 +28,8 @@
* Decrease ad polling rate from every 100ms to every 200ms, to line up
with Media Rating Council (MRC) recommendations.
* Text:
* Change `Player.getCurrentCues()` to return `CueGroup` instead of
`List<Cue>`.
* SSA: Support `OutlineColour` style setting when `BorderStyle == 3` (i.e.
`OutlineColour` sets the background of the cue)
([#8435](https://github.com/google/ExoPlayer/issues/8435)).

View File

@ -42,7 +42,7 @@ import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ListenerSet;
@ -705,10 +705,10 @@ public final class CastPlayer extends BasePlayer {
return VideoSize.UNKNOWN;
}
/** This method is not supported and returns an empty list. */
/** This method is not supported and returns an empty {@link CueGroup}. */
@Override
public ImmutableList<Cue> getCurrentCues() {
return ImmutableList.of();
public CueGroup getCurrentCues() {
return CueGroup.EMPTY;
}
/** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */

View File

@ -22,6 +22,7 @@ import android.view.SurfaceView;
import android.view.TextureView;
import androidx.annotation.Nullable;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.UnstableApi;
import java.util.List;
@ -752,7 +753,7 @@ public class ForwardingPlayer implements Player {
/** Calls {@link Player#getCurrentCues()} on the delegate and returns the result. */
@Override
public List<Cue> getCurrentCues() {
public CueGroup getCurrentCues() {
return player.getCurrentCues();
}
@ -992,6 +993,11 @@ public class ForwardingPlayer implements Player {
listener.onCues(cues);
}
@Override
public void onCues(CueGroup cueGroup) {
listener.onCues(cueGroup);
}
@Override
public void onMetadata(Metadata metadata) {
listener.onMetadata(metadata);

View File

@ -32,6 +32,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
@ -1024,16 +1025,29 @@ public interface Player {
/**
* Called when there is a change in the {@link Cue Cues}.
*
* <p>{@code cues} is in ascending order of priority. If any of the cue boxes overlap when
* displayed, the {@link Cue} nearer the end of the list should be shown on top.
* <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
* in the cues. You should only implement one or the other.
*
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration.
*
* @param cues The {@link Cue Cues}. May be empty.
* @deprecated Use {@link #onCues(CueGroup)} instead.
*/
@Deprecated
@UnstableApi
default void onCues(List<Cue> cues) {}
/**
* Called when there is a change in the {@link CueGroup}.
*
* <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
* in the cues. You should only implement one or the other.
*
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration.
*/
default void onCues(CueGroup cueGroup) {}
/**
* Called when there is metadata associated with the current playback time.
*
@ -2469,8 +2483,8 @@ public interface Player {
*/
VideoSize getVideoSize();
/** Returns the current {@link Cue Cues}. This list may be empty. */
List<Cue> getCurrentCues();
/** Returns the current {@link CueGroup}. */
CueGroup getCurrentCues();
/** Gets the device information. */
DeviceInfo getDeviceInfo();

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2022 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 androidx.media3.common.text;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.graphics.Bitmap;
import android.os.Bundle;
import androidx.annotation.IntDef;
import androidx.media3.common.Bundleable;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
/** Class to represent the state of active {@link Cue Cues} at a particular time. */
public final class CueGroup implements Bundleable {
/** Empty {@link CueGroup}. */
@UnstableApi public static final CueGroup EMPTY = new CueGroup(ImmutableList.of());
/**
* The cues in this group.
*
* <p>This list is in ascending order of priority. If any of the cue boxes overlap when displayed,
* the {@link Cue} nearer the end of the list should be shown on top.
*
* <p>This list may be empty if the group represents a state with no cues.
*/
public final ImmutableList<Cue> cues;
/** Creates a CueGroup. */
@UnstableApi
public CueGroup(List<Cue> cues) {
this.cues = ImmutableList.copyOf(cues);
}
// Bundleable implementation.
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({FIELD_CUES})
private @interface FieldNumber {}
private static final int FIELD_CUES = 0;
@UnstableApi
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(
keyForField(FIELD_CUES), BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues)));
return bundle;
}
@UnstableApi public static final Creator<CueGroup> CREATOR = CueGroup::fromBundle;
private static final CueGroup fromBundle(Bundle bundle) {
List<Cue> cues =
BundleableUtil.fromBundleNullableList(
Cue.CREATOR,
bundle.getParcelableArrayList(keyForField(FIELD_CUES)),
/* defaultValue= */ ImmutableList.of());
return new CueGroup(cues);
}
private static String keyForField(@FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX);
}
/**
* Filters out {@link Cue} objects containing {@link Bitmap}. It is used when transferring cues
* between processes to prevent transferring too much data.
*/
private static ImmutableList<Cue> filterOutBitmapCues(List<Cue> cues) {
ImmutableList.Builder<Cue> builder = ImmutableList.builder();
for (int i = 0; i < cues.size(); i++) {
if (cues.get(i).bitmap != null) {
continue;
}
builder.add(cues.get(i));
}
return builder.build();
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2022 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 androidx.media3.common.text;
import static com.google.common.truth.Truth.assertThat;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Parcel;
import android.text.SpannedString;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link CueGroup}. */
@RunWith(AndroidJUnit4.class)
public class CueGroupTest {
@Test
public void bundleAndUnBundleCueGroup() {
Cue textCue = new Cue.Builder().setText(SpannedString.valueOf("text")).build();
Cue bitmapCue =
new Cue.Builder().setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)).build();
ImmutableList<Cue> cues = ImmutableList.of(textCue, bitmapCue);
CueGroup cueGroup = new CueGroup(cues);
Parcel parcel = Parcel.obtain();
try {
parcel.writeBundle(cueGroup.toBundle());
parcel.setDataPosition(0);
Bundle bundle = parcel.readBundle();
CueGroup filteredCueGroup = CueGroup.CREATOR.fromBundle(bundle);
assertThat(filteredCueGroup.cues).containsExactly(textCue);
} finally {
parcel.recycle();
}
}
}

View File

@ -40,7 +40,7 @@ import androidx.media3.common.PriorityTaskManager;
import androidx.media3.common.Timeline;
import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
@ -358,7 +358,7 @@ public interface ExoPlayer extends Player {
* @deprecated Use {@link Player#getCurrentCues()} instead.
*/
@Deprecated
List<Cue> getCurrentCues();
CueGroup getCurrentCues();
}
/**

View File

@ -73,6 +73,7 @@ import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ConditionVariable;
@ -196,7 +197,7 @@ import java.util.concurrent.TimeoutException;
private AudioAttributes audioAttributes;
private float volume;
private boolean skipSilenceEnabled;
private List<Cue> currentCues;
private CueGroup currentCueGroup;
@Nullable private VideoFrameMetadataListener videoFrameMetadataListener;
@Nullable private CameraMotionListener cameraMotionListener;
private boolean throwsWhenUsingWrongThread;
@ -353,7 +354,7 @@ import java.util.concurrent.TimeoutException;
} else {
audioSessionId = Util.generateAudioSessionIdV21(applicationContext);
}
currentCues = ImmutableList.of();
currentCueGroup = CueGroup.EMPTY;
throwsWhenUsingWrongThread = true;
addListener(analyticsCollector);
@ -936,7 +937,7 @@ import java.util.concurrent.TimeoutException;
verifyApplicationThread();
audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE);
stopInternal(reset, /* error= */ null);
currentCues = ImmutableList.of();
currentCueGroup = CueGroup.EMPTY;
}
@Override
@ -990,7 +991,7 @@ import java.util.concurrent.TimeoutException;
checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
isPriorityTaskManagerRegistered = false;
}
currentCues = ImmutableList.of();
currentCueGroup = CueGroup.EMPTY;
playerReleased = true;
}
@ -1587,9 +1588,9 @@ import java.util.concurrent.TimeoutException;
}
@Override
public List<Cue> getCurrentCues() {
public CueGroup getCurrentCues() {
verifyApplicationThread();
return currentCues;
return currentCueGroup;
}
@Override
@ -2850,13 +2851,17 @@ import java.util.concurrent.TimeoutException;
}
// TextOutput implementation
@Override
public void onCues(List<Cue> cues) {
currentCues = cues;
listeners.sendEvent(EVENT_CUES, listener -> listener.onCues(cues));
}
@Override
public void onCues(CueGroup cueGroup) {
currentCueGroup = cueGroup;
listeners.sendEvent(EVENT_CUES, listener -> listener.onCues(cueGroup));
}
// MetadataOutput implementation
@Override

View File

@ -38,7 +38,7 @@ import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.UnstableApi;
@ -690,7 +690,7 @@ public class SimpleExoPlayer extends BasePlayer
}
@Override
public List<Cue> getCurrentCues() {
public CueGroup getCurrentCues() {
blockUntilConstructorFinished();
return player.getCurrentCues();
}

View File

@ -49,6 +49,7 @@ import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.decoder.DecoderException;
import androidx.media3.exoplayer.DecoderCounters;
@ -875,15 +876,29 @@ public interface AnalyticsListener {
/**
* Called when there is a change in the {@link Cue Cues}.
*
* <p>{@code cues} is in ascending order of priority. If any of the cue boxes overlap when
* displayed, the {@link Cue} nearer the end of the list should be shown on top.
* <p>Both {@link #onCues(EventTime, List)} and {@link #onCues(EventTime, CueGroup)} are called
* when there is a change in the cues. You should only implement one or the other.
*
* @param eventTime The event time.
* @param cues The {@link Cue Cues}. May be empty.
* @param cues The {@link Cue Cues}.
* @deprecated Use {@link #onCues(EventTime, CueGroup)} instead.
*/
@Deprecated
@UnstableApi
default void onCues(EventTime eventTime, List<Cue> cues) {}
/**
* Called when there is a change in the {@link CueGroup}.
*
* <p>Both {@link #onCues(EventTime, List)} and {@link #onCues(EventTime, CueGroup)} are called
* when there is a change in the cues. You should only implement one or the other.
*
* @param eventTime The event time.
* @param cueGroup The {@link CueGroup}.
*/
@UnstableApi
default void onCues(EventTime eventTime, CueGroup cueGroup) {}
/**
* @deprecated Use {@link #onAudioEnabled} and {@link #onVideoEnabled} instead.
*/

View File

@ -42,6 +42,7 @@ import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.ListenerSet;
@ -695,6 +696,7 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector {
listener -> listener.onMetadata(eventTime, metadata));
}
@SuppressWarnings("deprecation") // Implementing and calling deprecated listener method.
@Override
public void onCues(List<Cue> cues) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
@ -702,6 +704,13 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector {
eventTime, AnalyticsListener.EVENT_CUES, listener -> listener.onCues(eventTime, cues));
}
@Override
public void onCues(CueGroup cueGroup) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
sendEvent(
eventTime, AnalyticsListener.EVENT_CUES, listener -> listener.onCues(eventTime, cueGroup));
}
@SuppressWarnings("deprecation") // Implementing and calling deprecated listener method.
@Override
public final void onSeekProcessed() {

View File

@ -16,6 +16,7 @@
package androidx.media3.exoplayer.text;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.UnstableApi;
import java.util.List;
@ -26,10 +27,19 @@ public interface TextOutput {
/**
* Called when there is a change in the {@link Cue Cues}.
*
* <p>{@code cues} is in ascending order of priority. If any of the cue boxes overlap when
* displayed, the {@link Cue} nearer the end of the list should be shown on top.
* <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
* in the cues. You should only implement one or the other.
*
* @param cues The {@link Cue Cues}. May be empty.
* @deprecated Use {@link #onCues(CueGroup)} instead.
*/
@Deprecated
void onCues(List<Cue> cues);
/**
* Called when there is a change in the {@link CueGroup}.
*
* <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
* in the cues You should only implement one or the other.
*/
default void onCues(CueGroup cueGroup) {}
}

View File

@ -29,6 +29,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
@ -395,6 +396,7 @@ public final class TextRenderer extends BaseRenderer implements Callback {
private void invokeUpdateOutputInternal(List<Cue> cues) {
output.onCues(cues);
output.onCues(new CueGroup(cues));
}
/**

View File

@ -52,12 +52,11 @@ import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
@ -1583,9 +1582,9 @@ public class MediaController implements Player {
}
@Override
public List<Cue> getCurrentCues() {
public CueGroup getCurrentCues() {
verifyApplicationThread();
return isConnected() ? impl.getCurrentCues() : ImmutableList.of();
return isConnected() ? impl.getCurrentCues() : CueGroup.EMPTY;
}
@Override
@ -1981,7 +1980,7 @@ public class MediaController implements Player {
void clearVideoTextureView(@Nullable TextureView textureView);
List<Cue> getCurrentCues();
CueGroup getCurrentCues();
float getVolume();

View File

@ -134,7 +134,7 @@ import androidx.media3.common.Timeline.RemotableTimeline;
import androidx.media3.common.Timeline.Window;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ListenerSet;
@ -1490,8 +1490,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
}
@Override
public List<Cue> getCurrentCues() {
return playerInfo.cues;
public CueGroup getCurrentCues() {
return playerInfo.cueGroup;
}
@Override
@ -2356,6 +2356,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
});
}
@SuppressWarnings("deprecation") // Implementing and calling deprecated listener method.
void onPlayerInfoChanged(
PlayerInfo newPlayerInfo,
@TimelineChangeReason int timelineChangedReason,
@ -2427,10 +2428,12 @@ import org.checkerframework.checker.nullness.qual.NonNull;
/* eventFlag= */ C.INDEX_UNSET,
listener -> listener.onAudioAttributesChanged(playerInfo.audioAttributes));
}
if (!Util.areEqual(oldPlayerInfo.cues, playerInfo.cues)) {
if (!oldPlayerInfo.cueGroup.cues.equals(playerInfo.cueGroup.cues)) {
// TODO(b/187152483): Set proper event code when available.
listeners.queueEvent(
/* eventFlag= */ C.INDEX_UNSET, listener -> listener.onCues(playerInfo.cues));
/* eventFlag= */ C.INDEX_UNSET, listener -> listener.onCues(playerInfo.cueGroup.cues));
listeners.queueEvent(
/* eventFlag= */ C.INDEX_UNSET, listener -> listener.onCues(playerInfo.cueGroup));
}
if (!Util.areEqual(oldPlayerInfo.deviceInfo, playerInfo.deviceInfo)) {
// TODO(b/187152483): Set proper event code when available.

View File

@ -91,7 +91,7 @@ import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Window;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ListenerSet;
import androidx.media3.common.util.Log;
@ -1052,9 +1052,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
@Override
public List<Cue> getCurrentCues() {
public CueGroup getCurrentCues() {
Log.w(TAG, "Session doesn't support getting Cue");
return ImmutableList.of();
return CueGroup.EMPTY;
}
@Override
@ -2042,7 +2042,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/* playlistMetadata= */ playlistMetadata,
/* volume= */ 1.0f,
/* audioAttributes= */ audioAttributes,
/* cues= */ Collections.emptyList(),
/* cueGroup= */ CueGroup.EMPTY,
/* deviceInfo= */ deviceInfo,
/* deviceVolume= */ deviceVolume,
/* deviceMuted= */ deviceMuted,

View File

@ -62,7 +62,7 @@ import androidx.media3.common.Rating;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.session.MediaSession.ControllerCb;
@ -1016,7 +1016,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
}
@Override
public void onCues(List<Cue> cues) {
public void onCues(CueGroup cueGroup) {
@Nullable MediaSessionImpl session = getSession();
if (session == null) {
return;
@ -1026,7 +1026,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
if (player == null) {
return;
}
session.playerInfo = new PlayerInfo.Builder(session.playerInfo).setCues(cues).build();
session.playerInfo = new PlayerInfo.Builder(session.playerInfo).setCues(cueGroup).build();
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true);
}

View File

@ -81,7 +81,6 @@ import androidx.media3.common.ThumbRating;
import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Period;
import androidx.media3.common.Timeline.Window;
import androidx.media3.common.text.Cue;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.session.MediaLibraryService.LibraryParams;
@ -1267,21 +1266,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return intersectCommandsBuilder.build();
}
/**
* Filters out {@link Cue} objects containing {@link Bitmap}. It is used when transferring cues
* between processes to prevent transferring too large data.
*/
public static ImmutableList<Cue> filterOutBitmapCues(List<Cue> cues) {
ImmutableList.Builder<Cue> builder = ImmutableList.builder();
for (int i = 0; i < cues.size(); i++) {
if (cues.get(i).bitmap != null) {
continue;
}
builder.add(cues.get(i));
}
return builder.build();
}
private static byte[] convertToByteArray(Bitmap bitmap) throws IOException {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
bitmap.compress(Bitmap.CompressFormat.PNG, /* ignored */ 0, stream);

View File

@ -43,15 +43,13 @@ import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Window;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.BundleableUtil;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
/**
* Information about the player that {@link MediaSession} uses to send its state to {@link
@ -75,7 +73,7 @@ import java.util.List;
private MediaMetadata playlistMetadata;
private float volume;
private AudioAttributes audioAttributes;
private ImmutableList<Cue> cues;
private CueGroup cueGroup;
private DeviceInfo deviceInfo;
private int deviceVolume;
private boolean deviceMuted;
@ -106,7 +104,7 @@ import java.util.List;
playlistMetadata = playerInfo.playlistMetadata;
volume = playerInfo.volume;
audioAttributes = playerInfo.audioAttributes;
cues = ImmutableList.copyOf(playerInfo.cues);
cueGroup = playerInfo.cueGroup;
deviceInfo = playerInfo.deviceInfo;
deviceVolume = playerInfo.deviceVolume;
deviceMuted = playerInfo.deviceMuted;
@ -194,8 +192,8 @@ import java.util.List;
return this;
}
public Builder setCues(List<Cue> cues) {
this.cues = ImmutableList.copyOf(cues);
public Builder setCues(CueGroup cueGroup) {
this.cueGroup = cueGroup;
return this;
}
@ -290,7 +288,7 @@ import java.util.List;
playlistMetadata,
volume,
audioAttributes,
cues,
cueGroup,
deviceInfo,
deviceVolume,
deviceMuted,
@ -335,7 +333,7 @@ import java.util.List;
MediaMetadata.EMPTY,
/* volume= */ 1f,
AudioAttributes.DEFAULT,
/* cues = */ ImmutableList.of(),
/* cueGroup = */ CueGroup.EMPTY,
DeviceInfo.UNKNOWN,
/* deviceVolume= */ 0,
/* deviceMuted= */ false,
@ -379,7 +377,7 @@ import java.util.List;
public final AudioAttributes audioAttributes;
public final List<Cue> cues;
public final CueGroup cueGroup;
public final DeviceInfo deviceInfo;
@ -597,7 +595,7 @@ import java.util.List;
MediaMetadata playlistMetadata,
float volume,
AudioAttributes audioAttributes,
List<Cue> cues,
CueGroup cueGroup,
DeviceInfo deviceInfo,
int deviceVolume,
boolean deviceMuted,
@ -626,7 +624,7 @@ import java.util.List;
this.playlistMetadata = playlistMetadata;
this.volume = volume;
this.audioAttributes = audioAttributes;
this.cues = cues;
this.cueGroup = cueGroup;
this.deviceInfo = deviceInfo;
this.deviceVolume = deviceVolume;
this.deviceMuted = deviceMuted;
@ -689,7 +687,7 @@ import java.util.List;
FIELD_OLD_POSITION_INFO,
FIELD_NEW_POSITION_INFO,
FIELD_DISCONTINUITY_REASON,
FIELD_CUES,
FIELD_CUE_GROUP,
FIELD_MEDIA_METADATA,
FIELD_SEEK_BACK_INCREMENT_MS,
FIELD_SEEK_FORWARD_INCREMENT_MS,
@ -721,7 +719,7 @@ import java.util.List;
private static final int FIELD_OLD_POSITION_INFO = 21;
private static final int FIELD_NEW_POSITION_INFO = 22;
private static final int FIELD_DISCONTINUITY_REASON = 23;
private static final int FIELD_CUES = 24;
private static final int FIELD_CUE_GROUP = 24;
private static final int FIELD_MEDIA_METADATA = 25;
private static final int FIELD_SEEK_BACK_INCREMENT_MS = 26;
private static final int FIELD_SEEK_FORWARD_INCREMENT_MS = 27;
@ -759,9 +757,7 @@ import java.util.List;
bundle.putFloat(keyForField(FIELD_VOLUME), volume);
bundle.putBundle(keyForField(FIELD_AUDIO_ATTRIBUTES), audioAttributes.toBundle());
if (!excludeCues) {
bundle.putParcelableArrayList(
keyForField(FIELD_CUES),
BundleableUtil.toBundleArrayList(MediaUtils.filterOutBitmapCues(cues)));
bundle.putBundle(keyForField(FIELD_CUE_GROUP), cueGroup.toBundle());
}
bundle.putBundle(keyForField(FIELD_DEVICE_INFO), deviceInfo.toBundle());
bundle.putInt(keyForField(FIELD_DEVICE_VOLUME), deviceVolume);
@ -851,11 +847,9 @@ import java.util.List;
AudioAttributes.CREATOR,
bundle.getBundle(keyForField(FIELD_AUDIO_ATTRIBUTES)),
/* defaultValue= */ AudioAttributes.DEFAULT);
List<Cue> cues =
BundleableUtil.fromBundleNullableList(
Cue.CREATOR,
bundle.getParcelableArrayList(keyForField(FIELD_CUES)),
/* defaultValue= */ ImmutableList.of());
CueGroup cueGroup =
BundleableUtil.fromNullableBundle(
CueGroup.CREATOR, bundle.getBundle(keyForField(FIELD_CUE_GROUP)), CueGroup.EMPTY);
@Nullable Bundle deviceInfoBundle = bundle.getBundle(keyForField(FIELD_DEVICE_INFO));
DeviceInfo deviceInfo =
BundleableUtil.fromNullableBundle(
@ -912,7 +906,7 @@ import java.util.List;
playlistMetadata,
volume,
audioAttributes,
cues,
cueGroup,
deviceInfo,
deviceVolume,
deviceMuted,

View File

@ -42,7 +42,7 @@ import androidx.media3.common.Player;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
@ -636,7 +636,7 @@ import java.util.List;
}
@Override
public List<Cue> getCurrentCues() {
public CueGroup getCurrentCues() {
verifyApplicationThread();
return super.getCurrentCues();
}

View File

@ -77,7 +77,7 @@ interface IRemoteMediaSession {
void notifySeekBackIncrementChanged(String sessionId, long seekBackIncrementMs);
void notifySeekForwardIncrementChanged(String sessionId, long seekForwardIncrementMs);
void notifyDeviceVolumeChanged(String sessionId, int volume, boolean muted);
void notifyCuesChanged(String sessionId, in List<Bundle> cues);
void notifyCuesChanged(String sessionId, in Bundle cueGroup);
void notifyDeviceInfoChanged(String sessionId, in Bundle deviceInfo);
void notifyMediaMetadataChanged(String sessionId, in Bundle mediaMetadata);
void notifyRenderedFirstFrame(String sessionId);

View File

@ -99,7 +99,7 @@ public class CommonConstants {
public static final String KEY_IS_PLAYING_AD = "isPlayingAd";
public static final String KEY_CURRENT_AD_GROUP_INDEX = "currentAdGroupIndex";
public static final String KEY_CURRENT_AD_INDEX_IN_AD_GROUP = "currentAdIndexInAdGroup";
public static final String KEY_CURRENT_CUES = "currentCues";
public static final String KEY_CURRENT_CUE_GROUP = "currentCueGroup";
public static final String KEY_MEDIA_METADATA = "mediaMetadata";
public static final String KEY_MAX_SEEK_TO_PREVIOUS_POSITION_MS = "maxSeekToPreviousPositionMs";
public static final String KEY_TRACK_SELECTION_PARAMETERS = "trackSelectionParameters";

View File

@ -63,6 +63,7 @@ import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.session.RemoteMediaSession.RemoteMockPlayer;
import androidx.media3.test.session.common.HandlerThreadTestRule;
import androidx.media3.test.session.common.MainLooperTestRule;
@ -1842,12 +1843,14 @@ public class MediaControllerListenerTest {
List<Cue> testCues = ImmutableList.of(testCue1, testCue2);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setCurrentCues(testCues).build();
new RemoteMediaSession.MockPlayerConfigBuilder()
.setCurrentCues(new CueGroup(testCues))
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
assertThat(threadTestRule.getHandler().postAndSync(controller::getCurrentCues))
assertThat(threadTestRule.getHandler().postAndSync(controller::getCurrentCues).cues)
.isEqualTo(testCues);
}
@ -1858,7 +1861,9 @@ public class MediaControllerListenerTest {
List<Cue> testCues = ImmutableList.of(testCue1, testCue2);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setCurrentCues(testCues).build();
new RemoteMediaSession.MockPlayerConfigBuilder()
.setCurrentCues(new CueGroup(testCues))
.build();
remoteSession.setPlayer(playerConfig);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
@ -1873,7 +1878,7 @@ public class MediaControllerListenerTest {
cuesFromParam.clear();
cuesFromParam.addAll(cues);
cuesFromGetter.clear();
cuesFromGetter.addAll(controller.getCurrentCues());
cuesFromGetter.addAll(controller.getCurrentCues().cues);
latch.countDown();
}
};
@ -1902,14 +1907,16 @@ public class MediaControllerListenerTest {
@Override
public void onCues(List<Cue> cues) {
cuesFromParam.addAll(cues);
cuesFromGetter.addAll(controller.getCurrentCues());
cuesFromGetter.addAll(controller.getCurrentCues().cues);
latch.countDown();
}
};
controller.addListener(listener);
Bundle playerConfig =
new RemoteMediaSession.MockPlayerConfigBuilder().setCurrentCues(testCues).build();
new RemoteMediaSession.MockPlayerConfigBuilder()
.setCurrentCues(new CueGroup(testCues))
.build();
remoteSession.setPlayer(playerConfig);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -1932,13 +1939,13 @@ public class MediaControllerListenerTest {
@Override
public void onCues(List<Cue> cues) {
cuesFromParam.addAll(cues);
cuesFromGetter.addAll(controller.getCurrentCues());
cuesFromGetter.addAll(controller.getCurrentCues().cues);
latch.countDown();
}
};
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
remoteSession.getMockPlayer().notifyCuesChanged(testCues);
remoteSession.getMockPlayer().notifyCuesChanged(new CueGroup(testCues));
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(cuesFromParam).isEqualTo(testCues);

View File

@ -20,7 +20,6 @@ import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_Q
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Parcel;
import android.service.media.MediaBrowserService;
@ -31,7 +30,6 @@ import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.text.SpannedString;
import android.text.TextUtils;
import androidx.media.AudioAttributesCompat;
import androidx.media3.common.AudioAttributes;
@ -44,12 +42,10 @@ import androidx.media3.common.Player;
import androidx.media3.common.Rating;
import androidx.media3.common.StarRating;
import androidx.media3.common.ThumbRating;
import androidx.media3.common.text.Cue;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -424,17 +420,6 @@ public final class MediaUtilsTest {
assertThat(MediaUtils.convertToAudioAttributesCompat(aa)).isEqualTo(aaCompat);
}
@Test
public void filterOutBitmapCues_dropsBitmap() {
Cue textCue = new Cue.Builder().setText(SpannedString.valueOf("text")).build();
Cue bitmapCue =
new Cue.Builder().setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)).build();
List<Cue> filteredCues = MediaUtils.filterOutBitmapCues(ImmutableList.of(textCue, bitmapCue));
assertThat(filteredCues).hasSize(1);
assertThat(filteredCues.get(0)).isEqualTo(textCue);
}
@Test
public void convertToCurrentPosition_byDefault_returnsZero() {
long currentPositionMs =

View File

@ -24,7 +24,7 @@ import static androidx.media3.test.session.common.CommonConstants.KEY_CONTENT_DU
import static androidx.media3.test.session.common.CommonConstants.KEY_CONTENT_POSITION;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_AD_GROUP_INDEX;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_AD_INDEX_IN_AD_GROUP;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_CUES;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_CUE_GROUP;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_LIVE_OFFSET;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_MEDIA_ITEM_INDEX;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_PERIOD_INDEX;
@ -78,7 +78,7 @@ import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
@ -306,10 +306,9 @@ public class MediaSessionProviderService extends Service {
AudioAttributes.CREATOR,
config.getBundle(KEY_AUDIO_ATTRIBUTES),
player.audioAttributes);
@Nullable List<Bundle> cuesBundleList = config.getParcelableArrayList(KEY_CURRENT_CUES);
if (cuesBundleList != null) {
player.cues = BundleableUtil.fromBundleList(Cue.CREATOR, cuesBundleList);
}
player.cueGroup =
BundleableUtil.fromNullableBundle(
CueGroup.CREATOR, config.getBundle(KEY_CURRENT_CUE_GROUP), CueGroup.EMPTY);
player.deviceInfo =
BundleableUtil.fromNullableBundle(
DeviceInfo.CREATOR, config.getBundle(KEY_DEVICE_INFO), player.deviceInfo);
@ -886,14 +885,13 @@ public class MediaSessionProviderService extends Service {
}
@Override
public void notifyCuesChanged(String sessionId, List<Bundle> cueBundleList)
throws RemoteException {
List<Cue> cues = BundleableUtil.fromBundleList(Cue.CREATOR, cueBundleList);
public void notifyCuesChanged(String sessionId, Bundle cueGroupBundle) throws RemoteException {
CueGroup cueGroup = CueGroup.CREATOR.fromBundle(cueGroupBundle);
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
player.cues = cues;
player.cueGroup = cueGroup;
player.notifyCuesChanged();
});
}

View File

@ -38,7 +38,7 @@ import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
@ -234,7 +234,7 @@ public class MockPlayer implements Player {
@Nullable public SurfaceView surfaceView;
@Nullable public TextureView textureView;
public float volume;
public List<Cue> cues;
public CueGroup cueGroup;
public DeviceInfo deviceInfo;
public int deviceVolume;
public boolean deviceMuted;
@ -277,7 +277,7 @@ public class MockPlayer implements Player {
repeatMode = Player.REPEAT_MODE_OFF;
videoSize = VideoSize.UNKNOWN;
volume = 1.0f;
cues = ImmutableList.of();
cueGroup = CueGroup.EMPTY;
deviceInfo = DeviceInfo.UNKNOWN;
seekPositionMs = C.TIME_UNSET;
seekMediaItemIndex = C.INDEX_UNSET;
@ -621,8 +621,8 @@ public class MockPlayer implements Player {
}
@Override
public List<Cue> getCurrentCues() {
return cues;
public CueGroup getCurrentCues() {
return cueGroup;
}
@Override
@ -1134,9 +1134,11 @@ public class MockPlayer implements Player {
}
}
@SuppressWarnings("deprecation") // Implementing and calling deprecated listener method.
public void notifyCuesChanged() {
for (Listener listener : listeners) {
listener.onCues(cues);
listener.onCues(cueGroup.cues);
listener.onCues(cueGroup);
}
}

View File

@ -24,7 +24,7 @@ import static androidx.media3.test.session.common.CommonConstants.KEY_CONTENT_DU
import static androidx.media3.test.session.common.CommonConstants.KEY_CONTENT_POSITION;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_AD_GROUP_INDEX;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_AD_INDEX_IN_AD_GROUP;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_CUES;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_CUE_GROUP;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_LIVE_OFFSET;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_MEDIA_ITEM_INDEX;
import static androidx.media3.test.session.common.CommonConstants.KEY_CURRENT_PERIOD_INDEX;
@ -78,7 +78,7 @@ import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
@ -389,8 +389,8 @@ public class RemoteMediaSession {
binder.notifyDeviceVolumeChanged(sessionId, volume, muted);
}
public void notifyCuesChanged(List<Cue> cues) throws RemoteException {
binder.notifyCuesChanged(sessionId, BundleableUtil.toBundleList(cues));
public void notifyCuesChanged(CueGroup cueGroup) throws RemoteException {
binder.notifyCuesChanged(sessionId, cueGroup.toBundle());
}
public void notifyDeviceInfoChanged(DeviceInfo deviceInfo) throws RemoteException {
@ -595,8 +595,8 @@ public class RemoteMediaSession {
return this;
}
public MockPlayerConfigBuilder setCurrentCues(List<Cue> cues) {
bundle.putParcelableArrayList(KEY_CURRENT_CUES, BundleableUtil.toBundleArrayList(cues));
public MockPlayerConfigBuilder setCurrentCues(CueGroup cueGroup) {
bundle.putBundle(KEY_CURRENT_CUE_GROUP, cueGroup.toBundle());
return this;
}

View File

@ -33,7 +33,7 @@ import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.UnstableApi;
import java.util.List;
@ -346,7 +346,7 @@ public class StubPlayer extends BasePlayer {
}
@Override
public List<Cue> getCurrentCues() {
public CueGroup getCurrentCues() {
throw new UnsupportedOperationException();
}

View File

@ -553,7 +553,7 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
updateAspectRatio();
}
if (subtitleView != null && player.isCommandAvailable(COMMAND_GET_TEXT)) {
subtitleView.setCues(player.getCurrentCues());
subtitleView.setCues(player.getCurrentCues().cues);
}
player.addListener(componentListener);
maybeShowController(false);