vlcplugin_base.cpp 18 KB
Newer Older
1
/*****************************************************************************
2
 * vlcplugin_base.cpp: a VLC plugin for Mozilla
3
 *****************************************************************************
Jean-Paul Saman's avatar
Jean-Paul Saman committed
4
 * Copyright (C) 2002-2010 the VideoLAN team
5
 * $Id$
6 7
 *
 * Authors: Samuel Hocevar <sam@zoy.org>
8
 *          Damien Fouilleul <damienf.fouilleul@laposte.net>
9
 *          Jean-Paul Saman <jpsaman@videolan.org>
10
 *          Sergey Radionov <rsatom@gmail.com>
11
 *          Cheng Sun <chengsun9@gmail.com>
12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * 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
Antoine Cellerier's avatar
Antoine Cellerier committed
25
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 27 28 29 30
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
31 32 33 34
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

35
#include "vlcplugin.h"
Jean-Paul Saman's avatar
Jean-Paul Saman committed
36

37
#include "control/npolibvlc.h"
38

39
#include <cctype>
40

41 42 43
#include <cstdio>
#include <cstdlib>
#include <cstring>
44

Jean-Paul Saman's avatar
Jean-Paul Saman committed
45
/*****************************************************************************
46
 * VlcPluginBase constructor and destructor
Jean-Paul Saman's avatar
Jean-Paul Saman committed
47
 *****************************************************************************/
48
VlcPluginBase::VlcPluginBase( NPP instance, NPuint16_t mode ) :
Jean-Paul Saman's avatar
Jean-Paul Saman committed
49 50 51 52 53 54 55
    i_npmode(mode),
    b_stream(0),
    psz_target(NULL),
    libvlc_instance(NULL),
    p_scriptClass(NULL),
    p_browser(instance),
    psz_baseURL(NULL)
56
{
Jean-Paul Saman's avatar
Jean-Paul Saman committed
57
    memset(&npwindow, 0, sizeof(NPWindow));
58
    _instances.insert(this);
59 60
}

Jean-Paul Saman's avatar
Jean-Paul Saman committed
61 62 63 64 65
static bool boolValue(const char *value) {
    return ( !strcmp(value, "1") ||
             !strcasecmp(value, "true") ||
             !strcasecmp(value, "yes") );
}
66

67 68
std::set<VlcPluginBase*> VlcPluginBase::_instances;

69
void VlcPluginBase::eventAsync(void *param)
70
{
71
    VlcPluginBase *plugin = (VlcPluginBase*)param;
72
    if( _instances.find(plugin) == _instances.end() )
73 74
        return;

Jean-Paul Saman's avatar
Jean-Paul Saman committed
75
    plugin->events.deliver(plugin->getBrowser());
76
    plugin->update_controls();
77 78
}

79 80
void VlcPluginBase::event_callback(const libvlc_event_t* event,
                NPVariant *npparams, uint32_t npcount)
Jean-Paul Saman's avatar
Jean-Paul Saman committed
81
{
82
#if defined(XP_UNIX) || defined(XP_WIN)
83 84
    events.callback(event, npparams, npcount);
    NPN_PluginThreadAsyncCall(getBrowser(), eventAsync, this);
Jean-Paul Saman's avatar
Jean-Paul Saman committed
85
#else
Cheng Sun's avatar
Cheng Sun committed
86
#   warning NPN_PluginThreadAsyncCall not implemented yet.
Jean-Paul Saman's avatar
Jean-Paul Saman committed
87 88 89
    printf("No NPN_PluginThreadAsyncCall(), doing nothing.\n");
#endif
}
90

91
NPError VlcPluginBase::init(int argc, char* const argn[], char* const argv[])
92
{
93
    /* prepare VLC command line */
94
    const char *ppsz_argv[32];
95
    int ppsz_argc = 0;
96

97 98 99 100
#ifndef NDEBUG
    ppsz_argv[ppsz_argc++] = "--no-plugins-cache";
#endif

101 102
    /* locate VLC module path */
#ifdef XP_MACOSX
103
    ppsz_argv[ppsz_argc++] = "--vout=vout_macosx";
104 105 106 107 108 109
#elif defined(XP_WIN)
    HKEY h_key;
    DWORD i_type, i_data = MAX_PATH + 1;
    char p_data[MAX_PATH + 1];
    if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, "Software\\VideoLAN\\VLC",
                      0, KEY_READ, &h_key ) == ERROR_SUCCESS )
110
    {
111 112 113 114 115
         if( RegQueryValueEx( h_key, "InstallDir", 0, &i_type,
                              (LPBYTE)p_data, &i_data ) == ERROR_SUCCESS )
         {
             if( i_type == REG_SZ )
             {
116
                 strcat( p_data, "\\plugins" );
117 118
                 ppsz_argv[ppsz_argc++] = "--plugin-path";
                 ppsz_argv[ppsz_argc++] = p_data;
119 120 121
             }
         }
         RegCloseKey( h_key );
122
    }
123
    ppsz_argv[ppsz_argc++] = "--no-one-instance";
124

125
#endif /* XP_MACOSX */
126

127 128 129 130
    /* common settings */
    ppsz_argv[ppsz_argc++] = "-vv";
    ppsz_argv[ppsz_argc++] = "--no-stats";
    ppsz_argv[ppsz_argc++] = "--no-media-library";
131
    ppsz_argv[ppsz_argc++] = "--intf=dummy";
132
    ppsz_argv[ppsz_argc++] = "--no-video-title-show";
Christophe Mutricy's avatar
Christophe Mutricy committed
133
    ppsz_argv[ppsz_argc++] = "--no-xlib";
134

135 136
    bool b_autoloop = false;

137
    /* parse plugin arguments */
138
    for( int i = 0; (i < argc) && (ppsz_argc < 32); i++ )
139
    {
140
       /* fprintf(stderr, "argn=%s, argv=%s\n", argn[i], argv[i]); */
141 142 143 144 145 146 147 148

        if( !strcmp( argn[i], "target" )
         || !strcmp( argn[i], "mrl")
         || !strcmp( argn[i], "filename")
         || !strcmp( argn[i], "src") )
        {
            psz_target = argv[i];
        }
149 150
        else if( !strcmp( argn[i], "text" ) )
        {
151
            set_bg_text( argv[i] );
152
        }
153 154 155
        else if( !strcmp( argn[i], "autoplay")
              || !strcmp( argn[i], "autostart") )
        {
156
            set_autoplay(boolValue(argv[i]));
157
        }
158 159
        else if( !strcmp( argn[i], "fullscreen" )
              || !strcmp( argn[i], "allowfullscreen" ) )
160
        {
161
            set_enable_fs( boolValue(argv[i]) );
162 163 164 165 166
        }
        else if( !strcmp( argn[i], "mute" ) )
        {
            if( boolValue(argv[i]) )
            {
167
                ppsz_argv[ppsz_argc++] = "--volume=0";
168 169 170 171 172
            }
        }
        else if( !strcmp( argn[i], "loop")
              || !strcmp( argn[i], "autoloop") )
        {
173
            b_autoloop = boolValue(argv[i]);
174
        }
175
        else if( !strcmp( argn[i], "toolbar" ) )
176
        {
177
            set_show_toolbar( boolValue(argv[i]) );
178
        }
179 180
        else if( !strcmp( argn[i], "bgcolor" ) )
        {
181
            set_bg_color( argv[i] );
182
        }
183 184
    }

185
    libvlc_instance = libvlc_new(ppsz_argc, ppsz_argv);
186 187
    if( !libvlc_instance )
        return NPERR_GENERIC_ERROR;
188 189

    vlc_player::open(libvlc_instance);
190

191 192 193
    vlc_player::set_mode(b_autoloop ? libvlc_playback_mode_loop :
                                      libvlc_playback_mode_default);

194 195 196 197 198
    /*
    ** fetch plugin base URL, which is the URL of the page containing the plugin
    ** this URL is used for making absolute URL from relative URL that may be
    ** passed as an MRL argument
    */
Jean-Paul Saman's avatar
Jean-Paul Saman committed
199
    NPObject *plugin = NULL;
200 201 202 203 204 205 206 207 208 209

    if( NPERR_NO_ERROR == NPN_GetValue(p_browser, NPNVWindowNPObject, &plugin) )
    {
        /*
        ** is there a better way to get that info ?
        */
        static const char docLocHref[] = "document.location.href";
        NPString script;
        NPVariant result;

210 211
        script.UTF8Characters = docLocHref;
        script.UTF8Length = sizeof(docLocHref)-1;
212 213 214 215 216 217 218

        if( NPN_Evaluate(p_browser, plugin, &script, &result) )
        {
            if( NPVARIANT_IS_STRING(result) )
            {
                NPString &location = NPVARIANT_TO_STRING(result);

219
                psz_baseURL = (char *) malloc(location.UTF8Length+1);
220 221
                if( psz_baseURL )
                {
222 223
                    strncpy(psz_baseURL, location.UTF8Characters, location.UTF8Length);
                    psz_baseURL[location.UTF8Length] = '\0';
224 225 226 227 228 229 230 231 232 233
                }
            }
            NPN_ReleaseVariantValue(&result);
        }
        NPN_ReleaseObject(plugin);
    }

    if( psz_target )
    {
        // get absolute URL from src
234 235
        char *psz_absurl = getAbsoluteURL(psz_target);
        psz_target = psz_absurl ? psz_absurl : strdup(psz_target);
236 237 238
    }

    /* assign plugin script root class */
239 240
    /* new APIs */
    p_scriptClass = RuntimeNPClass<LibvlcRootNPObject>::getClass();
241

242
    if( !events.init() )
243 244
        return NPERR_GENERIC_ERROR;

245 246 247 248 249 250
    libvlc_media_player_t *p_md = getMD();
    if( p_md ) {
      libvlc_event_manager_t *p_em;
      p_em = libvlc_media_player_event_manager( getMD() );
      events.hook_manager( p_em, this );
    }
251

252
    return NPERR_NO_ERROR;
253 254
}

255
VlcPluginBase::~VlcPluginBase()
256
{
257 258
    free(psz_baseURL);
    free(psz_target);
Jean-Paul Saman's avatar
Jean-Paul Saman committed
259

260
    if( vlc_player::is_open() )
261
    {
262

263 264
        if( playlist_isplaying() )
            playlist_stop();
Jean-Paul Saman's avatar
Jean-Paul Saman committed
265
        events.unhook_manager( this );
266
        vlc_player::close();
267
    }
268
    if( libvlc_instance )
Jean-Paul Saman's avatar
Jean-Paul Saman committed
269
        libvlc_release( libvlc_instance );
270 271

    _instances.erase(this);
272 273
}

274
void VlcPluginBase::setWindow(const NPWindow &window)
275 276
{
    npwindow = window;
277
}
278

279 280 281 282 283
bool VlcPluginBase::handle_event(void *event)
{
    return false;
}

284
/*****************************************************************************
285
 * VlcPluginBase playlist replacement methods
286
 *****************************************************************************/
287
bool  VlcPluginBase::player_has_vout()
288
{
Jean-Paul Saman's avatar
Jean-Paul Saman committed
289
    bool r = false;
290
    if( playlist_isplaying() )
291
        r = libvlc_media_player_has_vout(get_player().get_mp())!=0;
292 293 294
    return r;
}

295
/*****************************************************************************
296
 * VlcPluginBase methods
297
 *****************************************************************************/
298

299
char *VlcPluginBase::getAbsoluteURL(const char *url)
300
{
301
    if( NULL != url )
302
    {
303 304 305
        // check whether URL is already absolute
        const char *end=strchr(url, ':');
        if( (NULL != end) && (end != url) )
306
        {
307 308
            // validate protocol header
            const char *start = url;
309 310 311
            char c = *start;
            if( isalpha(c) )
            {
312
                ++start;
313 314 315 316 317 318 319 320 321 322 323 324 325 326
                while( start != end )
                {
                    c  = *start;
                    if( ! (isalnum(c)
                       || ('-' == c)
                       || ('+' == c)
                       || ('.' == c)
                       || ('/' == c)) ) /* VLC uses / to allow user to specify a demuxer */
                        // not valid protocol header, assume relative URL
                        goto relativeurl;
                    ++start;
                }
                /* we have a protocol header, therefore URL is absolute */
                return strdup(url);
327
            }
328
            // not a valid protocol header, assume relative URL
329
        }
330

331 332
relativeurl:

333 334 335
        if( psz_baseURL )
        {
            size_t baseLen = strlen(psz_baseURL);
Jean-Paul Saman's avatar
Jean-Paul Saman committed
336
            char *href = (char *) malloc(baseLen+strlen(url)+1);
337 338 339
            if( href )
            {
                /* prepend base URL */
JP Dinger's avatar
JP Dinger committed
340
                memcpy(href, psz_baseURL, baseLen+1);
341 342 343 344 345 346 347 348 349 350 351 352 353 354

                /*
                ** relative url could be empty,
                ** in which case return base URL
                */
                if( '\0' == *url )
                    return href;

                /*
                ** locate pathname part of base URL
                */

                /* skip over protocol part  */
                char *pathstart = strchr(href, ':');
JP Dinger's avatar
JP Dinger committed
355
                char *pathend = href+baseLen;
356
                if( pathstart )
357 358 359
                {
                    if( '/' == *(++pathstart) )
                    {
360 361 362 363 364 365 366 367 368 369 370 371
                        if( '/' == *(++pathstart) )
                        {
                            ++pathstart;
                        }
                    }
                    /* skip over host part */
                    pathstart = strchr(pathstart, '/');
                    if( ! pathstart )
                    {
                        // no path, add a / past end of url (over '\0')
                        pathstart = pathend;
                        *pathstart = '/';
372 373
                    }
                }
374
                else
375
                {
376 377 378 379
                    /* baseURL is just a UNIX path */
                    if( '/' != *href )
                    {
                        /* baseURL is not an absolute path */
Jean-Paul Saman's avatar
Jean-Paul Saman committed
380
                        free(href);
381 382 383
                        return NULL;
                    }
                    pathstart = href;
384 385 386 387 388 389 390 391 392 393
                }

                /* relative URL made of an absolute path ? */
                if( '/' == *url )
                {
                    /* replace path completely */
                    strcpy(pathstart, url);
                    return href;
                }

394
                /* find last path component and replace it */
395 396
                while( '/' != *pathend)
                    --pathend;
397 398 399 400 401 402 403 404 405 406 407 408

                /*
                ** if relative url path starts with one or more '../',
                ** factor them out of href so that we return a
                ** normalized URL
                */
                while( pathend != pathstart )
                {
                    const char *p = url;
                    if( '.' != *p )
                        break;
                    ++p;
409 410 411 412
                    if( '\0' == *p  )
                    {
                        /* relative url is just '.' */
                        url = p;
413
                        break;
414 415 416 417 418 419 420
                    }
                    if( '/' == *p  )
                    {
                        /* relative url starts with './' */
                        url = ++p;
                        continue;
                    }
421
                    if( '.' != *p )
422 423
                        break;
                    ++p;
424 425 426 427 428 429
                    if( '\0' == *p )
                    {
                        /* relative url is '..' */
                    }
                    else
                    {
430
                        if( '/' != *p )
431 432 433 434
                            break;
                        /* relative url starts with '../' */
                        ++p;
                    }
435
                    url = p;
436 437 438 439 440
                    do
                    {
                        --pathend;
                    }
                    while( '/' != *pathend );
441
                }
442 443
                /* skip over '/' separator */
                ++pathend;
444
                /* concatenate remaining base URL and relative URL */
445
                strcpy(pathend, url);
446 447 448
            }
            return href;
        }
449
    }
450 451
    return NULL;
}
452

453
void VlcPluginBase::control_handler(vlc_toolbar_clicked_t clicked)
454
{
455
    switch( clicked )
456
    {
457 458 459 460 461
        case clicked_Play:
        {
            playlist_play();
        }
        break;
462

463 464 465 466 467
        case clicked_Pause:
        {
            playlist_pause();
        }
        break;
468

469 470 471 472 473
        case clicked_Stop:
        {
            playlist_stop();
        }
        break;
474

475 476
        case clicked_Fullscreen:
        {
477
            toggle_fullscreen();
478 479
        }
        break;
480

481 482 483 484 485 486 487 488 489
        case clicked_Mute:
        case clicked_Unmute:
#if 0
        {
            if( p_md )
                libvlc_audio_toggle_mute( p_md );
        }
#endif
        break;
490

491 492 493 494 495 496 497 498
        case clicked_timeline:
#if 0
        {
            /* if a movie is loaded */
            if( p_md )
            {
                int64_t f_length;
                f_length = libvlc_media_player_get_length( p_md ) / 100;
499

500 501
                f_length = (float)f_length *
                        ( ((float)i_xPos-4.0 ) / ( ((float)i_width-8.0)/100) );
502

503 504 505
                libvlc_media_player_set_time( p_md, f_length );
            }
        }
Jean-Paul Saman's avatar
Jean-Paul Saman committed
506
#endif
507
        break;
508

509 510 511 512 513
        case clicked_Time:
        {
            /* Not implemented yet*/
        }
        break;
514

515 516 517 518
        default: /* button_Unknown */
            fprintf(stderr, "button Unknown!\n");
        break;
    }
519
}
520 521 522 523

// Verifies the version of the NPAPI.
// The eventListeners use a NPAPI function available
// since Gecko 1.9.
524
bool VlcPluginBase::canUseEventListener()
525 526 527 528 529 530 531 532 533 534 535
{
    int plugin_major, plugin_minor;
    int browser_major, browser_minor;

    NPN_Version(&plugin_major, &plugin_minor,
                &browser_major, &browser_minor);

    if (browser_minor >= 19 || browser_major > 0)
        return true;
    return false;
}
536

537
#ifdef WINDOWLESS
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
VlcWindowlessBase::VlcWindowlessBase(NPP instance, NPuint16_t mode) :
    VlcPluginBase(instance, mode)
{
}

unsigned VlcWindowlessBase::video_format_cb(char *chroma,
                                unsigned *width, unsigned *height,
                                unsigned *pitches, unsigned *lines)
{
    if ( p_browser ) {
        float src_aspect = (float)(*width) / (*height);
        float dst_aspect = (float)npwindow.width/npwindow.height;
        if ( src_aspect > dst_aspect ) {
            if( npwindow.width != (*width) ) { //don't scale if size equal
                (*width) = npwindow.width;
                (*height) = static_cast<unsigned>( (*width) / src_aspect + 0.5);
            }
        }
        else {
            if( npwindow.height != (*height) ) { //don't scale if size equal
                (*height) = npwindow.height;
                (*width) = static_cast<unsigned>( (*height) * src_aspect + 0.5);
            }
        }
    }

    m_media_width = (*width);
    m_media_height = (*height);

    memcpy(chroma, DEF_CHROMA, sizeof(DEF_CHROMA)-1);
    (*pitches) = m_media_width * DEF_PIXEL_BYTES;
    (*lines) = m_media_height;

    //+1 for vlc 2.0.3/2.1 bug workaround.
    //They writes after buffer end boundary by some reason unknown to me...
    m_frame_buf.resize( (*pitches) * ((*lines)+1) );

    return 1;
}

void VlcWindowlessBase::video_cleanup_cb()
{
    m_frame_buf.resize(0);
    m_media_width = 0;
    m_media_height = 0;
}

void* VlcWindowlessBase::video_lock_cb(void **planes)
{
    (*planes) = m_frame_buf.empty()? 0 : &m_frame_buf[0];
    return 0;
}

void VlcWindowlessBase::video_unlock_cb(void* /*picture*/, void *const * /*planes*/)
{
}

void VlcWindowlessBase::invalidate_window()
{
    NPRect rect;
    rect.left = 0;
    rect.top = 0;
    rect.right = npwindow.width;
    rect.bottom = npwindow.height;
    NPN_InvalidateRect(p_browser, &rect);
    NPN_ForceRedraw(p_browser);
}

void VlcWindowlessBase::video_display_cb(void * /*picture*/)
{
    if (p_browser) {
        NPN_PluginThreadAsyncCall(p_browser,
                                  VlcWindowlessBase::invalidate_window_proxy,
                                  this);
    }
}

void VlcWindowlessBase::set_player_window() {
    libvlc_video_set_format_callbacks(getMD(),
                                      video_format_proxy,
                                      video_cleanup_proxy);
    libvlc_video_set_callbacks(getMD(),
                               video_lock_proxy,
                               video_unlock_proxy,
                               video_display_proxy,
                               this);
}
625
#endif