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