Rollforward of Move VideoComponent to ExoPlayer
The original cl has been fixed by not implementing VideoListener but Player.Listener in StyledPlayerView. VideoFrameMetadataListener and CameraMotionListener are still part of the Player interface as a good way to break the UI dependency on them has not yet been finalised. PiperOrigin-RevId: 369417682
This commit is contained in:
parent
f201b617d7
commit
08336e372d
@ -3,6 +3,11 @@
|
||||
### dev-v2 (not yet released)
|
||||
|
||||
* Core Library:
|
||||
* Move `Player` components to `ExoPlayer`. For example
|
||||
`Player.VideoComponent` is now `ExoPlayer.VideoComponent`.
|
||||
* The most used methods of `Player`'s audio/video/text/metadata components
|
||||
have been added to `Player`. Support can be queried using
|
||||
`Player.isCommandAvailable` instead of testing for component nullness.
|
||||
* Add position info of the old and the new position as arguments to
|
||||
`EventListener.onPositionDiscontinuity`. Add the new reasons
|
||||
`DISCONTINUITY_REASON_SKIP` and `DISCONTINUITY_REASON_REMOVE` and rename
|
||||
|
@ -25,8 +25,8 @@ import android.os.Handler;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.GlUtil;
|
||||
import com.google.android.exoplayer2.util.TimedValueQueue;
|
||||
@ -72,7 +72,7 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView {
|
||||
|
||||
@Nullable private SurfaceTexture surfaceTexture;
|
||||
@Nullable private Surface surface;
|
||||
@Nullable private Player.VideoComponent videoComponent;
|
||||
@Nullable private ExoPlayer.VideoComponent videoComponent;
|
||||
|
||||
/**
|
||||
* Creates a new instance. Pass {@code true} for {@code requireSecureContext} if the {@link
|
||||
@ -151,7 +151,7 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView {
|
||||
*
|
||||
* @param newVideoComponent The new video component, or {@code null} to detach this view.
|
||||
*/
|
||||
public void setVideoComponent(@Nullable Player.VideoComponent newVideoComponent) {
|
||||
public void setVideoComponent(@Nullable ExoPlayer.VideoComponent newVideoComponent) {
|
||||
if (newVideoComponent == videoComponent) {
|
||||
return;
|
||||
}
|
||||
|
@ -19,6 +19,10 @@ import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
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 androidx.annotation.VisibleForTesting;
|
||||
import com.google.android.exoplayer2.BasePlayer;
|
||||
@ -44,6 +48,8 @@ import com.google.android.exoplayer2.util.ListenerSet;
|
||||
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.VideoFrameMetadataListener;
|
||||
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
|
||||
import com.google.android.gms.cast.CastStatusCodes;
|
||||
import com.google.android.gms.cast.MediaInfo;
|
||||
import com.google.android.gms.cast.MediaQueueItem;
|
||||
@ -276,12 +282,6 @@ public final class CastPlayer extends BasePlayer {
|
||||
|
||||
// Player implementation.
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public VideoComponent getVideoComponent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Looper getApplicationLooper() {
|
||||
return Looper.getMainLooper();
|
||||
@ -653,6 +653,57 @@ public final class CastPlayer extends BasePlayer {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setCameraMotionListener(CameraMotionListener listener) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearCameraMotionListener(CameraMotionListener listener) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoSurface() {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoSurface(@Nullable Surface surface) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setVideoSurface(@Nullable Surface surface) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setVideoTextureView(@Nullable TextureView textureView) {}
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoTextureView(@Nullable TextureView textureView) {}
|
||||
|
||||
/** This method is not supported and returns an empty list. */
|
||||
@Override
|
||||
public ImmutableList<Cue> getCurrentCues() {
|
||||
|
@ -37,7 +37,6 @@ import com.google.android.exoplayer2.Player.TimelineChangeReason;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.util.ErrorMessageProvider;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoListener;
|
||||
|
||||
/** Leanback {@code PlayerAdapter} implementation for {@link Player}. */
|
||||
public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnable {
|
||||
@ -49,7 +48,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab
|
||||
private final Context context;
|
||||
private final Player player;
|
||||
private final Handler handler;
|
||||
private final ComponentListener componentListener;
|
||||
private final PlayerListener playerListener;
|
||||
private final int updatePeriodMs;
|
||||
|
||||
@Nullable private PlaybackPreparer playbackPreparer;
|
||||
@ -73,7 +72,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab
|
||||
this.player = player;
|
||||
this.updatePeriodMs = updatePeriodMs;
|
||||
handler = Util.createHandlerForCurrentOrMainLooper();
|
||||
componentListener = new ComponentListener();
|
||||
playerListener = new PlayerListener();
|
||||
controlDispatcher = new DefaultControlDispatcher();
|
||||
}
|
||||
|
||||
@ -118,23 +117,15 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab
|
||||
public void onAttachedToHost(PlaybackGlueHost host) {
|
||||
if (host instanceof SurfaceHolderGlueHost) {
|
||||
surfaceHolderGlueHost = ((SurfaceHolderGlueHost) host);
|
||||
surfaceHolderGlueHost.setSurfaceHolderCallback(componentListener);
|
||||
surfaceHolderGlueHost.setSurfaceHolderCallback(playerListener);
|
||||
}
|
||||
notifyStateChanged();
|
||||
player.addListener(componentListener);
|
||||
Player.VideoComponent videoComponent = player.getVideoComponent();
|
||||
if (videoComponent != null) {
|
||||
videoComponent.addVideoListener(componentListener);
|
||||
}
|
||||
player.addListener(playerListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromHost() {
|
||||
player.removeListener(componentListener);
|
||||
Player.VideoComponent videoComponent = player.getVideoComponent();
|
||||
if (videoComponent != null) {
|
||||
videoComponent.removeVideoListener(componentListener);
|
||||
}
|
||||
player.removeListener(playerListener);
|
||||
if (surfaceHolderGlueHost != null) {
|
||||
removeSurfaceHolderCallback(surfaceHolderGlueHost);
|
||||
surfaceHolderGlueHost = null;
|
||||
@ -227,10 +218,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab
|
||||
|
||||
/* package */ void setVideoSurface(@Nullable Surface surface) {
|
||||
hasSurface = surface != null;
|
||||
Player.VideoComponent videoComponent = player.getVideoComponent();
|
||||
if (videoComponent != null) {
|
||||
videoComponent.setVideoSurface(surface);
|
||||
}
|
||||
player.setVideoSurface(surface);
|
||||
maybeNotifyPreparedStateChanged(getCallback());
|
||||
}
|
||||
|
||||
@ -258,8 +246,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab
|
||||
surfaceHolderGlueHost.setSurfaceHolderCallback(null);
|
||||
}
|
||||
|
||||
private final class ComponentListener
|
||||
implements Player.EventListener, SurfaceHolder.Callback, VideoListener {
|
||||
private final class PlayerListener implements Player.Listener, SurfaceHolder.Callback {
|
||||
|
||||
// SurfaceHolder.Callback implementation.
|
||||
|
||||
|
@ -67,146 +67,6 @@ import java.util.List;
|
||||
*/
|
||||
public interface Player {
|
||||
|
||||
/** The video component of a {@link Player}. */
|
||||
interface VideoComponent {
|
||||
|
||||
/**
|
||||
* Sets the {@link C.VideoScalingMode}.
|
||||
*
|
||||
* @param videoScalingMode The {@link C.VideoScalingMode}.
|
||||
*/
|
||||
void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode);
|
||||
|
||||
/** Returns the {@link C.VideoScalingMode}. */
|
||||
@C.VideoScalingMode
|
||||
int getVideoScalingMode();
|
||||
|
||||
/**
|
||||
* Adds a listener to receive video events.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
*/
|
||||
void addVideoListener(VideoListener listener);
|
||||
|
||||
/**
|
||||
* Removes a listener of video events.
|
||||
*
|
||||
* @param listener The listener to unregister.
|
||||
*/
|
||||
void removeVideoListener(VideoListener listener);
|
||||
|
||||
/**
|
||||
* Sets a listener to receive video frame metadata events.
|
||||
*
|
||||
* <p>This method is intended to be called by the same component that sets the {@link Surface}
|
||||
* onto which video will be rendered. If using ExoPlayer's standard UI components, this method
|
||||
* should not be called directly from application code.
|
||||
*
|
||||
* @param listener The listener.
|
||||
*/
|
||||
void setVideoFrameMetadataListener(VideoFrameMetadataListener listener);
|
||||
|
||||
/**
|
||||
* Clears the listener which receives video frame metadata events if it matches the one passed.
|
||||
* Else does nothing.
|
||||
*
|
||||
* @param listener The listener to clear.
|
||||
*/
|
||||
void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener);
|
||||
|
||||
/**
|
||||
* Sets a listener of camera motion events.
|
||||
*
|
||||
* @param listener The listener.
|
||||
*/
|
||||
void setCameraMotionListener(CameraMotionListener listener);
|
||||
|
||||
/**
|
||||
* Clears the listener which receives camera motion events if it matches the one passed. Else
|
||||
* does nothing.
|
||||
*
|
||||
* @param listener The listener to clear.
|
||||
*/
|
||||
void clearCameraMotionListener(CameraMotionListener listener);
|
||||
|
||||
/**
|
||||
* Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView}
|
||||
* currently set on the player.
|
||||
*/
|
||||
void clearVideoSurface();
|
||||
|
||||
/**
|
||||
* Clears the {@link Surface} onto which video is being rendered if it matches the one passed.
|
||||
* Else does nothing.
|
||||
*
|
||||
* @param surface The surface to clear.
|
||||
*/
|
||||
void clearVideoSurface(@Nullable Surface surface);
|
||||
|
||||
/**
|
||||
* Sets the {@link Surface} onto which video will be rendered. The caller is responsible for
|
||||
* tracking the lifecycle of the surface, and must clear the surface by calling {@code
|
||||
* setVideoSurface(null)} if the surface is destroyed.
|
||||
*
|
||||
* <p>If the surface is held by a {@link SurfaceView}, {@link TextureView} or {@link
|
||||
* SurfaceHolder} then it's recommended to use {@link #setVideoSurfaceView(SurfaceView)}, {@link
|
||||
* #setVideoTextureView(TextureView)} or {@link #setVideoSurfaceHolder(SurfaceHolder)} rather
|
||||
* than this method, since passing the holder allows the player to track the lifecycle of the
|
||||
* surface automatically.
|
||||
*
|
||||
* @param surface The {@link Surface}.
|
||||
*/
|
||||
void setVideoSurface(@Nullable Surface surface);
|
||||
|
||||
/**
|
||||
* Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be
|
||||
* rendered. The player will track the lifecycle of the surface automatically.
|
||||
*
|
||||
* @param surfaceHolder The surface holder.
|
||||
*/
|
||||
void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder);
|
||||
|
||||
/**
|
||||
* Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being
|
||||
* rendered if it matches the one passed. Else does nothing.
|
||||
*
|
||||
* @param surfaceHolder The surface holder to clear.
|
||||
*/
|
||||
void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder);
|
||||
|
||||
/**
|
||||
* Sets the {@link SurfaceView} onto which video will be rendered. The player will track the
|
||||
* lifecycle of the surface automatically.
|
||||
*
|
||||
* @param surfaceView The surface view.
|
||||
*/
|
||||
void setVideoSurfaceView(@Nullable SurfaceView surfaceView);
|
||||
|
||||
/**
|
||||
* Clears the {@link SurfaceView} onto which video is being rendered if it matches the one
|
||||
* passed. Else does nothing.
|
||||
*
|
||||
* @param surfaceView The texture view to clear.
|
||||
*/
|
||||
void clearVideoSurfaceView(@Nullable SurfaceView surfaceView);
|
||||
|
||||
/**
|
||||
* Sets the {@link TextureView} onto which video will be rendered. The player will track the
|
||||
* lifecycle of the surface automatically.
|
||||
*
|
||||
* @param textureView The texture view.
|
||||
*/
|
||||
void setVideoTextureView(@Nullable TextureView textureView);
|
||||
|
||||
/**
|
||||
* Clears the {@link TextureView} onto which video is being rendered if it matches the one
|
||||
* passed. Else does nothing.
|
||||
*
|
||||
* @param textureView The texture view to clear.
|
||||
*/
|
||||
void clearVideoTextureView(@Nullable TextureView textureView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener of changes in player state.
|
||||
*
|
||||
@ -1152,10 +1012,6 @@ public interface Player {
|
||||
/** Command to get the text that should currently be displayed by the player. */
|
||||
int COMMAND_GET_TEXT = 22;
|
||||
|
||||
/** Returns the component of this player for video output, or null if video is not supported. */
|
||||
@Nullable
|
||||
VideoComponent getVideoComponent();
|
||||
|
||||
/**
|
||||
* Returns the {@link Looper} associated with the application thread that's used to access the
|
||||
* player and on which player events are received.
|
||||
@ -1830,6 +1686,117 @@ public interface Player {
|
||||
*/
|
||||
float getVolume();
|
||||
|
||||
/**
|
||||
* Sets a listener to receive video frame metadata events.
|
||||
*
|
||||
* <p>This method is intended to be called by the same component that sets the {@link Surface}
|
||||
* onto which video will be rendered. If using ExoPlayer's standard UI components, this method
|
||||
* should not be called directly from application code.
|
||||
*
|
||||
* @param listener The listener.
|
||||
*/
|
||||
void setVideoFrameMetadataListener(VideoFrameMetadataListener listener);
|
||||
|
||||
/**
|
||||
* Clears the listener which receives video frame metadata events if it matches the one passed.
|
||||
* Else does nothing.
|
||||
*
|
||||
* @param listener The listener to clear.
|
||||
*/
|
||||
void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener);
|
||||
|
||||
/**
|
||||
* Sets a listener of camera motion events.
|
||||
*
|
||||
* @param listener The listener.
|
||||
*/
|
||||
void setCameraMotionListener(CameraMotionListener listener);
|
||||
|
||||
/**
|
||||
* Clears the listener which receives camera motion events if it matches the one passed. Else does
|
||||
* nothing.
|
||||
*
|
||||
* @param listener The listener to clear.
|
||||
*/
|
||||
void clearCameraMotionListener(CameraMotionListener listener);
|
||||
|
||||
/**
|
||||
* Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView}
|
||||
* currently set on the player.
|
||||
*/
|
||||
void clearVideoSurface();
|
||||
|
||||
/**
|
||||
* Clears the {@link Surface} onto which video is being rendered if it matches the one passed.
|
||||
* Else does nothing.
|
||||
*
|
||||
* @param surface The surface to clear.
|
||||
*/
|
||||
void clearVideoSurface(@Nullable Surface surface);
|
||||
|
||||
/**
|
||||
* Sets the {@link Surface} onto which video will be rendered. The caller is responsible for
|
||||
* tracking the lifecycle of the surface, and must clear the surface by calling {@code
|
||||
* setVideoSurface(null)} if the surface is destroyed.
|
||||
*
|
||||
* <p>If the surface is held by a {@link SurfaceView}, {@link TextureView} or {@link
|
||||
* SurfaceHolder} then it's recommended to use {@link #setVideoSurfaceView(SurfaceView)}, {@link
|
||||
* #setVideoTextureView(TextureView)} or {@link #setVideoSurfaceHolder(SurfaceHolder)} rather than
|
||||
* this method, since passing the holder allows the player to track the lifecycle of the surface
|
||||
* automatically.
|
||||
*
|
||||
* @param surface The {@link Surface}.
|
||||
*/
|
||||
void setVideoSurface(@Nullable Surface surface);
|
||||
|
||||
/**
|
||||
* Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be
|
||||
* rendered. The player will track the lifecycle of the surface automatically.
|
||||
*
|
||||
* @param surfaceHolder The surface holder.
|
||||
*/
|
||||
void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder);
|
||||
|
||||
/**
|
||||
* Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being
|
||||
* rendered if it matches the one passed. Else does nothing.
|
||||
*
|
||||
* @param surfaceHolder The surface holder to clear.
|
||||
*/
|
||||
void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder);
|
||||
|
||||
/**
|
||||
* Sets the {@link SurfaceView} onto which video will be rendered. The player will track the
|
||||
* lifecycle of the surface automatically.
|
||||
*
|
||||
* @param surfaceView The surface view.
|
||||
*/
|
||||
void setVideoSurfaceView(@Nullable SurfaceView surfaceView);
|
||||
|
||||
/**
|
||||
* Clears the {@link SurfaceView} onto which video is being rendered if it matches the one passed.
|
||||
* Else does nothing.
|
||||
*
|
||||
* @param surfaceView The texture view to clear.
|
||||
*/
|
||||
void clearVideoSurfaceView(@Nullable SurfaceView surfaceView);
|
||||
|
||||
/**
|
||||
* Sets the {@link TextureView} onto which video will be rendered. The player will track the
|
||||
* lifecycle of the surface automatically.
|
||||
*
|
||||
* @param textureView The texture view.
|
||||
*/
|
||||
void setVideoTextureView(@Nullable TextureView textureView);
|
||||
|
||||
/**
|
||||
* Clears the {@link TextureView} onto which video is being rendered if it matches the one passed.
|
||||
* Else does nothing.
|
||||
*
|
||||
* @param textureView The texture view to clear.
|
||||
*/
|
||||
void clearVideoTextureView(@Nullable TextureView textureView);
|
||||
|
||||
/** Returns the current {@link Cue Cues}. This list may be empty. */
|
||||
List<Cue> getCurrentCues();
|
||||
|
||||
|
@ -18,6 +18,10 @@ package com.google.android.exoplayer2;
|
||||
import android.content.Context;
|
||||
import android.media.AudioTrack;
|
||||
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 androidx.annotation.VisibleForTesting;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
|
||||
@ -48,6 +52,9 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
|
||||
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
|
||||
import com.google.android.exoplayer2.video.VideoListener;
|
||||
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -223,6 +230,146 @@ public interface ExoPlayer extends Player {
|
||||
boolean getSkipSilenceEnabled();
|
||||
}
|
||||
|
||||
/** The video component of an {@link ExoPlayer}. */
|
||||
interface VideoComponent {
|
||||
|
||||
/**
|
||||
* Sets the {@link C.VideoScalingMode}.
|
||||
*
|
||||
* @param videoScalingMode The {@link C.VideoScalingMode}.
|
||||
*/
|
||||
void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode);
|
||||
|
||||
/** Returns the {@link C.VideoScalingMode}. */
|
||||
@C.VideoScalingMode
|
||||
int getVideoScalingMode();
|
||||
|
||||
/**
|
||||
* Adds a listener to receive video events.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
*/
|
||||
void addVideoListener(VideoListener listener);
|
||||
|
||||
/**
|
||||
* Removes a listener of video events.
|
||||
*
|
||||
* @param listener The listener to unregister.
|
||||
*/
|
||||
void removeVideoListener(VideoListener listener);
|
||||
|
||||
/**
|
||||
* Sets a listener to receive video frame metadata events.
|
||||
*
|
||||
* <p>This method is intended to be called by the same component that sets the {@link Surface}
|
||||
* onto which video will be rendered. If using ExoPlayer's standard UI components, this method
|
||||
* should not be called directly from application code.
|
||||
*
|
||||
* @param listener The listener.
|
||||
*/
|
||||
void setVideoFrameMetadataListener(VideoFrameMetadataListener listener);
|
||||
|
||||
/**
|
||||
* Clears the listener which receives video frame metadata events if it matches the one passed.
|
||||
* Else does nothing.
|
||||
*
|
||||
* @param listener The listener to clear.
|
||||
*/
|
||||
void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener);
|
||||
|
||||
/**
|
||||
* Sets a listener of camera motion events.
|
||||
*
|
||||
* @param listener The listener.
|
||||
*/
|
||||
void setCameraMotionListener(CameraMotionListener listener);
|
||||
|
||||
/**
|
||||
* Clears the listener which receives camera motion events if it matches the one passed. Else
|
||||
* does nothing.
|
||||
*
|
||||
* @param listener The listener to clear.
|
||||
*/
|
||||
void clearCameraMotionListener(CameraMotionListener listener);
|
||||
|
||||
/**
|
||||
* Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView}
|
||||
* currently set on the player.
|
||||
*/
|
||||
void clearVideoSurface();
|
||||
|
||||
/**
|
||||
* Clears the {@link Surface} onto which video is being rendered if it matches the one passed.
|
||||
* Else does nothing.
|
||||
*
|
||||
* @param surface The surface to clear.
|
||||
*/
|
||||
void clearVideoSurface(@Nullable Surface surface);
|
||||
|
||||
/**
|
||||
* Sets the {@link Surface} onto which video will be rendered. The caller is responsible for
|
||||
* tracking the lifecycle of the surface, and must clear the surface by calling {@code
|
||||
* setVideoSurface(null)} if the surface is destroyed.
|
||||
*
|
||||
* <p>If the surface is held by a {@link SurfaceView}, {@link TextureView} or {@link
|
||||
* SurfaceHolder} then it's recommended to use {@link #setVideoSurfaceView(SurfaceView)}, {@link
|
||||
* #setVideoTextureView(TextureView)} or {@link #setVideoSurfaceHolder(SurfaceHolder)} rather
|
||||
* than this method, since passing the holder allows the player to track the lifecycle of the
|
||||
* surface automatically.
|
||||
*
|
||||
* @param surface The {@link Surface}.
|
||||
*/
|
||||
void setVideoSurface(@Nullable Surface surface);
|
||||
|
||||
/**
|
||||
* Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be
|
||||
* rendered. The player will track the lifecycle of the surface automatically.
|
||||
*
|
||||
* @param surfaceHolder The surface holder.
|
||||
*/
|
||||
void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder);
|
||||
|
||||
/**
|
||||
* Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being
|
||||
* rendered if it matches the one passed. Else does nothing.
|
||||
*
|
||||
* @param surfaceHolder The surface holder to clear.
|
||||
*/
|
||||
void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder);
|
||||
|
||||
/**
|
||||
* Sets the {@link SurfaceView} onto which video will be rendered. The player will track the
|
||||
* lifecycle of the surface automatically.
|
||||
*
|
||||
* @param surfaceView The surface view.
|
||||
*/
|
||||
void setVideoSurfaceView(@Nullable SurfaceView surfaceView);
|
||||
|
||||
/**
|
||||
* Clears the {@link SurfaceView} onto which video is being rendered if it matches the one
|
||||
* passed. Else does nothing.
|
||||
*
|
||||
* @param surfaceView The texture view to clear.
|
||||
*/
|
||||
void clearVideoSurfaceView(@Nullable SurfaceView surfaceView);
|
||||
|
||||
/**
|
||||
* Sets the {@link TextureView} onto which video will be rendered. The player will track the
|
||||
* lifecycle of the surface automatically.
|
||||
*
|
||||
* @param textureView The texture view.
|
||||
*/
|
||||
void setVideoTextureView(@Nullable TextureView textureView);
|
||||
|
||||
/**
|
||||
* Clears the {@link TextureView} onto which video is being rendered if it matches the one
|
||||
* passed. Else does nothing.
|
||||
*
|
||||
* @param textureView The texture view to clear.
|
||||
*/
|
||||
void clearVideoTextureView(@Nullable TextureView textureView);
|
||||
}
|
||||
|
||||
/** The text component of an {@link ExoPlayer}. */
|
||||
interface TextComponent {
|
||||
|
||||
@ -651,6 +798,10 @@ public interface ExoPlayer extends Player {
|
||||
@Nullable
|
||||
AudioComponent getAudioComponent();
|
||||
|
||||
/** Returns the component of this player for video output, or null if video is not supported. */
|
||||
@Nullable
|
||||
VideoComponent getVideoComponent();
|
||||
|
||||
/** Returns the component of this player for text output, or null if text is not supported. */
|
||||
@Nullable
|
||||
TextComponent getTextComponent();
|
||||
|
@ -25,6 +25,10 @@ import android.annotation.SuppressLint;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Pair;
|
||||
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.PlayerMessage.Target;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
|
||||
@ -48,6 +52,8 @@ import com.google.android.exoplayer2.util.HandlerWrapper;
|
||||
import com.google.android.exoplayer2.util.ListenerSet;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
|
||||
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -1004,6 +1010,58 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setCameraMotionListener(CameraMotionListener listener) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearCameraMotionListener(CameraMotionListener listener) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoSurface() {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoSurface(@Nullable Surface surface) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setVideoSurface(@Nullable Surface surface) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void setVideoTextureView(@Nullable TextureView textureView) {}
|
||||
|
||||
/** This method is not supported and does nothing. */
|
||||
@Override
|
||||
public void clearVideoTextureView(@Nullable TextureView textureView) {}
|
||||
|
||||
/** This method is not supported and returns an empty list. */
|
||||
@Override
|
||||
public ImmutableList<Cue> getCurrentCues() {
|
||||
|
@ -79,7 +79,7 @@ import java.util.concurrent.TimeoutException;
|
||||
public class SimpleExoPlayer extends BasePlayer
|
||||
implements ExoPlayer,
|
||||
ExoPlayer.AudioComponent,
|
||||
Player.VideoComponent,
|
||||
ExoPlayer.VideoComponent,
|
||||
ExoPlayer.TextComponent,
|
||||
ExoPlayer.MetadataComponent,
|
||||
ExoPlayer.DeviceComponent {
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer2.ui;
|
||||
|
||||
import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT;
|
||||
import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
@ -67,7 +68,6 @@ import com.google.android.exoplayer2.util.ErrorMessageProvider;
|
||||
import com.google.android.exoplayer2.util.RepeatModeUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView;
|
||||
import com.google.android.exoplayer2.video.VideoListener;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
@ -563,14 +563,13 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
|
||||
@Nullable Player oldPlayer = this.player;
|
||||
if (oldPlayer != null) {
|
||||
oldPlayer.removeListener(componentListener);
|
||||
@Nullable Player.VideoComponent oldVideoComponent = oldPlayer.getVideoComponent();
|
||||
if (oldVideoComponent != null) {
|
||||
if (oldPlayer.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) {
|
||||
if (surfaceView instanceof TextureView) {
|
||||
oldVideoComponent.clearVideoTextureView((TextureView) surfaceView);
|
||||
oldPlayer.clearVideoTextureView((TextureView) surfaceView);
|
||||
} else if (surfaceView instanceof SphericalGLSurfaceView) {
|
||||
((SphericalGLSurfaceView) surfaceView).setVideoComponent(null);
|
||||
((SphericalGLSurfaceView) surfaceView).setPlayer(null);
|
||||
} else if (surfaceView instanceof SurfaceView) {
|
||||
oldVideoComponent.clearVideoSurfaceView((SurfaceView) surfaceView);
|
||||
oldPlayer.clearVideoSurfaceView((SurfaceView) surfaceView);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -585,16 +584,14 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
|
||||
updateErrorMessage();
|
||||
updateForCurrentTrackSelections(/* isNewPlayer= */ true);
|
||||
if (player != null) {
|
||||
@Nullable Player.VideoComponent newVideoComponent = player.getVideoComponent();
|
||||
if (newVideoComponent != null) {
|
||||
if (player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) {
|
||||
if (surfaceView instanceof TextureView) {
|
||||
newVideoComponent.setVideoTextureView((TextureView) surfaceView);
|
||||
player.setVideoTextureView((TextureView) surfaceView);
|
||||
} else if (surfaceView instanceof SphericalGLSurfaceView) {
|
||||
((SphericalGLSurfaceView) surfaceView).setVideoComponent(newVideoComponent);
|
||||
((SphericalGLSurfaceView) surfaceView).setPlayer(player);
|
||||
} else if (surfaceView instanceof SurfaceView) {
|
||||
newVideoComponent.setVideoSurfaceView((SurfaceView) surfaceView);
|
||||
player.setVideoSurfaceView((SurfaceView) surfaceView);
|
||||
}
|
||||
newVideoComponent.addVideoListener(componentListener);
|
||||
}
|
||||
if (subtitleView != null && player.isCommandAvailable(COMMAND_GET_TEXT)) {
|
||||
subtitleView.setCues(player.getCurrentCues());
|
||||
@ -1486,7 +1483,6 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
|
||||
|
||||
private final class ComponentListener
|
||||
implements Player.Listener,
|
||||
VideoListener,
|
||||
OnLayoutChangeListener,
|
||||
OnClickListener,
|
||||
PlayerControlView.VisibilityListener {
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer2.ui;
|
||||
|
||||
import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT;
|
||||
import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
@ -68,7 +69,6 @@ import com.google.android.exoplayer2.util.ErrorMessageProvider;
|
||||
import com.google.android.exoplayer2.util.RepeatModeUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView;
|
||||
import com.google.android.exoplayer2.video.VideoListener;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
@ -571,15 +571,12 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider {
|
||||
@Nullable Player oldPlayer = this.player;
|
||||
if (oldPlayer != null) {
|
||||
oldPlayer.removeListener(componentListener);
|
||||
@Nullable Player.VideoComponent oldVideoComponent = oldPlayer.getVideoComponent();
|
||||
if (oldVideoComponent != null) {
|
||||
if (surfaceView instanceof TextureView) {
|
||||
oldVideoComponent.clearVideoTextureView((TextureView) surfaceView);
|
||||
oldPlayer.clearVideoTextureView((TextureView) surfaceView);
|
||||
} else if (surfaceView instanceof SphericalGLSurfaceView) {
|
||||
((SphericalGLSurfaceView) surfaceView).setVideoComponent(null);
|
||||
((SphericalGLSurfaceView) surfaceView).setPlayer(null);
|
||||
} else if (surfaceView instanceof SurfaceView) {
|
||||
oldVideoComponent.clearVideoSurfaceView((SurfaceView) surfaceView);
|
||||
}
|
||||
oldPlayer.clearVideoSurfaceView((SurfaceView) surfaceView);
|
||||
}
|
||||
}
|
||||
if (subtitleView != null) {
|
||||
@ -593,16 +590,14 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider {
|
||||
updateErrorMessage();
|
||||
updateForCurrentTrackSelections(/* isNewPlayer= */ true);
|
||||
if (player != null) {
|
||||
@Nullable Player.VideoComponent newVideoComponent = player.getVideoComponent();
|
||||
if (newVideoComponent != null) {
|
||||
if (player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) {
|
||||
if (surfaceView instanceof TextureView) {
|
||||
newVideoComponent.setVideoTextureView((TextureView) surfaceView);
|
||||
player.setVideoTextureView((TextureView) surfaceView);
|
||||
} else if (surfaceView instanceof SphericalGLSurfaceView) {
|
||||
((SphericalGLSurfaceView) surfaceView).setVideoComponent(newVideoComponent);
|
||||
((SphericalGLSurfaceView) surfaceView).setPlayer(player);
|
||||
} else if (surfaceView instanceof SurfaceView) {
|
||||
newVideoComponent.setVideoSurfaceView((SurfaceView) surfaceView);
|
||||
player.setVideoSurfaceView((SurfaceView) surfaceView);
|
||||
}
|
||||
newVideoComponent.addVideoListener(componentListener);
|
||||
}
|
||||
if (subtitleView != null && player.isCommandAvailable(COMMAND_GET_TEXT)) {
|
||||
subtitleView.setCues(player.getCurrentCues());
|
||||
@ -1507,7 +1502,6 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider {
|
||||
|
||||
private final class ComponentListener
|
||||
implements Player.Listener,
|
||||
VideoListener,
|
||||
OnLayoutChangeListener,
|
||||
OnClickListener,
|
||||
StyledPlayerControlView.VisibilityListener {
|
||||
|
@ -72,7 +72,7 @@ public final class SphericalGLSurfaceView extends GLSurfaceView {
|
||||
private final SceneRenderer scene;
|
||||
@Nullable private SurfaceTexture surfaceTexture;
|
||||
@Nullable private Surface surface;
|
||||
@Nullable private Player.VideoComponent videoComponent;
|
||||
@Nullable private Player player;
|
||||
private boolean useSensorRotation;
|
||||
private boolean isStarted;
|
||||
private boolean isOrientationListenerRegistered;
|
||||
@ -125,23 +125,23 @@ public final class SphericalGLSurfaceView extends GLSurfaceView {
|
||||
scene.setDefaultStereoMode(stereoMode);
|
||||
}
|
||||
|
||||
/** Sets the {@link Player.VideoComponent} to use. */
|
||||
public void setVideoComponent(@Nullable Player.VideoComponent newVideoComponent) {
|
||||
if (newVideoComponent == videoComponent) {
|
||||
/** Sets the {@link Player} to use. */
|
||||
public void setPlayer(@Nullable Player newPlayer) {
|
||||
if (newPlayer == player) {
|
||||
return;
|
||||
}
|
||||
if (videoComponent != null) {
|
||||
if (player != null) {
|
||||
if (surface != null) {
|
||||
videoComponent.clearVideoSurface(surface);
|
||||
player.clearVideoSurface(surface);
|
||||
}
|
||||
videoComponent.clearVideoFrameMetadataListener(scene);
|
||||
videoComponent.clearCameraMotionListener(scene);
|
||||
player.clearVideoFrameMetadataListener(scene);
|
||||
player.clearCameraMotionListener(scene);
|
||||
}
|
||||
videoComponent = newVideoComponent;
|
||||
if (videoComponent != null) {
|
||||
videoComponent.setVideoFrameMetadataListener(scene);
|
||||
videoComponent.setCameraMotionListener(scene);
|
||||
videoComponent.setVideoSurface(surface);
|
||||
player = newPlayer;
|
||||
if (this.player != null) {
|
||||
player.setVideoFrameMetadataListener(scene);
|
||||
player.setCameraMotionListener(scene);
|
||||
player.setVideoSurface(surface);
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,8 +174,8 @@ public final class SphericalGLSurfaceView extends GLSurfaceView {
|
||||
mainHandler.post(
|
||||
() -> {
|
||||
if (surface != null) {
|
||||
if (videoComponent != null) {
|
||||
videoComponent.clearVideoSurface(surface);
|
||||
if (player != null) {
|
||||
player.clearVideoSurface(surface);
|
||||
}
|
||||
releaseSurface(surfaceTexture, surface);
|
||||
surfaceTexture = null;
|
||||
@ -206,8 +206,8 @@ public final class SphericalGLSurfaceView extends GLSurfaceView {
|
||||
Surface oldSurface = this.surface;
|
||||
this.surfaceTexture = surfaceTexture;
|
||||
this.surface = new Surface(surfaceTexture);
|
||||
if (videoComponent != null) {
|
||||
videoComponent.setVideoSurface(surface);
|
||||
if (player != null) {
|
||||
player.setVideoSurface(surface);
|
||||
}
|
||||
releaseSurface(oldSurfaceTexture, oldSurface);
|
||||
});
|
||||
|
@ -16,6 +16,10 @@
|
||||
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.ExoPlaybackException;
|
||||
@ -37,6 +41,8 @@ import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
|
||||
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -440,6 +446,71 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCameraMotionListener(CameraMotionListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCameraMotionListener(CameraMotionListener listener) {
|
||||
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 List<Cue> getCurrentCues() {
|
||||
throw new UnsupportedOperationException();
|
||||
|
Loading…
x
Reference in New Issue
Block a user