mirror of
https://github.com/androidx/media.git
synced 2025-05-07 15:40:37 +08:00
Merge branch 'dev-v2' of https://github.com/google/ExoPlayer into dev-v2
This commit is contained in:
commit
e77fa14eb0
@ -178,7 +178,10 @@ MediaItem preRollAd = MediaItem.fromUri(preRollAdUri);
|
||||
MediaItem contentStart =
|
||||
new MediaItem.Builder()
|
||||
.setUri(contentUri)
|
||||
.setClipEndPositionMs(120_000)
|
||||
.setClippingConfiguration(
|
||||
new ClippingConfiguration.Builder()
|
||||
.setEndPositionMs(120_000)
|
||||
.build())
|
||||
.build();
|
||||
// A mid-roll ad.
|
||||
MediaItem midRollAd = MediaItem.fromUri(midRollAdUri);
|
||||
@ -186,7 +189,10 @@ MediaItem midRollAd = MediaItem.fromUri(midRollAdUri);
|
||||
MediaItem contentEnd =
|
||||
new MediaItem.Builder()
|
||||
.setUri(contentUri)
|
||||
.setClipStartPositionMs(120_000)
|
||||
.setClippingConfiguration(
|
||||
new ClippingConfiguration.Builder()
|
||||
.setStartPositionMs(120_000)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
// Build the playlist.
|
||||
|
19
docs/drm.md
19
docs/drm.md
@ -24,7 +24,8 @@ outlined in the sections below.
|
||||
### Key rotation ###
|
||||
|
||||
To play streams with rotating keys, pass `true` to
|
||||
`MediaItem.Builder.setDrmMultiSession` when building the media item.
|
||||
`MediaItem.DrmConfiguration.Builder.setMultiSession` when building the media
|
||||
item.
|
||||
|
||||
### Multi-key content ###
|
||||
|
||||
@ -49,8 +50,9 @@ to access the different streams.
|
||||
|
||||
In this case, the license server is configured to respond with only the key
|
||||
specified in the request. Multi-key content can be played with this license
|
||||
server configuration by passing `true` to `MediaItem.Builder.setDrmMultiSession`
|
||||
when building the media item.
|
||||
server configuration by passing `true` to
|
||||
`MediaItem.DrmConfiguration.Builder.setMultiSession` when building the media
|
||||
item.
|
||||
|
||||
We do not recommend configuring your license server to behave in this way. It
|
||||
requires extra license requests to play multi-key content, which is less
|
||||
@ -59,9 +61,9 @@ efficient and robust than the alternative described above.
|
||||
### Offline keys ###
|
||||
|
||||
An offline key set can be loaded by passing the key set ID to
|
||||
`MediaItem.Builder.setDrmKeySetId` when building the media item. This
|
||||
allows playback using the keys stored in the offline key set with the specified
|
||||
ID.
|
||||
`MediaItem.DrmConfiguration.Builder.setKeySetId` when building the media item.
|
||||
This allows playback using the keys stored in the offline key set with the
|
||||
specified ID.
|
||||
|
||||
{% include known-issue-box.html issue-id="3872" description="Only one offline
|
||||
key set can be specified per playback. As a result, offline playback of
|
||||
@ -75,8 +77,9 @@ clear content as are used when playing encrypted content. When media contains
|
||||
both clear and encrypted sections, you may want to use placeholder `DrmSessions`
|
||||
to avoid re-creation of decoders when transitions between clear and encrypted
|
||||
sections occur. Use of placeholder `DrmSessions` for audio and video tracks can
|
||||
be enabled by passing `true` to `MediaItem.Builder.setDrmSessionForClearPeriods`
|
||||
when building the media item.
|
||||
be enabled by passing `true` to
|
||||
`MediaItem.DrmConfiguration.Builder.forceSessionsForAudioAndVideoTracks` when
|
||||
building the media item.
|
||||
|
||||
### Using a custom DrmSessionManager ###
|
||||
|
||||
|
@ -89,7 +89,7 @@ components to support additional modes when playing live streams.
|
||||
|
||||
By default, ExoPlayer uses live playback parameters defined by the media. If you
|
||||
want to configure the live playback parameters yourself, you can set them on a
|
||||
per `MediaItem` basis by calling `MediaItem.Builder.setLiveXXX` methods. If
|
||||
per `MediaItem` basis by calling `MediaItem.Builder.setLiveConfiguration`. If
|
||||
you'd like to set these values globally for all items, you can set them on the
|
||||
`DefaultMediaSourceFactory` provided to the player. In both cases, the provided
|
||||
values will override parameters defined by the media.
|
||||
|
@ -86,17 +86,17 @@ To sideload subtitle tracks, `MediaItem.Subtitle` instances can be added when
|
||||
when building a media item:
|
||||
|
||||
~~~
|
||||
MediaItem.Subtitle subtitle =
|
||||
new MediaItem.Subtitle(
|
||||
subtitleUri,
|
||||
MimeTypes.APPLICATION_SUBRIP, // The correct MIME type.
|
||||
language, // The subtitle language. May be null.
|
||||
selectionFlags); // Selection flags for the track.
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setUri(videoUri)
|
||||
.setSubtitles(Lists.newArrayList(subtitle))
|
||||
.build();
|
||||
MediaItem.SubtitleConfiguration subtitle =
|
||||
new MediaItem.SubtitleConfiguration.Builder(subtitleUri)
|
||||
.setMimeType(MimeTypes.APPLICATION_SUBRIP) // The correct MIME type (required).
|
||||
.setLanguage(language) // The subtitle language (optional).
|
||||
.setSelectionFlags(selectionFlags) // Selection flags for the track (optional).
|
||||
.build();
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setUri(videoUri)
|
||||
.setSubtitleConfigurations(ImmutableList.of(subtitle))
|
||||
.build();
|
||||
~~~
|
||||
{: .language-java}
|
||||
|
||||
@ -110,11 +110,15 @@ It's possible to clip the content referred to by a media item by setting custom
|
||||
start and end positions:
|
||||
|
||||
~~~
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setUri(videoUri)
|
||||
.setClipStartPositionMs(startPositionMs)
|
||||
.setClipEndPositionMs(endPositionMs)
|
||||
.build();
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setUri(videoUri)
|
||||
.setClippingConfiguration(
|
||||
new ClippingConfiguration.Builder()
|
||||
.setStartPositionMs(startPositionMs)
|
||||
.setEndPositionMs(endPositionMs)
|
||||
.build())
|
||||
.build();
|
||||
~~~
|
||||
{: .language-java}
|
||||
|
||||
|
@ -15,9 +15,9 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.ext.ima;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
@ -29,8 +29,8 @@ import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.ListenerSet;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/** A fake player for testing content/ad playback. */
|
||||
/* package */ final class FakePlayer extends StubExoPlayer {
|
||||
/** A fake {@link ExoPlayer} for testing content/ad playback. */
|
||||
/* package */ final class FakeExoPlayer extends StubExoPlayer {
|
||||
|
||||
private final ListenerSet<Listener> listeners;
|
||||
private final Timeline.Period period;
|
||||
@ -48,8 +48,7 @@ import com.google.android.exoplayer2.util.Util;
|
||||
private int adGroupIndex;
|
||||
private int adIndexInAdGroup;
|
||||
|
||||
public FakePlayer(Context context) {
|
||||
super(context);
|
||||
public FakeExoPlayer() {
|
||||
listeners =
|
||||
new ListenerSet<>(
|
||||
Looper.getMainLooper(),
|
@ -139,13 +139,13 @@ public final class ImaAdsLoaderTest {
|
||||
private ContentProgressProvider contentProgressProvider;
|
||||
private VideoAdPlayer videoAdPlayer;
|
||||
private TestAdsLoaderListener adsLoaderListener;
|
||||
private FakePlayer fakePlayer;
|
||||
private FakeExoPlayer fakePlayer;
|
||||
private ImaAdsLoader imaAdsLoader;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
setupMocks();
|
||||
fakePlayer = new FakePlayer(getApplicationContext());
|
||||
fakePlayer = new FakeExoPlayer();
|
||||
adViewGroup = new FrameLayout(getApplicationContext());
|
||||
View adOverlayView = new View(getApplicationContext());
|
||||
adViewProvider =
|
||||
|
@ -49,7 +49,6 @@ dependencies {
|
||||
testImplementation 'junit:junit:' + junitVersion
|
||||
testImplementation 'com.google.truth:truth:' + truthVersion
|
||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||
testImplementation project(modulePrefix + 'library-core')
|
||||
testImplementation project(modulePrefix + 'testutils')
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
|
||||
import com.google.android.exoplayer2.testutil.FakeMetadataEntry;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.video.ColorInfo;
|
||||
import java.util.ArrayList;
|
||||
@ -65,10 +65,7 @@ public final class FormatTest {
|
||||
|
||||
byte[] projectionData = new byte[] {1, 2, 3};
|
||||
|
||||
Metadata metadata =
|
||||
new Metadata(
|
||||
new TextInformationFrame("id1", "description1", "value1"),
|
||||
new TextInformationFrame("id2", "description2", "value2"));
|
||||
Metadata metadata = new Metadata(new FakeMetadataEntry("id1"), new FakeMetadataEntry("id2"));
|
||||
|
||||
ColorInfo colorInfo =
|
||||
new ColorInfo(
|
||||
|
@ -24,10 +24,8 @@ import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.testutil.StubExoPlayer;
|
||||
import com.google.android.exoplayer2.testutil.StubPlayer;
|
||||
import com.google.android.exoplayer2.util.FlagSet;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -48,7 +46,7 @@ public class ForwardingPlayerTest {
|
||||
|
||||
@Test
|
||||
public void addListener_addsForwardingListener() {
|
||||
FakePlayer player = new FakePlayer(ApplicationProvider.getApplicationContext());
|
||||
FakePlayer player = new FakePlayer();
|
||||
Player.Listener listener1 = mock(Player.Listener.class);
|
||||
Player.Listener listener2 = mock(Player.Listener.class);
|
||||
|
||||
@ -63,7 +61,7 @@ public class ForwardingPlayerTest {
|
||||
|
||||
@Test
|
||||
public void removeListener_removesForwardingListener() {
|
||||
FakePlayer player = new FakePlayer(ApplicationProvider.getApplicationContext());
|
||||
FakePlayer player = new FakePlayer();
|
||||
Player.Listener listener1 = mock(Player.Listener.class);
|
||||
Player.Listener listener2 = mock(Player.Listener.class);
|
||||
ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player);
|
||||
@ -81,7 +79,7 @@ public class ForwardingPlayerTest {
|
||||
|
||||
@Test
|
||||
public void onEvents_passesForwardingPlayerAsArgument() {
|
||||
FakePlayer player = new FakePlayer(ApplicationProvider.getApplicationContext());
|
||||
FakePlayer player = new FakePlayer();
|
||||
Player.Listener listener = mock(Player.Listener.class);
|
||||
ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player);
|
||||
forwardingPlayer.addListener(listener);
|
||||
@ -180,14 +178,10 @@ public class ForwardingPlayerTest {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
private static class FakePlayer extends StubExoPlayer {
|
||||
private static class FakePlayer extends StubPlayer {
|
||||
|
||||
private final Set<Listener> listeners = new HashSet<>();
|
||||
|
||||
public FakePlayer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Listener listener) {
|
||||
listeners.add(listener);
|
||||
|
@ -19,7 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.os.Parcel;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.metadata.id3.BinaryFrame;
|
||||
import com.google.android.exoplayer2.testutil.FakeMetadataEntry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@ -30,8 +30,7 @@ public class MetadataTest {
|
||||
@Test
|
||||
public void parcelable() {
|
||||
Metadata metadataToParcel =
|
||||
new Metadata(
|
||||
new BinaryFrame("id1", new byte[] {1}), new BinaryFrame("id2", new byte[] {2}));
|
||||
new Metadata(new FakeMetadataEntry("id1"), new FakeMetadataEntry("id2"));
|
||||
|
||||
Parcel parcel = Parcel.obtain();
|
||||
metadataToParcel.writeToParcel(parcel, 0);
|
||||
|
@ -19,10 +19,8 @@ import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.Bundleable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -183,17 +181,4 @@ public final class TrackSelectionParametersTest {
|
||||
assertThat(parameters.viewportHeight).isEqualTo(Integer.MAX_VALUE);
|
||||
assertThat(parameters.viewportOrientationMayChange).isTrue();
|
||||
}
|
||||
|
||||
/** Tests {@link SelectionOverride}'s {@link Bundleable} implementation. */
|
||||
@Test
|
||||
public void roundTripViaBundle_ofSelectionOverride_yieldsEqualInstance() {
|
||||
SelectionOverride selectionOverrideToBundle =
|
||||
new SelectionOverride(/* groupIndex= */ 1, /* tracks...= */ 2, 3);
|
||||
|
||||
SelectionOverride selectionOverrideFromBundle =
|
||||
DefaultTrackSelector.SelectionOverride.CREATOR.fromBundle(
|
||||
selectionOverrideToBundle.toBundle());
|
||||
|
||||
assertThat(selectionOverrideFromBundle).isEqualTo(selectionOverrideToBundle);
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public final class AtomicFileTest {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
tempFolder =
|
||||
Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest");
|
||||
Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "AtomicFileTest");
|
||||
file = new File(tempFolder, "atomicFile");
|
||||
atomicFile = new AtomicFile(file);
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import org.junit.runner.RunWith;
|
||||
public class MediaFormatUtilTest {
|
||||
|
||||
@Test
|
||||
public void createMediaFormatFromEmptyExoPlayerFormat_generatesExpectedEntries() {
|
||||
public void createMediaFormatFromFormat_withEmptyFormat_generatesExpectedEntries() {
|
||||
MediaFormat mediaFormat =
|
||||
MediaFormatUtil.createMediaFormatFromFormat(new Format.Builder().build());
|
||||
// Assert that no invalid keys are accidentally being populated.
|
||||
@ -59,7 +59,7 @@ public class MediaFormatUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMediaFormatFromPopulatedExoPlayerFormat_generatesExpectedMediaFormatEntries() {
|
||||
public void createMediaFormatFromFormat_withPopulatedFormat_generatesExpectedEntries() {
|
||||
Format format =
|
||||
new Format.Builder()
|
||||
.setAverageBitrate(1)
|
||||
@ -145,7 +145,7 @@ public class MediaFormatUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMediaFormatWithExoPlayerPcmEncoding_containsExoPlayerSpecificEncoding() {
|
||||
public void createMediaFormatFromFormat_withPcmEncoding_setsCustomPcmEncodingEntry() {
|
||||
Format format = new Format.Builder().setPcmEncoding(C.ENCODING_PCM_32BIT).build();
|
||||
MediaFormat mediaFormat = MediaFormatUtil.createMediaFormatFromFormat(format);
|
||||
assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_EXO_PCM_ENCODING))
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||
|
||||
import android.content.Context;
|
||||
@ -59,6 +60,7 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
|
||||
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
|
||||
import com.google.android.exoplayer2.video.VideoSize;
|
||||
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
|
||||
import com.google.common.base.Supplier;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -366,12 +368,12 @@ public interface ExoPlayer extends Player {
|
||||
|
||||
/* package */ Clock clock;
|
||||
/* package */ long foregroundModeTimeoutMs;
|
||||
/* package */ RenderersFactory renderersFactory;
|
||||
/* package */ MediaSourceFactory mediaSourceFactory;
|
||||
/* package */ TrackSelector trackSelector;
|
||||
/* package */ LoadControl loadControl;
|
||||
/* package */ BandwidthMeter bandwidthMeter;
|
||||
/* package */ AnalyticsCollector analyticsCollector;
|
||||
/* package */ Supplier<RenderersFactory> renderersFactorySupplier;
|
||||
/* package */ Supplier<MediaSourceFactory> mediaSourceFactorySupplier;
|
||||
/* package */ Supplier<TrackSelector> trackSelectorSupplier;
|
||||
/* package */ Supplier<LoadControl> loadControlSupplier;
|
||||
/* package */ Supplier<BandwidthMeter> bandwidthMeterSupplier;
|
||||
/* package */ Supplier<AnalyticsCollector> analyticsCollectorSupplier;
|
||||
/* package */ Looper looper;
|
||||
@Nullable /* package */ PriorityTaskManager priorityTaskManager;
|
||||
/* package */ AudioAttributes audioAttributes;
|
||||
@ -437,8 +439,8 @@ public interface ExoPlayer extends Player {
|
||||
public Builder(Context context) {
|
||||
this(
|
||||
context,
|
||||
new DefaultRenderersFactory(context),
|
||||
new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory()));
|
||||
() -> new DefaultRenderersFactory(context),
|
||||
() -> new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -456,8 +458,8 @@ public interface ExoPlayer extends Player {
|
||||
public Builder(Context context, RenderersFactory renderersFactory) {
|
||||
this(
|
||||
context,
|
||||
renderersFactory,
|
||||
new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory()));
|
||||
() -> renderersFactory,
|
||||
() -> new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -474,7 +476,7 @@ public interface ExoPlayer extends Player {
|
||||
* MediaItem}.
|
||||
*/
|
||||
public Builder(Context context, MediaSourceFactory mediaSourceFactory) {
|
||||
this(context, new DefaultRenderersFactory(context), mediaSourceFactory);
|
||||
this(context, () -> new DefaultRenderersFactory(context), () -> mediaSourceFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -494,14 +496,7 @@ public interface ExoPlayer extends Player {
|
||||
*/
|
||||
public Builder(
|
||||
Context context, RenderersFactory renderersFactory, MediaSourceFactory mediaSourceFactory) {
|
||||
this(
|
||||
context,
|
||||
renderersFactory,
|
||||
mediaSourceFactory,
|
||||
new DefaultTrackSelector(context),
|
||||
new DefaultLoadControl(),
|
||||
DefaultBandwidthMeter.getSingletonInstance(context),
|
||||
new AnalyticsCollector(Clock.DEFAULT));
|
||||
this(context, () -> renderersFactory, () -> mediaSourceFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -527,13 +522,48 @@ public interface ExoPlayer extends Player {
|
||||
LoadControl loadControl,
|
||||
BandwidthMeter bandwidthMeter,
|
||||
AnalyticsCollector analyticsCollector) {
|
||||
this(
|
||||
context,
|
||||
() -> renderersFactory,
|
||||
() -> mediaSourceFactory,
|
||||
() -> trackSelector,
|
||||
() -> loadControl,
|
||||
() -> bandwidthMeter,
|
||||
() -> analyticsCollector);
|
||||
}
|
||||
|
||||
private Builder(
|
||||
Context context,
|
||||
Supplier<RenderersFactory> renderersFactorySupplier,
|
||||
Supplier<MediaSourceFactory> mediaSourceFactorySupplier) {
|
||||
this(
|
||||
context,
|
||||
renderersFactorySupplier,
|
||||
mediaSourceFactorySupplier,
|
||||
() -> new DefaultTrackSelector(context),
|
||||
DefaultLoadControl::new,
|
||||
() -> DefaultBandwidthMeter.getSingletonInstance(context),
|
||||
/* analyticsCollectorSupplier= */ null);
|
||||
}
|
||||
|
||||
private Builder(
|
||||
Context context,
|
||||
Supplier<RenderersFactory> renderersFactorySupplier,
|
||||
Supplier<MediaSourceFactory> mediaSourceFactorySupplier,
|
||||
Supplier<TrackSelector> trackSelectorSupplier,
|
||||
Supplier<LoadControl> loadControlSupplier,
|
||||
Supplier<BandwidthMeter> bandwidthMeterSupplier,
|
||||
@Nullable Supplier<AnalyticsCollector> analyticsCollectorSupplier) {
|
||||
this.context = context;
|
||||
this.renderersFactory = renderersFactory;
|
||||
this.mediaSourceFactory = mediaSourceFactory;
|
||||
this.trackSelector = trackSelector;
|
||||
this.loadControl = loadControl;
|
||||
this.bandwidthMeter = bandwidthMeter;
|
||||
this.analyticsCollector = analyticsCollector;
|
||||
this.renderersFactorySupplier = renderersFactorySupplier;
|
||||
this.mediaSourceFactorySupplier = mediaSourceFactorySupplier;
|
||||
this.trackSelectorSupplier = trackSelectorSupplier;
|
||||
this.loadControlSupplier = loadControlSupplier;
|
||||
this.bandwidthMeterSupplier = bandwidthMeterSupplier;
|
||||
this.analyticsCollectorSupplier =
|
||||
analyticsCollectorSupplier != null
|
||||
? analyticsCollectorSupplier
|
||||
: () -> new AnalyticsCollector(checkNotNull(clock));
|
||||
looper = Util.getCurrentOrMainLooper();
|
||||
audioAttributes = AudioAttributes.DEFAULT;
|
||||
wakeMode = C.WAKE_MODE_NONE;
|
||||
@ -573,7 +603,7 @@ public interface ExoPlayer extends Player {
|
||||
*/
|
||||
public Builder setRenderersFactory(RenderersFactory renderersFactory) {
|
||||
checkState(!buildCalled);
|
||||
this.renderersFactory = renderersFactory;
|
||||
this.renderersFactorySupplier = () -> renderersFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -586,7 +616,7 @@ public interface ExoPlayer extends Player {
|
||||
*/
|
||||
public Builder setMediaSourceFactory(MediaSourceFactory mediaSourceFactory) {
|
||||
checkState(!buildCalled);
|
||||
this.mediaSourceFactory = mediaSourceFactory;
|
||||
this.mediaSourceFactorySupplier = () -> mediaSourceFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -599,7 +629,7 @@ public interface ExoPlayer extends Player {
|
||||
*/
|
||||
public Builder setTrackSelector(TrackSelector trackSelector) {
|
||||
checkState(!buildCalled);
|
||||
this.trackSelector = trackSelector;
|
||||
this.trackSelectorSupplier = () -> trackSelector;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -612,7 +642,7 @@ public interface ExoPlayer extends Player {
|
||||
*/
|
||||
public Builder setLoadControl(LoadControl loadControl) {
|
||||
checkState(!buildCalled);
|
||||
this.loadControl = loadControl;
|
||||
this.loadControlSupplier = () -> loadControl;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -625,7 +655,7 @@ public interface ExoPlayer extends Player {
|
||||
*/
|
||||
public Builder setBandwidthMeter(BandwidthMeter bandwidthMeter) {
|
||||
checkState(!buildCalled);
|
||||
this.bandwidthMeter = bandwidthMeter;
|
||||
this.bandwidthMeterSupplier = () -> bandwidthMeter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -652,7 +682,7 @@ public interface ExoPlayer extends Player {
|
||||
*/
|
||||
public Builder setAnalyticsCollector(AnalyticsCollector analyticsCollector) {
|
||||
checkState(!buildCalled);
|
||||
this.analyticsCollector = analyticsCollector;
|
||||
this.analyticsCollectorSupplier = () -> analyticsCollector;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLE
|
||||
import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER;
|
||||
import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT;
|
||||
import static com.google.android.exoplayer2.Renderer.MSG_SET_VOLUME;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
@ -66,7 +67,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.ConditionVariable;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
@ -409,12 +409,14 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
Clock clock,
|
||||
Looper applicationLooper) {
|
||||
this(
|
||||
new ExoPlayer.Builder(context, renderersFactory)
|
||||
.setTrackSelector(trackSelector)
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.setLoadControl(loadControl)
|
||||
.setBandwidthMeter(bandwidthMeter)
|
||||
.setAnalyticsCollector(analyticsCollector)
|
||||
new ExoPlayer.Builder(
|
||||
context,
|
||||
renderersFactory,
|
||||
mediaSourceFactory,
|
||||
trackSelector,
|
||||
loadControl,
|
||||
bandwidthMeter,
|
||||
analyticsCollector)
|
||||
.setUseLazyPreparation(useLazyPreparation)
|
||||
.setClock(clock)
|
||||
.setLooper(applicationLooper));
|
||||
@ -431,7 +433,7 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
constructorFinished = new ConditionVariable();
|
||||
try {
|
||||
applicationContext = builder.context.getApplicationContext();
|
||||
analyticsCollector = builder.analyticsCollector;
|
||||
analyticsCollector = builder.analyticsCollectorSupplier.get();
|
||||
priorityTaskManager = builder.priorityTaskManager;
|
||||
audioAttributes = builder.audioAttributes;
|
||||
videoScalingMode = builder.videoScalingMode;
|
||||
@ -443,12 +445,15 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
listeners = new CopyOnWriteArraySet<>();
|
||||
Handler eventHandler = new Handler(builder.looper);
|
||||
renderers =
|
||||
builder.renderersFactory.createRenderers(
|
||||
eventHandler,
|
||||
componentListener,
|
||||
componentListener,
|
||||
componentListener,
|
||||
componentListener);
|
||||
builder
|
||||
.renderersFactorySupplier
|
||||
.get()
|
||||
.createRenderers(
|
||||
eventHandler,
|
||||
componentListener,
|
||||
componentListener,
|
||||
componentListener,
|
||||
componentListener);
|
||||
|
||||
// Set initial values.
|
||||
volume = 1;
|
||||
@ -476,10 +481,10 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
player =
|
||||
new ExoPlayerImpl(
|
||||
renderers,
|
||||
builder.trackSelector,
|
||||
builder.mediaSourceFactory,
|
||||
builder.loadControl,
|
||||
builder.bandwidthMeter,
|
||||
builder.trackSelectorSupplier.get(),
|
||||
builder.mediaSourceFactorySupplier.get(),
|
||||
builder.loadControlSupplier.get(),
|
||||
builder.bandwidthMeterSupplier.get(),
|
||||
analyticsCollector,
|
||||
builder.useLazyPreparation,
|
||||
builder.seekParameters,
|
||||
@ -848,7 +853,7 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
@Override
|
||||
public void addAnalyticsListener(AnalyticsListener listener) {
|
||||
// Don't verify application thread. We allow calls to this method from any thread.
|
||||
Assertions.checkNotNull(listener);
|
||||
checkNotNull(listener);
|
||||
analyticsCollector.addListener(listener);
|
||||
}
|
||||
|
||||
@ -874,7 +879,7 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
return;
|
||||
}
|
||||
if (isPriorityTaskManagerRegistered) {
|
||||
Assertions.checkNotNull(this.priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
|
||||
checkNotNull(this.priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
|
||||
}
|
||||
if (priorityTaskManager != null && isLoading()) {
|
||||
priorityTaskManager.add(C.PRIORITY_PLAYBACK);
|
||||
@ -982,7 +987,7 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
|
||||
@Override
|
||||
public void addListener(Listener listener) {
|
||||
Assertions.checkNotNull(listener);
|
||||
checkNotNull(listener);
|
||||
listeners.add(listener);
|
||||
EventListener eventListener = listener;
|
||||
addListener(eventListener);
|
||||
@ -992,13 +997,13 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
@Override
|
||||
public void addListener(Player.EventListener listener) {
|
||||
// Don't verify application thread. We allow calls to this method from any thread.
|
||||
Assertions.checkNotNull(listener);
|
||||
checkNotNull(listener);
|
||||
player.addEventListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(Listener listener) {
|
||||
Assertions.checkNotNull(listener);
|
||||
checkNotNull(listener);
|
||||
listeners.remove(listener);
|
||||
EventListener eventListener = listener;
|
||||
removeListener(eventListener);
|
||||
@ -1322,7 +1327,7 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
ownedSurface = null;
|
||||
}
|
||||
if (isPriorityTaskManagerRegistered) {
|
||||
Assertions.checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
|
||||
checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
|
||||
isPriorityTaskManagerRegistered = false;
|
||||
}
|
||||
currentCues = Collections.emptyList();
|
||||
|
@ -24,6 +24,7 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.util.GlUtil;
|
||||
import java.nio.FloatBuffer;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* Utility class to render spherical meshes for video or images. Call {@link #init()} on the GL
|
||||
@ -93,9 +94,9 @@ import java.nio.FloatBuffer;
|
||||
private int stereoMode;
|
||||
@Nullable private MeshData leftMeshData;
|
||||
@Nullable private MeshData rightMeshData;
|
||||
@Nullable private GlUtil.Program program;
|
||||
private GlUtil.@MonotonicNonNull Program program;
|
||||
|
||||
// Program related GL items. These are only valid if program is non-null.
|
||||
// Program related GL items. These are only valid if Program is valid.
|
||||
private int mvpMatrixHandle;
|
||||
private int uTexMatrixHandle;
|
||||
private int positionHandle;
|
||||
@ -195,11 +196,10 @@ import java.nio.FloatBuffer;
|
||||
GLES20.glDisableVertexAttribArray(texCoordsHandle);
|
||||
}
|
||||
|
||||
/** Cleans up the GL resources. */
|
||||
/** Cleans up GL resources. */
|
||||
/* package */ void shutdown() {
|
||||
if (program != null) {
|
||||
program.delete();
|
||||
program = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
projectionRenderer.draw(textureId, tempMatrix, rightEye);
|
||||
}
|
||||
|
||||
/** Cleans up the GL resources. */
|
||||
/** Cleans up GL resources. */
|
||||
public void shutdown() {
|
||||
projectionRenderer.shutdown();
|
||||
}
|
||||
|
@ -1750,6 +1750,19 @@ public final class DefaultTrackSelectorTest {
|
||||
assertThat(trackGroupInfos.get(0).getTrackSupport(0)).isEqualTo(FORMAT_HANDLED);
|
||||
}
|
||||
|
||||
/** Tests {@link SelectionOverride}'s {@link Bundleable} implementation. */
|
||||
@Test
|
||||
public void roundTripViaBundle_ofSelectionOverride_yieldsEqualInstance() {
|
||||
SelectionOverride selectionOverrideToBundle =
|
||||
new SelectionOverride(/* groupIndex= */ 1, /* tracks...= */ 2, 3);
|
||||
|
||||
SelectionOverride selectionOverrideFromBundle =
|
||||
DefaultTrackSelector.SelectionOverride.CREATOR.fromBundle(
|
||||
selectionOverrideToBundle.toBundle());
|
||||
|
||||
assertThat(selectionOverrideFromBundle).isEqualTo(selectionOverrideToBundle);
|
||||
}
|
||||
|
||||
private static void assertSelections(TrackSelectorResult result, TrackSelection[] expected) {
|
||||
assertThat(result.length).isEqualTo(expected.length);
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
|
@ -19,6 +19,7 @@ import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
@ -34,8 +35,14 @@ import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
/** Extracts data from WAV byte streams. */
|
||||
public final class WavExtractor implements Extractor {
|
||||
@ -50,13 +57,26 @@ public final class WavExtractor implements Extractor {
|
||||
/** Factory for {@link WavExtractor} instances. */
|
||||
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new WavExtractor()};
|
||||
|
||||
/** Parser state. */
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.TYPE_USE})
|
||||
@IntDef({STATE_READING_HEADER, STATE_SKIPPING_TO_SAMPLE_DATA, STATE_READING_SAMPLE_DATA})
|
||||
private @interface State {}
|
||||
|
||||
private static final int STATE_READING_HEADER = 0;
|
||||
private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 1;
|
||||
private static final int STATE_READING_SAMPLE_DATA = 2;
|
||||
|
||||
private @MonotonicNonNull ExtractorOutput extractorOutput;
|
||||
private @MonotonicNonNull TrackOutput trackOutput;
|
||||
private @State int state;
|
||||
private @MonotonicNonNull OutputWriter outputWriter;
|
||||
private int dataStartPosition;
|
||||
private long dataEndPosition;
|
||||
|
||||
public WavExtractor() {
|
||||
state = STATE_READING_HEADER;
|
||||
dataStartPosition = C.POSITION_UNSET;
|
||||
dataEndPosition = C.POSITION_UNSET;
|
||||
}
|
||||
@ -75,6 +95,7 @@ public final class WavExtractor implements Extractor {
|
||||
|
||||
@Override
|
||||
public void seek(long position, long timeUs) {
|
||||
state = position == 0 ? STATE_READING_HEADER : STATE_READING_SAMPLE_DATA;
|
||||
if (outputWriter != null) {
|
||||
outputWriter.reset(timeUs);
|
||||
}
|
||||
@ -86,59 +107,21 @@ public final class WavExtractor implements Extractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReadResult
|
||||
public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
|
||||
assertInitialized();
|
||||
if (outputWriter == null) {
|
||||
WavHeader header = WavHeaderReader.peek(input);
|
||||
if (header == null) {
|
||||
// Should only happen if the media wasn't sniffed.
|
||||
throw ParserException.createForMalformedContainer(
|
||||
"Unsupported or unrecognized wav header.", /* cause= */ null);
|
||||
}
|
||||
|
||||
if (header.formatType == WavUtil.TYPE_IMA_ADPCM) {
|
||||
outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header);
|
||||
} else if (header.formatType == WavUtil.TYPE_ALAW) {
|
||||
outputWriter =
|
||||
new PassthroughOutputWriter(
|
||||
extractorOutput,
|
||||
trackOutput,
|
||||
header,
|
||||
MimeTypes.AUDIO_ALAW,
|
||||
/* pcmEncoding= */ Format.NO_VALUE);
|
||||
} else if (header.formatType == WavUtil.TYPE_MLAW) {
|
||||
outputWriter =
|
||||
new PassthroughOutputWriter(
|
||||
extractorOutput,
|
||||
trackOutput,
|
||||
header,
|
||||
MimeTypes.AUDIO_MLAW,
|
||||
/* pcmEncoding= */ Format.NO_VALUE);
|
||||
} else {
|
||||
@C.PcmEncoding
|
||||
int pcmEncoding = WavUtil.getPcmEncodingForType(header.formatType, header.bitsPerSample);
|
||||
if (pcmEncoding == C.ENCODING_INVALID) {
|
||||
throw ParserException.createForUnsupportedContainerFeature(
|
||||
"Unsupported WAV format type: " + header.formatType);
|
||||
}
|
||||
outputWriter =
|
||||
new PassthroughOutputWriter(
|
||||
extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding);
|
||||
}
|
||||
switch (state) {
|
||||
case STATE_READING_HEADER:
|
||||
readHeader(input);
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
case STATE_SKIPPING_TO_SAMPLE_DATA:
|
||||
skipToSampleData(input);
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
case STATE_READING_SAMPLE_DATA:
|
||||
return readSampleData(input);
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
if (dataStartPosition == C.POSITION_UNSET) {
|
||||
Pair<Long, Long> dataBounds = WavHeaderReader.skipToData(input);
|
||||
dataStartPosition = dataBounds.first.intValue();
|
||||
dataEndPosition = dataBounds.second;
|
||||
outputWriter.init(dataStartPosition, dataEndPosition);
|
||||
} else if (input.getPosition() == 0) {
|
||||
input.skipFully(dataStartPosition);
|
||||
}
|
||||
|
||||
Assertions.checkState(dataEndPosition != C.POSITION_UNSET);
|
||||
long bytesLeft = dataEndPosition - input.getPosition();
|
||||
return outputWriter.sampleData(input, bytesLeft) ? RESULT_END_OF_INPUT : RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
@EnsuresNonNull({"extractorOutput", "trackOutput"})
|
||||
@ -147,6 +130,71 @@ public final class WavExtractor implements Extractor {
|
||||
Util.castNonNull(extractorOutput);
|
||||
}
|
||||
|
||||
@RequiresNonNull({"extractorOutput", "trackOutput"})
|
||||
private void readHeader(ExtractorInput input) throws IOException {
|
||||
Assertions.checkState(input.getPosition() == 0);
|
||||
if (dataStartPosition != C.POSITION_UNSET) {
|
||||
input.skipFully(dataStartPosition);
|
||||
state = STATE_READING_SAMPLE_DATA;
|
||||
return;
|
||||
}
|
||||
WavHeader header = WavHeaderReader.peek(input);
|
||||
if (header == null) {
|
||||
// Should only happen if the media wasn't sniffed.
|
||||
throw ParserException.createForMalformedContainer(
|
||||
"Unsupported or unrecognized wav header.", /* cause= */ null);
|
||||
}
|
||||
input.skipFully((int) (input.getPeekPosition() - input.getPosition()));
|
||||
|
||||
if (header.formatType == WavUtil.TYPE_IMA_ADPCM) {
|
||||
outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header);
|
||||
} else if (header.formatType == WavUtil.TYPE_ALAW) {
|
||||
outputWriter =
|
||||
new PassthroughOutputWriter(
|
||||
extractorOutput,
|
||||
trackOutput,
|
||||
header,
|
||||
MimeTypes.AUDIO_ALAW,
|
||||
/* pcmEncoding= */ Format.NO_VALUE);
|
||||
} else if (header.formatType == WavUtil.TYPE_MLAW) {
|
||||
outputWriter =
|
||||
new PassthroughOutputWriter(
|
||||
extractorOutput,
|
||||
trackOutput,
|
||||
header,
|
||||
MimeTypes.AUDIO_MLAW,
|
||||
/* pcmEncoding= */ Format.NO_VALUE);
|
||||
} else {
|
||||
@C.PcmEncoding
|
||||
int pcmEncoding = WavUtil.getPcmEncodingForType(header.formatType, header.bitsPerSample);
|
||||
if (pcmEncoding == C.ENCODING_INVALID) {
|
||||
throw ParserException.createForUnsupportedContainerFeature(
|
||||
"Unsupported WAV format type: " + header.formatType);
|
||||
}
|
||||
outputWriter =
|
||||
new PassthroughOutputWriter(
|
||||
extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding);
|
||||
}
|
||||
state = STATE_SKIPPING_TO_SAMPLE_DATA;
|
||||
}
|
||||
|
||||
private void skipToSampleData(ExtractorInput input) throws IOException {
|
||||
Pair<Long, Long> dataBounds = WavHeaderReader.skipToSampleData(input);
|
||||
dataStartPosition = dataBounds.first.intValue();
|
||||
dataEndPosition = dataBounds.second;
|
||||
Assertions.checkNotNull(outputWriter).init(dataStartPosition, dataEndPosition);
|
||||
state = STATE_READING_SAMPLE_DATA;
|
||||
}
|
||||
|
||||
@ReadResult
|
||||
private int readSampleData(ExtractorInput input) throws IOException {
|
||||
Assertions.checkState(dataEndPosition != C.POSITION_UNSET);
|
||||
long bytesLeft = dataEndPosition - input.getPosition();
|
||||
return Assertions.checkNotNull(outputWriter).sampleData(input, bytesLeft)
|
||||
? RESULT_END_OF_INPUT
|
||||
: RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
/** Writes to the extractor's output. */
|
||||
private interface OutputWriter {
|
||||
|
||||
|
@ -108,7 +108,7 @@ import java.io.IOException;
|
||||
* @throws ParserException If an error occurs parsing chunks.
|
||||
* @throws IOException If reading from the input fails.
|
||||
*/
|
||||
public static Pair<Long, Long> skipToData(ExtractorInput input) throws IOException {
|
||||
public static Pair<Long, Long> skipToSampleData(ExtractorInput input) throws IOException {
|
||||
Assertions.checkNotNull(input);
|
||||
|
||||
// Make sure the peek position is set to the read position before we peek the first header.
|
||||
@ -118,14 +118,8 @@ import java.io.IOException;
|
||||
// Skip all chunks until we find the data header.
|
||||
ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch);
|
||||
while (chunkHeader.id != WavUtil.DATA_FOURCC) {
|
||||
if (chunkHeader.id != WavUtil.RIFF_FOURCC && chunkHeader.id != WavUtil.FMT_FOURCC) {
|
||||
Log.w(TAG, "Ignoring unknown WAV chunk: " + chunkHeader.id);
|
||||
}
|
||||
Log.w(TAG, "Ignoring unknown WAV chunk: " + chunkHeader.id);
|
||||
long bytesToSkip = ChunkHeader.SIZE_IN_BYTES + chunkHeader.size;
|
||||
// Override size of RIFF chunk, since it describes its size as the entire file.
|
||||
if (chunkHeader.id == WavUtil.RIFF_FOURCC) {
|
||||
bytesToSkip = ChunkHeader.SIZE_IN_BYTES + 4;
|
||||
}
|
||||
if (bytesToSkip > Integer.MAX_VALUE) {
|
||||
throw ParserException.createForUnsupportedContainerFeature(
|
||||
"Chunk is too large (~2GB+) to skip; id: " + chunkHeader.id);
|
||||
|
@ -45,7 +45,6 @@ import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.RenderersFactory;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.TracksInfo;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
||||
@ -56,6 +55,7 @@ import com.google.android.exoplayer2.source.MediaSourceFactory;
|
||||
import com.google.android.exoplayer2.text.TextOutput;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
@ -92,12 +92,16 @@ public final class TranscodingTransformer {
|
||||
/** A builder for {@link TranscodingTransformer} instances. */
|
||||
public static final class Builder {
|
||||
|
||||
// Mandatory field.
|
||||
private @MonotonicNonNull Context context;
|
||||
|
||||
// Optional fields.
|
||||
private @MonotonicNonNull MediaSourceFactory mediaSourceFactory;
|
||||
private Muxer.Factory muxerFactory;
|
||||
private boolean removeAudio;
|
||||
private boolean removeVideo;
|
||||
private boolean flattenForSlowMotion;
|
||||
private int outputHeight;
|
||||
private String outputMimeType;
|
||||
@Nullable private String audioMimeType;
|
||||
@Nullable private String videoMimeType;
|
||||
@ -122,6 +126,7 @@ public final class TranscodingTransformer {
|
||||
this.removeAudio = transcodingTransformer.transformation.removeAudio;
|
||||
this.removeVideo = transcodingTransformer.transformation.removeVideo;
|
||||
this.flattenForSlowMotion = transcodingTransformer.transformation.flattenForSlowMotion;
|
||||
this.outputHeight = transcodingTransformer.transformation.outputHeight;
|
||||
this.outputMimeType = transcodingTransformer.transformation.outputMimeType;
|
||||
this.audioMimeType = transcodingTransformer.transformation.audioMimeType;
|
||||
this.videoMimeType = transcodingTransformer.transformation.videoMimeType;
|
||||
@ -214,6 +219,21 @@ public final class TranscodingTransformer {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the output resolution for the video, using the output height. The default value is to
|
||||
* use the same height as the input. Output width will scale to preserve the input video's
|
||||
* aspect ratio.
|
||||
*
|
||||
* <p>For example, a 1920x1440 video can be scaled to 640x480 by calling setResolution(480).
|
||||
*
|
||||
* @param outputHeight The output height for the video, in pixels.
|
||||
* @return This builder.
|
||||
*/
|
||||
public Builder setResolution(int outputHeight) {
|
||||
this.outputHeight = outputHeight;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the MIME type of the output. The default value is {@link MimeTypes#VIDEO_MP4}. Supported
|
||||
* values are:
|
||||
@ -357,6 +377,12 @@ public final class TranscodingTransformer {
|
||||
checkState(
|
||||
muxerFactory.supportsOutputMimeType(outputMimeType),
|
||||
"Unsupported output MIME type: " + outputMimeType);
|
||||
// TODO(ME): Test with values of 10, 100, 1000).
|
||||
Log.e("TranscodingTransformer", "outputHeight = " + outputHeight);
|
||||
if (outputHeight == 0) {
|
||||
// TODO(ME): get output height from input video.
|
||||
outputHeight = 480;
|
||||
}
|
||||
if (audioMimeType != null) {
|
||||
checkSampleMimeType(audioMimeType);
|
||||
}
|
||||
@ -368,6 +394,7 @@ public final class TranscodingTransformer {
|
||||
removeAudio,
|
||||
removeVideo,
|
||||
flattenForSlowMotion,
|
||||
outputHeight,
|
||||
outputMimeType,
|
||||
audioMimeType,
|
||||
videoMimeType);
|
||||
@ -454,6 +481,7 @@ public final class TranscodingTransformer {
|
||||
checkState(
|
||||
!transformation.removeAudio || !transformation.removeVideo,
|
||||
"Audio and video cannot both be removed.");
|
||||
checkState(!(transformation.removeVideo));
|
||||
this.context = context;
|
||||
this.mediaSourceFactory = mediaSourceFactory;
|
||||
this.muxerFactory = muxerFactory;
|
||||
@ -573,8 +601,7 @@ public final class TranscodingTransformer {
|
||||
.setClock(clock)
|
||||
.build();
|
||||
player.setMediaItem(mediaItem);
|
||||
player.addAnalyticsListener(
|
||||
new TranscodingTransformerAnalyticsListener(mediaItem, muxerWrapper));
|
||||
player.addListener(new TranscodingTransformerPlayerListener(mediaItem, muxerWrapper));
|
||||
player.prepare();
|
||||
|
||||
progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
||||
@ -688,30 +715,30 @@ public final class TranscodingTransformer {
|
||||
}
|
||||
}
|
||||
|
||||
private final class TranscodingTransformerAnalyticsListener implements AnalyticsListener {
|
||||
private final class TranscodingTransformerPlayerListener implements Player.Listener {
|
||||
|
||||
private final MediaItem mediaItem;
|
||||
private final MuxerWrapper muxerWrapper;
|
||||
|
||||
public TranscodingTransformerAnalyticsListener(MediaItem mediaItem, MuxerWrapper muxerWrapper) {
|
||||
public TranscodingTransformerPlayerListener(MediaItem mediaItem, MuxerWrapper muxerWrapper) {
|
||||
this.mediaItem = mediaItem;
|
||||
this.muxerWrapper = muxerWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(EventTime eventTime, int state) {
|
||||
public void onPlaybackStateChanged(int state) {
|
||||
if (state == Player.STATE_ENDED) {
|
||||
handleTransformationEnded(/* exception= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(EventTime eventTime, int reason) {
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
if (progressState != PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
|
||||
return;
|
||||
}
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
eventTime.timeline.getWindow(/* windowIndex= */ 0, window);
|
||||
timeline.getWindow(/* windowIndex= */ 0, window);
|
||||
if (!window.isPlaceholder) {
|
||||
long durationUs = window.durationUs;
|
||||
// Make progress permanently unavailable if the duration is unknown, so that it doesn't jump
|
||||
@ -726,7 +753,7 @@ public final class TranscodingTransformer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksInfoChanged(EventTime eventTime, TracksInfo tracksInfo) {
|
||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
||||
if (muxerWrapper.getTrackCount() == 0) {
|
||||
handleTransformationEnded(
|
||||
new IllegalStateException(
|
||||
@ -736,7 +763,7 @@ public final class TranscodingTransformer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(EventTime eventTime, PlaybackException error) {
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
handleTransformationEnded(error);
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import androidx.annotation.Nullable;
|
||||
public final boolean removeAudio;
|
||||
public final boolean removeVideo;
|
||||
public final boolean flattenForSlowMotion;
|
||||
public final int outputHeight;
|
||||
public final String outputMimeType;
|
||||
@Nullable public final String audioMimeType;
|
||||
@Nullable public final String videoMimeType;
|
||||
@ -32,12 +33,14 @@ import androidx.annotation.Nullable;
|
||||
boolean removeAudio,
|
||||
boolean removeVideo,
|
||||
boolean flattenForSlowMotion,
|
||||
int outputHeight,
|
||||
String outputMimeType,
|
||||
@Nullable String audioMimeType,
|
||||
@Nullable String videoMimeType) {
|
||||
this.removeAudio = removeAudio;
|
||||
this.removeVideo = removeVideo;
|
||||
this.flattenForSlowMotion = flattenForSlowMotion;
|
||||
this.outputHeight = outputHeight;
|
||||
this.outputMimeType = outputMimeType;
|
||||
this.audioMimeType = audioMimeType;
|
||||
this.videoMimeType = videoMimeType;
|
||||
|
@ -46,7 +46,6 @@ import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.RenderersFactory;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.TracksInfo;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
||||
@ -298,11 +297,13 @@ public final class Transformer {
|
||||
checkState(
|
||||
muxerFactory.supportsOutputMimeType(outputMimeType),
|
||||
"Unsupported output MIME type: " + outputMimeType);
|
||||
int outputHeight = 0; // TODO(ME): How do we get the input height here?
|
||||
Transformation transformation =
|
||||
new Transformation(
|
||||
removeAudio,
|
||||
removeVideo,
|
||||
flattenForSlowMotion,
|
||||
outputHeight,
|
||||
outputMimeType,
|
||||
/* audioMimeType= */ null,
|
||||
/* videoMimeType= */ null);
|
||||
@ -496,7 +497,7 @@ public final class Transformer {
|
||||
.setClock(clock)
|
||||
.build();
|
||||
player.setMediaItem(mediaItem);
|
||||
player.addAnalyticsListener(new TransformerAnalyticsListener(mediaItem, muxerWrapper));
|
||||
player.addListener(new TransformerPlayerListener(mediaItem, muxerWrapper));
|
||||
player.prepare();
|
||||
|
||||
progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
||||
@ -606,30 +607,30 @@ public final class Transformer {
|
||||
}
|
||||
}
|
||||
|
||||
private final class TransformerAnalyticsListener implements AnalyticsListener {
|
||||
private final class TransformerPlayerListener implements Player.Listener {
|
||||
|
||||
private final MediaItem mediaItem;
|
||||
private final MuxerWrapper muxerWrapper;
|
||||
|
||||
public TransformerAnalyticsListener(MediaItem mediaItem, MuxerWrapper muxerWrapper) {
|
||||
public TransformerPlayerListener(MediaItem mediaItem, MuxerWrapper muxerWrapper) {
|
||||
this.mediaItem = mediaItem;
|
||||
this.muxerWrapper = muxerWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(EventTime eventTime, int state) {
|
||||
public void onPlaybackStateChanged(int state) {
|
||||
if (state == Player.STATE_ENDED) {
|
||||
handleTransformationEnded(/* exception= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(EventTime eventTime, int reason) {
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
if (progressState != PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
|
||||
return;
|
||||
}
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
eventTime.timeline.getWindow(/* windowIndex= */ 0, window);
|
||||
timeline.getWindow(/* windowIndex= */ 0, window);
|
||||
if (!window.isPlaceholder) {
|
||||
long durationUs = window.durationUs;
|
||||
// Make progress permanently unavailable if the duration is unknown, so that it doesn't jump
|
||||
@ -644,7 +645,7 @@ public final class Transformer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksInfoChanged(EventTime eventTime, TracksInfo tracksInfo) {
|
||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
||||
if (muxerWrapper.getTrackCount() == 0) {
|
||||
handleTransformationEnded(
|
||||
new IllegalStateException(
|
||||
@ -654,7 +655,7 @@ public final class Transformer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(EventTime eventTime, PlaybackException error) {
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
handleTransformationEnded(error);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 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.testutil;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
|
||||
/** A fake {@link Metadata.Entry}. */
|
||||
public final class FakeMetadataEntry implements Metadata.Entry {
|
||||
|
||||
public final String data;
|
||||
|
||||
public FakeMetadataEntry(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/* package */ FakeMetadataEntry(Parcel in) {
|
||||
data = castNonNull(in.readString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
FakeMetadataEntry other = (FakeMetadataEntry) obj;
|
||||
return data.equals(other.data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return data.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(data);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<FakeMetadataEntry> CREATOR =
|
||||
new Parcelable.Creator<FakeMetadataEntry>() {
|
||||
|
||||
@Override
|
||||
public FakeMetadataEntry createFromParcel(Parcel in) {
|
||||
return new FakeMetadataEntry(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FakeMetadataEntry[] newArray(int size) {
|
||||
return new FakeMetadataEntry[size];
|
||||
}
|
||||
};
|
||||
}
|
@ -15,26 +15,14 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.testutil;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.BasePlayer;
|
||||
import com.google.android.exoplayer2.DeviceInfo;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.MediaMetadata;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.PlayerMessage;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.TracksInfo;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
@ -42,15 +30,10 @@ import com.google.android.exoplayer2.audio.AuxEffectInfo;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.ShuffleOrder;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.PriorityTaskManager;
|
||||
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
|
||||
import com.google.android.exoplayer2.video.VideoSize;
|
||||
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
|
||||
import java.util.List;
|
||||
|
||||
@ -58,11 +41,7 @@ import java.util.List;
|
||||
* An abstract {@link ExoPlayer} implementation that throws {@link UnsupportedOperationException}
|
||||
* from every method.
|
||||
*/
|
||||
public class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||
|
||||
public StubExoPlayer(Context context) {
|
||||
super();
|
||||
}
|
||||
public class StubExoPlayer extends StubPlayer implements ExoPlayer {
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@ -93,31 +72,16 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Looper getApplicationLooper() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clock getClock() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Listener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Player.EventListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(Listener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(Player.EventListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
@ -148,68 +112,29 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@State
|
||||
public int getPlaybackState() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@PlaybackSuppressionReason
|
||||
public int getPlaybackSuppressionReason() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExoPlaybackException getPlayerError() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #prepare()} instead. */
|
||||
@Deprecated
|
||||
@Override
|
||||
public void retry() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #setMediaSource(MediaSource)} and {@link ExoPlayer#prepare()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void prepare() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #setMediaSource(MediaSource)} and {@link ExoPlayer#prepare()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void prepare(MediaSource mediaSource) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #setMediaSource(MediaSource, boolean)} and {@link ExoPlayer#prepare()}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMediaSource(MediaSource mediaSource) {
|
||||
throw new UnsupportedOperationException();
|
||||
@ -241,11 +166,6 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMediaItems(int index, List<MediaItem> mediaItems) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMediaSource(MediaSource mediaSource) {
|
||||
throw new UnsupportedOperationException();
|
||||
@ -266,41 +186,6 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeMediaItems(int fromIndex, int toIndex) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Commands getAvailableCommands() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayWhenReady(boolean playWhenReady) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPlayWhenReady() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRepeatMode(@RepeatMode int repeatMode) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRepeatMode() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShuffleOrder(ShuffleOrder shuffleOrder) {
|
||||
throw new UnsupportedOperationException();
|
||||
@ -381,51 +266,6 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getShuffleModeEnabled() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoading() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(int mediaItemIndex, long positionMs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeekBackIncrement() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeekForwardIncrement() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxSeekToPreviousPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaybackParameters getPlaybackParameters() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeekParameters(@Nullable SeekParameters seekParameters) {
|
||||
throw new UnsupportedOperationException();
|
||||
@ -436,22 +276,6 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerMessage createMessage(PlayerMessage.Target target) {
|
||||
throw new UnsupportedOperationException();
|
||||
@ -473,211 +297,6 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackGroupArray getCurrentTrackGroups() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackSelectionArray getCurrentTrackSelections() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracksInfo getCurrentTracksInfo() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackSelectionParameters getTrackSelectionParameters() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrackSelectionParameters(TrackSelectionParameters parameters) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaMetadata getMediaMetadata() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaMetadata getPlaylistMetadata() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaylistMetadata(MediaMetadata mediaMetadata) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeline getCurrentTimeline() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentPeriodIndex() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentMediaItemIndex() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCurrentPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalBufferedDuration() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayingAd() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentAdGroupIndex() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentAdIndexInAdGroup() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentBufferedPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioAttributes getAudioAttributes() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVolume(float volume) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVolume() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoSurface() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoSurface(@Nullable Surface surface) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoSurface(@Nullable Surface surface) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoTextureView(@Nullable TextureView textureView) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoTextureView(@Nullable TextureView textureView) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoSize getVideoSize() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Cue> getCurrentCues() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceInfo getDeviceInfo() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeviceVolume() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeviceMuted() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDeviceVolume(int volume) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseDeviceVolume() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decreaseDeviceVolume() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDeviceMuted(boolean muted) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForegroundMode(boolean foregroundMode) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
@ -0,0 +1,399 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.testutil;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.BasePlayer;
|
||||
import com.google.android.exoplayer2.DeviceInfo;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.MediaMetadata;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.TracksInfo;
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
|
||||
import com.google.android.exoplayer2.video.VideoSize;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An abstract {@link Player} implementation that throws {@link UnsupportedOperationException} from
|
||||
* every method.
|
||||
*/
|
||||
public class StubPlayer extends BasePlayer {
|
||||
|
||||
@Override
|
||||
public Looper getApplicationLooper() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Listener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(Listener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@State
|
||||
public int getPlaybackState() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@PlaybackSuppressionReason
|
||||
public int getPlaybackSuppressionReason() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaybackException getPlayerError() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMediaItems(int index, List<MediaItem> mediaItems) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeMediaItems(int fromIndex, int toIndex) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Commands getAvailableCommands() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayWhenReady(boolean playWhenReady) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPlayWhenReady() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRepeatMode(@RepeatMode int repeatMode) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRepeatMode() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getShuffleModeEnabled() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoading() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(int mediaItemIndex, long positionMs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeekBackIncrement() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeekForwardIncrement() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxSeekToPreviousPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaybackParameters getPlaybackParameters() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackGroupArray getCurrentTrackGroups() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackSelectionArray getCurrentTrackSelections() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracksInfo getCurrentTracksInfo() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackSelectionParameters getTrackSelectionParameters() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrackSelectionParameters(TrackSelectionParameters parameters) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaMetadata getMediaMetadata() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaMetadata getPlaylistMetadata() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaylistMetadata(MediaMetadata mediaMetadata) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeline getCurrentTimeline() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentPeriodIndex() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentMediaItemIndex() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCurrentPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalBufferedDuration() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayingAd() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentAdGroupIndex() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentAdIndexInAdGroup() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentBufferedPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioAttributes getAudioAttributes() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVolume(float volume) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVolume() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoSurface() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoSurface(@Nullable Surface surface) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoSurface(@Nullable Surface surface) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoTextureView(@Nullable TextureView textureView) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoTextureView(@Nullable TextureView textureView) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoSize getVideoSize() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Cue> getCurrentCues() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceInfo getDeviceInfo() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeviceVolume() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeviceMuted() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDeviceVolume(int volume) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseDeviceVolume() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decreaseDeviceVolume() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDeviceMuted(boolean muted) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user