1 /* 2 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.jfxmediaimpl; 27 28 import com.sun.media.jfxmedia.Media; 29 import com.sun.media.jfxmedia.MediaError; 30 import com.sun.media.jfxmedia.MediaException; 31 import com.sun.media.jfxmedia.MediaPlayer; 32 import com.sun.media.jfxmedia.control.VideoRenderControl; 33 import com.sun.media.jfxmedia.effects.AudioEqualizer; 34 import com.sun.media.jfxmedia.effects.AudioSpectrum; 35 import com.sun.media.jfxmedia.events.AudioSpectrumEvent; 36 import com.sun.media.jfxmedia.events.AudioSpectrumListener; 37 import com.sun.media.jfxmedia.events.BufferListener; 38 import com.sun.media.jfxmedia.events.BufferProgressEvent; 39 import com.sun.media.jfxmedia.events.MarkerEvent; 40 import com.sun.media.jfxmedia.events.MarkerListener; 41 import com.sun.media.jfxmedia.events.MediaErrorListener; 42 import com.sun.media.jfxmedia.events.NewFrameEvent; 43 import com.sun.media.jfxmedia.events.PlayerEvent; 44 import com.sun.media.jfxmedia.events.PlayerStateEvent; 45 import com.sun.media.jfxmedia.events.PlayerStateEvent.PlayerState; 46 import com.sun.media.jfxmedia.events.PlayerStateListener; 47 import com.sun.media.jfxmedia.events.PlayerTimeListener; 48 import com.sun.media.jfxmedia.events.VideoFrameRateListener; 49 import com.sun.media.jfxmedia.events.VideoRendererListener; 50 import com.sun.media.jfxmedia.events.VideoTrackSizeListener; 51 import com.sun.media.jfxmedia.logging.Logger; 52 import com.sun.media.jfxmedia.track.AudioTrack; 53 import com.sun.media.jfxmedia.track.SubtitleTrack; 54 import com.sun.media.jfxmedia.track.Track; 55 import com.sun.media.jfxmedia.track.Track.Encoding; 56 import com.sun.media.jfxmedia.track.VideoResolution; 57 import com.sun.media.jfxmedia.track.VideoTrack; 58 import java.lang.ref.WeakReference; 59 import java.util.*; 60 import java.util.concurrent.BlockingQueue; 61 import java.util.concurrent.LinkedBlockingQueue; 62 import java.util.concurrent.atomic.AtomicBoolean; 63 import java.util.concurrent.locks.Lock; 64 import java.util.concurrent.locks.ReentrantLock; 65 66 /** 67 * Base implementation of a 68 * <code>MediaPlayer</code>. 69 */ 70 public abstract class NativeMediaPlayer implements MediaPlayer, MarkerStateListener { 71 //***** Event IDs for PlayerStateEvent. IDs sent from native JNI layer. 72 73 public final static int eventPlayerUnknown = 100; 74 public final static int eventPlayerReady = 101; 75 public final static int eventPlayerPlaying = 102; 76 public final static int eventPlayerPaused = 103; 77 public final static int eventPlayerStopped = 104; 78 public final static int eventPlayerStalled = 105; 79 public final static int eventPlayerFinished = 106; 80 public final static int eventPlayerError = 107; 81 // Nominal video frames per second. 82 private static final int NOMINAL_VIDEO_FPS = 30; 83 // Nanoseconds per second. 84 public static final long ONE_SECOND = 1000000000L; 85 86 /** 87 * The 88 * <code>Media</code> corresponding to the media source. 89 */ 90 private NativeMedia media; 91 private VideoRenderControl videoRenderControl; 92 private final List<WeakReference<MediaErrorListener>> errorListeners = new ArrayList<>(); 93 private final List<WeakReference<PlayerStateListener>> playerStateListeners = new ArrayList<>(); 94 private final List<WeakReference<PlayerTimeListener>> playerTimeListeners = new ArrayList<>(); 95 private final List<WeakReference<VideoTrackSizeListener>> videoTrackSizeListeners = new ArrayList<>(); 96 private final List<WeakReference<VideoRendererListener>> videoUpdateListeners = new ArrayList<>(); 97 private final List<WeakReference<VideoFrameRateListener>> videoFrameRateListeners = new ArrayList<>(); 98 private final List<WeakReference<MarkerListener>> markerListeners = new ArrayList<>(); 99 private final List<WeakReference<BufferListener>> bufferListeners = new ArrayList<>(); 100 private final List<WeakReference<AudioSpectrumListener>> audioSpectrumListeners = new ArrayList<>(); 101 private final List<PlayerStateEvent> cachedStateEvents = new ArrayList<>(); 102 private final List<PlayerTimeEvent> cachedTimeEvents = new ArrayList<>(); 103 private final List<BufferProgressEvent> cachedBufferEvents = new ArrayList<>(); 104 private final List<MediaErrorEvent> cachedErrorEvents = new ArrayList<>(); 105 private boolean isFirstFrame = true; 106 private NewFrameEvent firstFrameEvent = null; 107 private double firstFrameTime; 108 private final Object firstFrameLock = new Object(); 109 private EventQueueThread eventLoop = new EventQueueThread(); 110 private int frameWidth = -1; 111 private int frameHeight = -1; 112 private final AtomicBoolean isMediaPulseEnabled = new AtomicBoolean(false); 113 private final Lock mediaPulseLock = new ReentrantLock(); 114 private Timer mediaPulseTimer; 115 private final Lock markerLock = new ReentrantLock(); 116 private boolean checkSeek = false; 117 private double timeBeforeSeek = 0.0; 118 private double timeAfterSeek = 0.0; 119 private double previousTime = 0.0; 120 private double firedMarkerTime = -1.0; 121 private double startTime = 0.0; 122 private double stopTime = Double.POSITIVE_INFINITY; 123 private boolean isStartTimeUpdated = false; 124 private boolean isStopTimeSet = false; 125 126 // --- Begin decoded frame rate fields 127 private double encodedFrameRate = 0.0; 128 private boolean recomputeFrameRate = true; 129 private double previousFrameTime; 130 private long numFramesSincePlaying; 131 private double meanFrameDuration; 132 private double decodedFrameRate; 133 // --- End decoded frame rate fields 134 private PlayerState playerState = PlayerState.UNKNOWN; 135 private final Lock disposeLock = new ReentrantLock(); 136 private boolean isDisposed = false; 137 private Runnable onDispose; 138 139 //************************************************************************** 140 //***** Constructors 141 //************************************************************************** 142 /** 143 * Construct a NativeMediaPlayer for the referenced clip. 144 * 145 * @param clip Media object 146 * @throws IllegalArgumentException if 147 * <code>clip</code> is 148 * <code>null</code>. 149 */ 150 protected NativeMediaPlayer(NativeMedia clip) { 151 if (clip == null) { 152 throw new IllegalArgumentException("clip == null!"); 153 } 154 media = clip; 155 videoRenderControl = new VideoRenderer(); 156 } 157 158 /** 159 * Initialization method which must be called after construction to 160 * initialize the internal state of the player. This method should be 161 * invoked directly after the player is constructed. 162 */ 163 protected void init() { 164 media.addMarkerStateListener(this); 165 eventLoop.start(); 166 } 167 168 /** 169 * Set a callback to invoke when the player is disposed. 170 * 171 * @param onDispose object on which to invoke {@link Runnable#run()} in 172 * {@link #dispose()}. 173 */ 174 void setOnDispose(Runnable onDispose) { 175 disposeLock.lock(); 176 try { 177 if (!isDisposed) { 178 this.onDispose = onDispose; 179 } 180 } finally { 181 disposeLock.unlock(); 182 } 183 } 184 185 /** 186 * Event to be posted to any registered {@link MediaErrorListener}s. 187 */ 188 private static class WarningEvent extends PlayerEvent { 189 190 private final Object source; 191 private final String message; 192 193 WarningEvent(Object source, String message) { 194 this.source = source; 195 this.message = message; 196 } 197 198 public Object getSource() { 199 return source; 200 } 201 202 public String getMessage() { 203 return message; 204 } 205 } 206 207 /** 208 * Event to be posted to any registered (@link MediaErrorListener)s 209 */ 210 public static class MediaErrorEvent extends PlayerEvent { 211 212 private final Object source; 213 private final MediaError error; 214 215 public MediaErrorEvent(Object source, MediaError error) { 216 this.source = source; 217 this.error = error; 218 } 219 220 public Object getSource() { 221 return source; 222 } 223 224 public String getMessage() { 225 return error.description(); 226 } 227 228 public int getErrorCode() { 229 return error.code(); 230 } 231 } 232 233 private static class PlayerTimeEvent extends PlayerEvent { 234 235 private final double time; 236 237 public PlayerTimeEvent(double time) { 238 this.time = time; 239 } 240 241 public double getTime() { 242 return time; 243 } 244 } 245 246 /** 247 * Event to be posted to any registered {@link PlayerStateListener}s. 248 */ 249 private static class TrackEvent extends PlayerEvent { 250 251 private final Track track; 252 253 TrackEvent(Track track) { 254 this.track = track; 255 } 256 257 public Track getTrack() { 258 return this.track; 259 } 260 } 261 262 /** 263 * Event to be posted to any registered {@link VideoTrackSizeListener}s. 264 */ 265 private static class FrameSizeChangedEvent extends PlayerEvent { 266 267 private final int width; 268 private final int height; 269 270 public FrameSizeChangedEvent(int width, int height) { 271 if (width > 0) { 272 this.width = width; 273 } else { 274 this.width = 0; 275 } 276 277 if (height > 0) { 278 this.height = height; 279 } else { 280 this.height = 0; 281 } 282 } 283 284 public int getWidth() { 285 return width; 286 } 287 288 public int getHeight() { 289 return height; 290 } 291 } 292 293 /** 294 * Helper class which managers {@link VideoRendererListener}s. This allows 295 * any registered listeners, specifically AWT and Prism, to receive video 296 * frames. 297 */ 298 private class VideoRenderer implements VideoRenderControl { 299 300 /** 301 * adds the listener to the player's videoUpdate. The listener will be 302 * called whenever a new frame of video is ready to be painted or 303 * fetched by getData() 304 * 305 * @param listener the object which provides the VideoUpdateListener 306 * callback interface 307 */ 308 @Override 309 public void addVideoRendererListener(VideoRendererListener listener) { 310 if (listener != null) { 311 synchronized (firstFrameLock) { 312 // If the first frame is cached, post it to the listener 313 // directly. The lock is obtained first so the cached 314 // frame is not cleared between the non-null test and 315 // posting the event. 316 if (firstFrameEvent != null) { 317 listener.videoFrameUpdated(firstFrameEvent); 318 } 319 } 320 videoUpdateListeners.add(new WeakReference<>(listener)); 321 } 322 } 323 324 /** 325 * removes the listener from the player. 326 * 327 * @param listener to be removed from the player 328 */ 329 @Override 330 public void removeVideoRendererListener(VideoRendererListener listener) { 331 if (listener != null) { 332 for (ListIterator<WeakReference<VideoRendererListener>> it = videoUpdateListeners.listIterator(); it.hasNext();) { 333 VideoRendererListener l = it.next().get(); 334 if (l == null || l == listener) { 335 it.remove(); 336 } 337 } 338 } 339 } 340 341 @Override 342 public void addVideoFrameRateListener(VideoFrameRateListener listener) { 343 if (listener != null) { 344 videoFrameRateListeners.add(new WeakReference<>(listener)); 345 } 346 } 347 348 @Override 349 public void removeVideoFrameRateListener(VideoFrameRateListener listener) { 350 if (listener != null) { 351 for (ListIterator<WeakReference<VideoFrameRateListener>> it = videoFrameRateListeners.listIterator(); it.hasNext();) { 352 VideoFrameRateListener l = it.next().get(); 353 if (l == null || l == listener) { 354 it.remove(); 355 } 356 } 357 } 358 } 359 360 @Override 361 public int getFrameWidth() { 362 return frameWidth; 363 } 364 365 @Override 366 public int getFrameHeight() { 367 return frameHeight; 368 } 369 } 370 371 //***** EventQueueThread Helper Class -- Provides event handling. 372 /** 373 * Thread for media player event processing. The thread maintains an 374 * internal queue of 375 * <code>PlayerEvent</code>s to which callers post using 376 * <code>postEvent()</code>. The thread blocks until an event becomes 377 * available on the queue, and then removes the event from the queue and 378 * posts it to any registered listeners appropriate to the type of event. 379 */ 380 private class EventQueueThread extends Thread { 381 382 private final BlockingQueue<PlayerEvent> eventQueue = 383 new LinkedBlockingQueue<>(); 384 private volatile boolean stopped = false; 385 386 EventQueueThread() { 387 setName("JFXMedia Player EventQueueThread"); 388 setDaemon(true); 389 } 390 391 @Override 392 public void run() { 393 while (!stopped) { 394 try { 395 // trying to take an event from the queue. 396 // this method will block until an event becomes available. 397 PlayerEvent evt = eventQueue.take(); 398 399 if (!stopped) { 400 if (evt instanceof NewFrameEvent) { 401 try { 402 HandleRendererEvents((NewFrameEvent) evt); 403 } catch (Throwable t) { 404 if (Logger.canLog(Logger.ERROR)) { 405 Logger.logMsg(Logger.ERROR, "Caught exception in HandleRendererEvents: " + t.toString()); 406 } 407 } 408 } else if (evt instanceof PlayerStateEvent) { 409 HandleStateEvents((PlayerStateEvent) evt); 410 } else if (evt instanceof FrameSizeChangedEvent) { 411 HandleFrameSizeChangedEvents((FrameSizeChangedEvent) evt); 412 } else if (evt instanceof TrackEvent) { 413 HandleTrackEvents((TrackEvent) evt); 414 } else if (evt instanceof MarkerEvent) { 415 HandleMarkerEvents((MarkerEvent) evt); 416 } else if (evt instanceof WarningEvent) { 417 HandleWarningEvents((WarningEvent) evt); 418 } else if (evt instanceof PlayerTimeEvent) { 419 HandlePlayerTimeEvents((PlayerTimeEvent) evt); 420 } else if (evt instanceof BufferProgressEvent) { 421 HandleBufferEvents((BufferProgressEvent) evt); 422 } else if (evt instanceof AudioSpectrumEvent) { 423 HandleAudioSpectrumEvents((AudioSpectrumEvent) evt); 424 } else if (evt instanceof MediaErrorEvent) { 425 HandleErrorEvents((MediaErrorEvent) evt); 426 } 427 } 428 } catch (Exception e) { 429 // eventQueue.take() can throw InterruptedException, 430 // also in rare case it can throw wrong 431 // IllegalMonitorStateException 432 // so we catch Exception 433 // nothing to do, restart the loop unless it was properly stopped. 434 } 435 } 436 437 eventQueue.clear(); 438 } 439 440 private void HandleRendererEvents(NewFrameEvent evt) { 441 if (isFirstFrame) { 442 // Cache first frame. Frames are delivered time-sequentially 443 // so there should be no thread contention problem here. 444 isFirstFrame = false; 445 synchronized (firstFrameLock) { 446 firstFrameEvent = evt; 447 firstFrameTime = firstFrameEvent.getFrameData().getTimestamp(); 448 firstFrameEvent.getFrameData().holdFrame(); // hold as long as we cache it, else we'll crash 449 } 450 } else if (firstFrameEvent != null 451 && firstFrameTime != evt.getFrameData().getTimestamp()) { 452 // If this branch is entered then it cannot be the first frame. 453 // This means that the player must be in the PLAYING state as 454 // the first frame will arrive upon completion of prerolling. 455 // When playing, listeners should receive the current frame, 456 // not the first frame in the stream. 457 458 // Clear the cached first frame. Obtain the lock first to avoid 459 // a race condition with a listener newly being added. 460 synchronized (firstFrameLock) { 461 firstFrameEvent.getFrameData().releaseFrame(); 462 firstFrameEvent = null; 463 } 464 } 465 466 // notify videoUpdateListeners 467 for (ListIterator<WeakReference<VideoRendererListener>> it = videoUpdateListeners.listIterator(); it.hasNext();) { 468 VideoRendererListener l = it.next().get(); 469 if (l != null) { 470 l.videoFrameUpdated(evt); 471 } else { 472 it.remove(); 473 } 474 } 475 // done with the frame, we can release our hold now 476 evt.getFrameData().releaseFrame(); 477 478 if (!videoFrameRateListeners.isEmpty()) { 479 // Decoded frame rate calculations. 480 double currentFrameTime = System.nanoTime() / (double) ONE_SECOND; 481 482 if (recomputeFrameRate) { 483 // First frame in new computation sequence. 484 recomputeFrameRate = false; 485 previousFrameTime = currentFrameTime; 486 numFramesSincePlaying = 1; 487 } else { 488 boolean fireFrameRateEvent = false; 489 490 if (numFramesSincePlaying == 1) { 491 // Second frame. Estimate the initial frame rate and 492 // set event flag. 493 meanFrameDuration = currentFrameTime - previousFrameTime; 494 if (meanFrameDuration > 0.0) { 495 decodedFrameRate = 1.0 / meanFrameDuration; 496 fireFrameRateEvent = true; 497 } 498 } else { 499 // Update decoded frame rate estimate using a moving 500 // average over encodedFrameRate frames. 501 double previousMeanFrameDuration = meanFrameDuration; 502 503 // Determine moving average length. 504 int movingAverageLength = encodedFrameRate != 0.0 505 ? ((int) (encodedFrameRate + 0.5)) : NOMINAL_VIDEO_FPS; 506 507 // Claculate number of frames in current average. 508 long numFrames = numFramesSincePlaying < movingAverageLength 509 ? numFramesSincePlaying : movingAverageLength; 510 511 // Update the mean frame duration. 512 meanFrameDuration = ((numFrames - 1) * previousMeanFrameDuration 513 + currentFrameTime - previousFrameTime) / numFrames; 514 515 // If mean frame duration changed by more than 0.5 set 516 // event flag. 517 if (meanFrameDuration > 0.0 518 && Math.abs(decodedFrameRate - 1.0 / meanFrameDuration) > 0.5) { 519 decodedFrameRate = 1.0 / meanFrameDuration; 520 fireFrameRateEvent = true; 521 } 522 } 523 524 if (fireFrameRateEvent) { 525 // Fire event. 526 for (ListIterator<WeakReference<VideoFrameRateListener>> it = videoFrameRateListeners.listIterator(); it.hasNext();) { 527 VideoFrameRateListener l = it.next().get(); 528 if (l != null) { 529 l.onFrameRateChanged(decodedFrameRate); 530 } else { 531 it.remove(); 532 } 533 } 534 } 535 536 // Update running values. 537 previousFrameTime = currentFrameTime; 538 numFramesSincePlaying++; 539 } 540 } 541 } 542 543 private void HandleStateEvents(PlayerStateEvent evt) { 544 playerState = evt.getState(); 545 546 recomputeFrameRate = PlayerState.PLAYING == evt.getState(); 547 548 switch (playerState) { 549 case READY: 550 onNativeInit(); 551 sendFakeBufferProgressEvent(); 552 break; 553 case PLAYING: 554 isMediaPulseEnabled.set(true); 555 break; 556 case STOPPED: 557 case FINISHED: 558 // Force a time update here to catch the time going to 559 // zero for STOPPED and any trailing markers for FINISHED. 560 doMediaPulseTask(); 561 case PAUSED: 562 case STALLED: 563 case HALTED: 564 isMediaPulseEnabled.set(false); 565 break; 566 default: 567 break; 568 } 569 570 synchronized (cachedStateEvents) { 571 if (playerStateListeners.isEmpty()) { 572 // Cache event for processing when first listener registers. 573 cachedStateEvents.add(evt); 574 return; 575 } 576 } 577 578 for (ListIterator<WeakReference<PlayerStateListener>> it = playerStateListeners.listIterator(); it.hasNext();) { 579 PlayerStateListener listener = it.next().get(); 580 if (listener != null) { 581 switch (playerState) { 582 case READY: 583 onNativeInit(); 584 sendFakeBufferProgressEvent(); 585 listener.onReady(evt); 586 break; 587 588 case PLAYING: 589 listener.onPlaying(evt); 590 break; 591 592 case PAUSED: 593 listener.onPause(evt); 594 break; 595 596 case STOPPED: 597 listener.onStop(evt); 598 break; 599 600 case STALLED: 601 listener.onStall(evt); 602 break; 603 604 case FINISHED: 605 listener.onFinish(evt); 606 break; 607 608 case HALTED: 609 listener.onHalt(evt); 610 break; 611 612 default: 613 break; 614 } 615 } else { 616 it.remove(); 617 } 618 } 619 } 620 621 private void HandlePlayerTimeEvents(PlayerTimeEvent evt) { 622 synchronized (cachedTimeEvents) { 623 if (playerTimeListeners.isEmpty()) { 624 // Cache event for processing when first listener registers. 625 cachedTimeEvents.add(evt); 626 return; 627 } 628 } 629 630 for (ListIterator<WeakReference<PlayerTimeListener>> it = playerTimeListeners.listIterator(); it.hasNext();) { 631 PlayerTimeListener listener = it.next().get(); 632 if (listener != null) { 633 listener.onDurationChanged(evt.getTime()); 634 } else { 635 it.remove(); 636 } 637 } 638 } 639 640 private void HandleFrameSizeChangedEvents(FrameSizeChangedEvent evt) { 641 frameWidth = evt.getWidth(); 642 frameHeight = evt.getHeight(); 643 Logger.logMsg(Logger.DEBUG, "** Frame size changed (" + frameWidth + ", " + frameHeight + ")"); 644 for (ListIterator<WeakReference<VideoTrackSizeListener>> it = videoTrackSizeListeners.listIterator(); it.hasNext();) { 645 VideoTrackSizeListener listener = it.next().get(); 646 if (listener != null) { 647 listener.onSizeChanged(frameWidth, frameHeight); 648 } else { 649 it.remove(); 650 } 651 } 652 } 653 654 private void HandleTrackEvents(TrackEvent evt) { 655 media.addTrack(evt.getTrack()); 656 657 if (evt.getTrack() instanceof VideoTrack) { 658 encodedFrameRate = ((VideoTrack) evt.getTrack()).getEncodedFrameRate(); 659 } 660 } 661 662 private void HandleMarkerEvents(MarkerEvent evt) { 663 for (ListIterator<WeakReference<MarkerListener>> it = markerListeners.listIterator(); it.hasNext();) { 664 MarkerListener listener = it.next().get(); 665 if (listener != null) { 666 listener.onMarker(evt); 667 } else { 668 it.remove(); 669 } 670 } 671 } 672 673 private void HandleWarningEvents(WarningEvent evt) { 674 Logger.logMsg(Logger.WARNING, evt.getSource() + evt.getMessage()); 675 } 676 677 private void HandleErrorEvents(MediaErrorEvent evt) { 678 Logger.logMsg(Logger.ERROR, evt.getMessage()); 679 680 synchronized (cachedErrorEvents) { 681 if (errorListeners.isEmpty()) { 682 // cache error events until at least one listener is added 683 cachedErrorEvents.add(evt); 684 return; 685 } 686 } 687 688 for (ListIterator<WeakReference<MediaErrorListener>> it = errorListeners.listIterator(); it.hasNext();) { 689 MediaErrorListener l = it.next().get(); 690 if (l != null) { 691 l.onError(evt.getSource(), evt.getErrorCode(), evt.getMessage()); 692 } else { 693 it.remove(); 694 } 695 } 696 } 697 698 private void HandleBufferEvents(BufferProgressEvent evt) { 699 synchronized (cachedBufferEvents) { 700 if (bufferListeners.isEmpty()) { 701 // Cache event for processing when first listener registers. 702 cachedBufferEvents.add(evt); 703 return; 704 } 705 } 706 707 for (ListIterator<WeakReference<BufferListener>> it = bufferListeners.listIterator(); it.hasNext();) { 708 BufferListener listener = it.next().get(); 709 if (listener != null) { 710 listener.onBufferProgress(evt); 711 } else { 712 it.remove(); 713 } 714 } 715 } 716 717 private void HandleAudioSpectrumEvents(AudioSpectrumEvent evt) { 718 for (ListIterator<WeakReference<AudioSpectrumListener>> it = audioSpectrumListeners.listIterator(); it.hasNext();) { 719 AudioSpectrumListener listener = it.next().get(); 720 if (listener != null) { 721 listener.onAudioSpectrumEvent(evt); 722 } else { 723 it.remove(); 724 } 725 } 726 } 727 728 /** 729 * Puts an event to the EventQuery. 730 */ 731 public void postEvent(PlayerEvent event) { 732 if (eventQueue != null) { 733 eventQueue.offer(event); 734 } 735 } 736 737 /** 738 * Signals the thread to terminate. 739 */ 740 public void terminateLoop() { 741 stopped = true; 742 // put an event to unblock eventQueue.take() 743 try { 744 eventQueue.put(new PlayerEvent()); 745 } catch(InterruptedException ex) {} 746 } 747 748 private void sendFakeBufferProgressEvent() { 749 // Send fake 100% buffer progress event for HLS or !http protcol 750 String contentType = media.getLocator().getContentType(); 751 String protocol = media.getLocator().getProtocol(); 752 if ((contentType != null && (contentType.equals(MediaUtils.CONTENT_TYPE_M3U) || contentType.equals(MediaUtils.CONTENT_TYPE_M3U8))) 753 || (protocol != null && !protocol.equals("http"))) { 754 HandleBufferEvents(new BufferProgressEvent(getDuration(), 0, 1, 1)); 755 } 756 } 757 } 758 759 /** 760 * Internal function to get called when the native player is ready. 761 */ 762 private synchronized void onNativeInit() { 763 try { 764 playerInit(); 765 } catch (MediaException me) { 766 sendPlayerMediaErrorEvent(me.getMediaError().code()); 767 } 768 } 769 770 //************************************************************************** 771 //***** MediaPlayer implementation 772 //************************************************************************** 773 //***** Listener (un)registration. 774 @Override 775 public void addMediaErrorListener(MediaErrorListener listener) { 776 if (listener != null) { 777 this.errorListeners.add(new WeakReference<>(listener)); 778 779 synchronized (cachedErrorEvents) { 780 if (!cachedErrorEvents.isEmpty() && !errorListeners.isEmpty()) { 781 cachedErrorEvents.stream().forEach((evt) -> { 782 sendPlayerEvent(evt); 783 }); 784 cachedErrorEvents.clear(); 785 } 786 } 787 } 788 } 789 790 @Override 791 public void removeMediaErrorListener(MediaErrorListener listener) { 792 if (listener != null) { 793 for (ListIterator<WeakReference<MediaErrorListener>> it = errorListeners.listIterator(); it.hasNext();) { 794 MediaErrorListener l = it.next().get(); 795 if (l == null || l == listener) { 796 it.remove(); 797 } 798 } 799 } 800 } 801 802 @Override 803 public void addMediaPlayerListener(PlayerStateListener listener) { 804 if (listener != null) { 805 synchronized (cachedStateEvents) { 806 if (!cachedStateEvents.isEmpty() && playerStateListeners.isEmpty()) { 807 // Forward all cached state events to first listener to register. 808 Iterator<PlayerStateEvent> events = cachedStateEvents.iterator(); 809 while (events.hasNext()) { 810 PlayerStateEvent evt = events.next(); 811 switch (evt.getState()) { 812 case READY: 813 listener.onReady(evt); 814 break; 815 case PLAYING: 816 listener.onPlaying(evt); 817 break; 818 case PAUSED: 819 listener.onPause(evt); 820 break; 821 case STOPPED: 822 listener.onStop(evt); 823 break; 824 case STALLED: 825 listener.onStall(evt); 826 break; 827 case FINISHED: 828 listener.onFinish(evt); 829 break; 830 default: 831 break; 832 } 833 } 834 835 // Clear state event cache. 836 cachedStateEvents.clear(); 837 } 838 839 playerStateListeners.add(new WeakReference(listener)); 840 } 841 } 842 } 843 844 @Override 845 public void removeMediaPlayerListener(PlayerStateListener listener) { 846 if (listener != null) { 847 for (ListIterator<WeakReference<PlayerStateListener>> it = playerStateListeners.listIterator(); it.hasNext();) { 848 PlayerStateListener l = it.next().get(); 849 if (l == null || l == listener) { 850 it.remove(); 851 } 852 } 853 } 854 } 855 856 @Override 857 public void addMediaTimeListener(PlayerTimeListener listener) { 858 if (listener != null) { 859 synchronized (cachedTimeEvents) { 860 if (!cachedTimeEvents.isEmpty() && playerTimeListeners.isEmpty()) { 861 // Forward all cached time events to first listener to register. 862 Iterator<PlayerTimeEvent> events = cachedTimeEvents.iterator(); 863 while (events.hasNext()) { 864 PlayerTimeEvent evt = events.next(); 865 listener.onDurationChanged(evt.getTime()); 866 } 867 868 // Clear time event cache. 869 cachedTimeEvents.clear(); 870 } else { 871 // Let listener to know about duration 872 double duration = getDuration(); 873 if (duration != Double.POSITIVE_INFINITY) { 874 listener.onDurationChanged(duration); 875 } 876 } 877 878 playerTimeListeners.add(new WeakReference(listener)); 879 } 880 } 881 } 882 883 @Override 884 public void removeMediaTimeListener(PlayerTimeListener listener) { 885 if (listener != null) { 886 for (ListIterator<WeakReference<PlayerTimeListener>> it = playerTimeListeners.listIterator(); it.hasNext();) { 887 PlayerTimeListener l = it.next().get(); 888 if (l == null || l == listener) { 889 it.remove(); 890 } 891 } 892 } 893 } 894 895 @Override 896 public void addVideoTrackSizeListener(VideoTrackSizeListener listener) { 897 if (listener != null) { 898 if (frameWidth != -1 && frameHeight != -1) { 899 listener.onSizeChanged(frameWidth, frameHeight); 900 } 901 videoTrackSizeListeners.add(new WeakReference(listener)); 902 } 903 } 904 905 @Override 906 public void removeVideoTrackSizeListener(VideoTrackSizeListener listener) { 907 if (listener != null) { 908 for (ListIterator<WeakReference<VideoTrackSizeListener>> it = videoTrackSizeListeners.listIterator(); it.hasNext();) { 909 VideoTrackSizeListener l = it.next().get(); 910 if (l == null || l == listener) { 911 it.remove(); 912 } 913 } 914 } 915 } 916 917 @Override 918 public void addMarkerListener(MarkerListener listener) { 919 if (listener != null) { 920 markerListeners.add(new WeakReference(listener)); 921 } 922 } 923 924 @Override 925 public void removeMarkerListener(MarkerListener listener) { 926 if (listener != null) { 927 for (ListIterator<WeakReference<MarkerListener>> it = markerListeners.listIterator(); it.hasNext();) { 928 MarkerListener l = it.next().get(); 929 if (l == null || l == listener) { 930 it.remove(); 931 } 932 } 933 } 934 } 935 936 @Override 937 public void addBufferListener(BufferListener listener) { 938 if (listener != null) { 939 synchronized (cachedBufferEvents) { 940 if (!cachedBufferEvents.isEmpty() && bufferListeners.isEmpty()) { 941 cachedBufferEvents.stream().forEach((evt) -> { 942 listener.onBufferProgress(evt); 943 }); 944 // Clear buffer event cache. 945 cachedBufferEvents.clear(); 946 } 947 948 bufferListeners.add(new WeakReference(listener)); 949 } 950 } 951 } 952 953 @Override 954 public void removeBufferListener(BufferListener listener) { 955 if (listener != null) { 956 for (ListIterator<WeakReference<BufferListener>> it = bufferListeners.listIterator(); it.hasNext();) { 957 BufferListener l = it.next().get(); 958 if (l == null || l == listener) { 959 it.remove(); 960 } 961 } 962 } 963 } 964 965 @Override 966 public void addAudioSpectrumListener(AudioSpectrumListener listener) { 967 if (listener != null) { 968 audioSpectrumListeners.add(new WeakReference(listener)); 969 } 970 } 971 972 @Override 973 public void removeAudioSpectrumListener(AudioSpectrumListener listener) { 974 if (listener != null) { 975 for (ListIterator<WeakReference<AudioSpectrumListener>> it = audioSpectrumListeners.listIterator(); it.hasNext();) { 976 AudioSpectrumListener l = it.next().get(); 977 if (l == null || l == listener) { 978 it.remove(); 979 } 980 } 981 } 982 } 983 984 //***** Control functions 985 @Override 986 public VideoRenderControl getVideoRenderControl() { 987 return videoRenderControl; 988 } 989 990 @Override 991 public Media getMedia() { 992 return media; 993 } 994 995 @Override 996 public void setAudioSyncDelay(long delay) { 997 try { 998 playerSetAudioSyncDelay(delay); 999 } catch (MediaException me) { 1000 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1001 } 1002 } 1003 1004 @Override 1005 public long getAudioSyncDelay() { 1006 try { 1007 return playerGetAudioSyncDelay(); 1008 } catch (MediaException me) { 1009 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1010 } 1011 return 0; 1012 } 1013 1014 @Override 1015 public void play() { 1016 try { 1017 if (isStartTimeUpdated) { 1018 playerSeek(startTime); 1019 } 1020 isMediaPulseEnabled.set(true); 1021 playerPlay(); 1022 } catch (MediaException me) { 1023 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1024 } 1025 } 1026 1027 @Override 1028 public void stop() { 1029 try { 1030 playerStop(); 1031 playerSeek(startTime); 1032 } catch (MediaException me) { 1033 // sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1034 MediaUtils.warning(this, "stop() failed!"); 1035 } 1036 } 1037 1038 @Override 1039 public void pause() { 1040 try { 1041 playerPause(); 1042 } catch (MediaException me) { 1043 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1044 } 1045 } 1046 1047 @Override 1048 public float getRate() { 1049 try { 1050 return playerGetRate(); 1051 } catch (MediaException me) { 1052 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1053 } 1054 return 0; 1055 } 1056 1057 //***** Public properties 1058 @Override 1059 public void setRate(float rate) { 1060 try { 1061 playerSetRate(rate); 1062 } catch (MediaException me) { 1063 // sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1064 MediaUtils.warning(this, "setRate(" + rate + ") failed!"); 1065 } 1066 } 1067 1068 @Override 1069 public double getPresentationTime() { 1070 try { 1071 return playerGetPresentationTime(); 1072 } catch (MediaException me) { 1073 // sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1074 } 1075 return -1.0; 1076 } 1077 1078 @Override 1079 public float getVolume() { 1080 try { 1081 return playerGetVolume(); 1082 } catch (MediaException me) { 1083 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1084 } 1085 return 0; 1086 } 1087 1088 @Override 1089 public void setVolume(float vol) { 1090 if (vol < 0.0F) { 1091 vol = 0.0F; 1092 } else if (vol > 1.0F) { 1093 vol = 1.0F; 1094 } 1095 1096 try { 1097 playerSetVolume(vol); 1098 } catch (MediaException me) { 1099 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1100 } 1101 } 1102 1103 @Override 1104 public boolean getMute() { 1105 try { 1106 return playerGetMute(); 1107 } catch (MediaException me) { 1108 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1109 } 1110 return false; 1111 } 1112 1113 /** 1114 * Enables/disable mute. If mute is enabled then disabled, the previous 1115 * volume goes into effect. 1116 */ 1117 @Override 1118 public void setMute(boolean enable) { 1119 try { 1120 playerSetMute(enable); 1121 } catch (MediaException me) { 1122 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1123 } 1124 } 1125 1126 @Override 1127 public float getBalance() { 1128 try { 1129 return playerGetBalance(); 1130 } catch (MediaException me) { 1131 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1132 } 1133 return 0; 1134 } 1135 1136 @Override 1137 public void setBalance(float bal) { 1138 if (bal < -1.0F) { 1139 bal = -1.0F; 1140 } else if (bal > 1.0F) { 1141 bal = 1.0F; 1142 } 1143 1144 try { 1145 playerSetBalance(bal); 1146 } catch (MediaException me) { 1147 sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1148 } 1149 } 1150 1151 @Override 1152 public abstract AudioEqualizer getEqualizer(); 1153 1154 @Override 1155 public abstract AudioSpectrum getAudioSpectrum(); 1156 1157 @Override 1158 public double getDuration() { 1159 try { 1160 return playerGetDuration(); 1161 } catch (MediaException me) { 1162 // sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1163 } 1164 return Double.POSITIVE_INFINITY; 1165 } 1166 1167 /** 1168 * Gets the time within the duration of the media to start playing. 1169 */ 1170 @Override 1171 public double getStartTime() { 1172 return startTime; 1173 } 1174 1175 /** 1176 * Sets the start time within the media to play. 1177 */ 1178 @Override 1179 public void setStartTime(double startTime) { 1180 try { 1181 markerLock.lock(); 1182 this.startTime = startTime; 1183 if (playerState != PlayerState.PLAYING && playerState != PlayerState.FINISHED && playerState != PlayerState.STOPPED) { 1184 playerSeek(startTime); 1185 } else if (playerState == PlayerState.STOPPED) { 1186 isStartTimeUpdated = true; 1187 } 1188 } finally { 1189 markerLock.unlock(); 1190 } 1191 } 1192 1193 /** 1194 * Gets the time within the duration of the media to stop playing. 1195 */ 1196 @Override 1197 public double getStopTime() { 1198 return stopTime; 1199 } 1200 1201 /** 1202 * Sets the stop time within the media to stop playback. 1203 */ 1204 @Override 1205 public void setStopTime(double stopTime) { 1206 try { 1207 markerLock.lock(); 1208 this.stopTime = stopTime; 1209 isStopTimeSet = true; 1210 createMediaPulse(); 1211 } finally { 1212 markerLock.unlock(); 1213 } 1214 } 1215 1216 @Override 1217 public void seek(double streamTime) { 1218 if (playerState == PlayerState.STOPPED) { 1219 return; // No seek in stopped state 1220 } 1221 1222 if (streamTime < 0.0) { 1223 streamTime = 0.0; 1224 } else { 1225 double duration = getDuration(); 1226 if (duration >= 0.0 && streamTime > duration) { 1227 streamTime = duration; 1228 } 1229 } 1230 1231 if (!isMediaPulseEnabled.get()) { 1232 if ((playerState == PlayerState.PLAYING 1233 || playerState == PlayerState.PAUSED 1234 || playerState == PlayerState.FINISHED) 1235 && getStartTime() <= streamTime && streamTime <= getStopTime()) { 1236 isMediaPulseEnabled.set(true); 1237 } 1238 } 1239 1240 markerLock.lock(); 1241 try { 1242 timeBeforeSeek = getPresentationTime(); 1243 timeAfterSeek = streamTime; 1244 checkSeek = timeBeforeSeek != timeAfterSeek; 1245 previousTime = streamTime; 1246 firedMarkerTime = -1.0; 1247 // System.out.println("seek @ "+System.currentTimeMillis()); 1248 // System.out.println("seek to "+streamTime+" previousTime "+previousTime); 1249 1250 try { 1251 playerSeek(streamTime); 1252 } catch (MediaException me) { 1253 //sendPlayerEvent(new MediaErrorEvent(this, me.getMediaError())); 1254 MediaUtils.warning(this, "seek(" + streamTime + ") failed!"); 1255 } 1256 } finally { 1257 markerLock.unlock(); 1258 } 1259 } 1260 1261 protected abstract long playerGetAudioSyncDelay() throws MediaException; 1262 1263 protected abstract void playerSetAudioSyncDelay(long delay) throws MediaException; 1264 1265 protected abstract void playerPlay() throws MediaException; 1266 1267 protected abstract void playerStop() throws MediaException; 1268 1269 protected abstract void playerPause() throws MediaException; 1270 1271 protected abstract void playerFinish() throws MediaException; 1272 1273 protected abstract float playerGetRate() throws MediaException; 1274 1275 protected abstract void playerSetRate(float rate) throws MediaException; 1276 1277 protected abstract double playerGetPresentationTime() throws MediaException; 1278 1279 protected abstract boolean playerGetMute() throws MediaException; 1280 1281 protected abstract void playerSetMute(boolean state) throws MediaException; 1282 1283 protected abstract float playerGetVolume() throws MediaException; 1284 1285 protected abstract void playerSetVolume(float volume) throws MediaException; 1286 1287 protected abstract float playerGetBalance() throws MediaException; 1288 1289 protected abstract void playerSetBalance(float balance) throws MediaException; 1290 1291 protected abstract double playerGetDuration() throws MediaException; 1292 1293 protected abstract void playerSeek(double streamTime) throws MediaException; 1294 1295 protected abstract void playerInit() throws MediaException; 1296 1297 protected abstract void playerDispose(); 1298 1299 /** 1300 * Retrieves the current {@link PlayerState state} of the player. 1301 * 1302 * @return the current player state. 1303 */ 1304 @Override 1305 public PlayerState getState() { 1306 return playerState; 1307 } 1308 1309 @Override 1310 final public void dispose() { 1311 disposeLock.lock(); 1312 try { 1313 if (!isDisposed) { 1314 // Terminate event firing 1315 destroyMediaPulse(); 1316 1317 if (eventLoop != null) { 1318 eventLoop.terminateLoop(); 1319 } 1320 1321 synchronized (firstFrameLock) { 1322 if (firstFrameEvent != null) { 1323 firstFrameEvent.getFrameData().releaseFrame(); 1324 firstFrameEvent = null; 1325 } 1326 } 1327 1328 // Terminate native layer 1329 playerDispose(); 1330 1331 // Dispose media object and clear reference 1332 if (media != null) { 1333 media.dispose(); 1334 media = null; 1335 } 1336 1337 // Clear event loop reference 1338 if (eventLoop != null) { 1339 eventLoop.eventQueue.clear(); 1340 eventLoop = null; 1341 } 1342 1343 if (videoUpdateListeners != null) { 1344 for (ListIterator<WeakReference<VideoRendererListener>> it = videoUpdateListeners.listIterator(); it.hasNext();) { 1345 VideoRendererListener l = it.next().get(); 1346 if (l != null) { 1347 l.releaseVideoFrames(); 1348 } else { 1349 it.remove(); 1350 } 1351 } 1352 1353 videoUpdateListeners.clear(); 1354 } 1355 1356 if (playerStateListeners != null) { 1357 playerStateListeners.clear(); 1358 } 1359 1360 if (videoTrackSizeListeners != null) { 1361 videoTrackSizeListeners.clear(); 1362 } 1363 1364 if (videoFrameRateListeners != null) { 1365 videoFrameRateListeners.clear(); 1366 } 1367 1368 if (cachedStateEvents != null) { 1369 cachedStateEvents.clear(); 1370 } 1371 1372 if (cachedTimeEvents != null) { 1373 cachedTimeEvents.clear(); 1374 } 1375 1376 if (cachedBufferEvents != null) { 1377 cachedBufferEvents.clear(); 1378 } 1379 1380 if (errorListeners != null) { 1381 errorListeners.clear(); 1382 } 1383 1384 if (playerTimeListeners != null) { 1385 playerTimeListeners.clear(); 1386 } 1387 1388 if (markerListeners != null) { 1389 markerListeners.clear(); 1390 } 1391 1392 if (bufferListeners != null) { 1393 bufferListeners.clear(); 1394 } 1395 1396 if (audioSpectrumListeners != null) { 1397 audioSpectrumListeners.clear(); 1398 } 1399 1400 if (videoRenderControl != null) { 1401 videoRenderControl = null; 1402 } 1403 1404 if (onDispose != null) { 1405 onDispose.run(); 1406 } 1407 1408 isDisposed = true; 1409 } 1410 } finally { 1411 disposeLock.unlock(); 1412 } 1413 } 1414 1415 //************************************************************************** 1416 //***** Non-JNI methods called by the native layer. These methods are called 1417 //***** from the native layer via the invocation API. Their purpose is to 1418 //***** dispatch certain events to the Java layer. Each of these methods 1419 //***** posts an event on the <code>EventQueueThread</code> which in turn 1420 //***** forwards the event to any registered listeners. 1421 //************************************************************************** 1422 protected void sendWarning(int warningCode, String warningMessage) { 1423 if (eventLoop != null) { 1424 String message = String.format(MediaUtils.NATIVE_MEDIA_WARNING_FORMAT, 1425 warningCode); 1426 if (warningMessage != null) { 1427 message += ": " + warningMessage; 1428 } 1429 eventLoop.postEvent(new WarningEvent(this, message)); 1430 } 1431 } 1432 1433 protected void sendPlayerEvent(PlayerEvent evt) { 1434 if (eventLoop != null) { 1435 eventLoop.postEvent(evt); 1436 } 1437 } 1438 1439 protected void sendPlayerHaltEvent(String message, double time) { 1440 // Log the error. Since these are most likely playback engine message (e.g. GStreamer or PacketVideo), 1441 // it makes no sense to propogate it above. 1442 Logger.logMsg(Logger.ERROR, message); 1443 1444 if (eventLoop != null) { 1445 eventLoop.postEvent(new PlayerStateEvent(PlayerStateEvent.PlayerState.HALTED, time, message)); 1446 } 1447 } 1448 1449 protected void sendPlayerMediaErrorEvent(int errorCode) { 1450 sendPlayerEvent(new MediaErrorEvent(this, MediaError.getFromCode(errorCode))); 1451 } 1452 1453 protected void sendPlayerStateEvent(int eventID, double time) { 1454 switch (eventID) { 1455 case eventPlayerReady: 1456 sendPlayerEvent(new PlayerStateEvent(PlayerStateEvent.PlayerState.READY, time)); 1457 break; 1458 case eventPlayerPlaying: 1459 sendPlayerEvent(new PlayerStateEvent(PlayerStateEvent.PlayerState.PLAYING, time)); 1460 break; 1461 case eventPlayerPaused: 1462 sendPlayerEvent(new PlayerStateEvent(PlayerStateEvent.PlayerState.PAUSED, time)); 1463 break; 1464 case eventPlayerStopped: 1465 sendPlayerEvent(new PlayerStateEvent(PlayerStateEvent.PlayerState.STOPPED, time)); 1466 break; 1467 case eventPlayerStalled: 1468 sendPlayerEvent(new PlayerStateEvent(PlayerStateEvent.PlayerState.STALLED, time)); 1469 break; 1470 case eventPlayerFinished: 1471 sendPlayerEvent(new PlayerStateEvent(PlayerStateEvent.PlayerState.FINISHED, time)); 1472 break; 1473 default: 1474 break; 1475 } 1476 } 1477 1478 protected void sendNewFrameEvent(long nativeRef) { 1479 NativeVideoBuffer newFrameData = NativeVideoBuffer.createVideoBuffer(nativeRef); 1480 // createVideoBuffer puts a hold on the frame 1481 // we need to keep that hold until the event thread can process this event 1482 sendPlayerEvent(new NewFrameEvent(newFrameData)); 1483 } 1484 1485 protected void sendFrameSizeChangedEvent(int width, int height) { 1486 sendPlayerEvent(new FrameSizeChangedEvent(width, height)); 1487 } 1488 1489 protected void sendAudioTrack(boolean enabled, long trackID, String name, int encoding, 1490 String language, int numChannels, 1491 int channelMask, float sampleRate) { 1492 Locale locale = null; 1493 if (!language.equals("und")) { 1494 locale = new Locale(language); 1495 } 1496 1497 Track track = new AudioTrack(enabled, trackID, name, 1498 locale, Encoding.toEncoding(encoding), 1499 numChannels, channelMask, sampleRate); 1500 1501 TrackEvent evt = new TrackEvent(track); 1502 1503 sendPlayerEvent(evt); 1504 } 1505 1506 protected void sendVideoTrack(boolean enabled, long trackID, String name, int encoding, 1507 int width, int height, float frameRate, 1508 boolean hasAlphaChannel) { 1509 // No locale (currently) for video, so pass null 1510 Track track = new VideoTrack(enabled, trackID, name, null, 1511 Encoding.toEncoding(encoding), 1512 new VideoResolution(width, height), frameRate, hasAlphaChannel); 1513 1514 TrackEvent evt = new TrackEvent(track); 1515 1516 sendPlayerEvent(evt); 1517 } 1518 1519 protected void sendSubtitleTrack(boolean enabled, long trackID, String name, 1520 int encoding, String language) 1521 { 1522 Locale locale = null; 1523 if (null != language) { 1524 locale = new Locale(language); 1525 } 1526 Track track = new SubtitleTrack(enabled, trackID, name, locale, 1527 Encoding.toEncoding(encoding)); 1528 1529 sendPlayerEvent(new TrackEvent(track)); 1530 } 1531 1532 protected void sendMarkerEvent(String name, double time) { 1533 sendPlayerEvent(new MarkerEvent(name, time)); 1534 } 1535 1536 protected void sendDurationUpdateEvent(double duration) { 1537 sendPlayerEvent(new PlayerTimeEvent(duration)); 1538 } 1539 1540 protected void sendBufferProgressEvent(double clipDuration, long bufferStart, long bufferStop, long bufferPosition) { 1541 sendPlayerEvent(new BufferProgressEvent(clipDuration, bufferStart, bufferStop, bufferPosition)); 1542 } 1543 1544 protected void sendAudioSpectrumEvent(double timestamp, double duration) { 1545 sendPlayerEvent(new AudioSpectrumEvent(getAudioSpectrum(), timestamp, duration)); 1546 } 1547 1548 @Override 1549 public void markerStateChanged(boolean hasMarkers) { 1550 if (hasMarkers) { 1551 markerLock.lock(); 1552 try { 1553 previousTime = getPresentationTime(); 1554 } finally { 1555 markerLock.unlock(); 1556 } 1557 createMediaPulse(); 1558 } else { 1559 if (!isStopTimeSet) { 1560 destroyMediaPulse(); 1561 } 1562 } 1563 } 1564 1565 private void createMediaPulse() { 1566 mediaPulseLock.lock(); 1567 try { 1568 if (mediaPulseTimer == null) { 1569 mediaPulseTimer = new Timer(true); 1570 mediaPulseTimer.scheduleAtFixedRate(new MediaPulseTask(this), 0, 40 /* 1571 * period ms 1572 */); 1573 } 1574 } finally { 1575 mediaPulseLock.unlock(); 1576 } 1577 } 1578 1579 private void destroyMediaPulse() { 1580 mediaPulseLock.lock(); 1581 try { 1582 if (mediaPulseTimer != null) { 1583 mediaPulseTimer.cancel(); 1584 mediaPulseTimer = null; 1585 } 1586 } finally { 1587 mediaPulseLock.unlock(); 1588 } 1589 } 1590 1591 boolean doMediaPulseTask() { 1592 if (this.isMediaPulseEnabled.get()) { 1593 disposeLock.lock(); 1594 1595 if (isDisposed) { 1596 disposeLock.unlock(); 1597 return false; 1598 } 1599 1600 double thisTime = getPresentationTime(); 1601 1602 markerLock.lock(); 1603 1604 try { 1605 //System.out.println("Media pulse @ pts "+thisTime+" previous "+previousTime); 1606 1607 if (checkSeek) { 1608 if (timeAfterSeek > timeBeforeSeek) { 1609 // Forward seek 1610 if (thisTime >= timeAfterSeek) { 1611 // System.out.println("bail 1"); 1612 checkSeek = false; 1613 } else { 1614 return true; 1615 } 1616 } else if (timeAfterSeek < timeBeforeSeek) { 1617 // Backward seek 1618 if (thisTime >= timeBeforeSeek) { 1619 // System.out.println("bail 2"); 1620 return true; 1621 } else { 1622 checkSeek = false; 1623 } 1624 } 1625 } 1626 1627 Map.Entry<Double, String> marker = media.getNextMarker(previousTime, true); 1628 // System.out.println("marker "+marker); 1629 // System.out.println("Checking: " + previousTime + " " + thisTime + " " 1630 // + getStartTime() + " " + getStopTime() + " " 1631 // + marker.getKey()); 1632 1633 while (marker != null) { 1634 double nextMarkerTime = marker.getKey(); 1635 if (nextMarkerTime > thisTime) { 1636 break; 1637 } else if (nextMarkerTime != firedMarkerTime 1638 && nextMarkerTime >= previousTime 1639 && nextMarkerTime >= getStartTime() 1640 && nextMarkerTime <= getStopTime()) { 1641 // System.out.println("Firing: "+previousTime+" "+thisTime+" "+ 1642 // getStartTime()+" "+getStopTime()+" "+ 1643 // nextMarkerTime); 1644 MarkerEvent evt = new MarkerEvent(marker.getValue(), nextMarkerTime); 1645 for (ListIterator<WeakReference<MarkerListener>> it = markerListeners.listIterator(); it.hasNext();) { 1646 MarkerListener listener = it.next().get(); 1647 if (listener != null) { 1648 listener.onMarker(evt); 1649 } else { 1650 it.remove(); 1651 } 1652 } 1653 firedMarkerTime = nextMarkerTime; 1654 } 1655 marker = media.getNextMarker(nextMarkerTime, false); 1656 } 1657 1658 previousTime = thisTime; 1659 1660 // Do stopTime 1661 if (isStopTimeSet && thisTime >= stopTime) { 1662 playerFinish(); 1663 } 1664 } finally { 1665 disposeLock.unlock(); 1666 markerLock.unlock(); 1667 } 1668 } 1669 1670 return true; 1671 } 1672 1673 /* Audio EQ and spectrum creation, used by sub-classes */ 1674 protected AudioEqualizer createNativeAudioEqualizer(long nativeRef) { 1675 return new NativeAudioEqualizer(nativeRef); 1676 } 1677 1678 protected AudioSpectrum createNativeAudioSpectrum(long nativeRef) { 1679 return new NativeAudioSpectrum(nativeRef); 1680 } 1681 } 1682 1683 class MediaPulseTask extends TimerTask { 1684 1685 WeakReference<NativeMediaPlayer> playerRef; 1686 1687 MediaPulseTask(NativeMediaPlayer player) { 1688 playerRef = new WeakReference<>(player); 1689 } 1690 1691 @Override 1692 public void run() { 1693 final NativeMediaPlayer player = playerRef.get(); 1694 if (player != null) { 1695 if (!player.doMediaPulseTask()) { 1696 cancel(); // Stop if doMediaPulseTask() returns false. False means doMediaPulseTask() cannot continue (like after dispose).cy 1697 } 1698 } else { 1699 cancel(); 1700 } 1701 } 1702 }