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