1 /*
   2  * Copyright (c) 2010, 2016, 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 #include "JavaPlayerEventDispatcher.h"
  27 #include "JniUtils.h"
  28 #include "Logger.h"
  29 #include <com_sun_media_jfxmedia_track_AudioTrack.h>
  30 #include <com_sun_media_jfxmediaimpl_NativeMediaPlayer.h>
  31 #include <Common/VSMemory.h>
  32 #include <Utils/LowLevelPerf.h>
  33 #include <jni/Logger.h>
  34 
  35 static bool areJMethodIDsInitialized = false;
  36 
  37 jmethodID CJavaPlayerEventDispatcher::m_SendWarningMethod = 0;
  38 
  39 jmethodID CJavaPlayerEventDispatcher::m_SendPlayerMediaErrorEventMethod = 0;
  40 jmethodID CJavaPlayerEventDispatcher::m_SendPlayerHaltEventMethod = 0;
  41 jmethodID CJavaPlayerEventDispatcher::m_SendPlayerStateEventMethod = 0;
  42 jmethodID CJavaPlayerEventDispatcher::m_SendNewFrameEventMethod = 0;
  43 jmethodID CJavaPlayerEventDispatcher::m_SendFrameSizeChangedEventMethod = 0;
  44 jmethodID CJavaPlayerEventDispatcher::m_SendAudioTrackEventMethod = 0;
  45 jmethodID CJavaPlayerEventDispatcher::m_SendVideoTrackEventMethod = 0;
  46 jmethodID CJavaPlayerEventDispatcher::m_SendSubtitleTrackEventMethod = 0;
  47 jmethodID CJavaPlayerEventDispatcher::m_SendMarkerEventMethod = 0;
  48 jmethodID CJavaPlayerEventDispatcher::m_SendBufferProgressEventMethod = 0;
  49 jmethodID CJavaPlayerEventDispatcher::m_SendDurationUpdateEventMethod = 0;
  50 jmethodID CJavaPlayerEventDispatcher::m_SendAudioSpectrumEventMethod = 0;
  51 
  52 CJavaPlayerEventDispatcher::CJavaPlayerEventDispatcher()
  53 : m_PlayerVM(NULL),
  54   m_PlayerInstance(NULL),
  55   m_MediaReference(0L)
  56 {
  57 }
  58 
  59 CJavaPlayerEventDispatcher::~CJavaPlayerEventDispatcher()
  60 {
  61     Dispose();
  62 }
  63 
  64 void CJavaPlayerEventDispatcher::Init(JNIEnv *env, jobject PlayerInstance, CMedia* pMedia)
  65 {
  66     LOWLEVELPERF_EXECTIMESTART("CJavaPlayerEventDispatcher::Init()");
  67 
  68     if (env->GetJavaVM(&m_PlayerVM) != JNI_OK) {
  69         // FIXME: Warning/error message??
  70         return;
  71     }
  72     m_PlayerInstance = env->NewGlobalRef(PlayerInstance);
  73     m_MediaReference = (jlong) ptr_to_jlong(pMedia);
  74 
  75     // Initialize jmethodID data members. These are derived from the class of
  76     // the object and not its instance. No, this particular implementation is
  77     // not thread-safe, but the worst that can happen is that the jmethodIDs are
  78     // initialized more than once which is still better than once per player.
  79     if (false == areJMethodIDsInitialized)
  80     {
  81         jclass klass = env->GetObjectClass(m_PlayerInstance);
  82 
  83         m_SendWarningMethod               = env->GetMethodID(klass, "sendWarning", "(ILjava/lang/String;)V");
  84 
  85         m_SendPlayerMediaErrorEventMethod = env->GetMethodID(klass, "sendPlayerMediaErrorEvent", "(I)V");
  86         m_SendPlayerHaltEventMethod       = env->GetMethodID(klass, "sendPlayerHaltEvent", "(Ljava/lang/String;D)V");
  87         m_SendPlayerStateEventMethod      = env->GetMethodID(klass, "sendPlayerStateEvent", "(ID)V");
  88         m_SendNewFrameEventMethod         = env->GetMethodID(klass, "sendNewFrameEvent", "(J)V");
  89         m_SendFrameSizeChangedEventMethod = env->GetMethodID(klass, "sendFrameSizeChangedEvent", "(II)V");
  90         m_SendAudioTrackEventMethod       = env->GetMethodID(klass, "sendAudioTrack", "(ZJLjava/lang/String;ILjava/lang/String;IIF)V");
  91         m_SendVideoTrackEventMethod       = env->GetMethodID(klass, "sendVideoTrack", "(ZJLjava/lang/String;IIIFZ)V");
  92         m_SendSubtitleTrackEventMethod    = env->GetMethodID(klass, "sendSubtitleTrack", "(ZJLjava/lang/String;ILjava/lang/String;)V");
  93         m_SendMarkerEventMethod           = env->GetMethodID(klass, "sendMarkerEvent", "(Ljava/lang/String;D)V");
  94         m_SendBufferProgressEventMethod   = env->GetMethodID(klass, "sendBufferProgressEvent", "(DJJJ)V");
  95         m_SendDurationUpdateEventMethod  = env->GetMethodID(klass, "sendDurationUpdateEvent", "(D)V");
  96         m_SendAudioSpectrumEventMethod  = env->GetMethodID(klass, "sendAudioSpectrumEvent", "(DD)V");
  97 
  98         env->DeleteLocalRef(klass);
  99 
 100         areJMethodIDsInitialized = true;
 101     }
 102 
 103     LOWLEVELPERF_EXECTIMESTOP("CJavaPlayerEventDispatcher::Init()");
 104 }
 105 
 106 void CJavaPlayerEventDispatcher::Dispose()
 107 {
 108     LOWLEVELPERF_EXECTIMESTART("CJavaPlayerEventDispatcher::Dispose()");
 109     CJavaEnvironment jenv(m_PlayerVM);
 110     JNIEnv *pEnv = jenv.getEnvironment();
 111     if (pEnv) {
 112         pEnv->DeleteGlobalRef(m_PlayerInstance);
 113         m_PlayerInstance = NULL; // prevent further calls to this object
 114     }
 115 
 116     LOWLEVELPERF_EXECTIMESTOP("CJavaPlayerEventDispatcher::Dispose()");
 117 }
 118 
 119 void CJavaPlayerEventDispatcher::Warning(int warningCode, const char* warningMessage)
 120 {
 121     CJavaEnvironment jenv(m_PlayerVM);
 122     JNIEnv *pEnv = jenv.getEnvironment();
 123     if (pEnv) {
 124         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 125         if (localPlayer) {
 126             jstring jmessage = NULL;
 127             if (warningMessage) {
 128                 jmessage = pEnv->NewStringUTF(warningMessage);
 129             }
 130             pEnv->CallVoidMethod(localPlayer, m_SendWarningMethod,
 131                                  (jint)warningCode, jmessage);
 132             if (jmessage) {
 133                 pEnv->DeleteLocalRef(jmessage);
 134             }
 135             pEnv->DeleteLocalRef(localPlayer);
 136         }
 137     }
 138 }
 139 
 140 bool CJavaPlayerEventDispatcher::SendPlayerMediaErrorEvent(int errorCode)
 141 {
 142     bool bSucceeded = false;
 143     CJavaEnvironment jenv(m_PlayerVM);
 144     JNIEnv *pEnv = jenv.getEnvironment();
 145     if (pEnv) {
 146         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 147         if (localPlayer) {
 148             pEnv->CallVoidMethod(localPlayer, m_SendPlayerMediaErrorEventMethod, errorCode);
 149             pEnv->DeleteLocalRef(localPlayer);
 150             
 151             bSucceeded = !jenv.reportException();
 152         }
 153     }
 154     
 155     return bSucceeded;
 156 }
 157 
 158 bool CJavaPlayerEventDispatcher::SendPlayerHaltEvent(const char* message, double time)
 159 {
 160     bool bSucceeded = false;
 161     CJavaEnvironment jenv(m_PlayerVM);
 162     JNIEnv *pEnv = jenv.getEnvironment();
 163     if (pEnv) {
 164         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 165         if (localPlayer) {
 166             jstring jmessage = pEnv->NewStringUTF(message);
 167             pEnv->CallVoidMethod(localPlayer, m_SendPlayerHaltEventMethod, jmessage, time);
 168             pEnv->DeleteLocalRef(jmessage);
 169             pEnv->DeleteLocalRef(localPlayer);
 170             
 171             bSucceeded = !jenv.reportException();
 172         }
 173     }
 174     
 175     return bSucceeded;
 176 }
 177 
 178 bool CJavaPlayerEventDispatcher::SendPlayerStateEvent(int newState, double presentTime)
 179 {
 180     long newJavaState;
 181 
 182     switch(newState) {
 183     case CPipeline::Unknown:
 184         newJavaState = com_sun_media_jfxmediaimpl_NativeMediaPlayer_eventPlayerUnknown;
 185         break;
 186     case CPipeline::Ready:
 187         newJavaState = com_sun_media_jfxmediaimpl_NativeMediaPlayer_eventPlayerReady;
 188         break;
 189     case CPipeline::Playing:
 190         newJavaState = com_sun_media_jfxmediaimpl_NativeMediaPlayer_eventPlayerPlaying;
 191         break;
 192     case CPipeline::Paused:
 193         newJavaState = com_sun_media_jfxmediaimpl_NativeMediaPlayer_eventPlayerPaused;
 194         break;
 195     case CPipeline::Stopped:
 196         newJavaState = com_sun_media_jfxmediaimpl_NativeMediaPlayer_eventPlayerStopped;
 197         break;
 198     case CPipeline::Stalled:
 199         newJavaState = com_sun_media_jfxmediaimpl_NativeMediaPlayer_eventPlayerStalled;
 200         break;
 201     case CPipeline::Finished:
 202         newJavaState = com_sun_media_jfxmediaimpl_NativeMediaPlayer_eventPlayerFinished;
 203         break;
 204     case CPipeline::Error:
 205         newJavaState = com_sun_media_jfxmediaimpl_NativeMediaPlayer_eventPlayerError;
 206         break;
 207     default:
 208         return false;
 209     }
 210 
 211     LOWLEVELPERF_EXECTIMESTOP("gstInitPlatformToSendToJavaPlayerStateEventPaused");
 212     LOWLEVELPERF_EXECTIMESTOP("gstPauseToSendToJavaPlayerStateEventPaused");
 213     LOWLEVELPERF_EXECTIMESTOP("gstStopToSendToJavaPlayerStateEventStopped");
 214     LOWLEVELPERF_EXECTIMESTOP("gstPlayToSendToJavaPlayerStateEventPlaying");
 215 
 216     bool bSucceeded = false;
 217     CJavaEnvironment jenv(m_PlayerVM);
 218     JNIEnv *pEnv = jenv.getEnvironment();
 219     if (pEnv) {
 220         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 221         if (localPlayer) {
 222             pEnv->CallVoidMethod(localPlayer, m_SendPlayerStateEventMethod, newJavaState, presentTime);
 223             pEnv->DeleteLocalRef(localPlayer);
 224             
 225             bSucceeded = !jenv.reportException();
 226         }
 227     }
 228 
 229     return bSucceeded;
 230 }
 231 
 232 bool CJavaPlayerEventDispatcher::SendNewFrameEvent(CVideoFrame* pVideoFrame)
 233 {
 234     LOWLEVELPERF_EXECTIMESTART("CJavaPlayerEventDispatcher::SendNewFrameEvent()");
 235     bool bSucceeded = false;
 236     
 237     CJavaEnvironment jenv(m_PlayerVM);
 238     JNIEnv *pEnv = jenv.getEnvironment();
 239     if (pEnv) {
 240         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 241         if (localPlayer) {
 242             // SendNewFrameEvent will create the NativeVideoBuffer wrapper for the java side
 243             pEnv->CallVoidMethod(localPlayer, m_SendNewFrameEventMethod, ptr_to_jlong(pVideoFrame));
 244             pEnv->DeleteLocalRef(localPlayer);
 245             
 246             bSucceeded = !jenv.reportException();
 247         }
 248     }
 249     
 250     LOWLEVELPERF_EXECTIMESTOP("CJavaPlayerEventDispatcher::SendNewFrameEvent()");
 251     
 252     return bSucceeded;
 253 }
 254 
 255 bool CJavaPlayerEventDispatcher::SendFrameSizeChangedEvent(int width, int height)
 256 {
 257     bool bSucceeded = false;
 258     CJavaEnvironment jenv(m_PlayerVM);
 259     JNIEnv *pEnv = jenv.getEnvironment();
 260     if (pEnv) {
 261         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 262         if (localPlayer) {
 263             pEnv->CallVoidMethod(localPlayer, m_SendFrameSizeChangedEventMethod, (jint)width, (jint)height);
 264             pEnv->DeleteLocalRef(localPlayer);
 265             
 266             bSucceeded = !jenv.reportException();
 267         }
 268     }
 269     
 270     return bSucceeded;
 271 }
 272 
 273 bool CJavaPlayerEventDispatcher::SendAudioTrackEvent(CAudioTrack* pTrack)
 274 {
 275     bool bSucceeded = false;
 276     CJavaEnvironment jenv(m_PlayerVM);
 277     JNIEnv *pEnv = jenv.getEnvironment();
 278     if (pEnv) {
 279         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 280         if (localPlayer) {
 281             jstring name = pEnv->NewStringUTF(pTrack->GetName().c_str());
 282             jstring language = pEnv->NewStringUTF(pTrack->GetLanguage().c_str());
 283             
 284             // Translate channel mask bits from native values to Java values.
 285             int nativeChannelMask = pTrack->GetChannelMask();
 286             jint javaChannelMask = 0;
 287             if (nativeChannelMask & CAudioTrack::UNKNOWN)
 288                 javaChannelMask |= com_sun_media_jfxmedia_track_AudioTrack_UNKNOWN;
 289             if (nativeChannelMask & CAudioTrack::FRONT_LEFT)
 290                 javaChannelMask |= com_sun_media_jfxmedia_track_AudioTrack_FRONT_LEFT;
 291             if (nativeChannelMask & CAudioTrack::FRONT_RIGHT)
 292                 javaChannelMask |= com_sun_media_jfxmedia_track_AudioTrack_FRONT_RIGHT;
 293             if (nativeChannelMask & CAudioTrack::FRONT_CENTER)
 294                 javaChannelMask |= com_sun_media_jfxmedia_track_AudioTrack_FRONT_CENTER;
 295             if (nativeChannelMask & CAudioTrack::REAR_LEFT)
 296                 javaChannelMask |= com_sun_media_jfxmedia_track_AudioTrack_REAR_LEFT;
 297             if (nativeChannelMask & CAudioTrack::REAR_RIGHT)
 298                 javaChannelMask |= com_sun_media_jfxmedia_track_AudioTrack_REAR_RIGHT;
 299             if (nativeChannelMask & CAudioTrack::REAR_CENTER)
 300                 javaChannelMask |= com_sun_media_jfxmedia_track_AudioTrack_REAR_CENTER;
 301             
 302             pEnv->CallVoidMethod(localPlayer,
 303                                  m_SendAudioTrackEventMethod,
 304                                  (jboolean)pTrack->isEnabled(),
 305                                  (jlong)pTrack->GetTrackID(),
 306                                  name,
 307                                  pTrack->GetEncoding(),
 308                                  language,
 309                                  pTrack->GetNumChannels(),
 310                                  javaChannelMask,
 311                                  pTrack->GetSampleRate());
 312             
 313             pEnv->DeleteLocalRef(name);
 314             pEnv->DeleteLocalRef(language);
 315             pEnv->DeleteLocalRef(localPlayer);
 316             
 317             bSucceeded = !jenv.reportException();
 318         }
 319     }
 320     
 321     return bSucceeded;
 322 }
 323 
 324 bool CJavaPlayerEventDispatcher::SendVideoTrackEvent(CVideoTrack* pTrack)
 325 {
 326     bool bSucceeded = false;
 327     CJavaEnvironment jenv(m_PlayerVM);
 328     JNIEnv *pEnv = jenv.getEnvironment();
 329     if (pEnv) {
 330         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 331         if (localPlayer) {
 332             jstring name = pEnv->NewStringUTF(pTrack->GetName().c_str());
 333             pEnv->CallVoidMethod(localPlayer, m_SendVideoTrackEventMethod,
 334                                  (jboolean)pTrack->isEnabled(), (jlong)pTrack->GetTrackID(), name, pTrack->GetEncoding(),
 335                                  pTrack->GetWidth(), pTrack->GetHeight(),
 336                                  pTrack->GetFrameRate(), pTrack->HasAlphaChannel());
 337             pEnv->DeleteLocalRef(name);
 338             pEnv->DeleteLocalRef(localPlayer);
 339 
 340             bSucceeded = !jenv.reportException();
 341         }
 342     }
 343     
 344     return bSucceeded;
 345 }
 346 
 347 bool CJavaPlayerEventDispatcher::SendSubtitleTrackEvent(CSubtitleTrack* pTrack)
 348 {
 349     bool bSucceeded = false;
 350     CJavaEnvironment jenv(m_PlayerVM);
 351     JNIEnv *pEnv = jenv.getEnvironment();
 352     if (pEnv) {
 353         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 354         if (localPlayer) {
 355             jstring name = pEnv->NewStringUTF(pTrack->GetName().c_str());
 356             jstring language = pEnv->NewStringUTF(pTrack->GetLanguage().c_str());
 357             
 358             pEnv->CallVoidMethod(localPlayer, m_SendSubtitleTrackEventMethod,
 359                                  (jboolean)pTrack->isEnabled(), (jlong)pTrack->GetTrackID(),
 360                                  name, pTrack->GetEncoding(), language);
 361             pEnv->DeleteLocalRef(name);
 362             pEnv->DeleteLocalRef(language);
 363             pEnv->DeleteLocalRef(localPlayer);
 364 
 365             bSucceeded = !jenv.reportException();
 366         }
 367     }
 368     
 369     return bSucceeded;
 370 }
 371 
 372 bool CJavaPlayerEventDispatcher::SendMarkerEvent(string name, double time)
 373 {
 374     bool bSucceeded = false;
 375     CJavaEnvironment jenv(m_PlayerVM);
 376     JNIEnv *pEnv = jenv.getEnvironment();
 377     if (pEnv) {
 378         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 379         if (localPlayer) {
 380             jobject jname = pEnv->NewStringUTF(name.c_str());
 381             pEnv->CallVoidMethod(localPlayer, m_SendMarkerEventMethod,
 382                                  jname, time);
 383             pEnv->DeleteLocalRef(jname);
 384             pEnv->DeleteLocalRef(localPlayer);
 385 
 386             bSucceeded = !jenv.reportException();
 387         }
 388     }
 389     
 390     return bSucceeded;
 391 }
 392 
 393 bool CJavaPlayerEventDispatcher::SendBufferProgressEvent(double clipDuration, int64_t start, int64_t stop, int64_t position)
 394 {
 395     bool bSucceeded = false;
 396     CJavaEnvironment jenv(m_PlayerVM);
 397     JNIEnv *pEnv = jenv.getEnvironment();
 398     if (pEnv) {
 399         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 400         if (localPlayer) {
 401             pEnv->CallVoidMethod(localPlayer, m_SendBufferProgressEventMethod, clipDuration, start, stop, position);
 402             pEnv->DeleteLocalRef(localPlayer);
 403             
 404             bSucceeded = !jenv.reportException();
 405         }
 406     }
 407     
 408     return bSucceeded;
 409 }
 410 
 411 bool CJavaPlayerEventDispatcher::SendDurationUpdateEvent(double time)
 412 {
 413     bool bSucceeded = false;
 414     CJavaEnvironment jenv(m_PlayerVM);
 415     JNIEnv *pEnv = jenv.getEnvironment();
 416     if (pEnv) {
 417         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 418         if (localPlayer) {
 419             pEnv->CallVoidMethod(localPlayer, m_SendDurationUpdateEventMethod,
 420                                  (jdouble)time);
 421             pEnv->DeleteLocalRef(localPlayer);
 422             
 423             bSucceeded = !jenv.reportException();
 424         }
 425     }
 426     
 427     return bSucceeded;
 428 }
 429 
 430 bool CJavaPlayerEventDispatcher::SendAudioSpectrumEvent(double time, double duration)
 431 {
 432     bool bSucceeded = false;
 433     CJavaEnvironment jenv(m_PlayerVM);
 434     JNIEnv *pEnv = jenv.getEnvironment();
 435     if (pEnv) {
 436         jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
 437         if (localPlayer) {
 438             pEnv->CallVoidMethod(localPlayer, m_SendAudioSpectrumEventMethod, time, duration);
 439             pEnv->DeleteLocalRef(localPlayer);
 440             
 441             bSucceeded = !jenv.reportException();
 442         }
 443     }
 444     
 445     return bSucceeded;
 446 }
 447 
 448 /******************************************************************************************
 449  * Creates any object with any arguments
 450  ******************************************************************************************/
 451 jobject CJavaPlayerEventDispatcher::CreateObject(JNIEnv *env, jmethodID *cid,
 452                                                  const char* class_name, const char* signature,
 453                                                  jvalue* value)
 454 {
 455     jclass  classe;
 456     jobject result;
 457 
 458     classe = env->FindClass(class_name);
 459     if( classe == NULL )
 460         return NULL; /* can't find/load the class, exception thrown */
 461 
 462     if( *cid == NULL)
 463     {
 464         *cid = env->GetMethodID(classe, "<init>", signature);
 465         if( *cid == NULL )
 466         {
 467             env->DeleteLocalRef(classe);
 468             return NULL; /* can't find/get the method, exception thrown */
 469         }
 470     }
 471 
 472     result = env->NewObjectA(classe, *cid, value);
 473 
 474     env->DeleteLocalRef(classe);
 475     return result;
 476 }
 477 
 478 jobject CJavaPlayerEventDispatcher::CreateBoolean(JNIEnv *env, jboolean boolean_value)
 479 {
 480     static jmethodID cid = NULL;
 481     jvalue value;
 482 
 483     value.z = boolean_value;
 484 
 485     return CreateObject(env, &cid, "java/lang/Boolean", "(Z)V", &value);
 486 }
 487 
 488 jobject CJavaPlayerEventDispatcher::CreateInteger(JNIEnv *env, jint int_value)
 489 {
 490     static jmethodID cid = NULL;
 491     jvalue value;
 492 
 493     value.i = int_value;
 494 
 495     return CreateObject(env, &cid, "java/lang/Integer", "(I)V", &value);
 496 }
 497 
 498 jobject CJavaPlayerEventDispatcher::CreateLong(JNIEnv *env, jlong long_value)
 499 {
 500     static jmethodID cid = NULL;
 501     jvalue value;
 502 
 503     value.j = long_value;
 504 
 505     return CreateObject(env, &cid, "java/lang/Long", "(J)V", &value);
 506 }
 507 
 508 jobject CJavaPlayerEventDispatcher::CreateDouble(JNIEnv *env, jdouble double_value)
 509 {
 510     static jmethodID cid = NULL;
 511     jvalue value;
 512 
 513     value.d = double_value;
 514 
 515     return CreateObject(env, &cid, "java/lang/Double", "(D)V", &value);
 516 }
 517 
 518 jobject CJavaPlayerEventDispatcher::CreateDuration(JNIEnv *env, jlong duration)
 519 {
 520     static jmethodID constructorID = NULL;
 521     // We receive duration in nanoseconds, but javafx.util.Duration needs in milliseconds
 522     jdouble millis = duration/1000000.0;
 523 
 524     jclass durationClass = env->FindClass("javafx/util/Duration");
 525     if (durationClass == NULL)
 526         return NULL; /* can't find/load the class, exception thrown */
 527 
 528     if (constructorID == NULL)
 529     {
 530         constructorID = env->GetMethodID(durationClass, "<init>", "(D)V");
 531         if( constructorID == NULL )
 532         {
 533             env->DeleteLocalRef(durationClass);
 534             return NULL; /* can't find/get the method, exception thrown */
 535         }
 536     }
 537 
 538     jobject result = env->NewObject(durationClass, constructorID, millis);
 539 
 540     env->DeleteLocalRef(durationClass);
 541 
 542     return result;
 543 }