/* * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ 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.scene.DirtyBits; import com.sun.javafx.scene.NodeHelper; import com.sun.javafx.scene.media.MediaViewHelper; 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.jfxmedia.control.MediaPlayerOverlay; 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: *

*
{@code
 * 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);
 * }
 * }
* The foregoing code will display the video as: *
*
* Hello Media * * @since JavaFX 2.0 */ public class MediaView extends Node { static { // This is used by classes in different packages to get access to // private and package private methods. MediaViewHelper.setMediaViewAccessor(new MediaViewHelper.MediaViewAccessor() { @Override public NGNode doCreatePeer(Node node) { return ((MediaView) node).doCreatePeer(); } @Override public void doUpdatePeer(Node node) { ((MediaView) node).doUpdatePeer(); } @Override public void doTransformsChanged(Node node) { ((MediaView) node).doTransformsChanged(); } @Override public BaseBounds doComputeGeomBounds(Node node, BaseBounds bounds, BaseTransform tx) { return ((MediaView) node).doComputeGeomBounds(bounds, tx); } @Override public boolean doComputeContains(Node node, double localX, double localY) { return ((MediaView) node).doComputeContains(localX, localY); } }); } /** * The name of the property in the {@link ObservableMap} returned by * {@link #getProperties()}. This value must also be defined as a JVM * command line definition for the frame rate to be added to the properties. */ private static final String VIDEO_FRAME_RATE_PROPERTY_NAME = "jfxmedia.decodedVideoFPS"; private static final String DEFAULT_STYLE_CLASS = "media-view"; /** * Inner class used to convert a MediaPlayer error into a * Bean event. */ private class MediaErrorInvalidationListener implements InvalidationListener { @Override public void invalidated(Observable value) { ObservableObjectValue errorProperty = (ObservableObjectValue)value; fireEvent(new MediaErrorEvent(getMediaPlayer(), getMediaView(), errorProperty.get())); } } /** Listener which converts MediaPlayer 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 -> { NodeHelper.markDirty(this, DirtyBits.NODE_VIEWPORT); NodeHelper.geomChanged(this); }; /** 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); }); }; } } /***************************************** Media Player Overlay support ***************************/ private MediaPlayerOverlay mediaPlayerOverlay = null; private ChangeListener parentListener; private ChangeListener treeVisibleListener; private ChangeListener opacityListener; private void createListeners() { parentListener = (ov2, oldParent, newParent) -> { updateOverlayVisibility(); }; treeVisibleListener = (ov1, oldVisible, newVisible) -> { updateOverlayVisibility(); }; opacityListener = (ov, oldOpacity, newOpacity) -> { updateOverlayOpacity(); }; } private boolean determineVisibility() { return (getParent() != null && isVisible()); } private synchronized void updateOverlayVisibility() { if (mediaPlayerOverlay != null) { mediaPlayerOverlay.setOverlayVisible(determineVisibility()); } } private synchronized void updateOverlayOpacity() { if (mediaPlayerOverlay != null) { mediaPlayerOverlay.setOverlayOpacity(getOpacity()); } } private synchronized void updateOverlayX() { if (mediaPlayerOverlay != null) { mediaPlayerOverlay.setOverlayX(getX()); } } private synchronized void updateOverlayY() { if (mediaPlayerOverlay != null) { mediaPlayerOverlay.setOverlayY(getY()); } } private synchronized void updateOverlayWidth() { if (mediaPlayerOverlay != null) { mediaPlayerOverlay.setOverlayWidth(getFitWidth()); } } private synchronized void updateOverlayHeight() { if (mediaPlayerOverlay != null) { mediaPlayerOverlay.setOverlayHeight(getFitHeight()); } } private synchronized void updateOverlayPreserveRatio() { if (mediaPlayerOverlay != null) { mediaPlayerOverlay.setOverlayPreserveRatio(isPreserveRatio()); } } private static Affine3D calculateNodeToSceneTransform(Node node) { final Affine3D transform = new Affine3D(); do { transform.preConcatenate(NodeHelper.getLeafTransform(node)); node = node.getParent(); } while (node != null); return transform; } private void updateOverlayTransform() { if (mediaPlayerOverlay != null) { final Affine3D trans = MediaView.calculateNodeToSceneTransform(this); mediaPlayerOverlay.setOverlayTransform( trans.getMxx(), trans.getMxy(), trans.getMxz(), trans.getMxt(), trans.getMyx(), trans.getMyy(), trans.getMyz(), trans.getMyt(), trans.getMzx(), trans.getMzy(), trans.getMzz(), trans.getMzt()); } } private void updateMediaPlayerOverlay() { mediaPlayerOverlay.setOverlayX(getX()); mediaPlayerOverlay.setOverlayY(getY()); mediaPlayerOverlay.setOverlayPreserveRatio(isPreserveRatio()); mediaPlayerOverlay.setOverlayWidth(getFitWidth()); mediaPlayerOverlay.setOverlayHeight(getFitHeight()); mediaPlayerOverlay.setOverlayOpacity(getOpacity()); mediaPlayerOverlay.setOverlayVisible(determineVisibility()); updateOverlayTransform(); } /* * * Note: This method MUST only be called via its accessor method. */ private void doTransformsChanged() { if (mediaPlayerOverlay != null) { updateOverlayTransform(); } } /******************************************* End of iOS specific stuff ***************************/ /** * @return reference to MediaView */ private MediaView getMediaView() { return this; } { // To initialize the class helper at the begining each constructor of this class MediaViewHelper.initHelper(this); } /** * Creates a MediaView instance with no associated * {@link MediaPlayer}. */ public MediaView() { getStyleClass().add(DEFAULT_STYLE_CLASS); setSmooth(Toolkit.getToolkit().getDefaultImageSmooth()); decodedFrameRateListener = createVideoFrameRateListener(); setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); } /** * Creates a MediaView instance associated with the specified * {@link MediaPlayer}. Equivalent to *

     * MediaPlayer player; // initialization omitted
     * MediaView view = new MediaView();
     * view.setMediaPlayer(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 ObjectProperty mediaPlayer; /** * Sets the MediaPlayer 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 ObjectProperty mediaPlayerProperty() { if (mediaPlayer == null) { mediaPlayer = new ObjectPropertyBase() { MediaPlayer oldValue = null; @Override protected void invalidated() { if (oldValue != null) { Media media = oldValue.getMedia(); if (media != null) { media.widthProperty().removeListener(mediaDimensionListener); media.heightProperty().removeListener(mediaDimensionListener); } if (decodedFrameRateListener != null && getMediaPlayer().retrieveJfxPlayer() != null) { getMediaPlayer().retrieveJfxPlayer().getVideoRenderControl().removeVideoFrameRateListener(decodedFrameRateListener); } oldValue.errorProperty().removeListener(errorListener); oldValue.removeView(getMediaView()); } //Uncomment the line below to print whether media is using Prism or Swing frame handler. //System.err.println(getPGMediaView().getClass().getName()); //Uncomment the line below to print whether media is using Prism or Swing frame handler. //System.err.println(getPGMediaView().getClass().getName()); MediaPlayer newValue = get(); if (newValue != null) { newValue.addView(getMediaView()); newValue.errorProperty().addListener(errorListener); if (decodedFrameRateListener != null && getMediaPlayer().retrieveJfxPlayer() != null) { getMediaPlayer().retrieveJfxPlayer().getVideoRenderControl().addVideoFrameRateListener(decodedFrameRateListener); } else if (decodedFrameRateListener != null) { registerVideoFrameRateListener = true; } Media media = newValue.getMedia(); if (media != null) { media.widthProperty().addListener(mediaDimensionListener); media.heightProperty().addListener(mediaDimensionListener); } } NodeHelper.markDirty(MediaView.this, DirtyBits.MEDIAVIEW_MEDIA); NodeHelper.geomChanged(MediaView.this); oldValue = newValue; } @Override public Object getBean() { return MediaView.this; } @Override public String getName() { return "mediaPlayer"; } }; } return mediaPlayer; } /** * Event handler to be invoked whenever an error occurs on this * MediaView. * * @see MediaErrorEvent */ private ObjectProperty> onError; /** * Sets the error event handler. * @param value the error event handler. */ public final void setOnError(EventHandler value) { onErrorProperty().set( value); } /** * Retrieves the error event handler. * @return the error event handler. */ public final EventHandler getOnError() { return onError == null ? null : onError.get(); } public final ObjectProperty> onErrorProperty() { if (onError == null) { onError = new ObjectPropertyBase>() { @Override protected void invalidated() { setEventHandler(MediaErrorEvent.MEDIA_ERROR, get()); } @Override public Object getBean() { return MediaView.this; } @Override public String getName() { return "onError"; } }; } return onError; } /** * Whether to preserve the aspect ratio (width / height) of the media when * scaling it to fit the node. If the aspect ratio is not preserved, the * media will be stretched or sheared in both dimensions to fit the * dimensions of the node. The default value is true. */ 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 { NodeHelper.markDirty(MediaView.this, DirtyBits.NODE_VIEWPORT); NodeHelper.geomChanged(MediaView.this); } } @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() { NodeHelper.markDirty(MediaView.this, 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 { NodeHelper.markDirty(MediaView.this, DirtyBits.NODE_GEOMETRY); NodeHelper.geomChanged(MediaView.this); } } @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 { NodeHelper.markDirty(MediaView.this, DirtyBits.NODE_GEOMETRY); NodeHelper.geomChanged(MediaView.this); } } @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 { NodeHelper.markDirty(MediaView.this, DirtyBits.NODE_VIEWPORT); NodeHelper.geomChanged(MediaView.this); } } @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 { NodeHelper.markDirty(MediaView.this, DirtyBits.NODE_VIEWPORT); NodeHelper.geomChanged(MediaView.this); } } @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 viewport; /** * Sets the rectangular viewport into the media frame. * @param value the rectangular viewport. */ public final void setViewport(Rectangle2D value) { viewportProperty().set(value); }; /** * Retrieves the rectangular viewport into the media frame. * @return the rectangular viewport. */ public final Rectangle2D getViewport() { return viewport == null ? null : viewport.get(); } public final ObjectProperty viewportProperty() { if (viewport == null) { viewport = new ObjectPropertyBase() { @Override protected void invalidated() { NodeHelper.markDirty(MediaView.this, DirtyBits.NODE_VIEWPORT); NodeHelper.geomChanged(MediaView.this); } @Override public Object getBean() { return MediaView.this; } @Override public String getName() { return "viewport"; } }; } return viewport; } void notifyMediaChange() { MediaPlayer player = getMediaPlayer(); if (player != null) { final NGMediaView peer = NodeHelper.getPeer(this); peer.setMediaProvider(player); } NodeHelper.markDirty(this, DirtyBits.MEDIAVIEW_MEDIA); NodeHelper.geomChanged(this); } void notifyMediaSizeChange() { NodeHelper.markDirty(this, DirtyBits.NODE_VIEWPORT); NodeHelper.geomChanged(this); } void notifyMediaFrameUpdated() { decodedFrameCount++; NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS); } /* * Note: This method MUST only be called via its accessor method. */ private NGNode doCreatePeer() { NGMediaView peer = new NGMediaView(); // this has to be done on the main toolkit thread... peer.setFrameTracker(new MediaViewFrameTracker()); return peer; } /* * Note: This method MUST only be called via its accessor method. */ private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { // need to figure out the width/height to use for computing bounds Media media = (getMediaPlayer() == null) ? null : getMediaPlayer().getMedia(); double w = media != null ? media.getWidth() : 0; // if media is null, width will be 0 double h = media != null ? media.getHeight() : 0; // if media is null, height will be 0 double newW = getFitWidth(); double newH = getFitHeight(); final double vw = getViewport() != null ? getViewport().getWidth() : 0; // if viewport is null, width will be 0 final double vh = getViewport() != null ? getViewport().getHeight() : 0; // if viewport is null, height will be 0 if (vw > 0 && vh > 0) { w = vw; h = vh; } if (getFitWidth() <= 0.0 && getFitHeight() <= 0.0) { newW = w; newH = h; } else if (isPreserveRatio()) { if (getFitWidth() <= 0.0) { newW = h > 0 ? w * (getFitHeight() / h) : 0.0F; newH = getFitHeight(); } else if (getFitHeight() <= 0.0) { newW = getFitWidth(); newH = w > 0 ? h * (getFitWidth() / w) : 0.0F; } else { if (w == 0.0) w = getFitWidth(); if (h == 0.0) h = getFitHeight(); double scale = Math.min(getFitWidth() / w, getFitHeight() / h); newW = w * scale; newH = h * scale; } } else if (getFitHeight() <= 0.0) { newH = h; } else if (getFitWidth() <= 0.0) { newW = w; } if (newH < 1.0F) { newH = 1.0F; } if (newW < 1.0F) { newW = 1.0F; } w = newW; h = newH; // if the w or h are non-positive, then there is no size // for the media view if (w <= 0 || h <= 0) { return bounds.makeEmpty(); } bounds = bounds.deriveWithNewBounds((float)getX(), (float)getY(), 0.0f, (float)(getX()+w), (float)(getY()+h), 0.0f); bounds = tx.transform(bounds, bounds); return bounds; } /* * Note: This method MUST only be called via its accessor method. */ private boolean doComputeContains(double localX, double localY) { // Currently this is simply a local bounds test which is already tested // by the caller (Node.contains()). return true; } void updateViewport() { if (getMediaPlayer() == null) { return; } final NGMediaView peer = NodeHelper.getPeer(this); if (getViewport() != null) { peer.setViewport((float)getFitWidth(), (float)getFitHeight(), (float)getViewport().getMinX(), (float)getViewport().getMinY(), (float)getViewport().getWidth(), (float)getViewport().getHeight(), isPreserveRatio()); } else { peer.setViewport((float)getFitWidth(), (float)getFitHeight(), 0.0F, 0.0F, 0.0F, 0.0F, isPreserveRatio()); } } /* * Note: This method MUST only be called via its accessor method. */ private void doUpdatePeer() { final NGMediaView peer = NodeHelper.getPeer(this); if (NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY)) { peer.setX((float)getX()); peer.setY((float)getY()); } if (NodeHelper.isDirty(this, DirtyBits.NODE_SMOOTH)) { peer.setSmooth(isSmooth()); } if (NodeHelper.isDirty(this, DirtyBits.NODE_VIEWPORT)) { updateViewport(); } if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) { peer.renderNextFrame(); } if (NodeHelper.isDirty(this, DirtyBits.MEDIAVIEW_MEDIA)) { MediaPlayer player = getMediaPlayer(); if (player != null) { peer.setMediaProvider(player); updateViewport(); } else { peer.setMediaProvider(null); } } } private int decodedFrameCount; private int renderedFrameCount; void perfReset() { decodedFrameCount = 0; renderedFrameCount = 0; } /** * @return number of frames that have been submitted for rendering */ int perfGetDecodedFrameCount() { return decodedFrameCount; } /** * @return number of frames that have been rendered */ int perfGetRenderedFrameCount() { return renderedFrameCount; } private class MediaViewFrameTracker implements MediaFrameTracker { @Override public void incrementDecodedFrameCount(int count) { decodedFrameCount += count; } @Override public void incrementRenderedFrameCount(int count) { renderedFrameCount += count; } } /** * Called by MediaPlayer when it becomes ready */ void _mediaPlayerOnReady() { com.sun.media.jfxmedia.MediaPlayer jfxPlayer = getMediaPlayer().retrieveJfxPlayer(); if (jfxPlayer != null) { if (decodedFrameRateListener != null && registerVideoFrameRateListener) { jfxPlayer.getVideoRenderControl().addVideoFrameRateListener(decodedFrameRateListener); registerVideoFrameRateListener = false; } // Get media player overlay mediaPlayerOverlay = jfxPlayer.getMediaPlayerOverlay(); if (mediaPlayerOverlay != null) { // Init media player overlay support createListeners(); parentProperty().addListener(parentListener); NodeHelper.treeVisibleProperty(this).addListener(treeVisibleListener); opacityProperty().addListener(opacityListener); synchronized (this) { updateMediaPlayerOverlay(); } } } } }