/* * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package javafx.scene.media; import com.sun.javafx.geom.BaseBounds; import com.sun.javafx.geom.transform.Affine3D; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.jmx.MXNodeAlgorithm; import com.sun.javafx.jmx.MXNodeAlgorithmContext; import com.sun.javafx.scene.DirtyBits; import com.sun.javafx.sg.prism.MediaFrameTracker; import com.sun.javafx.sg.prism.NGNode; import com.sun.javafx.tk.Toolkit; import com.sun.media.jfxmediaimpl.HostUtils; import com.sun.media.jfxmediaimpl.platform.ios.IOSMediaPlayer; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.*; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableObjectValue; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableMap; import javafx.event.EventHandler; import javafx.geometry.NodeOrientation; import javafx.geometry.Rectangle2D; import javafx.scene.Node; import javafx.scene.Parent; /** * A {@link Node} that provides a view of {@link Media} being played by a * {@link MediaPlayer}. * *
The following code snippet provides a simple example of an
* {@link javafx.application.Application#start(javafx.stage.Stage) Application.start()}
* method which displays a video:
*
* The foregoing code will display the video as:
*
* public void start(Stage stage) {
* // Create and set the Scene.
* Scene scene = new Scene(new Group(), 540, 209);
* stage.setScene(scene);
*
* // Name and display the Stage.
* stage.setTitle("Hello Media");
* stage.show();
*
* // Create the media source.
* String source = getParameters().getRaw().get(0);
* Media media = new Media(source);
*
* // Create the player and set to play automatically.
* MediaPlayer mediaPlayer = new MediaPlayer(media);
* mediaPlayer.setAutoPlay(true);
*
* // Create the view and add it to the Scene.
* MediaView mediaView = new MediaView(mediaPlayer);
* ((Group) scene.getRoot()).getChildren().add(mediaView);
* }
*
*
*
*
MediaPlayer
error into a
* Bean
event.
*/
private class MediaErrorInvalidationListener implements InvalidationListener {
@Override public void invalidated(Observable value) {
ObservableObjectValueMediaPlayer
errors to events. */
private InvalidationListener errorListener = new MediaErrorInvalidationListener();
/** Listener which causes the geometry to be updated when the media dimension changes. */
private InvalidationListener mediaDimensionListener = value -> {
impl_markDirty(DirtyBits.NODE_VIEWPORT);
impl_geomChanged();
};
/** Listener for decoded frame rate. */
private com.sun.media.jfxmedia.events.VideoFrameRateListener decodedFrameRateListener;
private boolean registerVideoFrameRateListener = false;
/** Creates a decoded frame rate listener. Will return null
if
* the security manager does not permit retrieve system properties or if
* VIDEO_FRAME_RATE_PROPERTY_NAME is not set to "true."
*/
private com.sun.media.jfxmedia.events.VideoFrameRateListener createVideoFrameRateListener() {
String listenerProp = null;
try {
listenerProp = System.getProperty(VIDEO_FRAME_RATE_PROPERTY_NAME);
} catch (Throwable t) {
}
if (listenerProp == null || !Boolean.getBoolean(VIDEO_FRAME_RATE_PROPERTY_NAME)) {
return null;
} else {
return videoFrameRate -> {
Platform.runLater(() -> {
ObservableMap props = getProperties();
props.put(VIDEO_FRAME_RATE_PROPERTY_NAME, videoFrameRate);
});
};
}
}
/***************************************** iOS specific stuff ***************************/
private /*volatile*/ boolean mediaPlayerReady;
private ChangeListenerMediaView
instance with no associated
* {@link MediaPlayer}.
*/
public MediaView() {
getStyleClass().add(DEFAULT_STYLE_CLASS);
setSmooth(Toolkit.getToolkit().getDefaultImageSmooth());
decodedFrameRateListener = createVideoFrameRateListener();
setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT);
if (HostUtils.isIOS()) {
createListeners();
parentProperty().addListener(parentListener);
impl_treeVisibleProperty().addListener(treeVisibleListener);
opacityProperty().addListener(opacityListener);
}
}
/**
* Creates a MediaView
instance associated with the specified
* {@link MediaPlayer}. Equivalent to
*
* MediaPlayer player; // initialization omitted
* MediaView view = new MediaView();
* view.setPlayer(player);
*
*
* @param mediaPlayer the {@link MediaPlayer} the playback of which is to be
* viewed via this class.
*/
public MediaView(MediaPlayer mediaPlayer) {
this();
setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT);
setMediaPlayer(mediaPlayer);
}
/**
* The mediaPlayer
whose output will be handled by this view.
*
* Setting this value does not affect the status of the MediaPlayer
,
* e.g., if the MediaPlayer
was playing prior to setting
* mediaPlayer
then it will continue playing.
*
* @see MediaException
* @see MediaPlayer
*/
private ObjectPropertyMediaPlayer
whose output will be handled by this view.
* @param value the associated MediaPlayer
.
*/
public final void setMediaPlayer (MediaPlayer value) {
mediaPlayerProperty().set(value);
}
/**
* Retrieves the MediaPlayer
whose output is being handled by
* this view.
* @return the associated MediaPlayer
.
*/
public final MediaPlayer getMediaPlayer() {
return mediaPlayer == null ? null : mediaPlayer.get();
}
public final ObjectPropertyMediaView
.
*
* @see MediaErrorEvent
*/
private ObjectPropertytrue
.
*/
private BooleanProperty preserveRatio;
/**
* Sets whether to preserve the media aspect ratio when scaling.
* @param value whether to preserve the media aspect ratio.
*/
public final void setPreserveRatio(boolean value) {
preserveRatioProperty().set(value);
};
/**
* Returns whether the media aspect ratio is preserved when scaling.
* @return whether the media aspect ratio is preserved.
*/
public final boolean isPreserveRatio() {
return preserveRatio == null ? true : preserveRatio.get();
}
public final BooleanProperty preserveRatioProperty() {
if (preserveRatio == null) {
preserveRatio = new BooleanPropertyBase(true) {
@Override
protected void invalidated() {
if (HostUtils.isIOS()) {
updateOverlayPreserveRatio();
}
else {
impl_markDirty(DirtyBits.NODE_VIEWPORT);
impl_geomChanged();
}
}
@Override
public Object getBean() {
return MediaView.this;
}
@Override
public String getName() {
return "preserveRatio";
}
};
}
return preserveRatio;
}
/**
* If set to true
a better quality filtering
* algorithm will be used when scaling this video to fit within the
* bounding box provided by fitWidth
and fitHeight
or
* when transforming.
*
* If set to false
a faster but lesser quality filtering
* will be used.
*
* The default value depends on platform configuration.
*/
private BooleanProperty smooth;
/**
* Sets whether to smooth the media when scaling.
* @param value whether to smooth the media.
*/
public final void setSmooth(boolean value) {
smoothProperty().set(value);
}
/**
* Returns whether to smooth the media when scaling.
* @return whether to smooth the media
*/
public final boolean isSmooth() {
return smooth == null ? false : smooth.get();
}
public final BooleanProperty smoothProperty() {
if (smooth == null) {
smooth = new BooleanPropertyBase() {
@Override
protected void invalidated() {
impl_markDirty(DirtyBits.NODE_SMOOTH);
}
@Override
public Object getBean() {
return MediaView.this;
}
@Override
public String getName() {
return "smooth";
}
};
}
return smooth;
}
// PENDING_DOC_REVIEW
/**
* Defines the current x coordinate of the MediaView
origin.
*/
private DoubleProperty x;
/**
* Sets the x coordinate of the MediaView
origin.
* @param value the x coordinate of the origin of the view.
*/
public final void setX(double value) {
xProperty().set(value);
}
/**
* Retrieves the x coordinate of the MediaView
origin.
* @return the x coordinate of the origin of the view.
*/
public final double getX() {
return x == null ? 0.0 : x.get();
}
public final DoubleProperty xProperty() {
if (x == null) {
x = new DoublePropertyBase() {
@Override
protected void invalidated() {
if (HostUtils.isIOS()) {
updateOverlayX();
}
else {
impl_markDirty(DirtyBits.NODE_GEOMETRY);
impl_geomChanged();
}
}
@Override
public Object getBean() {
return MediaView.this;
}
@Override
public String getName() {
return "x";
}
};
}
return x;
}
// PENDING_DOC_REVIEW
/**
* Defines the current y coordinate of the MediaView
origin.
*/
private DoubleProperty y;
/**
* Sets the y coordinate of the MediaView
origin.
* @param value the y coordinate of the origin of the view.
*/
public final void setY(double value) {
yProperty().set(value);
}
/**
* Retrieves the y coordinate of the MediaView
origin.
* @return the y coordinate of the origin of the view.
*/
public final double getY() {
return y == null ? 0.0 : y.get();
}
public final DoubleProperty yProperty() {
if (y == null) {
y = new DoublePropertyBase() {
@Override
protected void invalidated() {
if (HostUtils.isIOS()) {
updateOverlayY();
}
else {
impl_markDirty(DirtyBits.NODE_GEOMETRY);
impl_geomChanged();
}
}
@Override
public Object getBean() {
return MediaView.this;
}
@Override
public String getName() {
return "y";
}
};
}
return y;
}
// PENDING_DOC_REVIEW
/**
* Determines the width of the bounding box within which the source media is
* resized as necessary to fit. If value ≤ 0
, then the width
* of the bounding box will be set to the natural width of the media, but
* fitWidth
will be set to the supplied parameter, even if
* non-positive.
* See {@link #preserveRatioProperty preserveRatio} for information on interaction
* between media views fitWidth
, fitHeight
and
* preserveRatio
attributes.
*/
private DoubleProperty fitWidth;
/**
* Sets the width of the bounding box of the resized media.
* @param value the width of the resized media.
*/
public final void setFitWidth(double value) {
fitWidthProperty().set(value);
}
/**
* Retrieves the width of the bounding box of the resized media.
* @return the height of the resized media.
*/
public final double getFitWidth() {
return fitWidth == null ? 0.0 : fitWidth.get();
}
public final DoubleProperty fitWidthProperty() {
if (fitWidth == null) {
fitWidth = new DoublePropertyBase() {
@Override
protected void invalidated() {
if (HostUtils.isIOS()) {
updateOverlayWidth();
}
else {
impl_markDirty(DirtyBits.NODE_VIEWPORT);
impl_geomChanged();
}
}
@Override
public Object getBean() {
return MediaView.this;
}
@Override
public String getName() {
return "fitWidth";
}
};
}
return fitWidth;
}
// PENDING_DOC_REVIEW
/**
* Determines the height of the bounding box within which the source media is
* resized as necessary to fit. If value ≤ 0
, then the height
* of the bounding box will be set to the natural height of the media, but
* fitHeight
will be set to the supplied parameter, even if
* non-positive.
* See {@link #preserveRatioProperty preserveRatio} for information on interaction
* between media views fitWidth
, fitHeight
and
* preserveRatio
attributes.
*/
private DoubleProperty fitHeight;
/**
* Sets the height of the bounding box of the resized media.
* @param value the height of the resized media.
*/
public final void setFitHeight(double value) {
fitHeightProperty().set(value);
};
/**
* Retrieves the height of the bounding box of the resized media.
* @return the height of the resized media.
*/
public final double getFitHeight() {
return fitHeight == null ? 0.0 : fitHeight.get();
}
public final DoubleProperty fitHeightProperty() {
if (fitHeight == null) {
fitHeight = new DoublePropertyBase() {
@Override
protected void invalidated() {
if (HostUtils.isIOS()) {
updateOverlayHeight();
}
else {
impl_markDirty(DirtyBits.NODE_VIEWPORT);
impl_geomChanged();
}
}
@Override
public Object getBean() {
return MediaView.this;
}
@Override
public String getName() {
return "fitHeight";
}
};
}
return fitHeight;
}
// PENDING_DOC_REVIEW
/**
* Specifies a rectangular viewport into the media frame.
* The viewport is a rectangle specified in the coordinates of the media frame.
* The resulting bounds prior to scaling will
* be the size of the viewport. The displayed image will include the
* intersection of the frame and the viewport. The viewport can exceed the
* size of the frame, but only the intersection will be displayed.
* Setting viewport
to null will clear the viewport.
*/
private ObjectProperty