Brion VIBBER has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/165479

Change subject: Work in progress: ogv.js media player for Safari/IE (3 of 3)
......................................................................

Work in progress: ogv.js media player for Safari/IE (3 of 3)

Mobile video/audio-player overlay, supporting both native and
ogv.js JavaScript-based playback (used for Safari).

* currently appears to be broken due to changes in MobileFrontend *

Note this doesn't work in IE for Windows Phone, which currently
lacks both Web Audio and Flash support.

Bug: 61823
Change-Id: Ib0083dc6b50eb91adb9e9624a5e90448860441e2
---
M TimedMediaHandler.hooks.php
M TimedMediaHandler_body.php
M i18n/en.json
M i18n/qqq.json
A resources/ext.tmh.mobile.MediaOverlay.js
A resources/ext.tmh.mobile.MediaOverlay.less
A resources/ext.tmh.mobile.css
A resources/ext.tmh.mobile.js
8 files changed, 480 insertions(+), 2 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/TimedMediaHandler 
refs/changes/79/165479/1

diff --git a/TimedMediaHandler.hooks.php b/TimedMediaHandler.hooks.php
index 7ab2230..8771157 100644
--- a/TimedMediaHandler.hooks.php
+++ b/TimedMediaHandler.hooks.php
@@ -89,6 +89,44 @@
                                'loaderScripts' => 
'resources/mw.MediaWikiPlayer.loader.js',
                        ),
                );
+
+               // Add OgvJs-related modules common to desktop Safari/IE and 
mobile Safari
+               $wgResourceModules += array(
+                       'ext.tmh.OgvJsSupport' => $baseExtensionResource + 
array(
+                               'scripts' => 
'resources/ext.tmh.OgvJsSupport.js',
+                               'targets' => array( 'mobile', 'desktop' ),
+                       ),
+               );
+
+               // Add the MobileFrontend-specific lightweight player
+               $wgResourceModules += array(
+                       'ext.tmh.mobile' => $baseExtensionResource + array(
+                               'scripts' => 'resources/ext.tmh.mobile.js',
+                               'styles' => array(
+                                       'resources/ext.tmh.mobile.css',
+                                       'resources/PopUpThumbVideo.css',
+                               ),
+                               'dependencies' => array(
+                                       'mobile.startup',
+                                       'ext.tmh.OgvJsSupport',
+                               ),
+                               'targets' => array( 'mobile' ),
+                       ),
+               );
+               $wgResourceModules += array(
+                       'ext.tmh.mobile.MediaOverlay' => $baseExtensionResource 
+ array(
+                               'scripts' => 
'resources/ext.tmh.mobile.MediaOverlay.js',
+                               'styles' => 
'resources/ext.tmh.mobile.MediaOverlay.less',
+                               'dependencies' => array(
+                                       'ext.tmh.mobile',
+                                       'mobile.overlays',
+                                       'mediawiki.Title',
+                               ),
+                               'messages' => 
'timedmedia-mobile-overlay-details',
+                               'targets' => array( 'mobile' ),
+                       ),
+               );
+
                // Setup a hook for iframe embed handling:
                $wgHooks['ArticleFromTitle'][] = 
'TimedMediaIframeOutput::iframeHook';
 
@@ -396,6 +434,7 @@
                if ( $addModules ) {
                        $out->addModuleScripts( 'mw.PopUpMediaTransform' );
                        $out->addModuleStyles( 'mw.PopUpMediaTransform' );
+                       $out->addModules( 'ext.tmh.mobile' );
                }
 
                return true;
diff --git a/TimedMediaHandler_body.php b/TimedMediaHandler_body.php
index 9bd172a..4283041 100644
--- a/TimedMediaHandler_body.php
+++ b/TimedMediaHandler_body.php
@@ -217,6 +217,7 @@
                $outputPage->addModuleScripts( 'mw.PopUpMediaTransform' );
                $outputPage->addModuleStyles( 'mw.PopUpMediaTransform' );
                $outputPage->addModules( 'mw.TMHGalleryHook.js' );
+               $outputPage->addModules( 'ext.tmh.mobile' );
        }
 
        /**
diff --git a/i18n/en.json b/i18n/en.json
index 63bebfc..0459ab9 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -115,5 +115,6 @@
     "orphanedtimedtext": "Orphaned TimedText pages",
     "orphanedtimedtext-summary": "List of 
[[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] pages which do not have a 
corresponding file.",
     "orphanedtimedtext-unsupported": "This special page is only supported on 
MySQL databases.",
-    "orphanedtimedtext-notimedtext": "TimedText is not enabled on this wiki."
+    "orphanedtimedtext-notimedtext": "TimedText is not enabled on this wiki.",
+    "timedmedia-mobile-overlay-details": "Details"
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 664b3cf..4c6f1ff 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -118,5 +118,6 @@
        "orphanedtimedtext": "{{doc-special|OrphanedTimedText}}",
        "orphanedtimedtext-summary": "Summary of Special:OrphanedTimedText.",
        "orphanedtimedtext-unsupported": "Shown if Special:OrphanedTimedText 
isn't supported for the current database back-end.",
-       "orphanedtimedtext-notimedtext": "Shown on Special:OrphanedTimedText if 
the TimedText namespace is disabled on that wiki (i.e. $wgEnableLocalTimedText 
is set to false). Note, do not use <nowiki>{{ns:TimedText}}</nowiki> in this 
message."
+       "orphanedtimedtext-notimedtext": "Shown on Special:OrphanedTimedText if 
the TimedText namespace is disabled on that wiki (i.e. $wgEnableLocalTimedText 
is set to false). Note, do not use <nowiki>{{ns:TimedText}}</nowiki> in this 
message.",
+    "timedmedia-mobile-overlay-details": "Button label on mobile media player 
overlay that links to the file detail page."
 }
diff --git a/resources/ext.tmh.mobile.MediaOverlay.js 
b/resources/ext.tmh.mobile.MediaOverlay.js
new file mode 100644
index 0000000..47209ca
--- /dev/null
+++ b/resources/ext.tmh.mobile.MediaOverlay.js
@@ -0,0 +1,239 @@
+( function( $, mw, M ) {
+
+       /**
+        * Transform a video or audio element into an ogvjs player
+        * if required and appropriate, otherwise leave it be.
+        *
+        * @param element video or audio element to transform
+        * @return promise fulfilled with the original or transformed media 
element
+        */
+       function transformMediaElementsForOgvJs( element ) {
+               var result = $.Deferred();
+
+               // First check for media sources that we *can't* play natively
+               var needOgvJs = false;
+
+               nativeSources = 0;
+               nonNativeSources = 0;
+               $( element ).find( 'source' ).each( function( j, source ) {
+                       var canPlay = element.canPlayType( source.type ) ;
+                       if ( canPlay == 'probably' || canPlay == 'maybe' ) {
+                               nativeSources++;
+                       } else {
+                               nonNativeSources++;
+                       }
+               } );
+               if (nativeSources == 0 && nonNativeSources >= 0) {
+                       needOgvJs = true;
+               }
+       
+               // Only load the magic loader if necessary
+               if ( needOgvJs ) {
+                       var ext = mw.config.get( 'wgExtensionAssetsPath' ),
+                               base = ext + 
'/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js',
+                               script = base + '/ogvjs.js?version=0.1.5';
+
+                       function transformMediaElement( element ) {
+
+                               var ogvjs = new OgvJsPlayer({
+                                       base: base,
+                                       audioContext: M.tmh.initAudioContext()
+                               });
+                               ogvjs.id = element.id;
+                       
+                               if (element.getAttribute('poster')) {
+                                       ogvjs.poster = 
element.getAttribute('poster');
+                               }
+                       
+                               var width, height;
+                               if (element.tagName == 'AUDIO') {
+                                       // Size to the button
+                                       width = 128;
+                                       height = 128;
+                               } else {
+                                       width = parseInt(element.style.width);
+                                       height = parseInt(element.style.height);
+                                       var contentWidth = 
$('#content').width();
+                       
+                                       if ( width > contentWidth ) {
+                                               // fit on screen :P
+                                               height = Math.round( height * 
contentWidth / width );
+                                               width = contentWidth;
+                                       }
+                               }
+                               ogvjs.width = width;
+                               ogvjs.height = height;
+                       
+                               var durationHint = element.getAttribute( 
'data-durationhint' );
+                               if ( durationHint ) {
+                                       ogvjs.durationHint = parseFloat( 
durationHint );
+                               }
+
+                               // Fixme pick size based on processor speed?
+                               var sources = [];
+                               $( element ).find( 'source' ).each( function( 
i, source ) {
+                                       var canPlay = ogvjs.canPlayType( 
source.type ) ;
+                                       if ( canPlay == 'probably' || canPlay 
== 'maybe' ) {
+                                               sources.push(source.src);
+                                       }
+                               } );
+                               ogvjs.src = sources[0];
+
+                               // Set up some basic controls
+                               var playIcon = 'url(' + ext + 
'/TimedMediaHandler/resources/player_big_play_button.png)';
+                               var playButton = document.createElement( 
'button' );
+                               playButton.style.appearance = 'none';
+                               playButton.style.webKitAppearance = 'none';
+                               playButton.style.display = 'block';
+                               playButton.style.position = 'absolute';
+                               playButton.style.left = '0';
+                               playButton.style.right = '0';
+                               playButton.style.width = width + 'px';
+                               playButton.style.height = height + 'px';
+                               playButton.style.backgroundImage = playIcon;
+                               playButton.style.backgroundRepeat = 'no-repeat';
+                               playButton.style.backgroundPosition = 'center 
center';
+                               playButton.addEventListener( 'click', 
function() {
+                                       if ( ogvjs.paused ) {
+                                               ogvjs.play();
+                                               
playButton.style.backgroundImage = 'none';
+                                       } else {
+                                               ogvjs.pause();
+                                               
playButton.style.backgroundImage = playIcon;
+                                       }
+                               });
+                               
+                               // quick hack
+                               if ( M.tmh.autoplay ) {
+                                       playButton.style.backgroundImage = 
'none';
+                               }
+
+                               ogvjs.appendChild(playButton); // hacky?
+
+                               element.parentNode.replaceChild(ogvjs, element);
+
+                               return ogvjs;
+                       }
+
+                       $.ajax({
+                               url: script,
+                               dataType: 'script',
+                               cacheable: true
+                       }).done( function() {
+                               var transformed = transformMediaElement( 
element );
+                               result.resolve( transformed );
+                       });
+               } else {
+                       result.resolve( element );
+               }
+
+               return result;
+       }
+
+       var Overlay = M.require( 'Overlay' );
+
+       /**
+        * @class MediaOverlay
+        * @extends Overlay
+        */
+       var MediaOverlay = Overlay.extend( {
+               className: 'overlay timedmedia-viewer',
+               closeOnBack: true,
+
+               template: '<div class="main">\n' +
+                       '       <div class="media-wrapper loading">\n' +
+                       '               <div class="media-inner-wrapper">\n' +
+                       '                       <div class="media"></div>\n' +
+                       '               </div>\n' +
+                       '       </div>\n' +
+                       '       <button class="cancel icon 
icon-cancel-light">{{closeMsg}}</button>\n' +
+                       '       <div class="details">\n' +
+                       '               <a class="mw-ui-button 
mw-ui-progressive button" href="#">{{detailsMsg}}</a>\n' +
+                       '       </div>\n' +
+                       '</div>',
+
+               defaults: {
+                       headerButtonsListClassName: '',
+                       closeMsg: mw.msg( 'mobile-frontend-overlay-close' ),
+                       detailsMsg: mw.msg( 'timedmedia-mobile-overlay-details' 
),
+                       fixedHeader: true
+               },
+
+               postRender: function( options ) {
+                       var self = this;
+                       this._super();
+
+                       var $target = $( document.getElementById( 
this.options.title ) );
+                       var $content;
+                       
+                       if ( $target.is( 'audio, video' ) ) {
+                               $content = $target.clone();
+                       } else if ( $target.attr( 'videopayload' ) ) {
+                               $content = $( '<div></div>' )
+                                       .html( $target.attr( 'videopayload' ) )
+                                       .find( 'audio, video' );
+                       } else {
+                               console.log( $target[0] );
+                               throw new Error( 'Invalid media player loaded' 
);
+                       }
+
+                       // Save the aspect ratio...
+                       this.thumbWidth = parseInt( $content.css( 'width' ) );
+                       this.thumbHeight = parseInt( $content.css( 'height' ) );
+                       this.imgRatio = this.thumbWidth / this.thumbHeight;
+
+                       var title = $content.data( 'mwtitle' ),
+                               titleObj = new mw.Title( 'File:' + title ),
+                               link = titleObj.getUrl();
+                       this.$el.find( '.details a' ).attr( 'href', link );
+
+                       transformMediaElementsForOgvJs( $content[0] ).done( 
function( element ) {
+                               self.$el.find( '.media-wrapper' ).removeClass( 
'loading' );
+                               self.$el.find( '.media' ).append( element );
+                               
+                               if ( M.tmh.autoplay ) {
+                                       element.play();
+                                       M.tmh.autoplay = false;
+                               }
+                       } );
+
+                       this._positionImage();
+
+                       $( window ).on( 'resize', $.proxy( this, 
'_positionImage' ) );
+               },
+
+               show: function() {
+                       this._super();
+                       this._positionImage();
+               },
+
+               _positionImage: function() {
+                       var detailsHeight = this.$( '.details' ).height(),
+                               windowWidth = $( window ).width(),
+                               windowHeight = $( window ).height() - 
detailsHeight,
+                               windowRatio = windowWidth / windowHeight,
+                               $media = this.$el.find( 'audio,video,ogvjs' );
+
+                       // display: table (which we use for vertical centering) 
makes the overlay
+                       // expand so simply setting width/height to 100% 
doesn't work
+                       if ( this.imgRatio > windowRatio ) {
+                               if ( windowWidth < this.thumbWidth ) {
+                                       $media.css( {
+                                               width: windowWidth,
+                                               height: Math.round( 
this.thumbHeight * windowWidth / this.thumbWidth )
+                                       } );
+                               }
+                       } else {
+                               if ( windowHeight < this.thumbHeight ) {
+                                       $media.css( {
+                                               width: Math.round( 
this.thumbWidth * windowHeight / this.thumbHeight ),
+                                               height: windowHeight
+                                       } );
+                               }
+                       }
+                       this.$el.find( '.media-wrapper' ).css( 'bottom', 
detailsHeight );
+               }
+       } );
+       M.define( 'modules/tmh/MediaOverlay', MediaOverlay );
+       
+} )( jQuery, mw, mw.mobileFrontend );
diff --git a/resources/ext.tmh.mobile.MediaOverlay.less 
b/resources/ext.tmh.mobile.MediaOverlay.less
new file mode 100644
index 0000000..86b14e9
--- /dev/null
+++ b/resources/ext.tmh.mobile.MediaOverlay.less
@@ -0,0 +1,66 @@
+.overlay.timedmedia-viewer {
+       background: #000;
+       padding-top: 0;
+
+       .main {
+               height: auto;
+               position: relative;
+               width: 100%;
+       }
+
+       .media-wrapper {
+               position: fixed;
+               left: 0;
+               right: 0;
+               top: 0;
+       }
+
+       .media-inner-wrapper {
+               width: 100%;
+               height: 100%;
+               display: table;
+               text-align: center;
+
+               .media {
+                       display: table-cell;
+                       vertical-align: middle;
+                       height: 100%;
+                       width: 100%;
+               }
+       }
+
+       video, audio, ogvjs {
+               // get rid of a small bottom margin and vertical scrollbar
+               vertical-align: bottom;
+
+               &.ratio-width {
+                       width: 100% !important;
+                       height: auto !important;
+               }
+
+               &.ratio-height {
+                       height: 100% !important;
+                       width: auto !important;
+               }
+       }
+
+       // FIXME: change styling after overlay overhaul
+       .cancel {
+               position: absolute;
+               right: 10px;
+               top: 10px;
+       }
+       .details {
+               bottom: 0;
+               height: auto;
+               position: fixed;
+               width: 100%;
+               padding: 8px 10px;
+               margin: 0;
+               background: white;
+
+               .button {
+                       float: right;
+               }
+       }
+}
diff --git a/resources/ext.tmh.mobile.css b/resources/ext.tmh.mobile.css
new file mode 100644
index 0000000..e03836b
--- /dev/null
+++ b/resources/ext.tmh.mobile.css
@@ -0,0 +1,21 @@
+/* Force these to fit onscreen */
+.mediaContainer {
+       max-width: 100% !important;
+}
+.mediaContainer video {
+       max-width: 100% !important;
+       height: 100% !important;
+}
+.PopUpMediaTransform {
+       max-width: 100% !important;
+       text-align: center;
+}
+.PopUpMediaTransform img {
+       max-width: 100% !important;
+       height: auto !important;
+}
+
+.timedmedia-mobile-viewer video {
+       max-width: 100% !important;
+       height: auto !important;
+}
diff --git a/resources/ext.tmh.mobile.js b/resources/ext.tmh.mobile.js
new file mode 100644
index 0000000..c651206
--- /dev/null
+++ b/resources/ext.tmh.mobile.js
@@ -0,0 +1,110 @@
+( function( $, mw, M ) {
+
+       // Globalish state for the lightweight mobile media player
+       M.tmh = {
+               autoplay: false,
+               audioContext: null,
+               initAudioContext: function() {
+                       if ( !M.tmh.audioContext ) {
+                               M.tmh.audioContext = 
mw.OgvJsSupport.initAudioContext();
+                       }
+                       return M.tmh.audioContext;
+               }
+       };
+
+       function autoplayClick( ev ) {
+               ev.preventDefault();
+
+               // Use a single audio context for all players,
+               // and pre-initialize it from event handler on iOS
+               // to work around the restrictions on auto playback.
+               M.tmh.initAudioContext();
+               M.tmh.autoplay = true;
+
+               M.router.navigate( this.getAttribute( 'href' ) );
+       }
+
+       function transformMediaElementsForPopup( container ) {
+               var $container = $( container ),
+                       $popupTransforms = $container.find( 
'.PopUpMediaTransform' ),
+                       $mediaContainers = $container.find( '.mediaContainer' );
+               
+               // Transform the target URL for existing popup players
+               $popupTransforms.each( function( i, el ) {
+                       var $popupTransform = $( el ),
+                               id = $popupTransform.attr( 'id' ),
+                               link = '#/media/' + id;
+                       $popupTransform
+                               .find( 'a' )
+                                       .attr( 'href', link )
+                                       .attr( 'target', '' )
+                                       .off()
+                                       .on( M.tapEvent( 'click' ), 
autoplayClick )
+                                       .end();
+               } );
+               
+               // Emulate the popup transform done in TMH for large players,
+               // since we want to encapsulate all media in popups for mobile.
+               $mediaContainers.each( function( i, el ) {
+                       var $mediaContainer = $( el ),
+                               $media = $mediaContainer.find( 'audio,video' ),
+                               id = $media.attr( 'id' ),
+                               link = '#/media/' + id,
+                               poster = $media.attr( 'poster' );
+                       
+                       $media.attr( 'id', null );
+                       $mediaContainer
+                               .attr( 'videopayload', $mediaContainer.html() )
+                               .html( '<img><a><span 
class="play-btn-large"><span class="mw-tmh-playtext"></span></span></a>' )
+                               .addClass( 'PopUpMediaTransform' )
+                               .attr( 'id', id )
+                               .find( 'img' )
+                                       .attr( 'src', poster )
+                                       .end()
+                               .find( 'a' )
+                                       .attr( 'href', link )
+                                       .off()
+                                       .on( M.tapEvent( 'click' ), 
autoplayClick )
+                                       .end();
+                       
+               } );
+       }
+
+       function loadMediaOverlay( title ) {
+               console.log('loading', title);
+               var result = $.Deferred();
+               
+               mw.loader.using( 'ext.tmh.mobile.MediaOverlay', function() {
+
+                       var MediaOverlay = M.require( 
'modules/tmh/MediaOverlay' );
+                       var overlay = new MediaOverlay( {
+                               title: decodeURIComponent( title )
+                       } );
+
+                       // ogv.js does not currently stop automatically
+                       // when you detach it from the DOM.
+                       overlay.on( 'hide', function() {
+                               console.log('hiding');
+                               console.log(overlay);
+                               var player = overlay.$el.find( 
'audio,video,ogvjs' )[0];
+                               if (player && player.stop ) {
+                                       player.stop();
+                               }
+                       } );
+
+                       result.resolve( overlay );
+               } );
+
+               return result;
+       }
+       M.overlayManager.add( /^\/media\/(.+)$/, loadMediaOverlay );
+       
+       // First, transform any live players immediately...
+       transformMediaElementsForPopup( $( '#content_wrapper' ) );
+
+       M.on( 'page-loaded', function( page ) {
+               // And if we dynamically load a new page...
+               transformMediaElementsForPopup( $( '#content_wrapper' ) );
+       } );
+
+} )( jQuery, mw, mw.mobileFrontend );

-- 
To view, visit https://gerrit.wikimedia.org/r/165479
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ib0083dc6b50eb91adb9e9624a5e90448860441e2
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/TimedMediaHandler
Gerrit-Branch: master
Gerrit-Owner: Brion VIBBER <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to