Support updating the mediaItem in ExternallyLoadedMediaSource

PiperOrigin-RevId: 571308986
This commit is contained in:
tofunmi 2023-10-06 05:22:34 -07:00 committed by Copybara-Service
parent addfd3e986
commit e5010e3d71
2 changed files with 169 additions and 10 deletions

View File

@ -16,7 +16,9 @@
package androidx.media3.exoplayer.source;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.msToUs;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
@ -27,6 +29,7 @@ import androidx.media3.exoplayer.drm.DrmSessionManagerProvider;
import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
import com.google.common.base.Charsets;
import java.util.Objects;
/**
* A {@link MediaSource} for media loaded outside of the usual ExoPlayer loading mechanism.
@ -81,23 +84,26 @@ public final class ExternallyLoadedMediaSource extends BaseMediaSource {
}
}
private final MediaItem mediaItem;
private final Timeline timeline;
private final long timelineDurationUs;
@GuardedBy("this")
private MediaItem mediaItem;
private ExternallyLoadedMediaSource(MediaItem mediaItem, long timelineDurationUs) {
this.mediaItem = mediaItem;
this.timeline =
this.timelineDurationUs = timelineDurationUs;
}
@Override
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
Timeline timeline =
new SinglePeriodTimeline(
timelineDurationUs,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* useLiveConfiguration= */ false,
/* manifest= */ null,
mediaItem);
}
@Override
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
getMediaItem());
refreshSourceInfo(timeline);
}
@ -107,10 +113,26 @@ public final class ExternallyLoadedMediaSource extends BaseMediaSource {
}
@Override
public MediaItem getMediaItem() {
public synchronized MediaItem getMediaItem() {
return mediaItem;
}
@Override
public synchronized boolean canUpdateMediaItem(MediaItem mediaItem) {
@Nullable MediaItem.LocalConfiguration newConfiguration = mediaItem.localConfiguration;
MediaItem.LocalConfiguration oldConfiguration = checkNotNull(getMediaItem().localConfiguration);
return newConfiguration != null
&& newConfiguration.uri.equals(oldConfiguration.uri)
&& Objects.equals(newConfiguration.mimeType, oldConfiguration.mimeType)
&& (newConfiguration.imageDurationMs == C.TIME_UNSET
|| msToUs(newConfiguration.imageDurationMs) == timelineDurationUs);
}
@Override
public synchronized void updateMediaItem(MediaItem mediaItem) {
this.mediaItem = mediaItem;
}
@Override
public void maybeThrowSourceInfoRefreshError() {
// Do nothing.
@ -118,6 +140,7 @@ public final class ExternallyLoadedMediaSource extends BaseMediaSource {
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
MediaItem mediaItem = getMediaItem();
checkNotNull(mediaItem.localConfiguration);
checkNotNull(
mediaItem.localConfiguration.mimeType, "Externally loaded mediaItems require a MIME type.");
@ -126,5 +149,7 @@ public final class ExternallyLoadedMediaSource extends BaseMediaSource {
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {}
public void releasePeriod(MediaPeriod mediaPeriod) {
// Do nothing.
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright 2023 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.exoplayer.source;
import static androidx.media3.common.util.Util.msToUs;
import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.Timeline;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.test.utils.TestUtil;
import androidx.media3.test.utils.robolectric.RobolectricUtil;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link ProgressiveMediaSource}. */
@RunWith(AndroidJUnit4.class)
public class ExternallyLoadedMediaSourceTest {
@Test
public void canUpdateMediaItem_withIrrelevantFieldsChanged_returnsTrue() {
MediaItem initialMediaItem =
new MediaItem.Builder()
.setUri("http://test.test")
.setImageDurationMs(5 * C.MILLIS_PER_SECOND)
.setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE)
.build();
MediaItem updatedMediaItem =
TestUtil.buildFullyCustomizedMediaItem()
.buildUpon()
.setUri("http://test.test")
.setImageDurationMs(5 * C.MILLIS_PER_SECOND)
.setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE)
.build();
MediaSource mediaSource = buildMediaSource(initialMediaItem);
boolean canUpdateMediaItem = mediaSource.canUpdateMediaItem(updatedMediaItem);
assertThat(canUpdateMediaItem).isTrue();
}
@Test
public void canUpdateMediaItem_withNullLocalConfiguration_returnsFalse() {
MediaItem initialMediaItem =
new MediaItem.Builder()
.setImageDurationMs(5 * C.MILLIS_PER_SECOND)
.setUri("http://test.test")
.build();
MediaItem updatedMediaItem =
new MediaItem.Builder()
.setImageDurationMs(5 * C.MILLIS_PER_SECOND)
.setMediaId("id")
.build();
MediaSource mediaSource = buildMediaSource(initialMediaItem);
boolean canUpdateMediaItem = mediaSource.canUpdateMediaItem(updatedMediaItem);
assertThat(canUpdateMediaItem).isFalse();
}
@Test
public void canUpdateMediaItem_withChangedUri_returnsFalse() {
MediaItem initialMediaItem =
new MediaItem.Builder()
.setImageDurationMs(5 * C.MILLIS_PER_SECOND)
.setUri("http://test.test")
.build();
MediaItem updatedMediaItem =
new MediaItem.Builder()
.setImageDurationMs(5 * C.MILLIS_PER_SECOND)
.setUri("http://test2.test")
.build();
MediaSource mediaSource = buildMediaSource(initialMediaItem);
boolean canUpdateMediaItem = mediaSource.canUpdateMediaItem(updatedMediaItem);
assertThat(canUpdateMediaItem).isFalse();
}
@Test
public void updateMediaItem_createsTimelineWithUpdatedItem() throws Exception {
MediaItem initialMediaItem =
new MediaItem.Builder()
.setUri("http://test.test")
.setImageDurationMs(5 * C.MILLIS_PER_SECOND)
.setTag("tag1")
.build();
MediaItem updatedMediaItem =
new MediaItem.Builder()
.setUri("http://test.test")
.setImageDurationMs(5 * C.MILLIS_PER_SECOND)
.setTag("tag2")
.build();
MediaSource mediaSource = buildMediaSource(initialMediaItem);
AtomicReference<Timeline> timelineReference = new AtomicReference<>();
mediaSource.updateMediaItem(updatedMediaItem);
mediaSource.prepareSource(
(source, timeline) -> timelineReference.set(timeline),
/* mediaTransferListener= */ null,
PlayerId.UNSET);
RobolectricUtil.runMainLooperUntil(() -> timelineReference.get() != null);
assertThat(
timelineReference
.get()
.getWindow(/* windowIndex= */ 0, new Timeline.Window())
.mediaItem)
.isEqualTo(updatedMediaItem);
}
private static MediaSource buildMediaSource(MediaItem mediaItem) {
return new ExternallyLoadedMediaSource.Factory(
msToUs(mediaItem.localConfiguration.imageDurationMs))
.createMediaSource(mediaItem);
}
}