Commit 3d8c52f2 by bachinger Committed by Ian Baker

Exclude tracks from `PlayerInfo` if not changed

This change includes a change in the `IMediaController.aidl` file and needs
to provide backwards compatibility for when a client connects that is of an older or
newer version of the current service implementation.

This CL proposes to create a new AIDL method `onPlayerInfoChangedWithExtensions`
that is easier to extend in the future because it does use an `Bundle` rather than
primitives. A `Bundle` can be changed in a backward/forwards compatible way
in case we need further changes.

The compatibility handling is provided in `MediaSessionStub` and `MediaControllerStub`. The approach is not based on specific AIDL/Binder features but implemented fully in application code.

Issue: androidx/media#102
#minor-release
PiperOrigin-RevId: 490483068
parent 10fac684
......@@ -677,7 +677,8 @@ public interface Player {
* to the current {@link #getRepeatMode() repeat mode}.
*
* <p>Note that this callback is also called when the playlist becomes non-empty or empty as a
* consequence of a playlist change.
* consequence of a playlist change or {@linkplain #onAvailableCommandsChanged(Commands) a
* change in available commands}.
*
* <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.
......
......@@ -35,14 +35,19 @@ oneway interface IMediaController {
void onSetCustomLayout(int seq, in List<Bundle> commandButtonList) = 3003;
void onCustomCommand(int seq, in Bundle command, in Bundle args) = 3004;
void onDisconnected(int seq) = 3005;
void onPlayerInfoChanged(int seq, in Bundle playerInfoBundle, boolean isTimelineExcluded) = 3006;
/** Deprecated: Use onPlayerInfoChangedWithExclusions from MediaControllerStub#VERSION_INT=2. */
void onPlayerInfoChanged(
int seq, in Bundle playerInfoBundle, boolean isTimelineExcluded) = 3006;
/** Introduced to deprecate onPlayerInfoChanged (from MediaControllerStub#VERSION_INT=2). */
void onPlayerInfoChangedWithExclusions(
int seq, in Bundle playerInfoBundle, in Bundle playerInfoExclusions) = 3012;
void onPeriodicSessionPositionInfoChanged(int seq, in Bundle sessionPositionInfo) = 3007;
void onAvailableCommandsChangedFromPlayer(int seq, in Bundle commandsBundle) = 3008;
void onAvailableCommandsChangedFromSession(
int seq, in Bundle sessionCommandsBundle, in Bundle playerCommandsBundle) = 3009;
void onRenderedFirstFrame(int seq) = 3010;
void onExtrasChanged(int seq, in Bundle extras) = 3011;
// Next Id for MediaController: 3012
// Next Id for MediaController: 3013
void onChildrenChanged(
int seq, String parentId, int itemCount, in @nullable Bundle libraryParams) = 4000;
......
......@@ -23,6 +23,7 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.usToMs;
import static androidx.media3.session.MediaUtils.calculateBufferedPercentage;
import static androidx.media3.session.MediaUtils.intersect;
import static androidx.media3.session.MediaUtils.mergePlayerInfo;
import static java.lang.Math.max;
import static java.lang.Math.min;
......@@ -42,6 +43,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.v4.media.MediaBrowserCompat;
import android.util.Pair;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
......@@ -79,6 +81,7 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util;
import androidx.media3.session.MediaController.MediaControllerImpl;
import androidx.media3.session.PlayerInfo.BundlingExclusions;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
......@@ -129,7 +132,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
@Nullable private IMediaSession iSession;
private long lastReturnedCurrentPositionMs;
private long lastSetPlayWhenReadyCalledTimeMs;
@Nullable private Timeline pendingPlayerInfoUpdateTimeline;
@Nullable private PlayerInfo pendingPlayerInfo;
@Nullable private BundlingExclusions pendingBundlingExclusions;
public MediaControllerImplBase(
Context context,
......@@ -2329,30 +2333,41 @@ import org.checkerframework.checker.nullness.qual.NonNull;
}
@SuppressWarnings("deprecation") // Implementing and calling deprecated listener method.
void onPlayerInfoChanged(PlayerInfo newPlayerInfo, boolean isTimelineExcluded) {
void onPlayerInfoChanged(PlayerInfo newPlayerInfo, BundlingExclusions bundlingExclusions) {
if (!isConnected()) {
return;
}
if (pendingPlayerInfo != null && pendingBundlingExclusions != null) {
Pair<PlayerInfo, BundlingExclusions> mergedPlayerInfoUpdate =
mergePlayerInfo(
pendingPlayerInfo,
pendingBundlingExclusions,
newPlayerInfo,
bundlingExclusions,
intersectedPlayerCommands);
newPlayerInfo = mergedPlayerInfoUpdate.first;
bundlingExclusions = mergedPlayerInfoUpdate.second;
}
pendingPlayerInfo = null;
pendingBundlingExclusions = null;
if (!pendingMaskingSequencedFutureNumbers.isEmpty()) {
// We are still waiting for all pending masking operations to be handled.
if (!isTimelineExcluded) {
pendingPlayerInfoUpdateTimeline = newPlayerInfo.timeline;
}
pendingPlayerInfo = newPlayerInfo;
pendingBundlingExclusions = bundlingExclusions;
return;
}
PlayerInfo oldPlayerInfo = playerInfo;
if (isTimelineExcluded) {
newPlayerInfo =
newPlayerInfo.copyWithTimeline(
pendingPlayerInfoUpdateTimeline != null
? pendingPlayerInfoUpdateTimeline
: oldPlayerInfo.timeline);
}
// Assigning class variable now so that all getters called from listeners see the updated value.
// But we need to use a local final variable to ensure listeners get consistent parameters.
playerInfo = newPlayerInfo;
PlayerInfo finalPlayerInfo = newPlayerInfo;
pendingPlayerInfoUpdateTimeline = null;
playerInfo =
mergePlayerInfo(
oldPlayerInfo,
/* oldBundlingExclusions= */ BundlingExclusions.NONE,
newPlayerInfo,
/* newBundlingExclusions= */ bundlingExclusions,
intersectedPlayerCommands)
.first;
PlayerInfo finalPlayerInfo = playerInfo;
PlaybackException oldPlayerError = oldPlayerInfo.playerError;
PlaybackException playerError = finalPlayerInfo.playerError;
boolean errorsMatch =
......@@ -2397,7 +2412,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
/* eventFlag= */ Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
listener -> listener.onShuffleModeEnabledChanged(finalPlayerInfo.shuffleModeEnabled));
}
if (!isTimelineExcluded && !Util.areEqual(oldPlayerInfo.timeline, finalPlayerInfo.timeline)) {
if (!Util.areEqual(oldPlayerInfo.timeline, finalPlayerInfo.timeline)) {
listeners.queueEvent(
/* eventFlag= */ Player.EVENT_TIMELINE_CHANGED,
listener ->
......
......@@ -26,6 +26,7 @@ import androidx.media3.common.Player.Commands;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.Log;
import androidx.media3.session.MediaLibraryService.LibraryParams;
import androidx.media3.session.PlayerInfo.BundlingExclusions;
import java.lang.ref.WeakReference;
import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull;
......@@ -35,7 +36,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
private static final String TAG = "MediaControllerStub";
/** The version of the IMediaController interface. */
public static final int VERSION_INT = 1;
public static final int VERSION_INT = 2;
private final WeakReference<MediaControllerImplBase> controller;
......@@ -169,8 +170,23 @@ import org.checkerframework.checker.nullness.qual.NonNull;
controller -> controller.notifyPeriodicSessionPositionInfoChanged(sessionPositionInfo));
}
/**
* @deprecated Use {@link #onPlayerInfoChangedWithExclusions} from {@link #VERSION_INT} 2.
*/
@Override
@Deprecated
public void onPlayerInfoChanged(int seq, Bundle playerInfoBundle, boolean isTimelineExcluded) {
onPlayerInfoChangedWithExclusions(
seq,
playerInfoBundle,
new BundlingExclusions(isTimelineExcluded, /* areCurrentTracksExcluded= */ true)
.toBundle());
}
/** Added in {@link #VERSION_INT} 2. */
@Override
public void onPlayerInfoChangedWithExclusions(
int seq, Bundle playerInfoBundle, Bundle playerInfoExclusions) {
PlayerInfo playerInfo;
try {
playerInfo = PlayerInfo.CREATOR.fromBundle(playerInfoBundle);
......@@ -178,8 +194,15 @@ import org.checkerframework.checker.nullness.qual.NonNull;
Log.w(TAG, "Ignoring malformed Bundle for PlayerInfo", e);
return;
}
BundlingExclusions bundlingExclusions;
try {
bundlingExclusions = BundlingExclusions.CREATOR.fromBundle(playerInfoExclusions);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for BundlingExclusions", e);
return;
}
dispatchControllerTaskOnHandler(
controller -> controller.onPlayerInfoChanged(playerInfo, isTimelineExcluded));
controller -> controller.onPlayerInfoChanged(playerInfo, bundlingExclusions));
}
@Override
......
......@@ -1136,7 +1136,8 @@ public class MediaSession {
boolean excludeMediaItemsMetadata,
boolean excludeCues,
boolean excludeTimeline,
boolean excludeTracks)
boolean excludeTracks,
int controllerInterfaceVersion)
throws RemoteException {}
default void onPeriodicSessionPositionInfoChanged(
......
......@@ -70,6 +70,7 @@ import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.Rating;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.Log;
......@@ -1596,17 +1597,32 @@ import java.util.concurrent.ExecutionException;
boolean excludeMediaItemsMetadata,
boolean excludeCues,
boolean excludeTimeline,
boolean excludeTracks)
boolean excludeTracks,
int controllerInterfaceVersion)
throws RemoteException {
iController.onPlayerInfoChanged(
sequenceNumber,
playerInfo.toBundle(
excludeMediaItems,
excludeMediaItemsMetadata,
excludeCues,
excludeTimeline,
excludeTracks),
/* isTimelineExcluded= */ excludeTimeline);
Assertions.checkState(controllerInterfaceVersion != 0);
if (controllerInterfaceVersion >= 2) {
iController.onPlayerInfoChangedWithExclusions(
sequenceNumber,
playerInfo.toBundle(
excludeMediaItems,
excludeMediaItemsMetadata,
excludeCues,
excludeTimeline,
excludeTracks),
new PlayerInfo.BundlingExclusions(excludeTimeline, excludeTracks).toBundle());
} else {
//noinspection deprecation
iController.onPlayerInfoChanged(
sequenceNumber,
playerInfo.toBundle(
excludeMediaItems,
excludeMediaItemsMetadata,
excludeCues,
excludeTimeline,
/* excludeTracks= */ true),
excludeTimeline);
}
}
@Override
......
......@@ -62,6 +62,7 @@ import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
import android.text.TextUtils;
import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.media.AudioAttributesCompat;
import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
......@@ -87,6 +88,7 @@ import androidx.media3.common.Timeline.Window;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.session.MediaLibraryService.LibraryParams;
import androidx.media3.session.PlayerInfo.BundlingExclusions;
import com.google.common.collect.ImmutableList;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
......@@ -1288,6 +1290,46 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return intersectCommandsBuilder.build();
}
/**
* Merges the excluded fields into the {@code newPlayerInfo} by taking the values of the {@code
* previousPlayerInfo} and taking into account the passed available commands.
*
* @param oldPlayerInfo The old {@link PlayerInfo}.
* @param oldBundlingExclusions The bundling exlusions in the old {@link PlayerInfo}.
* @param newPlayerInfo The new {@link PlayerInfo}.
* @param newBundlingExclusions The bundling exlusions in the new {@link PlayerInfo}.
* @param availablePlayerCommands The available commands to take into account when merging.
* @return A pair with the resulting {@link PlayerInfo} and {@link BundlingExclusions}.
*/
public static Pair<PlayerInfo, BundlingExclusions> mergePlayerInfo(
PlayerInfo oldPlayerInfo,
BundlingExclusions oldBundlingExclusions,
PlayerInfo newPlayerInfo,
BundlingExclusions newBundlingExclusions,
Commands availablePlayerCommands) {
PlayerInfo mergedPlayerInfo = newPlayerInfo;
BundlingExclusions mergedBundlingExclusions = newBundlingExclusions;
if (newBundlingExclusions.isTimelineExcluded
&& availablePlayerCommands.contains(Player.COMMAND_GET_TIMELINE)
&& !oldBundlingExclusions.isTimelineExcluded) {
// Use the previous timeline if it is excluded in the most recent update.
mergedPlayerInfo = mergedPlayerInfo.copyWithTimeline(oldPlayerInfo.timeline);
mergedBundlingExclusions =
new BundlingExclusions(
/* isTimelineExcluded= */ false, mergedBundlingExclusions.areCurrentTracksExcluded);
}
if (newBundlingExclusions.areCurrentTracksExcluded
&& availablePlayerCommands.contains(Player.COMMAND_GET_TRACKS)
&& !oldBundlingExclusions.areCurrentTracksExcluded) {
// Use the previous tracks if it is excluded in the most recent update.
mergedPlayerInfo = mergedPlayerInfo.copyWithCurrentTracks(oldPlayerInfo.currentTracks);
mergedBundlingExclusions =
new BundlingExclusions(
mergedBundlingExclusions.isTimelineExcluded, /* areCurrentTracksExcluded= */ false);
}
return new Pair<>(mergedPlayerInfo, mergedBundlingExclusions);
}
private static byte[] convertToByteArray(Bitmap bitmap) throws IOException {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
bitmap.compress(Bitmap.CompressFormat.PNG, /* ignored */ 0, stream);
......
......@@ -66,6 +66,10 @@ import java.lang.annotation.Target;
*/
public static class BundlingExclusions implements Bundleable {
/** Bundling exclusions with no exclusions. */
public static final BundlingExclusions NONE =
new BundlingExclusions(
/* isTimelineExcluded= */ false, /* areCurrentTracksExcluded= */ false);
/** Whether the {@linkplain PlayerInfo#timeline timeline} is excluded. */
public final boolean isTimelineExcluded;
/** Whether the {@linkplain PlayerInfo#currentTracks current tracks} are excluded. */
......
......@@ -20,6 +20,9 @@ import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABL
import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION;
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
import static androidx.media.utils.MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS;
import static androidx.media3.common.MimeTypes.AUDIO_AAC;
import static androidx.media3.common.MimeTypes.VIDEO_H264;
import static androidx.media3.common.MimeTypes.VIDEO_H265;
import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.SECONDS;
......@@ -36,11 +39,13 @@ 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.util.Pair;
import androidx.annotation.Nullable;
import androidx.media.AudioAttributesCompat;
import androidx.media.utils.MediaConstants;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.HeartRating;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
......@@ -49,10 +54,15 @@ import androidx.media3.common.Player;
import androidx.media3.common.Rating;
import androidx.media3.common.StarRating;
import androidx.media3.common.ThumbRating;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.Tracks;
import androidx.media3.session.PlayerInfo.BundlingExclusions;
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 com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collections;
......@@ -623,4 +633,134 @@ public final class MediaUtilsTest {
state, /* metadataCompat= */ null, /* timeDiffMs= */ C.INDEX_UNSET);
assertThat(totalBufferedDurationMs).isEqualTo(testTotalBufferedDurationMs);
}
@Test
public void mergePlayerInfo_timelineAndTracksExcluded_correctMerge() {
Timeline timeline =
new Timeline.RemotableTimeline(
ImmutableList.of(new Timeline.Window()),
ImmutableList.of(new Timeline.Period()),
/* shuffledWindowIndices= */ new int[] {0});
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().setSampleMimeType(AUDIO_AAC).build()),
/* adaptiveSupported= */ false,
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
/* trackSelected= */ new boolean[] {true}),
new Tracks.Group(
new TrackGroup(
new Format.Builder().setSampleMimeType(VIDEO_H264).build(),
new Format.Builder().setSampleMimeType(VIDEO_H265).build()),
/* adaptiveSupported= */ true,
new int[] {C.FORMAT_HANDLED, C.FORMAT_UNSUPPORTED_TYPE},
/* trackSelected= */ new boolean[] {false, true})));
PlayerInfo oldPlayerInfo =
PlayerInfo.DEFAULT.copyWithCurrentTracks(tracks).copyWithTimeline(timeline);
PlayerInfo newPlayerInfo = PlayerInfo.DEFAULT;
Player.Commands availableCommands =
Player.Commands.EMPTY
.buildUpon()
.add(Player.COMMAND_GET_TIMELINE)
.add(Player.COMMAND_GET_TRACKS)
.build();
Pair<PlayerInfo, BundlingExclusions> mergeResult =
MediaUtils.mergePlayerInfo(
oldPlayerInfo,
BundlingExclusions.NONE,
newPlayerInfo,
new BundlingExclusions(/* isTimelineExcluded= */ true, /* areTracksExcluded= */ true),
availableCommands);
assertThat(mergeResult.first.timeline).isSameInstanceAs(oldPlayerInfo.timeline);
assertThat(mergeResult.first.currentTracks).isSameInstanceAs(oldPlayerInfo.currentTracks);
assertThat(mergeResult.second.isTimelineExcluded).isFalse();
assertThat(mergeResult.second.areCurrentTracksExcluded).isFalse();
}
@Test
public void mergePlayerInfo_getTimelineCommandNotAvailable_emptyTimeline() {
Timeline timeline =
new Timeline.RemotableTimeline(
ImmutableList.of(new Timeline.Window()),
ImmutableList.of(new Timeline.Period()),
/* shuffledWindowIndices= */ new int[] {0});
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().setSampleMimeType(AUDIO_AAC).build()),
/* adaptiveSupported= */ false,
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
/* trackSelected= */ new boolean[] {true}),
new Tracks.Group(
new TrackGroup(
new Format.Builder().setSampleMimeType(VIDEO_H264).build(),
new Format.Builder().setSampleMimeType(VIDEO_H265).build()),
/* adaptiveSupported= */ true,
new int[] {C.FORMAT_HANDLED, C.FORMAT_UNSUPPORTED_TYPE},
/* trackSelected= */ new boolean[] {false, true})));
PlayerInfo oldPlayerInfo =
PlayerInfo.DEFAULT.copyWithCurrentTracks(tracks).copyWithTimeline(timeline);
PlayerInfo newPlayerInfo = PlayerInfo.DEFAULT;
Player.Commands availableCommands =
Player.Commands.EMPTY.buildUpon().add(Player.COMMAND_GET_TRACKS).build();
Pair<PlayerInfo, BundlingExclusions> mergeResult =
MediaUtils.mergePlayerInfo(
oldPlayerInfo,
BundlingExclusions.NONE,
newPlayerInfo,
new BundlingExclusions(/* isTimelineExcluded= */ true, /* areTracksExcluded= */ true),
availableCommands);
assertThat(mergeResult.first.timeline).isSameInstanceAs(Timeline.EMPTY);
assertThat(mergeResult.first.currentTracks).isSameInstanceAs(oldPlayerInfo.currentTracks);
assertThat(mergeResult.second.isTimelineExcluded).isTrue();
assertThat(mergeResult.second.areCurrentTracksExcluded).isFalse();
}
@Test
public void mergePlayerInfo_getTracksCommandNotAvailable_emptyTracks() {
Timeline timeline =
new Timeline.RemotableTimeline(
ImmutableList.of(new Timeline.Window()),
ImmutableList.of(new Timeline.Period()),
/* shuffledWindowIndices= */ new int[] {0});
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().setSampleMimeType(AUDIO_AAC).build()),
/* adaptiveSupported= */ false,
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
/* trackSelected= */ new boolean[] {true}),
new Tracks.Group(
new TrackGroup(
new Format.Builder().setSampleMimeType(VIDEO_H264).build(),
new Format.Builder().setSampleMimeType(VIDEO_H265).build()),
/* adaptiveSupported= */ true,
new int[] {C.FORMAT_HANDLED, C.FORMAT_UNSUPPORTED_TYPE},
/* trackSelected= */ new boolean[] {false, true})));
PlayerInfo oldPlayerInfo =
PlayerInfo.DEFAULT.copyWithCurrentTracks(tracks).copyWithTimeline(timeline);
PlayerInfo newPlayerInfo = PlayerInfo.DEFAULT;
Player.Commands availableCommands =
Player.Commands.EMPTY.buildUpon().add(Player.COMMAND_GET_TIMELINE).build();
Pair<PlayerInfo, BundlingExclusions> mergeResult =
MediaUtils.mergePlayerInfo(
oldPlayerInfo,
BundlingExclusions.NONE,
newPlayerInfo,
new BundlingExclusions(/* isTimelineExcluded= */ true, /* areTracksExcluded= */ true),
availableCommands);
assertThat(mergeResult.first.timeline).isSameInstanceAs(oldPlayerInfo.timeline);
assertThat(mergeResult.first.currentTracks).isSameInstanceAs(Tracks.EMPTY);
assertThat(mergeResult.second.isTimelineExcluded).isFalse();
assertThat(mergeResult.second.areCurrentTracksExcluded).isTrue();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment