libvlcjni.c 38.4 KB
Newer Older
Rafaël Carré's avatar
Rafaël Carré committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*****************************************************************************
 * libvlcjni.c
 *****************************************************************************
 * Copyright © 2010-2012 VLC authors and VideoLAN
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

21
22
#include <dirent.h>
#include <errno.h>
23
#include <string.h>
24
#include <pthread.h>
25
#include <sys/stat.h>
26
#include <sys/types.h>
27
#include <unistd.h>
28

29
#include <vlc/vlc.h>
30
31
#include <vlc_common.h>
#include <vlc_url.h>
Ludovic Fauvet's avatar
Ludovic Fauvet committed
32
#include <vlc_fourcc.h>
33

34
#include <jni.h>
35

36
#include <android/api-level.h>
37

38
#include "libvlcjni.h"
ivoire's avatar
ivoire committed
39
#include "aout.h"
40
#include "utils.h"
41
42

#define LOG_TAG "VLC/JNI/main"
43
44
#include "log.h"

45
46
47
48
#define AOUT_AUDIOTRACK      0
#define AOUT_AUDIOTRACK_JAVA 1
#define AOUT_OPENSLES        2

Edward Wang's avatar
Edward Wang committed
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
static jint getInt(JNIEnv *env, jobject thiz, const char* field) {
    jclass clazz = (*env)->GetObjectClass(env, thiz);
    jfieldID fieldMP = (*env)->GetFieldID(env, clazz,
                                          field, "I");
    return (*env)->GetIntField(env, thiz, fieldMP);
}
static void setInt(JNIEnv *env, jobject item, const char* field, jint value) {
    jclass cls;
    jfieldID fieldId;

    /* Get a reference to item's class */
    cls = (*env)->GetObjectClass(env, item);

    /* Look for the instance field s in cls */
    fieldId = (*env)->GetFieldID(env, cls, field, "I");
    if (fieldId == NULL)
        return;

    (*env)->SetIntField(env, item, fieldId, value);
}

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
static jlong getLong(JNIEnv *env, jobject thiz, const char* field) {
    jclass clazz = (*env)->GetObjectClass(env, thiz);
    jfieldID fieldMP = (*env)->GetFieldID(env, clazz,
                                          field, "J");
    return (*env)->GetLongField(env, thiz, fieldMP);
}
static void setLong(JNIEnv *env, jobject item, const char* field, jlong value) {
    jclass cls;
    jfieldID fieldId;

    /* Get a reference to item's class */
    cls = (*env)->GetObjectClass(env, item);

    /* Look for the instance field s in cls */
    fieldId = (*env)->GetFieldID(env, cls, field, "J");
    if (fieldId == NULL)
        return;

    (*env)->SetLongField(env, item, fieldId, value);
}

static void setFloat(JNIEnv *env, jobject item, const char* field, jfloat value) {
    jclass cls;
    jfieldID fieldId;

    /* Get a reference to item's class */
    cls = (*env)->GetObjectClass(env, item);

    /* Look for the instance field s in cls */
    fieldId = (*env)->GetFieldID(env, cls, field, "F");
    if (fieldId == NULL)
        return;

    (*env)->SetFloatField(env, item, fieldId, value);
}
static void setString(JNIEnv *env, jobject item, const char* field, const char* text) {
    jclass cls;
    jfieldID fieldId;
    jstring jstr;

    /* Get a reference to item's class */
    cls = (*env)->GetObjectClass(env, item);

    /* Look for the instance field s in cls */
    fieldId = (*env)->GetFieldID(env, cls, field, "Ljava/lang/String;");
    if (fieldId == NULL)
        return;

    /* Create a new string and overwrite the instance field */
    jstr = (*env)->NewStringUTF(env, text);
    if (jstr == NULL)
        return;
    (*env)->SetObjectField(env, item, fieldId, jstr);
}

125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
struct length_change_monitor {
    pthread_mutex_t doneMutex;
    pthread_cond_t doneCondVar;
    bool length_changed;
};

static void length_changed_callback(const libvlc_event_t *ev, void *data)
{
    struct length_change_monitor *monitor = data;
    pthread_mutex_lock(&monitor->doneMutex);
    monitor->length_changed = true;
    pthread_cond_signal(&monitor->doneCondVar);
    pthread_mutex_unlock(&monitor->doneMutex);
}

140
libvlc_media_t *new_media(jlong instance, JNIEnv *env, jobject thiz, jstring fileLocation, bool noOmx, bool noVideo)
141
{
142
143
    libvlc_instance_t *libvlc = (libvlc_instance_t*)instance;
    jboolean isCopy;
144
145
146
    const char *psz_location = (*env)->GetStringUTFChars(env, fileLocation, &isCopy);
    libvlc_media_t *p_md = libvlc_media_new_location(libvlc, psz_location);
    (*env)->ReleaseStringUTFChars(env, fileLocation, psz_location);
147
148
149
    if (!p_md)
        return NULL;

150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
    if (!noOmx) {
        jclass cls = (*env)->GetObjectClass(env, thiz);
        jmethodID methodId = (*env)->GetMethodID(env, cls, "useIOMX", "()Z");
        if ((*env)->CallBooleanMethod(env, thiz, methodId)) {
            /*
             * Set higher caching values if using iomx decoding, since some omx
             * decoders have a very high latency, and if the preroll data isn't
             * enough to make the decoder output a frame, the playback timing gets
             * started too soon, and every decoded frame appears to be too late.
             * On Nexus One, the decoder latency seems to be 25 input packets
             * for 320x170 H.264, a few packets less on higher resolutions.
             * On Nexus S, the decoder latency seems to be about 7 packets.
             */
            libvlc_media_add_option(p_md, ":file-caching=1500");
            libvlc_media_add_option(p_md, ":network-caching=1500");
            libvlc_media_add_option(p_md, ":codec=iomx,all");
        }
167
168
        if (noVideo)
            libvlc_media_add_option(p_md, ":no-video");
169
    }
170
    return p_md;
171
172
}

173
174
175
176
177
static libvlc_media_list_t *getMediaList(JNIEnv *env, jobject thiz)
{
    return (libvlc_media_list_t*)getLong(env, thiz, "mMediaListInstance");
}

Rafaël Carré's avatar
Rafaël Carré committed
178
static libvlc_media_player_t *getMediaPlayer(JNIEnv *env, jobject thiz)
179
{
180
181
182
183
184
185
    return (libvlc_media_player_t*)getLong(env, thiz, "mInternalMediaPlayerInstance");
}

static libvlc_media_list_player_t *getMediaListPlayer(JNIEnv *env, jobject thiz)
{
    return (libvlc_media_list_player_t*)getLong(env, thiz, "mMediaListPlayerInstance");
186
187
}

Rafaël Carré's avatar
Rafaël Carré committed
188
static void unsetMediaPlayer(JNIEnv *env, jobject thiz)
189
{
190
    setLong(env, thiz, "mInternalMediaPlayerInstance", (jlong)0);
Rafaël Carré's avatar
Rafaël Carré committed
191
192
193
194
}

static void releaseMediaPlayer(JNIEnv *env, jobject thiz)
{
195
196
    libvlc_media_list_player_t* p_mlp = getMediaListPlayer(env, thiz);
    if (p_mlp)
197
    {
198
199
200
201
        libvlc_media_list_player_stop(p_mlp);
        libvlc_media_list_player_release(p_mlp);
        /* libvlc_media_list_player_release frees the media player, so
         * we don't free it ourselves. */
Rafaël Carré's avatar
Rafaël Carré committed
202
        unsetMediaPlayer(env, thiz);
203
        setLong(env, thiz, "mInternalMediaPlayerInstance", 0);
204
205
206
    }
}

207
208
209
210
211
/* Pointer to the Java virtual machine
 * Note: It's okay to use a static variable for the VM pointer since there
 * can only be one instance of this shared library in a single VM
 */
JavaVM *myVm;
212

213
static jobject eventManagerInstance = NULL;
214

215
216
static pthread_mutex_t vout_android_lock;
static void *vout_android_surf = NULL;
217
static void *vout_android_gui = NULL;
218

219
void *jni_LockAndGetAndroidSurface() {
220
    pthread_mutex_lock(&vout_android_lock);
Ludovic Fauvet's avatar
Ludovic Fauvet committed
221
    return vout_android_surf;
222
223
}

224
void jni_UnlockAndroidSurface() {
225
226
227
    pthread_mutex_unlock(&vout_android_lock);
}

228
void jni_SetAndroidSurfaceSize(int width, int height)
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
{
    if (vout_android_gui == NULL)
        return;

    JNIEnv *p_env;

    (*myVm)->AttachCurrentThread (myVm, &p_env, NULL);
    jclass cls = (*p_env)->GetObjectClass (p_env, vout_android_gui);
    jmethodID methodId = (*p_env)->GetMethodID (p_env, cls, "setSurfaceSize", "(II)V");

    (*p_env)->CallVoidMethod (p_env, vout_android_gui, methodId, width, height);

    (*p_env)->DeleteLocalRef(p_env, cls);
    (*myVm)->DetachCurrentThread (myVm);
}

245
246
247
static void vlc_event_callback(const libvlc_event_t *ev, void *data)
{
    JNIEnv *env;
248
    JavaVM *myVm = data;
249

250
    bool isAttached = false;
251

252
    if (eventManagerInstance == NULL)
Sébastien Toque's avatar
Sébastien Toque committed
253
        return;
254

255
    int status = (*myVm)->GetEnv(myVm, (void**) &env, JNI_VERSION_1_2);
256
    if (status < 0) {
257
        LOGD("vlc_event_callback: failed to get JNI environment, "
258
259
260
261
             "assuming native thread");
        status = (*myVm)->AttachCurrentThread(myVm, &env, NULL);
        if (status < 0)
            return;
262
        isAttached = true;
263
264
    }

265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
    /* Creating the bundle in C allows us to subscribe to more events
     * and get better flexibility for each event. For example, we can
     * have totally different types of data for each event, instead of,
     * for example, only an integer and/or string.
     */
    jclass clsBundle = (*env)->FindClass(env, "android/os/Bundle");
    jmethodID clsCtor = (*env)->GetMethodID(env, clsBundle, "<init>", "()V" );
    jobject bundle = (*env)->NewObject(env, clsBundle, clsCtor);

    jmethodID putInt = (*env)->GetMethodID(env, clsBundle, "putInt", "(Ljava/lang/String;I)V" );
    jmethodID putString = (*env)->GetMethodID(env, clsBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V" );

    if(ev->type == libvlc_MediaPlayerVout) {
        /* For determining the vout/ES track change */
        jstring sData = (*env)->NewStringUTF(env, "data");
        (*env)->CallVoidMethod(env, bundle, putInt, sData, ev->u.media_player_vout.new_count);
        (*env)->DeleteLocalRef(env, sData);
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
    } else if(ev->type == libvlc_MediaListItemAdded ||
              ev->type == libvlc_MediaListItemDeleted ) {
        jstring item_uri = (*env)->NewStringUTF(env, "item_uri");
        jstring item_index = (*env)->NewStringUTF(env, "item_index");
        char* mrl = libvlc_media_get_mrl(
            ev->type == libvlc_MediaListItemAdded ?
            ev->u.media_list_item_added.item :
            ev->u.media_list_item_deleted.item
            );
        jstring item_uri_value = (*env)->NewStringUTF(env, mrl);
        jint item_index_value;
        if(ev->type == libvlc_MediaListItemAdded)
            item_index_value = ev->u.media_list_item_added.index;
        else
            item_index_value = ev->u.media_list_item_deleted.index;

        (*env)->CallVoidMethod(env, bundle, putString, item_uri, item_uri_value);
        (*env)->CallVoidMethod(env, bundle, putInt, item_index, item_index_value);

        (*env)->DeleteLocalRef(env, item_uri);
        (*env)->DeleteLocalRef(env, item_uri_value);
        (*env)->DeleteLocalRef(env, item_index);
        free(mrl);
305
306
    }

307
308
309
310
    /* Get the object class */
    jclass cls = (*env)->GetObjectClass(env, eventManagerInstance);
    if (!cls) {
        LOGE("EventManager: failed to get class reference");
311
        goto end;
312
313
314
    }

    /* Find the callback ID */
315
    jmethodID methodID = (*env)->GetMethodID(env, cls, "callback", "(ILandroid/os/Bundle;)V");
316
    if (methodID) {
317
        (*env)->CallVoidMethod(env, eventManagerInstance, methodID, ev->type, bundle);
318
    } else {
319
320
321
        LOGE("EventManager: failed to get the callback method");
    }

322
323
324
end:
    if (isAttached)
        (*myVm)->DetachCurrentThread(myVm);
325
326
}

327
jint JNI_OnLoad(JavaVM *vm, void *reserved)
328
{
329
330
331
    // Keep a reference on the Java VM.
    myVm = vm;

332
333
    pthread_mutex_init(&vout_android_lock, NULL);

334
    LOGD("JNI interface loaded.");
335
    return JNI_VERSION_1_2;
336
337
}

338
339
340
341
void JNI_OnUnload(JavaVM* vm, void* reserved) {
    pthread_mutex_destroy(&vout_android_lock);
}

Sébastien Toque's avatar
Sébastien Toque committed
342
void Java_org_videolan_vlc_LibVLC_attachSurface(JNIEnv *env, jobject thiz, jobject surf, jobject gui, jint width, jint height) {
343
344
345
346
347
348
349
    jclass clz;
    jfieldID fid;

    pthread_mutex_lock(&vout_android_lock);
    clz = (*env)->GetObjectClass(env, surf);
    fid = (*env)->GetFieldID(env, clz, "mSurface", "I");
    if (fid == NULL) {
350
        jthrowable exp = (*env)->ExceptionOccurred(env);
351
352
353
354
355
356
357
358
        if (exp) {
            (*env)->DeleteLocalRef(env, exp);
            (*env)->ExceptionClear(env);
        }
        fid = (*env)->GetFieldID(env, clz, "mNativeSurface", "I");
    }
    vout_android_surf = (void*)(*env)->GetIntField(env, surf, fid);
    (*env)->DeleteLocalRef(env, clz);
359
360

    vout_android_gui = (*env)->NewGlobalRef(env, gui);
361
362
363
    pthread_mutex_unlock(&vout_android_lock);
}

Sébastien Toque's avatar
Sébastien Toque committed
364
void Java_org_videolan_vlc_LibVLC_detachSurface(JNIEnv *env, jobject thiz) {
365
366
    pthread_mutex_lock(&vout_android_lock);
    vout_android_surf = NULL;
367
368
369
    if (vout_android_gui != NULL)
        (*env)->DeleteGlobalRef(env, vout_android_gui);
    vout_android_gui = NULL;
370
371
    pthread_mutex_unlock(&vout_android_lock);
}
372

373
374
static void debug_log(void *data, int level, const char *fmt, va_list ap)
{
Rafaël Carré's avatar
Rafaël Carré committed
375
    bool *verbose = data;
376
377
378
379
380
381
382
383
384
385
386
387
388

    static const uint8_t priority[5] = {
        [LIBVLC_DEBUG]   = ANDROID_LOG_DEBUG,
        [1 /* ??? */]    = ANDROID_LOG_DEBUG,
        [LIBVLC_NOTICE]  = ANDROID_LOG_INFO,
        [LIBVLC_WARNING] = ANDROID_LOG_WARN,
        [LIBVLC_ERROR]   = ANDROID_LOG_ERROR,
    };

    int prio = ANDROID_LOG_DEBUG;
    if (level >= LIBVLC_DEBUG && level <= LIBVLC_ERROR)
        prio = priority[level];

Ludovic Fauvet's avatar
Ludovic Fauvet committed
389
    if (!*verbose && prio < ANDROID_LOG_ERROR)
390
391
        return;

392
393
394
395
    __android_log_vprint(prio, "VLC", fmt, ap);
}

static libvlc_log_subscriber_t debug_subscriber;
Rafaël Carré's avatar
Rafaël Carré committed
396
static bool verbosity;
397

398
399
void Java_org_videolan_vlc_LibVLC_changeVerbosity(JNIEnv *env, jobject thiz, jboolean verbose)
{
Rafaël Carré's avatar
Rafaël Carré committed
400
    verbosity = verbose;
401
    libvlc_log_unsubscribe(&debug_subscriber);
Rafaël Carré's avatar
Rafaël Carré committed
402
    libvlc_log_subscribe(&debug_subscriber, debug_log, &verbosity);
403
404
405
}

void Java_org_videolan_vlc_LibVLC_nativeInit(JNIEnv *env, jobject thiz, jboolean verbose)
406
{
407
408
409
410
411
    //only use OpenSLES if java side says we can
    jclass cls = (*env)->GetObjectClass(env, thiz);
    jmethodID methodId = (*env)->GetMethodID(env, cls, "getAout", "()I");
    bool use_opensles = (*env)->CallIntMethod(env, thiz, methodId) == AOUT_OPENSLES;

412
413
414
    methodId = (*env)->GetMethodID(env, cls, "timeStretchingEnabled", "()Z");
    bool enable_time_stretch = (*env)->CallBooleanMethod(env, thiz, methodId);

Rafaël Carré's avatar
Rafaël Carré committed
415
416
    verbosity = verbose;
    libvlc_log_subscribe(&debug_subscriber, debug_log, &verbosity);
417

418
    /* Don't add any invalid options, otherwise it causes LibVLC to crash */
419
    const char *argv[] = {
420
        "-I", "dummy",
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
421
422
423
        "--no-osd",
        "--no-video-title-show",
        "--no-stats",
424
425
        "--no-plugins-cache",
        "--no-drop-late-frames",
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
426
        "--avcodec-fast",
427
        enable_time_stretch ? "--audio-time-stretch" : "--no-audio-time-stretch",
428
        use_opensles ? "--aout=opensles" : "--aout=android_audiotrack",
429
    };
430
    libvlc_instance_t *instance = libvlc_new(sizeof(argv) / sizeof(*argv), argv);
431

Edward Wang's avatar
Edward Wang committed
432
    setLong(env, thiz, "mLibVlcInstance", (jlong) instance);
433

434
435
    if (!instance)
    {
Sébastien Toque's avatar
Sébastien Toque committed
436
        jclass exc = (*env)->FindClass(env, "org/videolan/vlc/LibVlcException");
437
438
439
440
        (*env)->ThrowNew(env, exc, "Unable to instantiate LibVLC");
    }

    LOGI("LibVLC initialized: %p", instance);
441
442
443
444
445
446
447
448
449

    /* Initialize media list (a.k.a. playlist/history) */
    libvlc_media_list_t* pointer = libvlc_media_list_new( instance );
    if(!pointer) {
        jclass exc = (*env)->FindClass(env, "org/videolan/vlc/LibVlcException");
        (*env)->ThrowNew(env, exc, "Unable to create LibVLC media list");
        return;
    }

450
451
452
453
454
455
456
457
458
    /* Connect the event manager */
    libvlc_event_manager_t *ev = libvlc_media_list_event_manager(pointer);
    static const libvlc_event_type_t mp_events[] = {
        libvlc_MediaListItemAdded,
        libvlc_MediaListItemDeleted,
    };
    for(int i = 0; i < (sizeof(mp_events) / sizeof(*mp_events)); i++)
        libvlc_event_attach(ev, mp_events[i], vlc_event_callback, myVm);

459
    setLong(env, thiz, "mMediaListInstance", (jlong)pointer);
460
461
}

462
463
464
465
466
467
jstring Java_org_videolan_vlc_LibVLC_nativeToURI(JNIEnv *env, jobject thiz, jstring path)
{
    jboolean isCopy;
    /* Get C string */
    const char* psz_path = (*env)->GetStringUTFChars(env, path, &isCopy);
    /* Convert the path to URI */
468
469
470
471
472
    char* psz_location;
    if(unlikely( strstr( psz_path, "://" ) ))
        psz_location = strdup(psz_path);
    else
        psz_location = vlc_path2uri(psz_path, "file");
473
474
475
476
477
478
479
    /* Box into jstring */
    jstring t = (*env)->NewStringUTF(env, psz_location);
    /* Clean up */
    (*env)->ReleaseStringUTFChars(env, path, psz_path);
    free(psz_location);
    return t;
}
480

Sébastien Toque's avatar
Sébastien Toque committed
481
void Java_org_videolan_vlc_LibVLC_nativeDestroy(JNIEnv *env, jobject thiz)
482
{
483
    releaseMediaPlayer(env, thiz);
Edward Wang's avatar
Edward Wang committed
484
    jlong libVlcInstance = getLong(env, thiz, "mLibVlcInstance");
485
486
487
488
489
    if (!libVlcInstance)
        return; // Already destroyed

    libvlc_instance_t *instance = (libvlc_instance_t*) libVlcInstance;
    libvlc_release(instance);
490
    libvlc_log_unsubscribe(&debug_subscriber);
491

Edward Wang's avatar
Edward Wang committed
492
    setLong(env, thiz, "mLibVlcInstance", 0);
493
494
}

Sébastien Toque's avatar
Sébastien Toque committed
495
void Java_org_videolan_vlc_LibVLC_detachEventManager(JNIEnv *env, jobject thiz)
496
497
498
499
500
501
502
{
    if (eventManagerInstance != NULL) {
        (*env)->DeleteGlobalRef(env, eventManagerInstance);
        eventManagerInstance = NULL;
    }
}

Sébastien Toque's avatar
Sébastien Toque committed
503
void Java_org_videolan_vlc_LibVLC_setEventManager(JNIEnv *env, jobject thiz, jobject eventManager)
504
{
505
506
507
508
509
    if (eventManagerInstance != NULL) {
        (*env)->DeleteGlobalRef(env, eventManagerInstance);
        eventManagerInstance = NULL;
    }

510
511
512
513
514
515
    jclass cls = (*env)->GetObjectClass(env, eventManager);
    if (!cls) {
        LOGE("setEventManager: failed to get class reference");
        return;
    }

516
    jmethodID methodID = (*env)->GetMethodID(env, cls, "callback", "(ILandroid/os/Bundle;)V");
517
518
519
520
    if (!methodID) {
        LOGE("setEventManager: failed to get the callback method");
        return;
    }
521

522
523
524
    eventManagerInstance = (*env)->NewGlobalRef(env, eventManager);
}

Sébastien Toque's avatar
Sébastien Toque committed
525
jobjectArray Java_org_videolan_vlc_LibVLC_readMediaMeta(JNIEnv *env,
526
                                                        jobject thiz, jlong instance, jstring mrl)
Rafaël Carré's avatar
Rafaël Carré committed
527
528
529
530
531
{
    jobjectArray array = (*env)->NewObjectArray(env, 8,
            (*env)->FindClass(env, "java/lang/String"),
            (*env)->NewStringUTF(env, ""));

532
    libvlc_media_t *m = new_media(instance, env, thiz, mrl, false, false);
533
534
    if (!m)
    {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
535
        LOGE("readMediaMeta: Could not create the media!");
536
        return array;
537
538
539
    }

    libvlc_media_parse(m);
Rafaël Carré's avatar
Rafaël Carré committed
540

541
    static const char str[][7] = {
Rafaël Carré's avatar
Rafaël Carré committed
542
543
        "artist", "album", "title", "genre",
    };
544
    static const libvlc_meta_t meta_id[] = {
Rafaël Carré's avatar
Rafaël Carré committed
545
546
547
548
549
        libvlc_meta_Artist,
        libvlc_meta_Album,
        libvlc_meta_Title,
        libvlc_meta_Genre,
    };
550
    for (int i=0; i < sizeof(str) / sizeof(*str); i++) {
Rafaël Carré's avatar
Rafaël Carré committed
551
552
        char *meta = libvlc_media_get_meta(m, meta_id[i]);
        if (!meta)
553
            meta = strdup("");
Rafaël Carré's avatar
Rafaël Carré committed
554
555
556
557
558
559
560
561
562
563
564
565
566

        jstring k = (*env)->NewStringUTF(env, str[i]);
        (*env)->SetObjectArrayElement(env, array, 2*i, k);
        jstring v = (*env)->NewStringUTF(env, meta);
        (*env)->SetObjectArrayElement(env, array, 2*i+1, v);

        free(meta);
   }

   libvlc_media_release(m);
   return array;
}

567
568
static void create_player_and_play(JNIEnv* env, jobject thiz,
                                   jlong instance, int position) {
569
570
571
    /* Release previous media player, if any */
    releaseMediaPlayer(env, thiz);

572
573
    libvlc_media_list_t* p_mlist = getMediaList(env, thiz);

574
    /* Create a media player playing environment */
575
    libvlc_media_list_player_t* p_mlp = libvlc_media_list_player_new((libvlc_instance_t*)instance);
576
577
    libvlc_media_player_t *mp = libvlc_media_player_new((libvlc_instance_t*)instance);

578
579
    jobject myJavaLibVLC = (*env)->NewGlobalRef(env, thiz);

580
581
582
583
    //if AOUT_AUDIOTRACK_JAVA, we use amem
    jclass cls = (*env)->GetObjectClass(env, thiz);
    jmethodID methodId = (*env)->GetMethodID(env, cls, "getAout", "()I");
    if ( (*env)->CallIntMethod(env, thiz, methodId) == AOUT_AUDIOTRACK_JAVA )
584
    {
Sébastien Toque's avatar
Sébastien Toque committed
585
586
587
        libvlc_audio_set_callbacks(mp, aout_play, NULL, NULL, NULL, NULL,
                                   (void*) myJavaLibVLC);
        libvlc_audio_set_format_callbacks(mp, aout_open, aout_close);
588
589
    }

590
591
    /* Connect the event manager */
    libvlc_event_manager_t *ev = libvlc_media_player_event_manager(mp);
592
593
594
595
596
    static const libvlc_event_type_t mp_events[] = {
        libvlc_MediaPlayerPlaying,
        libvlc_MediaPlayerPaused,
        libvlc_MediaPlayerEndReached,
        libvlc_MediaPlayerStopped,
597
        libvlc_MediaPlayerVout,
598
    };
599
    for(int i = 0; i < (sizeof(mp_events) / sizeof(*mp_events)); i++)
600
601
        libvlc_event_attach(ev, mp_events[i], vlc_event_callback, myVm);

602
603
604
    libvlc_media_list_player_set_media_list(p_mlp, p_mlist);
    libvlc_media_list_player_set_media_player(p_mlp, mp);

605
    /* Keep a pointer to this media player */
606
607
    setLong(env, thiz, "mMediaListPlayerInstance", (jlong)p_mlp);
    setLong(env, thiz, "mInternalMediaPlayerInstance", (jlong)mp);
608

609
    libvlc_media_list_player_play_item_at_index(p_mlp, position);
610
611
}

612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
void Java_org_videolan_vlc_LibVLC_readMedia(JNIEnv *env, jobject thiz,
                                            jlong instance, jstring mrl, jboolean novideo)
{
    /* Create a new item */
    libvlc_media_t *m = new_media(instance, env, thiz, mrl, false, novideo);
    if (!m)
    {
        LOGE("readMedia: Could not create the media!");
        return;
    }

    libvlc_media_list_t* p_mlist = getMediaList(env, thiz);

    libvlc_media_list_lock(p_mlist);
    libvlc_media_list_add_media(p_mlist, m);
    int position = libvlc_media_list_index_of_item(p_mlist, m);
    libvlc_media_list_unlock(p_mlist);

    /* No need to keep the media now */
    libvlc_media_release(m);

    create_player_and_play(env, thiz, instance, position);
}

void Java_org_videolan_vlc_LibVLC_playIndex(JNIEnv *env, jobject thiz,
                                            jlong instance, int position) {
    create_player_and_play(env, thiz, instance, position);
}

void Java_org_videolan_vlc_LibVLC_getMediaListItems(
                JNIEnv *env, jobject thiz, jobject arrayList) {
    jclass arrayClass = (*env)->FindClass(env, "java/util/ArrayList");
    jmethodID methodID = (*env)->GetMethodID(env, arrayClass, "add", "(Ljava/lang/Object;)Z");
    jstring str;

    libvlc_media_list_t* p_mlist = getMediaList(env, thiz);
    libvlc_media_list_lock( p_mlist );
    for(int i = 0; i < libvlc_media_list_count( p_mlist ); i++) {
        char* mrl = libvlc_media_get_mrl( libvlc_media_list_item_at_index( p_mlist, i ) );
        str = (*env)->NewStringUTF(env, mrl);
        (*env)->CallBooleanMethod(env, arrayList, methodID, str);
        (*env)->DeleteLocalRef(env, str);
        free(mrl);
    }
    libvlc_media_list_unlock( p_mlist );
}

Edward Wang's avatar
Edward Wang committed
659
660
661
662
663
664
665
666
667
668
669
670
671
672
jfloat Java_org_videolan_vlc_LibVLC_getRate(JNIEnv *env, jobject thiz) {
    libvlc_media_player_t* mp = getMediaPlayer(env, thiz);
    if(mp)
        return libvlc_media_player_get_rate(mp);
    else
        return 1.00;
}

void Java_org_videolan_vlc_LibVLC_setRate(JNIEnv *env, jobject thiz, jfloat rate) {
    libvlc_media_player_t* mp = getMediaPlayer(env, thiz);
    if(mp)
        libvlc_media_player_set_rate(mp, rate);
}

Sébastien Toque's avatar
Sébastien Toque committed
673
jboolean Java_org_videolan_vlc_LibVLC_hasVideoTrack(JNIEnv *env, jobject thiz,
674
                                                    jint i_instance, jstring fileLocation)
675
{
676
    /* Create a new item and assign it to the media player. */
677
    libvlc_media_t *p_m = new_media(i_instance, env, thiz, fileLocation, false, false);
678
679
    if (p_m == NULL)
    {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
680
        LOGE("Could not create the media!");
Rafaël Carré's avatar
Rafaël Carré committed
681
        return JNI_FALSE;
682
683
684
685
686
    }

    /* Get the tracks information of the media. */
    libvlc_media_track_info_t *p_tracks;
    libvlc_media_parse(p_m);
687

688
689
690
691
692
693
694
695
696
697
    libvlc_media_player_t* p_mp = libvlc_media_player_new_from_media(p_m);

    struct length_change_monitor* monitor;
    monitor = malloc(sizeof(struct length_change_monitor));
    if (!monitor) return 0;

    /* Initialize pthread variables. */
    pthread_mutex_init(&monitor->doneMutex, NULL);
    pthread_cond_init(&monitor->doneCondVar, NULL);
    monitor->length_changed = false;
Rafaël Carré's avatar
Rafaël Carré committed
698

699
700
701
702
703
    libvlc_event_manager_t *ev = libvlc_media_player_event_manager(p_mp);
    libvlc_event_attach(ev, libvlc_MediaPlayerLengthChanged, length_changed_callback, monitor);
    libvlc_media_player_play( p_mp );

    pthread_mutex_lock(&monitor->doneMutex);
704
705
706
707
708
709
710
711
712

    struct timespec deadline;
    clock_gettime(CLOCK_REALTIME, &deadline);
    deadline.tv_sec += 2; /* If "VLC can't open the file", return */
    int mp_alive = 1;
    while( !monitor->length_changed && mp_alive ) {
        pthread_cond_timedwait(&monitor->doneCondVar, &monitor->doneMutex, &deadline);
        mp_alive = libvlc_media_player_will_play(p_mp);
    }
713
714
    pthread_mutex_unlock(&monitor->doneMutex);

715
716
717
718
719
    int i_nbTracks;
    if( mp_alive )
        i_nbTracks = libvlc_video_get_track_count(p_mp);
    else
        i_nbTracks = -1;
720
721
    LOGI("Number of video tracks: %d",i_nbTracks);

Edward Wang's avatar
Edward Wang committed
722
    libvlc_event_detach(ev, libvlc_MediaPlayerLengthChanged, length_changed_callback, monitor);
723
724
    libvlc_media_player_stop(p_mp);
    libvlc_media_player_release(p_mp);
Rafaël Carré's avatar
Rafaël Carré committed
725
726
    libvlc_media_release(p_m);

Edward Wang's avatar
Edward Wang committed
727
728
729
730
    pthread_mutex_destroy(&monitor->doneMutex);
    pthread_cond_destroy(&monitor->doneCondVar);
    free(monitor);

731
732
    if(i_nbTracks > 0)
        return JNI_TRUE;
733
734
    else if(i_nbTracks < 0)
        (*env)->ThrowNew(env, (*env)->FindClass(env, "java/io/IOException"), "VLC can't open the file");
735
736
    else
        return JNI_FALSE;
737
738
}

739
jobjectArray read_track_info_internal(JNIEnv *env, jobject thiz, libvlc_media_t* p_m)
740
741
742
743
744
{
    /* get java class */
    jclass cls = (*env)->FindClass( env, "org/videolan/vlc/TrackInfo" );
    if ( !cls )
    {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
745
        LOGE("Failed to load class (org/videolan/vlc/TrackInfo)" );
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
        return NULL;
    }

    /* get java class contructor */
    jmethodID clsCtor = (*env)->GetMethodID( env, cls, "<init>", "()V" );
    if ( !clsCtor )
    {
        LOGE("Failed to find class constructor (org/videolan/vlc/TrackInfo)" );
        return NULL;
    }

    /* Get the tracks information of the media. */
    libvlc_media_track_info_t *p_tracks;

    int i_nbTracks = libvlc_media_get_tracks_info(p_m, &p_tracks);
761
    jobjectArray array = (*env)->NewObjectArray(env, i_nbTracks + 1, cls, NULL);
762
763
764
765

    unsigned i;
    if (array != NULL)
    {
766
        for (i = 0; i <= i_nbTracks; ++i)
767
768
769
770
771
        {
            jobject item = (*env)->NewObject(env, cls, clsCtor);
            if (item == NULL)
                continue;
            (*env)->SetObjectArrayElement(env, array, i, item);
772
773
774
775
776
777
778
779
780
781

            // use last track for metadata
            if (i == i_nbTracks)
            {
                setInt(env, item, "Type", 3 /* TYPE_META */);
                setLong(env, item, "Length", libvlc_media_get_duration(p_m));
                setString(env, item, "Title", libvlc_media_get_meta(p_m, libvlc_meta_Title));
                setString(env, item, "Artist", libvlc_media_get_meta(p_m, libvlc_meta_Artist));
                setString(env, item, "Album", libvlc_media_get_meta(p_m, libvlc_meta_Album));
                setString(env, item, "Genre", libvlc_media_get_meta(p_m, libvlc_meta_Genre));
782
                setString(env, item, "ArtworkURL", libvlc_media_get_meta(p_m, libvlc_meta_ArtworkURL));
783
784
785
                continue;
            }

786
787
788
            setInt(env, item, "Id", p_tracks[i].i_id);
            setInt(env, item, "Type", p_tracks[i].i_type);
            setString(env, item, "Codec", (const char*)vlc_fourcc_GetDescription(0,p_tracks[i].i_codec));
789
            setString(env, item, "Language", p_tracks[i].psz_language);
790
791
792
793
794

            if (p_tracks[i].i_type == libvlc_track_video)
            {
                setInt(env, item, "Height", p_tracks[i].u.video.i_height);
                setInt(env, item, "Width", p_tracks[i].u.video.i_width);
795
                setFloat(env, item, "Framerate", p_tracks[i].u.video.f_frame_rate);
796
797
798
799
800
801
802
803
804
            }
            if (p_tracks[i].i_type == libvlc_track_audio)
            {
                setInt(env, item, "Channels", p_tracks[i].u.audio.i_channels);
                setInt(env, item, "Samplerate", p_tracks[i].u.audio.i_rate);
            }
        }
    }

805
    libvlc_media_tracks_info_release(p_tracks, i_nbTracks);
806
807
808
    return array;
}

809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839

jobjectArray Java_org_videolan_vlc_LibVLC_readTracksInfo(JNIEnv *env, jobject thiz,
                                                         jlong instance, jstring mrl)
{
    /* Create a new item and assign it to the media player. */
    libvlc_media_t *p_m = new_media(instance, env, thiz, mrl, false, false);
    if (p_m == NULL)
    {
        LOGE("Could not create the media!");
        return NULL;
    }

    libvlc_media_parse(p_m);
    jobjectArray jar = read_track_info_internal(env, thiz, p_m);
    libvlc_media_release(p_m);
    return jar;
}


jobjectArray Java_org_videolan_vlc_LibVLC_readTracksInfoPosition(JNIEnv *env, jobject thiz,
                                                         jint position)
{
    libvlc_media_list_t* p_mlist = getMediaList(env, thiz);
    libvlc_media_t *p_m = libvlc_media_list_item_at_index( p_mlist, position );
    if (p_m == NULL) {
        LOGE("Could not load get media @ position %d!", position);
        return NULL;
    } else
        return read_track_info_internal(env, thiz, p_m);
}

840
841
jlong Java_org_videolan_vlc_LibVLC_getLengthFromLocation(JNIEnv *env, jobject thiz,
                                                     jint i_instance, jstring fileLocation)
842
{
843
844
845
846
847
    jlong length = 0;
    struct length_change_monitor *monitor;
    monitor = malloc(sizeof(*monitor));
    if (!monitor)
        return 0;
848
849

    /* Initialize pthread variables. */
850
851
852
    pthread_mutex_init(&monitor->doneMutex, NULL);
    pthread_cond_init(&monitor->doneCondVar, NULL);
    monitor->length_changed = false;
853

854
    /* Create a new item and assign it to the media player. */
855
    libvlc_media_t *m = new_media(i_instance, env, thiz, fileLocation, false, false);
856
    if (m == NULL)
857
    {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
858
        LOGE("Could not create the media to play!");
859
        goto end;
860
861
    }

862
    /* Create a media player playing environment */
863
    libvlc_media_player_t *mp = libvlc_media_player_new_from_media (m);
864
    libvlc_event_manager_t *ev = libvlc_media_player_event_manager(mp);
865
    libvlc_event_attach(ev, libvlc_MediaPlayerLengthChanged, length_changed_callback, monitor);
866
867
    libvlc_media_release (m);
    libvlc_media_player_play( mp );
868
869
870
871
872
873
874

    pthread_mutex_lock(&monitor->doneMutex);
    while (!monitor->length_changed)
        pthread_cond_wait(&monitor->doneCondVar, &monitor->doneMutex);
    pthread_mutex_unlock(&monitor->doneMutex);

    length = libvlc_media_player_get_length( mp );
875
    libvlc_media_player_stop( mp );
876
    libvlc_media_player_release( mp );
877

878
879
880
881
end:
    pthread_mutex_destroy(&monitor->doneMutex);
    pthread_cond_destroy(&monitor->doneCondVar);
    free(monitor);
882
883

    return length;
884
885
}

Sébastien Toque's avatar
Sébastien Toque committed
886
jboolean Java_org_videolan_vlc_LibVLC_hasMediaPlayer(JNIEnv *env, jobject thiz)
887
{
888
    return !!getMediaListPlayer(env, thiz);
889
890
}

Sébastien Toque's avatar
Sébastien Toque committed
891
jboolean Java_org_videolan_vlc_LibVLC_isPlaying(JNIEnv *env, jobject thiz)
892
{
893
    libvlc_media_list_player_t *mp = getMediaListPlayer(env, thiz);
894
    if (mp)
895
896
897
        return !!libvlc_media_list_player_is_playing(mp);
    else
        return 0;
898
899
}

Sébastien Toque's avatar
Sébastien Toque committed
900
jboolean Java_org_videolan_vlc_LibVLC_isSeekable(JNIEnv *env, jobject thiz)
901
{
902
903
904
    libvlc_media_player_t *mp = getMediaPlayer(env, thiz);
    if (mp)
        return !!libvlc_media_player_is_seekable(mp);
905
    return 0;
906
907
}

Sébastien Toque's avatar
Sébastien Toque committed
908
void Java_org_videolan_vlc_LibVLC_play(JNIEnv *env, jobject thiz)
909
{
910
    libvlc_media_list_player_t *mp = getMediaListPlayer(env, thiz);
911
    if (mp)
912
        libvlc_media_list_player_play(mp);
913
914
}

Sébastien Toque's avatar
Sébastien Toque committed
915
void Java_org_videolan_vlc_LibVLC_pause(JNIEnv *env, jobject thiz)
916
{
917
    libvlc_media_list_player_t *mp = getMediaListPlayer(env, thiz);
918
    if (mp)
919
        libvlc_media_list_player_pause(mp);
920
921
}

Sébastien Toque's avatar
Sébastien Toque committed
922
void Java_org_videolan_vlc_LibVLC_stop(JNIEnv *env, jobject thiz)
923
{
924
    libvlc_media_list_player_t *mp = getMediaListPlayer(env, thiz);
925
    if (mp)
926
        libvlc_media_list_player_stop(mp);
927
928
}

Edward Wang's avatar
Edward Wang committed
929
930
931
932
933
934
935
936
937
938
939
940
941
942
void Java_org_videolan_vlc_LibVLC_previous(JNIEnv *env, jobject thiz)
{
    libvlc_media_list_player_t *mp = getMediaListPlayer(env, thiz);
    if (mp)
        libvlc_media_list_player_previous(mp);
}

void Java_org_videolan_vlc_LibVLC_next(JNIEnv *env, jobject thiz)
{
    libvlc_media_list_player_t *mp = getMediaListPlayer(env, thiz);
    if (mp)
        libvlc_media_list_player_next(mp);
}

Sébastien Toque's avatar
Sébastien Toque committed
943
jint Java_org_videolan_vlc_LibVLC_getVolume(JNIEnv *env, jobject thiz)
944
{
945
946
947
    libvlc_media_player_t *mp = getMediaPlayer(env, thiz);
    if (mp)
        return (jint) libvlc_audio_get_volume(mp);
948
949
950
    return -1;
}

Sébastien Toque's avatar
Sébastien Toque committed
951
jint Java_org_videolan_vlc_LibVLC_setVolume(JNIEnv *env, jobject thiz, jint volume)
952
{
953
954
    libvlc_media_player_t *mp = getMediaPlayer(env, thiz);
    if (mp)
955
        //Returns 0 if the volume was set, -1 if it was out of range or error
956
        return (jint) libvlc_audio_set_volume(mp, (int) volume);
957
958
959
    return -1;
}

Sébastien Toque's avatar
Sébastien Toque committed
960
jlong Java_org_videolan_vlc_LibVLC_getTime(JNIEnv *env, jobject thiz)
961
{
962
963
964
    libvlc_media_player_t *mp = getMediaPlayer(env, thiz);
    if (mp)
        return libvlc_media_player_get_time(mp);
965
966
967
    return -1;
}

Sébastien Toque's avatar
Sébastien Toque committed
968
void Java_org_videolan_vlc_LibVLC_setTime(JNIEnv *env, jobject thiz, jlong time)
969
{
970
971
972
    libvlc_media_player_t *mp = getMediaPlayer(env, thiz);
    if (mp)
        libvlc_media_player_set_time(mp, time);
973
974
}

Sébastien Toque's avatar
Sébastien Toque committed
975
jfloat Java_org_videolan_vlc_LibVLC_getPosition(JNIEnv *env, jobject thiz)
976
{
977
978
979
    libvlc_media_player_t *mp = getMediaPlayer(env, thiz);
    if (mp)
        return (jfloat) libvlc_media_player_get_position(mp);
980
981
982
    return -1;
}

Sébastien Toque's avatar
Sébastien Toque committed
983
void Java_org_videolan_vlc_LibVLC_setPosition(JNIEnv *env, jobject thiz, jfloat pos)
984
{
985
986
987
    libvlc_media_player_t *mp = getMediaPlayer(env, thiz);
    if (mp)
        libvlc_media_player_set_position(mp, pos);
988
989
}

Sébastien Toque's avatar
Sébastien Toque committed
990
jlong Java_org_videolan_vlc_LibVLC_getLength(JNIEnv *env, jobject thiz)
991
{
992
993
994
    libvlc_media_player_t *mp = getMediaPlayer(env, thiz);
    if (mp)
        return (jlong) libvlc_media_player_get_length(mp);
995
996
997
    return -1;
}

Sébastien Toque's avatar
Sébastien Toque committed
998
jstring Java_org_videolan_vlc_LibVLC_version(JNIEnv* env, jobject thiz)
999
{
1000
    return (*env)->NewStringUTF(env, libvlc_get_version());