Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.media; 027 028import javafx.application.Platform; 029import javafx.beans.InvalidationListener; 030import javafx.beans.Observable; 031import javafx.beans.property.BooleanProperty; 032import javafx.beans.property.BooleanPropertyBase; 033import javafx.beans.property.DoubleProperty; 034import javafx.beans.property.DoublePropertyBase; 035import javafx.beans.property.ObjectProperty; 036import javafx.beans.property.ObjectPropertyBase; 037import javafx.beans.value.ChangeListener; 038import javafx.beans.value.ObservableObjectValue; 039import javafx.beans.value.ObservableValue; 040import javafx.collections.ObservableMap; 041import javafx.event.EventHandler; 042import javafx.geometry.NodeOrientation; 043import javafx.geometry.Rectangle2D; 044import javafx.scene.Node; 045import javafx.scene.Parent; 046import com.sun.javafx.geom.BaseBounds; 047import com.sun.javafx.geom.transform.Affine3D; 048import com.sun.javafx.geom.transform.BaseTransform; 049import com.sun.javafx.jmx.MXNodeAlgorithm; 050import com.sun.javafx.jmx.MXNodeAlgorithmContext; 051import com.sun.javafx.scene.DirtyBits; 052import com.sun.javafx.sg.PGMediaView; 053import com.sun.javafx.sg.PGMediaView.MediaFrameTracker; 054import com.sun.javafx.sg.PGNode; 055import com.sun.javafx.tk.Toolkit; 056import com.sun.media.jfxmediaimpl.HostUtils; 057import com.sun.media.jfxmediaimpl.platform.ios.IOSMediaPlayer; 058 059/** 060 * A {@link Node} that provides a view of {@link Media} being played by a 061 * {@link MediaPlayer}. 062 * 063 * <p>The following code snippet provides a simple example of an 064 * {@link javafx.application.Application#start(javafx.stage.Stage) Application.start()} 065 * method which displays a video: 066 * <code><pre> 067 * public void start(Stage stage) { 068 * // Create and set the Scene. 069 * Scene scene = new Scene(new Group(), 540, 209); 070 * stage.setScene(scene); 071 * 072 * // Name and display the Stage. 073 * stage.setTitle("Hello Media"); 074 * stage.show(); 075 * 076 * // Create the media source. 077 * String source = getParameters().getRaw().get(0); 078 * Media media = new Media(source); 079 * 080 * // Create the player and set to play automatically. 081 * MediaPlayer mediaPlayer = new MediaPlayer(media); 082 * mediaPlayer.setAutoPlay(true); 083 * 084 * // Create the view and add it to the Scene. 085 * MediaView mediaView = new MediaView(mediaPlayer); 086 * ((Group) scene.getRoot()).getChildren().add(mediaView); 087 * } 088 * </pre></code> 089 * The foregoing code will display the video as: 090 * <br/> 091 * <br/> 092 * <img src="doc-files/mediaview.png" alt="Hello Media"/> 093 * </p> 094 */ 095public class MediaView extends Node { 096 /** 097 * The name of the property in the {@link ObservableMap} returned by 098 * {@link #getProperties()}. This value must also be defined as a JVM 099 * command line definition for the frame rate to be added to the properties. 100 */ 101 private static final String VIDEO_FRAME_RATE_PROPERTY_NAME = "jfxmedia.decodedVideoFPS"; 102 103 /** 104 * Inner class used to convert a <code>MediaPlayer</code> error into a 105 * <code>Bean</code> event. 106 */ 107 private class MediaErrorInvalidationListener implements InvalidationListener { 108 109 @Override public void invalidated(Observable value) { 110 ObservableObjectValue<MediaException> errorProperty = (ObservableObjectValue<MediaException>)value; 111 fireEvent(new MediaErrorEvent(getMediaPlayer(), getMediaView(), errorProperty.get())); 112 } 113 } 114 115 /** Listener which converts <code>MediaPlayer</code> errors to events. */ 116 private InvalidationListener errorListener = new MediaErrorInvalidationListener(); 117 118 /** Listener which causes the geometry to be updated when the media dimension changes. */ 119 private InvalidationListener mediaDimensionListener = new InvalidationListener() { 120 @Override public void invalidated(Observable value) { 121 impl_markDirty(DirtyBits.NODE_VIEWPORT); 122 impl_geomChanged(); 123 } 124 }; 125 126 /** Listener for decoded frame rate. */ 127 private com.sun.media.jfxmedia.events.VideoFrameRateListener decodedFrameRateListener; 128 private boolean registerVideoFrameRateListener = false; 129 130 /** Creates a decoded frame rate listener. Will return <code>null</code> if 131 * the security manager does not permit retrieve system properties or if 132 * VIDEO_FRAME_RATE_PROPERTY_NAME is not set to "true." 133 */ 134 private com.sun.media.jfxmedia.events.VideoFrameRateListener createVideoFrameRateListener() { 135 String listenerProp = null; 136 try { 137 listenerProp = System.getProperty(VIDEO_FRAME_RATE_PROPERTY_NAME); 138 } catch (Throwable t) { 139 } 140 141 if (listenerProp == null || !Boolean.getBoolean(VIDEO_FRAME_RATE_PROPERTY_NAME)) { 142 return null; 143 } else { 144 return new com.sun.media.jfxmedia.events.VideoFrameRateListener() { 145 146 @Override 147 public void onFrameRateChanged(final double videoFrameRate) { 148 Platform.runLater(new Runnable() { 149 150 @Override 151 public void run() { 152 ObservableMap props = getProperties(); 153 props.put(VIDEO_FRAME_RATE_PROPERTY_NAME, videoFrameRate); 154 } 155 }); 156 } 157 }; 158 } 159 } 160 161 /***************************************** iOS specific stuff ***************************/ 162 163 private /*volatile*/ boolean mediaPlayerReady; 164 165 private ChangeListener<Parent> parentListener; 166 private ChangeListener<Boolean> treeVisibleListener; 167 private ChangeListener<Number> opacityListener; 168 169 private void createListeners() { 170 parentListener = new ChangeListener<Parent>() { 171 @Override 172 public void changed(ObservableValue<? extends Parent> ov, Parent oldParent, Parent newParent) { 173 updateOverlayVisibility(); 174 } 175 }; 176 177 treeVisibleListener = new ChangeListener<Boolean>() { 178 @Override 179 public void changed(ObservableValue<? extends Boolean> ov, Boolean oldVisible, Boolean newVisible) { 180 updateOverlayVisibility(); 181 } 182 }; 183 184 opacityListener = new ChangeListener<Number>() { 185 @Override 186 public void changed(ObservableValue<? extends Number> ov, Number oldOpacity, Number newOpacity) { 187 updateOverlayOpacity(); 188 } 189 }; 190 } 191 192 private IOSMediaPlayer getIOSPlayer() { 193 return (IOSMediaPlayer) getMediaPlayer().retrieveJfxPlayer(); 194 } 195 196 private boolean determineVisibility() { 197 return (getParent() != null && isVisible()); 198 } 199 200 private synchronized void updateOverlayVisibility() { 201 if (mediaPlayerReady) { 202 getIOSPlayer().setOverlayVisible(determineVisibility()); 203 } 204 } 205 206 private synchronized void updateOverlayOpacity() { 207 if (mediaPlayerReady) { 208 getIOSPlayer().setOverlayOpacity(getOpacity()); 209 } 210 } 211 212 private synchronized void updateOverlayX() { 213 if (mediaPlayerReady) { 214 getIOSPlayer().setOverlayX(getX()); 215 } 216 } 217 218 private synchronized void updateOverlayY() { 219 if (mediaPlayerReady) { 220 getIOSPlayer().setOverlayY(getY()); 221 } 222 } 223 224 private synchronized void updateOverlayWidth() { 225 if (mediaPlayerReady) { 226 getIOSPlayer().setOverlayWidth(getFitWidth()); 227 } 228 } 229 230 private synchronized void updateOverlayHeight() { 231 if (mediaPlayerReady) { 232 getIOSPlayer().setOverlayHeight(getFitHeight()); 233 } 234 } 235 236 private synchronized void updateOverlayPreserveRatio() { 237 if (mediaPlayerReady) { 238 getIOSPlayer().setOverlayPreserveRatio(isPreserveRatio()); 239 } 240 } 241 242 private static Affine3D calculateNodeToSceneTransform(Node node) { 243 final Affine3D transform = new Affine3D(); 244 do { 245 transform.preConcatenate(node.impl_getLeafTransform()); 246 node = node.getParent(); 247 } while (node != null); 248 249 return transform; 250 } 251 252 private void updateOverlayTransformDirectly() { 253 final Affine3D trans = MediaView.calculateNodeToSceneTransform(this); 254 getIOSPlayer().setOverlayTransform( 255 trans.getMxx(), trans.getMxy(), trans.getMxz(), trans.getMxt(), 256 trans.getMyx(), trans.getMyy(), trans.getMyz(), trans.getMyt(), 257 trans.getMzx(), trans.getMzy(), trans.getMzz(), trans.getMzt()); 258 } 259 260 private synchronized void updateOverlayTransform() { 261 if (mediaPlayerReady) { 262 updateOverlayTransformDirectly(); 263 } 264 } 265 266 private void updateIOSOverlay() { 267 getIOSPlayer().setOverlayX(getX()); 268 getIOSPlayer().setOverlayY(getY()); 269 getIOSPlayer().setOverlayPreserveRatio(isPreserveRatio()); 270 getIOSPlayer().setOverlayWidth(getFitWidth()); 271 getIOSPlayer().setOverlayHeight(getFitHeight()); 272 getIOSPlayer().setOverlayOpacity(getOpacity()); 273 getIOSPlayer().setOverlayVisible(determineVisibility()); 274 updateOverlayTransformDirectly(); 275 } 276 277 @Override 278 public void impl_transformsChanged() { 279 super.impl_transformsChanged(); 280 if (HostUtils.isIOS()) { 281 updateOverlayTransform(); 282 } 283 } 284 285 /******************************************* End of iOS specific stuff ***************************/ 286 287 /** 288 * @return reference to MediaView 289 */ 290 private MediaView getMediaView() { 291 return this; 292 } 293 294 /** 295 * Creates a <code>MediaView</code> instance with no associated 296 * {@link MediaPlayer}. 297 */ 298 public MediaView() { 299 setSmooth(Toolkit.getToolkit().getDefaultImageSmooth()); 300 decodedFrameRateListener = createVideoFrameRateListener(); 301 setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); 302 if (HostUtils.isIOS()) { 303 createListeners(); 304 parentProperty().addListener(parentListener); 305 impl_treeVisibleProperty().addListener(treeVisibleListener); 306 opacityProperty().addListener(opacityListener); 307 } 308 } 309 310 /** 311 * Creates a <code>MediaView</code> instance associated with the specified 312 * {@link MediaPlayer}. Equivalent to 313 * <pre><code> 314 * MediaPlayer player; // initialization omitted 315 * MediaView view = new MediaView(); 316 * view.setPlayer(player); 317 * </code></pre> 318 * 319 * @param mediaPlayer the {@link MediaPlayer} the playback of which is to be 320 * viewed via this class. 321 */ 322 public MediaView(MediaPlayer mediaPlayer) { 323 this(); 324 setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); 325 setMediaPlayer(mediaPlayer); 326 } 327 /** 328 * The <code>mediaPlayer</code> whose output will be handled by this view. 329 * 330 * Setting this value does not affect the status of the <code>MediaPlayer</code>, 331 * e.g., if the <code>MediaPlayer</code> was playing prior to setting 332 * <code>mediaPlayer</code> then it will continue playing. 333 * 334 * @see MediaException 335 * @see MediaPlayer 336 */ 337 private ObjectProperty<MediaPlayer> mediaPlayer; 338 339 /** 340 * Sets the <code>MediaPlayer</code> whose output will be handled by this view. 341 * @param value the associated <code>MediaPlayer</code>. 342 */ 343 public final void setMediaPlayer (MediaPlayer value) { 344 mediaPlayerProperty().set(value); 345 } 346 347 /** 348 * Retrieves the <code>MediaPlayer</code> whose output is being handled by 349 * this view. 350 * @return the associated <code>MediaPlayer</code>. 351 */ 352 public final MediaPlayer getMediaPlayer() { 353 return mediaPlayer == null ? null : mediaPlayer.get(); 354 } 355 356 public final ObjectProperty<MediaPlayer> mediaPlayerProperty() { 357 if (mediaPlayer == null) { 358 mediaPlayer = new ObjectPropertyBase<MediaPlayer>() { 359 MediaPlayer oldValue = null; 360 @Override protected void invalidated() { 361 if (oldValue != null) { 362 Media media = oldValue.getMedia(); 363 if (media != null) { 364 media.widthProperty().removeListener(mediaDimensionListener); 365 media.heightProperty().removeListener(mediaDimensionListener); 366 } 367 if (decodedFrameRateListener != null && getMediaPlayer().retrieveJfxPlayer() != null) { 368 getMediaPlayer().retrieveJfxPlayer().getVideoRenderControl().removeVideoFrameRateListener(decodedFrameRateListener); 369 } 370 oldValue.errorProperty().removeListener(errorListener); 371 oldValue.removeView(getMediaView()); 372 } 373 374 //Uncomment the line below to print whether media is using Prism or Swing frame handler. 375 //System.err.println(getPGMediaView().getClass().getName()); 376 //Uncomment the line below to print whether media is using Prism or Swing frame handler. 377 //System.err.println(getPGMediaView().getClass().getName()); 378 MediaPlayer newValue = get(); 379 if (newValue != null) { 380 newValue.addView(getMediaView()); 381 newValue.errorProperty().addListener(errorListener); 382 if (decodedFrameRateListener != null && getMediaPlayer().retrieveJfxPlayer() != null) { 383 getMediaPlayer().retrieveJfxPlayer().getVideoRenderControl().addVideoFrameRateListener(decodedFrameRateListener); 384 } else if (decodedFrameRateListener != null) { 385 registerVideoFrameRateListener = true; 386 } 387 Media media = newValue.getMedia(); 388 if (media != null) { 389 media.widthProperty().addListener(mediaDimensionListener); 390 media.heightProperty().addListener(mediaDimensionListener); 391 } 392 } 393 impl_markDirty(DirtyBits.MEDIAVIEW_MEDIA); 394 impl_geomChanged(); 395 oldValue = newValue; 396 } 397 @Override 398 public Object getBean() { 399 return MediaView.this; 400 } 401 402 @Override 403 public String getName() { 404 return "mediaPlayer"; 405 } 406 }; 407 } 408 return mediaPlayer; 409 } 410 /** 411 * Event handler to be invoked whenever an error occurs on this 412 * <code>MediaView</code>. 413 * 414 * @see MediaErrorEvent 415 */ 416 private ObjectProperty<EventHandler<MediaErrorEvent>> onError; 417 418 /** 419 * Sets the error event handler. 420 * @param value the error event handler. 421 */ 422 public final void setOnError(EventHandler<MediaErrorEvent> value) { 423 onErrorProperty().set( value); 424 } 425 426 /** 427 * Retrieves the error event handler. 428 * @return the error event handler. 429 */ 430 public final EventHandler<MediaErrorEvent> getOnError() { 431 return onError == null ? null : onError.get(); 432 } 433 434 public final ObjectProperty<EventHandler<MediaErrorEvent>> onErrorProperty() { 435 if (onError == null) { 436 onError = new ObjectPropertyBase<EventHandler<MediaErrorEvent>>() { 437 438 @Override 439 protected void invalidated() { 440 setEventHandler(MediaErrorEvent.MEDIA_ERROR, get()); 441 } 442 443 @Override 444 public Object getBean() { 445 return MediaView.this; 446 } 447 448 @Override 449 public String getName() { 450 return "onError"; 451 } 452 }; 453 } 454 return onError; 455 } 456 /** 457 * Whether to preserve the aspect ratio (width / height) of the media when 458 * scaling it to fit the node. If the aspect ratio is not preserved, the 459 * media will be stretched or sheared in both dimensions to fit the 460 * dimensions of the node. The default value is <code>true</code>. 461 */ 462 private BooleanProperty preserveRatio; 463 464 /** 465 * Sets whether to preserve the media aspect ratio when scaling. 466 * @param value whether to preserve the media aspect ratio. 467 */ 468 public final void setPreserveRatio(boolean value) { 469 preserveRatioProperty().set(value); 470 }; 471 472 /** 473 * Returns whether the media aspect ratio is preserved when scaling. 474 * @return whether the media aspect ratio is preserved. 475 */ 476 public final boolean isPreserveRatio() { 477 return preserveRatio == null ? true : preserveRatio.get(); 478 } 479 480 public final BooleanProperty preserveRatioProperty() { 481 if (preserveRatio == null) { 482 preserveRatio = new BooleanPropertyBase(true) { 483 484 @Override 485 protected void invalidated() { 486 if (HostUtils.isIOS()) { 487 updateOverlayPreserveRatio(); 488 } 489 else { 490 impl_markDirty(DirtyBits.NODE_VIEWPORT); 491 impl_geomChanged(); 492 } 493 } 494 495 @Override 496 public Object getBean() { 497 return MediaView.this; 498 } 499 500 @Override 501 public String getName() { 502 return "preserveRatio"; 503 } 504 }; 505 } 506 return preserveRatio; 507 } 508 /** 509 * If set to <code>true</code> a better quality filtering 510 * algorithm will be used when scaling this video to fit within the 511 * bounding box provided by <code>fitWidth</code> and <code>fitHeight</code> or 512 * when transforming. 513 * 514 * If set to <code>false</code> a faster but lesser quality filtering 515 * will be used. 516 * 517 * The default value depends on platform configuration. 518 */ 519 private BooleanProperty smooth; 520 521 /** 522 * Sets whether to smooth the media when scaling. 523 * @param value whether to smooth the media. 524 */ 525 public final void setSmooth(boolean value) { 526 smoothProperty().set(value); 527 } 528 529 /** 530 * Returns whether to smooth the media when scaling. 531 * @return whether to smooth the media 532 */ 533 public final boolean isSmooth() { 534 return smooth == null ? false : smooth.get(); 535 } 536 537 public final BooleanProperty smoothProperty() { 538 if (smooth == null) { 539 smooth = new BooleanPropertyBase() { 540 541 @Override 542 protected void invalidated() { 543 impl_markDirty(DirtyBits.NODE_SMOOTH); 544 } 545 546 @Override 547 public Object getBean() { 548 return MediaView.this; 549 } 550 551 @Override 552 public String getName() { 553 return "smooth"; 554 } 555 }; 556 } 557 return smooth; 558 } 559 // PENDING_DOC_REVIEW 560 /** 561 * Defines the current x coordinate of the <code>MediaView</code> origin. 562 */ 563 private DoubleProperty x; 564 565 /** 566 * Sets the x coordinate of the <code>MediaView</code> origin. 567 * @param value the x coordinate of the origin of the view. 568 */ 569 public final void setX(double value) { 570 xProperty().set(value); 571 } 572 573 /** 574 * Retrieves the x coordinate of the <code>MediaView</code> origin. 575 * @return the x coordinate of the origin of the view. 576 */ 577 public final double getX() { 578 return x == null ? 0.0 : x.get(); 579 } 580 581 public final DoubleProperty xProperty() { 582 if (x == null) { 583 x = new DoublePropertyBase() { 584 585 @Override 586 protected void invalidated() { 587 if (HostUtils.isIOS()) { 588 updateOverlayX(); 589 } 590 else { 591 impl_markDirty(DirtyBits.NODE_GEOMETRY); 592 impl_geomChanged(); 593 } 594 } 595 596 @Override 597 public Object getBean() { 598 return MediaView.this; 599 } 600 601 @Override 602 public String getName() { 603 return "x"; 604 } 605 }; 606 } 607 return x; 608 } 609 // PENDING_DOC_REVIEW 610 /** 611 * Defines the current y coordinate of the <code>MediaView</code> origin. 612 */ 613 private DoubleProperty y; 614 615 /** 616 * Sets the y coordinate of the <code>MediaView</code> origin. 617 * @param value the y coordinate of the origin of the view. 618 */ 619 public final void setY(double value) { 620 yProperty().set(value); 621 } 622 623 /** 624 * Retrieves the y coordinate of the <code>MediaView</code> origin. 625 * @return the y coordinate of the origin of the view. 626 */ 627 public final double getY() { 628 return y == null ? 0.0 : y.get(); 629 } 630 631 public final DoubleProperty yProperty() { 632 if (y == null) { 633 y = new DoublePropertyBase() { 634 635 @Override 636 protected void invalidated() { 637 if (HostUtils.isIOS()) { 638 updateOverlayY(); 639 } 640 else { 641 impl_markDirty(DirtyBits.NODE_GEOMETRY); 642 impl_geomChanged(); 643 } 644 } 645 646 @Override 647 public Object getBean() { 648 return MediaView.this; 649 } 650 651 @Override 652 public String getName() { 653 return "y"; 654 } 655 }; 656 } 657 return y; 658 } 659 // PENDING_DOC_REVIEW 660 /** 661 * Determines the width of the bounding box within which the source media is 662 * resized as necessary to fit. If <code>value ≤ 0</code>, then the width 663 * of the bounding box will be set to the natural width of the media, but 664 * <code>fitWidth</code> will be set to the supplied parameter, even if 665 * non-positive.<p/> 666 * See {@link #preserveRatioProperty preserveRatio} for information on interaction 667 * between media views <code>fitWidth</code>, <code>fitHeight</code> and 668 * <code>preserveRatio</code> attributes. 669 */ 670 private DoubleProperty fitWidth; 671 672 /** 673 * Sets the width of the bounding box of the resized media. 674 * @param value the width of the resized media. 675 */ 676 public final void setFitWidth(double value) { 677 fitWidthProperty().set(value); 678 } 679 680 /** 681 * Retrieves the width of the bounding box of the resized media. 682 * @return the height of the resized media. 683 */ 684 public final double getFitWidth() { 685 return fitWidth == null ? 0.0 : fitWidth.get(); 686 } 687 688 public final DoubleProperty fitWidthProperty() { 689 if (fitWidth == null) { 690 fitWidth = new DoublePropertyBase() { 691 692 @Override 693 protected void invalidated() { 694 if (HostUtils.isIOS()) { 695 updateOverlayWidth(); 696 } 697 else { 698 impl_markDirty(DirtyBits.NODE_VIEWPORT); 699 impl_geomChanged(); 700 } 701 } 702 703 @Override 704 public Object getBean() { 705 return MediaView.this; 706 } 707 708 @Override 709 public String getName() { 710 return "fitWidth"; 711 } 712 }; 713 } 714 return fitWidth; 715 } 716 // PENDING_DOC_REVIEW 717 /** 718 * Determines the height of the bounding box within which the source media is 719 * resized as necessary to fit. If <code>value ≤ 0</code>, then the height 720 * of the bounding box will be set to the natural height of the media, but 721 * <code>fitHeight</code> will be set to the supplied parameter, even if 722 * non-positive.<p/> 723 * See {@link #preserveRatioProperty preserveRatio} for information on interaction 724 * between media views <code>fitWidth</code>, <code>fitHeight</code> and 725 * <code>preserveRatio</code> attributes. 726 */ 727 private DoubleProperty fitHeight; 728 729 /** 730 * Sets the height of the bounding box of the resized media. 731 * @param value the height of the resized media. 732 */ 733 public final void setFitHeight(double value) { 734 fitHeightProperty().set(value); 735 }; 736 737 /** 738 * Retrieves the height of the bounding box of the resized media. 739 * @return the height of the resized media. 740 */ 741 public final double getFitHeight() { 742 return fitHeight == null ? 0.0 : fitHeight.get(); 743 } 744 745 public final DoubleProperty fitHeightProperty() { 746 if (fitHeight == null) { 747 fitHeight = new DoublePropertyBase() { 748 749 @Override 750 protected void invalidated() { 751 if (HostUtils.isIOS()) { 752 updateOverlayHeight(); 753 } 754 else { 755 impl_markDirty(DirtyBits.NODE_VIEWPORT); 756 impl_geomChanged(); 757 } 758 } 759 760 @Override 761 public Object getBean() { 762 return MediaView.this; 763 } 764 765 @Override 766 public String getName() { 767 return "fitHeight"; 768 } 769 }; 770 } 771 return fitHeight; 772 } 773 // PENDING_DOC_REVIEW 774 /** 775 * Specifies a rectangular viewport into the media frame. 776 * The viewport is a rectangle specified in the coordinates of the media frame. 777 * The resulting bounds prior to scaling will 778 * be the size of the viewport. The displayed image will include the 779 * intersection of the frame and the viewport. The viewport can exceed the 780 * size of the frame, but only the intersection will be displayed. 781 * Setting <code>viewport</code> to null will clear the viewport. 782 */ 783 private ObjectProperty<Rectangle2D> viewport; 784 785 /** 786 * Sets the rectangular viewport into the media frame. 787 * @param value the rectangular viewport. 788 */ 789 public final void setViewport(Rectangle2D value) { 790 viewportProperty().set(value); 791 }; 792 793 /** 794 * Retrieves the rectangular viewport into the media frame. 795 * @return the rectangular viewport. 796 */ 797 public final Rectangle2D getViewport() { 798 return viewport == null ? null : viewport.get(); 799 } 800 801 public final ObjectProperty<Rectangle2D> viewportProperty() { 802 if (viewport == null) { 803 viewport = new ObjectPropertyBase<Rectangle2D>() { 804 805 @Override 806 protected void invalidated() { 807 impl_markDirty(DirtyBits.NODE_VIEWPORT); 808 impl_geomChanged(); 809 } 810 811 @Override 812 public Object getBean() { 813 return MediaView.this; 814 } 815 816 @Override 817 public String getName() { 818 return "viewport"; 819 } 820 }; 821 } 822 return viewport; 823 } 824 825 void notifyMediaChange() { 826 MediaPlayer player = getMediaPlayer(); 827 if (player != null) 828 getPGMediaView().setMediaProvider(player); 829 830 impl_markDirty(DirtyBits.MEDIAVIEW_MEDIA); 831 impl_geomChanged(); 832 } 833 834 void notifyMediaSizeChange() { 835 impl_markDirty(DirtyBits.NODE_VIEWPORT); 836 impl_geomChanged(); 837 } 838 839 void notifyMediaFrameUpdated() { 840 decodedFrameCount++; 841 impl_markDirty(DirtyBits.NODE_CONTENTS); 842 } 843 844 /** 845 * @treatAsPrivate implementation detail 846 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 847 */ 848 @Deprecated 849 @Override 850 protected PGNode impl_createPGNode() { 851 PGMediaView peer = new NGMediaView(); 852 // this has to be done on the main toolkit thread... 853 peer.setFrameTracker(new MediaViewFrameTracker()); 854 return peer; 855 } 856 857 PGMediaView getPGMediaView() { 858 return (PGMediaView)impl_getPGNode(); 859 } 860 861 /** 862 * @treatAsPrivate implementation detail 863 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 864 */ 865 @Deprecated 866 @Override 867 public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 868 869 // need to figure out the width/height to use for computing bounds 870 Media media = (getMediaPlayer() == null) ? null : getMediaPlayer().getMedia(); 871 double w = media != null ? media.getWidth() : 0; // if media is null, width will be 0 872 double h = media != null ? media.getHeight() : 0; // if media is null, height will be 0 873 double newW = getFitWidth(); 874 double newH = getFitHeight(); 875 final double vw = getViewport() != null ? getViewport().getWidth() : 0; // if viewport is null, width will be 0 876 final double vh = getViewport() != null ? getViewport().getHeight() : 0; // if viewport is null, height will be 0 877 878 if (vw > 0 && vh > 0) { 879 w = vw; 880 h = vh; 881 } 882 883 if (getFitWidth() <= 0.0 && getFitHeight() <= 0.0) { 884 newW = w; 885 newH = h; 886 } else if (isPreserveRatio()) { 887 if (getFitWidth() <= 0.0) { 888 newW = h > 0 ? w * (getFitHeight() / h) : 0.0F; 889 newH = getFitHeight(); 890 } else if (getFitHeight() <= 0.0) { 891 newW = getFitWidth(); 892 newH = w > 0 ? h * (getFitWidth() / w) : 0.0F; 893 } else { 894 if (w == 0.0) w = getFitWidth(); 895 if (h == 0.0) h = getFitHeight(); 896 double scale = Math.min(getFitWidth() / w, getFitHeight() / h); 897 newW = w * scale; 898 newH = h * scale; 899 } 900 } else if (getFitHeight() <= 0.0) { 901 newH = h; 902 } else if (getFitWidth() <= 0.0) { 903 newW = w; 904 } 905 if (newH < 1.0F) { 906 newH = 1.0F; 907 } 908 if (newW < 1.0F) { 909 newW = 1.0F; 910 } 911 912 w = newW; 913 h = newH; 914 915 // if the w or h are non-positive, then there is no size 916 // for the media view 917 if (w <= 0 || h <= 0) { 918 return bounds.makeEmpty(); 919 } 920 bounds = bounds.deriveWithNewBounds((float)getX(), (float)getY(), 0.0f, 921 (float)(getX()+w), (float)(getY()+h), 0.0f); 922 bounds = tx.transform(bounds, bounds); 923 return bounds; 924 } 925 926 /** 927 * @treatAsPrivate implementation detail 928 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 929 */ 930 @Deprecated 931 @Override 932 protected boolean impl_computeContains(double localX, double localY) { 933 // Currently this is simply a local bounds test which is already tested 934 // by the caller (Node.contains()). 935 return true; 936 } 937 938 void updateViewport() { 939 940 if (getMediaPlayer() == null) { 941 return; 942 } 943 944 if (getViewport() != null) { 945 getPGMediaView().setViewport((float)getFitWidth(), (float)getFitHeight(), 946 (float)getViewport().getMinX(), (float)getViewport().getMinY(), 947 (float)getViewport().getWidth(), (float)getViewport().getHeight(), 948 isPreserveRatio()); 949 } else { 950 getPGMediaView().setViewport((float)getFitWidth(), (float)getFitHeight(), 951 0.0F, 0.0F, 0.0F, 0.0F, 952 isPreserveRatio()); 953 } 954 } 955 956 957 /** 958 * @treatAsPrivate implementation detail 959 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 960 */ 961 @Deprecated 962 @Override 963 public void impl_updatePG() { 964 super.impl_updatePG(); 965 966 if (impl_isDirty(DirtyBits.NODE_GEOMETRY)) { 967 PGMediaView peer = getPGMediaView(); 968 peer.setX((float)getX()); 969 peer.setY((float)getY()); 970 } 971 if (impl_isDirty(DirtyBits.NODE_SMOOTH)) { 972 getPGMediaView().setSmooth(isSmooth()); 973 } 974 if (impl_isDirty(DirtyBits.NODE_VIEWPORT)) { 975 updateViewport(); 976 } 977 if (impl_isDirty(DirtyBits.NODE_CONTENTS)) { 978 getPGMediaView().renderNextFrame(); 979 } 980 if (impl_isDirty(DirtyBits.MEDIAVIEW_MEDIA)) { 981 MediaPlayer player = getMediaPlayer(); 982 if (player != null) { 983 getPGMediaView().setMediaProvider(player); 984 updateViewport(); 985 } else { 986 getPGMediaView().setMediaProvider(null); 987 } 988 } 989 } 990 991 992 private int decodedFrameCount; 993 private int renderedFrameCount; 994 995 /** 996 * @treatAsPrivate implementation detail 997 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 998 */ 999 @Deprecated 1000 public void impl_perfReset() { 1001 decodedFrameCount = 0; 1002 renderedFrameCount = 0; 1003 } 1004 1005 /** 1006 * @return number of frames that have been submitted for rendering 1007 * @treatAsPrivate implementation detail 1008 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1009 */ 1010 @Deprecated 1011 public int impl_perfGetDecodedFrameCount() { 1012 return decodedFrameCount; 1013 } 1014 1015 /** 1016 * @return number of frames that have been rendered 1017 * @treatAsPrivate implementation detail 1018 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1019 */ 1020 @Deprecated 1021 public int impl_perfGetRenderedFrameCount() { 1022 return renderedFrameCount; 1023 } 1024 1025 private class MediaViewFrameTracker implements MediaFrameTracker { 1026 @Override 1027 public void incrementDecodedFrameCount(int count) { 1028 decodedFrameCount += count; 1029 } 1030 1031 @Override 1032 public void incrementRenderedFrameCount(int count) { 1033 renderedFrameCount += count; 1034 } 1035 } 1036 1037 /** 1038 * @treatAsPrivate implementation detail 1039 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 1040 */ 1041 @Deprecated 1042 public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) { 1043 return alg.processLeafNode(this, ctx); 1044 } 1045 1046 /** 1047 * Called by MediaPlayer when it becomes ready 1048 */ 1049 void _mediaPlayerOnReady() { 1050 if (decodedFrameRateListener != null && getMediaPlayer().retrieveJfxPlayer() != null && registerVideoFrameRateListener) { 1051 getMediaPlayer().retrieveJfxPlayer().getVideoRenderControl().addVideoFrameRateListener(decodedFrameRateListener); 1052 registerVideoFrameRateListener = false; 1053 } 1054 if (HostUtils.isIOS()) { 1055 synchronized (this) { 1056 updateIOSOverlay(); 1057 mediaPlayerReady = true; 1058 } 1059 } 1060 } 1061}