Index: trunk/extensions/OggHandler/OggHandler.php |
— | — | @@ -22,6 +22,7 @@ |
23 | 23 | $wgFFmpegLocation = 'ffmpeg'; |
24 | 24 | $wgExtensionMessagesFiles['OggHandler'] = "$oggDir/OggHandler.i18n.php"; |
25 | 25 | $wgParserOutputHooks['OggHandler'] = array( 'OggHandler', 'outputHook' ); |
| 26 | +$wgHooks['LanguageGetMagic'][] = 'OggHandler::registerMagicWords'; |
26 | 27 | |
27 | 28 | // Filename or URL path to the Cortado Java player applet. |
28 | 29 | // |
Index: trunk/extensions/OggHandler/pause.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: trunk/extensions/OggHandler/pause.png |
___________________________________________________________________ |
Name: svn:mime-type |
29 | 30 | + image/png |
Index: trunk/extensions/OggHandler/OggPlayer.js |
— | — | @@ -13,21 +13,24 @@ |
14 | 14 | // Configuration from MW |
15 | 15 | 'msg': {}, |
16 | 16 | 'cortadoUrl' : '', |
17 | | - 'smallFileUrl' : '', |
| 17 | + 'extPathUrl' : '', |
18 | 18 | 'showPlayerSelect': true, |
19 | 19 | 'controlsHeightGuess': 20, |
20 | 20 | |
21 | | - // Main entry point: initialise a video player |
22 | | - // Player will be created as a child of the given ID |
23 | | - // There may be multiple players in a document |
24 | | - 'init': function ( player, id, videoUrl, width, height, length, linkUrl ) { |
25 | | - elt = document.getElementById( id ); |
| 21 | + /** |
| 22 | + * Main entry point: initialise a video player |
| 23 | + * Player will be created as a child of the given ID |
| 24 | + * There may be multiple players in a document. |
| 25 | + * Parameters are: id, videoUrl, width, height, length, linkUrl |
| 26 | + */ |
| 27 | + 'init': function ( player, params ) { |
| 28 | + elt = document.getElementById( params.id ); |
26 | 29 | |
27 | 30 | // Save still image HTML |
28 | | - if ( !(id in this.savedThumbs) ) { |
| 31 | + if ( !(params.id in this.savedThumbs) ) { |
29 | 32 | var thumb = document.createDocumentFragment(); |
30 | 33 | thumb.appendChild( elt.cloneNode( true ) ); |
31 | | - this.savedThumbs[id] = thumb; |
| 34 | + this.savedThumbs[params.id] = thumb; |
32 | 35 | } |
33 | 36 | |
34 | 37 | this.detect( elt ); |
— | — | @@ -62,40 +65,41 @@ |
63 | 66 | |
64 | 67 | switch ( player ) { |
65 | 68 | case 'videoElement': |
66 | | - this.embedVideoElement( elt, videoUrl, width, height, length ); |
| 69 | + this.embedVideoElement( elt, params ); |
67 | 70 | break; |
68 | 71 | case 'oggPlugin': |
69 | | - this.embedOggPlugin( elt, videoUrl, width, height, length ); |
| 72 | + this.embedOggPlugin( elt, params ); |
70 | 73 | break; |
71 | 74 | case 'vlc-mozilla': |
72 | | - this.embedVlcPlugin( elt, videoUrl, width, height, length ); |
| 75 | + this.embedVlcPlugin( elt, params ); |
73 | 76 | break; |
74 | 77 | case 'vlc-activex': |
75 | | - this.embedVlcActiveX( elt, videoUrl, width, height, length ); |
| 78 | + this.embedVlcActiveX( elt, params ); |
76 | 79 | break; |
77 | 80 | case 'cortado': |
78 | | - this.embedCortado( elt, videoUrl, width, height, length ); |
| 81 | + this.embedCortado( elt, params ); |
79 | 82 | break; |
80 | 83 | case 'quicktime-mozilla': |
81 | | - this.embedQuicktimePlugin( elt, videoUrl, width, height, length ); |
| 84 | + this.embedQuicktimePlugin( elt, params ); |
82 | 85 | break; |
83 | 86 | case 'thumbnail': |
84 | | - if ( id in this.savedThumbs ) { |
85 | | - elt.appendChild( this.savedThumbs[id].cloneNode( true ) ); |
| 87 | + if ( params.id in this.savedThumbs ) { |
| 88 | + elt.appendChild( this.savedThumbs[params.id].cloneNode( true ) ); |
86 | 89 | } else { |
87 | | - elt.appendChild( document.createTextNode( 'Missing saved thumbnail for ' + id ) ); |
| 90 | + elt.appendChild( document.createTextNode( 'Missing saved thumbnail for ' + params.id ) ); |
88 | 91 | } |
89 | 92 | break; |
90 | 93 | default: |
91 | | - elt.innerHTML = this.msg['ogg-no-player'] + '<br/>'; |
| 94 | + elt.innerHTML = '<div>' + this.msg['ogg-no-player'] + '</div>'; |
92 | 95 | player = 'none'; |
93 | 96 | } |
94 | 97 | if ( player != 'thumbnail' ) { |
95 | | - var optionsBox = this.makeOptionsBox( player, id, videoUrl, width, height, length, linkUrl ); |
96 | | - var optionsLink = this.makeOptionsLink( id ); |
97 | | - elt.appendChild( document.createElement( 'br' ) ); |
98 | | - elt.appendChild( optionsBox ); |
99 | | - elt.appendChild( optionsLink ); |
| 98 | + var optionsBox = this.makeOptionsBox( player, params ); |
| 99 | + var optionsLink = this.makeOptionsLink( params.id ); |
| 100 | + var div = document.createElement( 'div' ); |
| 101 | + div.appendChild( optionsBox ); |
| 102 | + div.appendChild( optionsLink ); |
| 103 | + elt.appendChild( div ); |
100 | 104 | } |
101 | 105 | }, |
102 | 106 | |
— | — | @@ -197,22 +201,22 @@ |
198 | 202 | } |
199 | 203 | }, |
200 | 204 | |
201 | | - 'makeOptionsBox' : function ( selectedPlayer, id, videoUrl, width, height, length, linkUrl ) { |
| 205 | + 'makeOptionsBox' : function ( selectedPlayer, params ) { |
202 | 206 | var div, p, a, ul, li, button; |
203 | 207 | |
204 | 208 | div = document.createElement( 'div' ); |
205 | | - div.style.cssText = "width: " + ( width - 10 ) + "px; display: none;"; |
| 209 | + div.style.cssText = "width: " + ( params.width - 10 ) + "px; display: none;"; |
206 | 210 | div.className = 'ogg-player-options'; |
207 | | - div.id = id + '_options_box'; |
| 211 | + div.id = params.id + '_options_box'; |
208 | 212 | div.align = 'center'; |
209 | 213 | |
210 | 214 | ul = document.createElement( 'ul' ); |
211 | 215 | |
212 | 216 | // Description page link |
213 | | - if ( linkUrl ) { |
| 217 | + if ( params.linkUrl ) { |
214 | 218 | li = document.createElement( 'li' ); |
215 | 219 | a = document.createElement( 'a' ); |
216 | | - a.href = linkUrl; |
| 220 | + a.href = params.linkUrl; |
217 | 221 | a.appendChild( document.createTextNode( this.msg['ogg-desc-link'] ) ); |
218 | 222 | li.appendChild( a ); |
219 | 223 | ul.appendChild( li ); |
— | — | @@ -221,7 +225,7 @@ |
222 | 226 | // Download link |
223 | 227 | li = document.createElement( 'li' ); |
224 | 228 | a = document.createElement( 'a' ); |
225 | | - a.href = videoUrl; |
| 229 | + a.href = params.videoUrl; |
226 | 230 | a.appendChild( document.createTextNode( this.msg['ogg-download'] ) ); |
227 | 231 | li.appendChild( a ); |
228 | 232 | ul.appendChild( li ); |
— | — | @@ -236,15 +240,21 @@ |
237 | 241 | // Make player list |
238 | 242 | ul = document.createElement( 'ul' ); |
239 | 243 | for ( var i = 0; i < this.players.length + 1; i++ ) { |
240 | | - var player; |
| 244 | + var player, playerMsg; |
241 | 245 | if ( i == this.players.length ) { |
242 | 246 | player = 'thumbnail'; |
| 247 | + if ( params.isVideo ) { |
| 248 | + playerMsg = 'ogg-player-thumbnail'; |
| 249 | + } else { |
| 250 | + playerMsg = 'ogg-player-soundthumb'; |
| 251 | + } |
243 | 252 | } else { |
244 | 253 | player = this.players[i]; |
245 | 254 | // Skip unsupported players |
246 | 255 | if ( ! this.clientSupports[player] ) { |
247 | 256 | continue; |
248 | 257 | } |
| 258 | + playerMsg = 'ogg-player-' + player; |
249 | 259 | } |
250 | 260 | |
251 | 261 | // Make list item |
— | — | @@ -252,13 +262,13 @@ |
253 | 263 | if ( player == selectedPlayer ) { |
254 | 264 | var strong = document.createElement( 'strong' ); |
255 | 265 | strong.appendChild( document.createTextNode( |
256 | | - this.msg['ogg-player-' + player] + ' ' + this.msg['ogg-player-selected'] ) ); |
| 266 | + this.msg[playerMsg] + ' ' + this.msg['ogg-player-selected'] ) ); |
257 | 267 | li.appendChild( strong ); |
258 | 268 | } else { |
259 | 269 | a = document.createElement( 'a' ); |
260 | 270 | a.href = 'javascript:void("' + player + '")'; |
261 | | - a.onclick = this.makePlayerFunction( player, id, videoUrl, width, height, length, linkUrl ); |
262 | | - a.appendChild( document.createTextNode( this.msg['ogg-player-' + player] ) ); |
| 271 | + a.onclick = this.makePlayerFunction( player, params ); |
| 272 | + a.appendChild( document.createTextNode( this.msg[playerMsg] ) ); |
263 | 273 | li.appendChild( a ); |
264 | 274 | } |
265 | 275 | ul.appendChild( li ); |
— | — | @@ -269,7 +279,7 @@ |
270 | 280 | div2.style.cssText = 'text-align: center;'; |
271 | 281 | button = document.createElement( 'button' ); |
272 | 282 | button.appendChild( document.createTextNode( this.msg['ogg-dismiss'] ) ); |
273 | | - button.onclick = this.makeDismissFunction( id ); |
| 283 | + button.onclick = this.makeDismissFunction( params.id ); |
274 | 284 | div2.appendChild( button ); |
275 | 285 | div.appendChild( div2 ); |
276 | 286 | |
— | — | @@ -319,165 +329,172 @@ |
320 | 330 | } |
321 | 331 | }, |
322 | 332 | |
323 | | - 'makePlayerFunction' : function ( player, id, videoUrl, width, height, length, linkUrl ) { |
| 333 | + 'makePlayerFunction' : function ( player, params ) { |
324 | 334 | var this_ = this; |
325 | 335 | return function () { |
326 | 336 | if ( player != 'thumbnail' ) { |
327 | 337 | document.cookie = "ogg_player=" + player; |
328 | 338 | } |
329 | | - this_.init( player, id, videoUrl, width, height, length, linkUrl ); |
| 339 | + this_.init( player, params ); |
330 | 340 | }; |
331 | 341 | }, |
332 | 342 | |
333 | | - 'newButton': function ( caption, callback ) { |
| 343 | + 'newButton': function ( caption, image, callback ) { |
334 | 344 | var elt = document.createElement('input'); |
335 | | - elt.type = 'button'; |
336 | | - elt.value = this.msg[caption]; |
| 345 | + elt.type = 'image'; |
| 346 | + elt.src = this.extPathUrl + '/' + image; |
| 347 | + elt.alt = elt.value = elt.title = this.msg[caption]; |
337 | 348 | elt.onclick = callback; |
338 | 349 | return elt; |
339 | 350 | }, |
340 | 351 | |
341 | 352 | 'newPlayButton': function ( videoElt ) { |
342 | | - return this.newButton( 'ogg-play', function () { videoElt.play(); } ); |
| 353 | + return this.newButton( 'ogg-play', 'play.png', function () { videoElt.play(); } ); |
343 | 354 | }, |
344 | 355 | |
345 | 356 | 'newPauseButton': function ( videoElt ) { |
346 | | - return this.newButton( 'ogg-pause', function () { videoElt.pause(); } ); |
| 357 | + return this.newButton( 'ogg-pause', 'pause.png', function () { videoElt.pause(); } ); |
347 | 358 | }, |
348 | 359 | |
349 | 360 | 'newStopButton': function ( videoElt ) { |
350 | | - return this.newButton( 'ogg-stop', function () { videoElt.stop(); } ); |
| 361 | + return this.newButton( 'ogg-stop', 'stop.png', function () { videoElt.stop(); } ); |
351 | 362 | }, |
352 | 363 | |
353 | | - 'embedVideoElement': function ( elt, videoUrl, width, height, length ) { |
| 364 | + 'embedVideoElement': function ( elt, params ) { |
354 | 365 | var videoElt = document.createElement('video'); |
355 | | - videoElt.setAttribute( 'width', width ); |
356 | | - videoElt.setAttribute( 'height', height + this.controlsHeightGuess ); |
357 | | - videoElt.setAttribute( 'src', videoUrl ); |
| 366 | + videoElt.setAttribute( 'width', params.width ); |
| 367 | + videoElt.setAttribute( 'height', params.height + this.controlsHeightGuess ); |
| 368 | + videoElt.setAttribute( 'src', params.videoUrl ); |
358 | 369 | videoElt.setAttribute( 'autoplay', '1' ); |
359 | 370 | videoElt.setAttribute( 'controls', '1' ); |
360 | | - elt.appendChild( videoElt ); |
| 371 | + var div = document.createElement( 'div' ); |
| 372 | + div.appendChild( videoElt ); |
| 373 | + elt.appendChild( div ); |
361 | 374 | |
362 | 375 | // Try to detect implementations that don't support controls |
363 | 376 | // This works for the Opera test build |
364 | 377 | if ( !videoElt.controls ) { |
365 | | - elt.appendChild( document.createElement( 'br' ) ); |
366 | | - elt.appendChild( this.newPlayButton( videoElt ) ); |
367 | | - elt.appendChild( this.newPauseButton( videoElt ) ); |
368 | | - elt.appendChild( this.newStopButton( videoElt ) ); |
| 378 | + div = document.createElement( 'div' ); |
| 379 | + div.appendChild( this.newPlayButton( videoElt ) ); |
| 380 | + div.appendChild( this.newPauseButton( videoElt ) ); |
| 381 | + div.appendChild( this.newStopButton( videoElt ) ); |
| 382 | + elt.appendChild( div ); |
369 | 383 | //videoElt.play(); |
370 | 384 | } |
371 | 385 | }, |
372 | 386 | |
373 | | - 'embedOggPlugin': function ( elt, videoUrl, width, height, length ) { |
| 387 | + 'embedOggPlugin': function ( elt, params ) { |
374 | 388 | var id = elt.id + "_obj"; |
375 | 389 | elt.innerHTML += |
376 | | - "<object id=" + this.hq( id ) + |
| 390 | + "<div><object id=" + this.hq( id ) + |
377 | 391 | " type='application/ogg'" + |
378 | | - " width=" + this.hq( width ) + |
379 | | - " height=" + this.hq( height + this.controlsHeightGuess ) + |
380 | | - " data=" + this.hq( videoUrl ) + "></object>"; |
| 392 | + " width=" + this.hq( params.width ) + |
| 393 | + " height=" + this.hq( params.height + this.controlsHeightGuess ) + |
| 394 | + " data=" + this.hq( params.videoUrl ) + "></object></div>"; |
381 | 395 | }, |
382 | 396 | |
383 | | - 'embedVlcPlugin' : function ( elt, videoUrl, width, height, length ) { |
| 397 | + 'embedVlcPlugin' : function ( elt, params ) { |
384 | 398 | var id = elt.id + "_obj"; |
385 | 399 | elt.innerHTML += |
386 | | - "<object id=" + this.hq( id ) + |
| 400 | + "<div><object id=" + this.hq( id ) + |
387 | 401 | " type='application/x-vlc-plugin'" + |
388 | | - " width=" + this.hq( width ) + |
389 | | - " height=" + this.hq( height ) + |
390 | | - " data=" + this.hq( videoUrl ) + "></object>"; |
| 402 | + " width=" + this.hq( params.width ) + |
| 403 | + " height=" + this.hq( params.height ) + |
| 404 | + " data=" + this.hq( params.videoUrl ) + "></object></div>"; |
391 | 405 | |
392 | 406 | var videoElt = document.getElementById( id ); |
393 | | - elt.appendChild( document.createElement( 'br' ) ); |
| 407 | + var div = document.createElement( 'div' ); |
394 | 408 | // TODO: seek bar |
395 | | - elt.appendChild( this.newPlayButton( videoElt ) ); |
396 | | - elt.appendChild( this.newPauseButton( videoElt ) ); |
397 | | - elt.appendChild( this.newStopButton( videoElt ) ); |
| 409 | + div.appendChild( this.newPlayButton( videoElt ) ); |
| 410 | + div.appendChild( this.newPauseButton( videoElt ) ); |
| 411 | + div.appendChild( this.newStopButton( videoElt ) ); |
| 412 | + elt.appendChild( div ); |
398 | 413 | }, |
399 | 414 | |
400 | | - 'embedVlcActiveX' : function ( elt, videoUrl, width, height, length ) { |
| 415 | + 'embedVlcActiveX' : function ( elt, params ) { |
401 | 416 | var id = elt.id + "_obj"; |
402 | 417 | |
403 | 418 | var html = |
404 | | - '<object id=' + this.hq( id ) + |
| 419 | + '<div><object id=' + this.hq( id ) + |
405 | 420 | ' classid="clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921"' + |
406 | 421 | ' codebase="http://downloads.videolan.org/pub/videolan/vlc/latest/win32/axvlc.cab#Version=0,8,6,0"' + |
407 | | - ' width=' + this.hq( width ) + |
408 | | - ' height=' + this.hq( height ) + |
409 | | - ' style="width: ' + this.hx( width ) + 'px; height: ' + this.hx( height ) + 'px;"' + |
| 422 | + ' width=' + this.hq( params.width ) + |
| 423 | + ' height=' + this.hq( params.height ) + |
| 424 | + ' style="width: ' + this.hx( params.width ) + 'px; height: ' + this.hx( params.height ) + 'px;"' + |
410 | 425 | ">" + |
411 | | - '<param name="mrl" value=' + this.hq( videoUrl ) + '/>' + |
412 | | - '</object>'; |
| 426 | + '<param name="mrl" value=' + this.hq( params.videoUrl ) + '/>' + |
| 427 | + '</object></div>'; |
413 | 428 | elt.innerHTML += html; |
414 | 429 | |
415 | 430 | var videoElt = document.getElementById( id ); |
416 | 431 | |
417 | 432 | // IE says "sorry, I wasn't listening, what were the dimensions again?" |
418 | | - if ( width && height ) { |
419 | | - videoElt.width = width; |
420 | | - videoElt.height = height; |
421 | | - videoElt.style.width = width + 'px'; |
422 | | - videoElt.style.height = height + 'px'; |
| 433 | + if ( params.width && params.height ) { |
| 434 | + videoElt.width = params.width; |
| 435 | + videoElt.height = params.height; |
| 436 | + videoElt.style.width = params.width + 'px'; |
| 437 | + videoElt.style.height = params.height + 'px'; |
423 | 438 | } |
424 | | - |
425 | | - elt.appendChild( document.createElement( 'br' ) ); |
| 439 | + var div = document.createElement( 'div' ); |
426 | 440 | // TODO: seek bar |
427 | | - elt.appendChild( this.newButton( 'ogg-play', function() { videoElt.playlist.play(); } ) ); |
| 441 | + div.appendChild( this.newButton( 'ogg-play', 'play.png', function() { videoElt.playlist.play(); } ) ); |
428 | 442 | // FIXME: playlist.pause() doesn't work |
429 | | - elt.appendChild( this.newButton( 'ogg-stop', function() { videoElt.playlist.stop(); } ) ); |
| 443 | + div.appendChild( this.newButton( 'ogg-stop', 'stop.png', function() { videoElt.playlist.stop(); } ) ); |
| 444 | + elt.appendChild( div ); |
430 | 445 | }, |
431 | 446 | |
432 | | - 'embedCortado' : function ( elt, videoUrl, width, height, length ) { |
| 447 | + 'embedCortado' : function ( elt, params ) { |
433 | 448 | var statusHeight = 18; |
434 | 449 | // Given extra vertical space, cortado centres the video and then overlays the status |
435 | 450 | // line, leaving an ugly black bar at the top. So we don't give it any. |
436 | | - var playerHeight = height < statusHeight ? statusHeight : height; |
| 451 | + var playerHeight = params.height < statusHeight ? statusHeight : params.height; |
437 | 452 | |
438 | 453 | // Create the applet all at once |
439 | 454 | // In Opera, document.createElement('applet') immediately creates |
440 | 455 | // a non-working applet with unchangeable parameters, similar to the |
441 | 456 | // problem with IE and ActiveX. |
442 | | - elt.innerHTML = |
| 457 | + elt.innerHTML = '<div>' + |
443 | 458 | '<applet code="com.fluendo.player.Cortado.class" ' + |
444 | | - ' width=' + this.hq( width ) + |
| 459 | + ' width=' + this.hq( params.width ) + |
445 | 460 | ' height=' + this.hq( playerHeight ) + |
446 | 461 | ' archive=' + this.hq( this.cortadoUrl ) + '>' + |
447 | | - ' <param name="url" value=' + this.hq( videoUrl ) + '/>' + |
448 | | - ' <param name="duration" value=' + this.hq( length ) + '/>' + |
| 462 | + ' <param name="url" value=' + this.hq( params.videoUrl ) + '/>' + |
| 463 | + ' <param name="duration" value=' + this.hq( params.length ) + '/>' + |
449 | 464 | ' <param name="seekable" value="true"/>' + |
450 | 465 | ' <param name="autoPlay" value="true"/>' + |
451 | 466 | ' <param name="showStatus" value="show"/>' + |
452 | 467 | ' <param name="statusHeight" value="' + statusHeight + '"/>' + |
453 | | - '</applet>'; |
| 468 | + '</applet>' + |
| 469 | + '</div>'; |
454 | 470 | |
455 | 471 | // Disable autoPlay in the DOM right now, to prevent Mozilla from |
456 | 472 | // restarting an arbitrary number of applet instances on a back button click. |
457 | 473 | // Unfortunately this means that some clients (e.g. Opera) won't autoplay at all |
458 | | - var videoElt = elt.getElementsByTagName( 'applet' )[0]; |
| 474 | + var videoElt = elt.getElementsByTagName( 'div' ) [0] . |
| 475 | + getElementsByTagName( 'applet' )[0]; |
459 | 476 | this.setParam( videoElt, 'autoPlay', '' ); |
460 | 477 | }, |
461 | 478 | |
462 | | - 'embedQuicktimePlugin': function ( elt, videoUrl, width, height, length ) { |
| 479 | + 'embedQuicktimePlugin': function ( elt, params ) { |
463 | 480 | var id = elt.id + "_obj"; |
464 | 481 | var controllerHeight = 16; // by observation |
465 | 482 | elt.innerHTML += |
466 | | - "<object id=" + this.hq( id ) + |
| 483 | + "<div><object id=" + this.hq( id ) + |
467 | 484 | " type='video/quicktime'" + |
468 | | - " width=" + this.hq( width ) + |
469 | | - " height=" + this.hq( height + controllerHeight ) + |
| 485 | + " width=" + this.hq( params.width ) + |
| 486 | + " height=" + this.hq( params.height + controllerHeight ) + |
470 | 487 | |
471 | 488 | // Use QTSRC parameter instead of data attribute to allow progressive download |
472 | 489 | // The data attribute and src parameter point to a small file, as recommended in |
473 | 490 | // http://developer.apple.com/documentation/QuickTime/Conceptual/QTScripting_HTML/QTScripting_HTML_Document/chapter_1000_section_6.html |
474 | | - " data=" + this.hq( this.smallFileUrl ) + |
| 491 | + " data=" + this.hq( this.extPathUrl + '/null_file' ) + |
475 | 492 | ">" + |
476 | 493 | // Scale, don't clip |
477 | 494 | "<param name='SCALE' value='Aspect'/>" + |
478 | 495 | "<param name='AUTOPLAY' value='True'/>" + |
479 | | - "<param name='src' value=" + this.hq( this.smallFileUrl ) + "/>" + |
480 | | - "<param name='QTSRC' value=" + this.hq( videoUrl ) + "/>" + |
481 | | - "</object>"; |
| 496 | + "<param name='src' value=" + this.hq( this.extPathUrl + '/null_file' ) + "/>" + |
| 497 | + "<param name='QTSRC' value=" + this.hq( params.videoUrl ) + "/>" + |
| 498 | + "</object></div>"; |
482 | 499 | |
483 | 500 | // Disable autoplay on back button |
484 | 501 | var this_ = this; |
Index: trunk/extensions/OggHandler/play.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: trunk/extensions/OggHandler/play.png |
___________________________________________________________________ |
Name: svn:mime-type |
485 | 502 | + image/png |
Index: trunk/extensions/OggHandler/stop.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: trunk/extensions/OggHandler/stop.png |
___________________________________________________________________ |
Name: svn:mime-type |
486 | 503 | + image/png |
Index: trunk/extensions/OggHandler/OggHandler_body.php |
— | — | @@ -1,8 +1,12 @@ |
2 | 2 | <?php |
3 | 3 | |
| 4 | +// TODO: Fix core printable stylesheet. Descendant selectors suck. |
| 5 | + |
4 | 6 | class OggHandler extends MediaHandler { |
5 | 7 | const OGG_METADATA_VERSION = 1; |
6 | 8 | |
| 9 | + static $magicDone = false; |
| 10 | + |
7 | 11 | var $videoTypes = array( 'Theora' ); |
8 | 12 | var $audioTypes = array( 'Vorbis', 'Speex', 'FLAC' ); |
9 | 13 | |
— | — | @@ -10,36 +14,80 @@ |
11 | 15 | return true; |
12 | 16 | } |
13 | 17 | |
| 18 | + static function registerMagicWords( &$magicData, $code ) { |
| 19 | + wfLoadExtensionMessages( 'OggHandler' ); |
| 20 | + return true; |
| 21 | + } |
| 22 | + |
14 | 23 | function getParamMap() { |
15 | | - // TODO: add thumbtime, noplayer |
16 | | - return array( 'img_width' => 'width' ); |
| 24 | + wfLoadExtensionMessages( 'OggHandler' ); |
| 25 | + return array( |
| 26 | + 'img_width' => 'width', |
| 27 | + 'ogg_noplayer' => 'noplayer', |
| 28 | + 'ogg_thumbtime' => 'thumbtime', |
| 29 | + ); |
17 | 30 | } |
18 | 31 | |
19 | 32 | function validateParam( $name, $value ) { |
20 | | - // TODO |
| 33 | + if ( $name == 'thumbtime' ) { |
| 34 | + if ( $this->parseTimeString( $value ) === false ) { |
| 35 | + return false; |
| 36 | + } |
| 37 | + } |
21 | 38 | return true; |
22 | 39 | } |
23 | 40 | |
| 41 | + function parseTimeString( $seekString, $length = false ) { |
| 42 | + $parts = explode( ':', $seekString ); |
| 43 | + $time = 0; |
| 44 | + for ( $i = 0; $i < count( $parts ); $i++ ) { |
| 45 | + if ( !is_numeric( $parts[$i] ) ) { |
| 46 | + return false; |
| 47 | + } |
| 48 | + $time += intval( $parts[$i] ) * pow( 60, count( $parts ) - $i - 1 ); |
| 49 | + } |
| 50 | + |
| 51 | + if ( $time < 0 ) { |
| 52 | + wfDebug( __METHOD__.": specified negative time, using zero\n" ); |
| 53 | + $time = 0; |
| 54 | + } elseif ( $length !== false && $time > $length - 1 ) { |
| 55 | + wfDebug( __METHOD__.": specified near-end or past-the-end time {$time}s, using end minus 1s\n" ); |
| 56 | + $time = $length - 1; |
| 57 | + } |
| 58 | + return $time; |
| 59 | + } |
| 60 | + |
24 | 61 | function makeParamString( $params ) { |
25 | | - // No parameters just yet, the thumbnails are always full-size |
26 | | - return ''; |
27 | | - /* |
28 | | - $s = ''; |
29 | | - foreach ( $params as $name => $value ) { |
30 | | - if ( $s !== '' ) { |
31 | | - $s .= '-'; |
| 62 | + if ( isset( $params['thumbtime'] ) ) { |
| 63 | + $time = $this->parseTimeString( $params['thumbtime'] ); |
| 64 | + if ( $time !== false ) { |
| 65 | + return 'seek=' . $time; |
32 | 66 | } |
33 | | - $s .= "$name=$value"; |
34 | | - }*/ |
| 67 | + } |
| 68 | + return 'mid'; |
35 | 69 | } |
36 | 70 | |
37 | 71 | function parseParamString( $str ) { |
38 | | - // TODO |
| 72 | + $m = false; |
| 73 | + if ( preg_match( '/^seek=(\d+)$/', $str, $m ) ) { |
| 74 | + return array( 'thumbtime' => $m[0] ); |
| 75 | + } |
39 | 76 | return array(); |
40 | 77 | } |
41 | 78 | |
42 | 79 | function normaliseParams( $image, &$params ) { |
43 | | - // TODO |
| 80 | + if ( isset( $params['thumbtime'] ) ) { |
| 81 | + $length = $this->getLength( $image ); |
| 82 | + $time = $this->parseTimeString( $params['thumbtime'] ); |
| 83 | + if ( $time === false ) { |
| 84 | + return false; |
| 85 | + } elseif ( $time > $length - 1 ) { |
| 86 | + $params['thumbtime'] = $length - 1; |
| 87 | + } elseif ( $time <= 0 ) { |
| 88 | + $params['thumbtime'] = 0; |
| 89 | + } |
| 90 | + } |
| 91 | + |
44 | 92 | return true; |
45 | 93 | } |
46 | 94 | |
— | — | @@ -124,32 +172,52 @@ |
125 | 173 | function doTransform( $file, $dstPath, $dstUrl, $params, $flags = 0 ) { |
126 | 174 | global $wgFFmpegLocation; |
127 | 175 | |
128 | | - // Hack for miscellaneous callers |
129 | | - global $wgOut; |
130 | | - $this->setHeaders( $wgOut ); |
131 | | - |
132 | 176 | $width = $params['width']; |
133 | 177 | $srcWidth = $file->getWidth(); |
134 | 178 | $srcHeight = $file->getHeight(); |
135 | 179 | $height = $srcWidth == 0 ? $srcHeight : $width * $srcHeight / $srcWidth; |
136 | 180 | $length = $this->getLength( $file ); |
| 181 | + $noPlayer = isset( $params['noplayer'] ); |
137 | 182 | |
| 183 | + if ( !$noPlayer ) { |
| 184 | + // Hack for miscellaneous callers |
| 185 | + global $wgOut; |
| 186 | + $this->setHeaders( $wgOut ); |
| 187 | + } |
| 188 | + |
138 | 189 | if ( $srcHeight == 0 || $srcWidth == 0 ) { |
139 | 190 | // Make audio player |
140 | | - $icon = $file->iconThumb(); |
| 191 | + if ( $noPlayer ) { |
| 192 | + $scriptPath = self::getMyScriptPath(); |
| 193 | + return new ThumbnailImage( $file, "$scriptPath/info.png", 22, 22 ); |
| 194 | + } |
141 | 195 | if ( empty( $params['width'] ) ) { |
142 | 196 | $width = 200; |
143 | 197 | } else { |
144 | 198 | $width = $params['width']; |
145 | 199 | } |
146 | | - $height = $icon->getHeight(); |
147 | | - return new OggAudioDisplay( $file, $file->getURL(), $icon->getUrl(), $width, $height, $length ); |
| 200 | + $height = empty( $params['height'] ) ? 20 : $params['height']; |
| 201 | + return new OggAudioDisplay( $file, $file->getURL(), $width, $height, $length ); |
148 | 202 | } |
149 | 203 | |
| 204 | + // Video thumbnail only |
| 205 | + if ( $noPlayer ) { |
| 206 | + return new ThumbnailImage( $file, $dstUrl, $width, $height, $dstPath ); |
| 207 | + } |
| 208 | + |
150 | 209 | if ( $flags & self::TRANSFORM_LATER ) { |
151 | 210 | return new OggVideoDisplay( $file, $file->getURL(), $dstUrl, $width, $height, $length ); |
152 | 211 | } |
153 | 212 | |
| 213 | + $thumbTime = false; |
| 214 | + if ( isset( $params['thumbtime'] ) ) { |
| 215 | + $thumbTime = $this->parseTimeString( $params['thumbtime'], $length ); |
| 216 | + } |
| 217 | + if ( $thumbTime === false ) { |
| 218 | + # Seek to midpoint by default, it tends to be more interesting than the start |
| 219 | + $thumbTime = $length / 2; |
| 220 | + } |
| 221 | + |
154 | 222 | wfMkdirParents( dirname( $dstPath ) ); |
155 | 223 | |
156 | 224 | wfDebug( "Creating video thumbnail at $dstPath\n" ); |
— | — | @@ -159,14 +227,13 @@ |
160 | 228 | # MJPEG, that's the same as JPEG except it's supported by the windows build of ffmpeg |
161 | 229 | # No audio, one frame |
162 | 230 | ' -f mjpeg -an -vframes 1' . |
163 | | - # Seek to midpoint, it tends to be more interesting than the fade in at the start |
164 | | - ' -ss ' . intval( $length / 2 ) . ' ' . |
| 231 | + ' -ss ' . intval( $thumbTime ) . ' ' . |
165 | 232 | wfEscapeShellArg( $dstPath ) . ' 2>&1'; |
166 | 233 | |
167 | 234 | $retval = 0; |
168 | 235 | $returnText = wfShellExec( $cmd, $retval ); |
169 | 236 | |
170 | | - if ( $retval ) { |
| 237 | + if ( $this->removeBadFile( $dstPath, $retval ) || $retval ) { |
171 | 238 | // Filter nonsense |
172 | 239 | $lines = explode( "\n", str_replace( "\r\n", "\n", $returnText ) ); |
173 | 240 | if ( substr( $lines[0], 0, 6 ) == 'FFmpeg' ) { |
— | — | @@ -308,8 +375,13 @@ |
309 | 376 | } |
310 | 377 | } |
311 | 378 | |
| 379 | + static function getMyScriptPath() { |
| 380 | + global $wgScriptPath; |
| 381 | + return "$wgScriptPath/extensions/OggHandler"; |
| 382 | + } |
| 383 | + |
312 | 384 | function setHeaders( $out ) { |
313 | | - global $wgScriptPath, $wgOggScriptVersion, $wgCortadoJarFile; |
| 385 | + global $wgOggScriptVersion, $wgCortadoJarFile; |
314 | 386 | if ( $out->hasHeadItem( 'OggHandler' ) ) { |
315 | 387 | return; |
316 | 388 | } |
— | — | @@ -320,23 +392,24 @@ |
321 | 393 | 'ogg-player-videoElement', 'ogg-player-oggPlugin', 'ogg-player-cortado', 'ogg-player-vlc-mozilla', |
322 | 394 | 'ogg-player-vlc-activex', 'ogg-player-quicktime-mozilla', 'ogg-player-quicktime-activex', |
323 | 395 | 'ogg-player-thumbnail', 'ogg-player-selected', 'ogg-use-player', 'ogg-more', 'ogg-download', |
324 | | - 'ogg-desc-link', 'ogg-dismiss' ); |
| 396 | + 'ogg-desc-link', 'ogg-dismiss', 'ogg-player-soundthumb' ); |
325 | 397 | $msgValues = array_map( 'wfMsg', $msgNames ); |
326 | 398 | $jsMsgs = Xml::encodeJsVar( (object)array_combine( $msgNames, $msgValues ) ); |
327 | 399 | $cortadoUrl = $wgCortadoJarFile; |
| 400 | + $scriptPath = self::getMyScriptPath(); |
328 | 401 | if( substr( $cortadoUrl, 0, 1 ) != '/' |
329 | 402 | && substr( $cortadoUrl, 0, 4 ) != 'http' ) { |
330 | | - $cortadoUrl = "$wgScriptPath/extensions/OggHandler/$cortadoUrl"; |
| 403 | + $cortadoUrl = "$scriptPath/$cortadoUrl"; |
331 | 404 | } |
332 | 405 | $encCortadoUrl = Xml::encodeJsVar( $cortadoUrl ); |
333 | | - $encSmallFileUrl = Xml::encodeJsVar( "$wgScriptPath/extensions/OggHandler/null_file" ); |
| 406 | + $encExtPathUrl = Xml::encodeJsVar( $scriptPath ); |
334 | 407 | |
335 | 408 | $out->addHeadItem( 'OggHandler', <<<EOT |
336 | | -<script type="text/javascript" src="$wgScriptPath/extensions/OggHandler/OggPlayer.js?$wgOggScriptVersion"></script> |
| 409 | +<script type="text/javascript" src="$scriptPath/OggPlayer.js?$wgOggScriptVersion"></script> |
337 | 410 | <script type="text/javascript"> |
338 | 411 | wgOggPlayer.msg = $jsMsgs; |
339 | 412 | wgOggPlayer.cortadoUrl = $encCortadoUrl; |
340 | | -wgOggPlayer.smallFileUrl = $encSmallFileUrl; |
| 413 | +wgOggPlayer.extPathUrl = $encExtPathUrl; |
341 | 414 | </script> |
342 | 415 | <style type="text/css"> |
343 | 416 | .ogg-player-options { |
— | — | @@ -390,47 +463,110 @@ |
391 | 464 | |
392 | 465 | if ( substr( $this->videoUrl, 0, 4 ) != 'http' ) { |
393 | 466 | global $wgServer; |
394 | | - $encUrl = Xml::encodeJsVar( $wgServer . $this->videoUrl ); |
| 467 | + $url = $wgServer . $this->videoUrl; |
395 | 468 | } else { |
396 | | - $encUrl = Xml::encodeJsVar( $this->videoUrl ); |
| 469 | + $url = $this->videoUrl; |
397 | 470 | } |
398 | 471 | $length = intval( $this->length ); |
399 | 472 | $width = intval( $this->width ); |
400 | 473 | $height = intval( $this->height ); |
401 | | - $alt = empty( $options['alt'] ) ? '' : $options['alt']; |
402 | | - $attribs = array( 'src' => $this->url ); |
| 474 | + $alt = empty( $options['alt'] ) ? $this->file->getTitle()->getText() : $options['alt']; |
| 475 | + $scriptPath = OggHandler::getMyScriptPath(); |
| 476 | + $thumbDivAttribs = array(); |
| 477 | + $showDescIcon = false; |
403 | 478 | if ( $this->isVideo ) { |
404 | 479 | $msgStartPlayer = wfMsg( 'ogg-play-video' ); |
405 | | - $attribs['width'] = $width; |
406 | | - $attribs['height'] = $height; |
| 480 | + $imgAttribs = array( |
| 481 | + 'src' => $this->url, |
| 482 | + 'width' => $width, |
| 483 | + 'height' => $height ); |
407 | 484 | $playerHeight = $height; |
408 | 485 | } else { |
| 486 | + // Sound file |
| 487 | + if ( $height > 100 ) { |
| 488 | + // Use a big file icon |
| 489 | + global $wgScriptPath; |
| 490 | + $imgAttribs = array( |
| 491 | + 'src' => "$wgScriptPath/skins/common/images/icons/fileicon-ogg.png", |
| 492 | + 'width' => 125, |
| 493 | + 'height' => 125, |
| 494 | + ); |
| 495 | + } else { |
| 496 | + // make an icon later if necessary |
| 497 | + $imgAttribs = false; |
| 498 | + $showDescIcon = true; |
| 499 | + //$thumbDivAttribs = array( 'style' => 'text-align: right;' ); |
| 500 | + } |
409 | 501 | $msgStartPlayer = wfMsg( 'ogg-play-sound' ); |
410 | 502 | $playerHeight = 0; |
411 | | - // Don't add width and height to the icon image, it won't match its true size |
412 | 503 | } |
413 | 504 | |
414 | | - $thumb = Xml::element( 'img', $attribs, null ); |
| 505 | + // Set $thumb to the thumbnail img tag, or the thing that goes where |
| 506 | + // the thumbnail usually goes |
| 507 | + $descIcon = false; |
415 | 508 | if ( !empty( $options['desc-link'] ) ) { |
416 | 509 | $linkAttribs = $this->getDescLinkAttribs( $alt ); |
417 | | - $thumb = Xml::tags( 'a', $linkAttribs, $thumb ); |
418 | | - $encLink = Xml::encodeJsVar( $linkAttribs['href'] ); |
| 510 | + if ( $showDescIcon ) { |
| 511 | + // Make image description icon link |
| 512 | + $imgAttribs = array( |
| 513 | + 'src' => "$scriptPath/info.png", |
| 514 | + 'width' => 22, |
| 515 | + 'height' => 22 |
| 516 | + ); |
| 517 | + $linkAttribs['title'] = wfMsg( 'ogg-desc-link' ); |
| 518 | + $descIcon = Xml::tags( 'a', $linkAttribs, |
| 519 | + Xml::element( 'img', $imgAttribs, null ) ); |
| 520 | + $thumb = ''; |
| 521 | + } else { |
| 522 | + $thumb = Xml::tags( 'a', $linkAttribs, |
| 523 | + Xml::element( 'img', $imgAttribs, null ) ); |
| 524 | + } |
| 525 | + $linkUrl = $linkAttribs['href']; |
419 | 526 | } else { |
420 | 527 | // We don't respect the file-link option, click-through to download is not appropriate |
421 | | - $encLink = 'false'; |
| 528 | + $linkUrl = false; |
| 529 | + if ( $imgAttribs ) { |
| 530 | + $thumb = Xml::element( 'img', $imgAttribs, null ); |
| 531 | + } else { |
| 532 | + $thumb = ''; |
| 533 | + } |
422 | 534 | } |
423 | | - $thumb .= "<br/>\n"; |
424 | 535 | |
425 | 536 | $id = "ogg_player_" . OggTransformOutput::$serial; |
426 | 537 | |
427 | | - $s = Xml::tags( 'div', array( 'id' => $id, /*'align' => 'center',*/ 'style' => 'width: ' . $width . 'px' ), |
428 | | - $thumb . |
429 | | - Xml::element( 'button', |
430 | | - array( |
431 | | - 'onclick' => "wgOggPlayer.init(false, '$id', $encUrl, $width, $playerHeight, $length, $encLink);", |
432 | | - ), |
433 | | - $msgStartPlayer |
434 | | - ) |
| 538 | + $playerParams = Xml::encodeJsVar( (object)array( |
| 539 | + 'id' => $id, |
| 540 | + 'videoUrl' => $url, |
| 541 | + 'width' => $width, |
| 542 | + 'height' => $playerHeight, |
| 543 | + 'length' => $length, |
| 544 | + 'linkUrl' => $linkUrl, |
| 545 | + 'isVideo' => $this->isVideo ) ); |
| 546 | + |
| 547 | + $s = Xml::tags( 'div', |
| 548 | + array( |
| 549 | + 'id' => $id, |
| 550 | + 'style' => "width: {$width}px;" ), |
| 551 | + ( $thumb ? Xml::tags( 'div', array(), $thumb ) : '' ) . |
| 552 | + Xml::tags( 'div', array(), |
| 553 | + Xml::tags( 'button', |
| 554 | + array( |
| 555 | + 'onclick' => "wgOggPlayer.init(false, $playerParams);", |
| 556 | + 'style' => "width: {$width}px;", |
| 557 | + 'title' => $msgStartPlayer, |
| 558 | + ), |
| 559 | + Xml::element( 'img', |
| 560 | + array( |
| 561 | + 'src' => "$scriptPath/play.png", |
| 562 | + 'width' => 22, |
| 563 | + 'height' => 22, |
| 564 | + 'alt' => $msgStartPlayer |
| 565 | + ), |
| 566 | + null |
| 567 | + ) |
| 568 | + ) |
| 569 | + ) . |
| 570 | + ( $descIcon ? Xml::tags( 'div', array(), $descIcon ) : '' ) |
435 | 571 | ); |
436 | 572 | return $s; |
437 | 573 | } |
— | — | @@ -443,8 +579,8 @@ |
444 | 580 | } |
445 | 581 | |
446 | 582 | class OggAudioDisplay extends OggTransformOutput { |
447 | | - function __construct( $file, $videoUrl, $iconUrl, $width, $height, $length ) { |
448 | | - parent::__construct( $file, $videoUrl, $iconUrl, $width, $height, $length, false ); |
| 583 | + function __construct( $file, $videoUrl, $width, $height, $length ) { |
| 584 | + parent::__construct( $file, $videoUrl, false, $width, $height, $length, false ); |
449 | 585 | } |
450 | 586 | } |
451 | 587 | |
Index: trunk/extensions/OggHandler/README |
— | — | @@ -20,9 +20,19 @@ |
21 | 21 | |
22 | 22 | http://www.flumotion.net/cortado/ |
23 | 23 | |
| 24 | +We have patched Cortado to allow multiple instances to exist on the one page, |
| 25 | +the patch is in cortado-tweak.diff. The recompiled binary is at |
| 26 | +cortado-ovt-stripped-0.2.2.1-patched.jar . |
| 27 | + |
24 | 28 | The PEAR directory contains a fork of the File_Ogg package, licensed under the |
25 | 29 | LGPL. The stock File_Ogg will not work -- I have made many aggressive changes |
26 | 30 | including support for stream formats other than Vorbis. I'll try to get my |
27 | 31 | changes committed to the official PEAR tree at some point in the future. |
28 | 32 | |
| 33 | +The icons play.png, pause.png, stop.png and info.png are from the Crystal Project: |
| 34 | + |
| 35 | + http://www.everaldo.com/crystal/ |
| 36 | + |
| 37 | +They are licensed under the LGPL. |
| 38 | + |
29 | 39 | -- Tim Starling |
Index: trunk/extensions/OggHandler/OggHandler.i18n.php |
— | — | @@ -25,6 +25,7 @@ |
26 | 26 | 'ogg-player-quicktime-mozilla' => 'QuickTime', # only translate this message to other languages if you have to change it |
27 | 27 | 'ogg-player-quicktime-activex' => 'QuickTime (ActiveX)', # only translate this message to other languages if you have to change it |
28 | 28 | 'ogg-player-thumbnail' => 'Still image only', |
| 29 | + 'ogg-player-soundthumb' => 'No player', |
29 | 30 | 'ogg-player-selected' => '(selected)', |
30 | 31 | 'ogg-use-player' => 'Use player: ', |
31 | 32 | 'ogg-more' => 'More...', |
— | — | @@ -79,3 +80,10 @@ |
80 | 81 | 'ogg-player-oggPlugin' => 'Ogg-plugin', |
81 | 82 | ), |
82 | 83 | ); |
| 84 | + |
| 85 | +$magicWords = array( |
| 86 | + 'en' => array( |
| 87 | + 'ogg_noplayer' => array( 0, 'noplayer' ), |
| 88 | + 'ogg_thumbtime' => array( 0, 'thumbtime=$1' ), |
| 89 | + ), |
| 90 | +); |
Index: trunk/extensions/OggHandler/info.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: trunk/extensions/OggHandler/info.png |
___________________________________________________________________ |
Name: svn:mime-type |
83 | 91 | + image/png |