MediaWiki:Gadget-Video.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
const Video = {
init( $content ) {
const $videos = $content.find( '.template-video' );
// Give each video an id and an index
$videos.each( ( index, element ) => {
element.id = 'video-' + index;
element.dataset.index = index;
} );
// Replace plain timestamps for links
$videos.find( '.template-video-annotations' ).html( ( index, html ) => {
if ( /(\d\d?:\d\d)/.test( html ) ) {
return html.replace( /(\d\d?:\d\d)/g, '<a class="timestamp" href="#$1">$1</a>' );
}
} );
// Play the relevant video when a timestamp is clicked
$videos.find( '.template-video-annotations .timestamp' ).on( 'click', event => {
const $link = $( event.target );
const $video = $link.closest( '.template-video' );
const index = $video.data( 'index' );
const hash = $link.attr( 'href' );
if ( $video.find( 'iframe, video' ).length ) {
Video.playVideoAtTime( index, hash );
} else if ( $video.hasClass( 'youtube' ) ) {
Video.loadYouTubeVideo( event );
} else if ( $video.hasClass( 'vimeo' ) ) {
Video.loadVimeoVideo( event );
}
} );
// Add event listeners to highlight the relevant annotation
$videos.filter( '.appropedia, .commons' ).each( ( index, element ) => {
const $video = $( element );
const player = $video.find( 'video' )[0];
let previousTime = -1;
player.addEventListener( 'timeupdate', () => {
const currentTime = Math.floor( player.currentTime );
if ( currentTime === previousTime ) {
return;
}
previousTime = currentTime;
Video.highlightAnnotation( $video, currentTime );
} );
} );
// Load a YouTube video when a YouTube play button or thumbnail is clicked
$videos.filter( '.youtube' ).find( '.template-video-play, .template-video-thumb' ).on( 'click', Video.loadYouTubeVideo );
// Load a Vimeo video when a Vimeo play button or thumbnail is clicked
$videos.filter( '.vimeo' ).find( '.template-video-play, .template-video-thumb' ).on( 'click', Video.loadVimeoVideo );
},
loadYouTubeVideo( event ) {
const $video = $( event.target ).closest( '.template-video' );
// Make the iframe
const params = new URLSearchParams( {
autoplay: 1,
enablejsapi: 1,
rel: 0,
origin: window.location.origin
} );
const id = $video.data( 'video-id' );
const url = 'https://www.youtube.com/embed/' + id + '?' + params.toString();
const $iframe = $( '<iframe src="' + url + '" width="640" height="360" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>' );
// Update the DOM
$video.find( '.template-video-content' ).html( $iframe );
// Load the YouTube IFrame Player API code only once
if ( window.onYouTubeIframeAPIReady === undefined ) {
mw.loader.getScript( 'https://www.youtube.com/iframe_api' );
window.onYouTubeIframeAPIReady = () => Video.makeYouTubePlayer( $video );
} else {
Video.makeYouTubePlayer( $video );
}
},
async loadVimeoVideo( event ) {
const $video = $( event.target ).closest( '.template-video' );
// Make the iframe
const id = $video.data( 'video-id' );
const url = 'https://player.vimeo.com/video/' + id;
const $iframe = $( '<iframe src="' + url + '" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>' );
// Update the DOM
$video.find( '.template-video-content' ).html( $iframe );
// Load the Vimeo Player SDK code only once
if ( Video.isVimeoLoaded === undefined ) {
await mw.loader.getScript( 'https://player.vimeo.com/api/player.js' );
Video.isVimeoLoaded = true;
}
Video.makeVimeoPlayer( $video );
},
// This will hold both YouTube and Vimeo players
players: [],
// Create a YouTube player
makeYouTubePlayer( $video ) {
const $iframe = $video.find( 'iframe' );
const index = $video.data( 'index' );
const id = 'video-iframe-' + index;
$iframe.attr( 'id', id );
const player = new YT.Player( id, { origin: window.location.origin } );
Video.players[ index ] = player;
player.addEventListener( 'onReady', () => {
Video.playVideoAtTime( index );
// Highlight the relevant annotation
let previousTime = -1;
setInterval( () => {
const currentTime = Math.floor( player.getCurrentTime() );
if ( currentTime === previousTime ) {
return;
}
previousTime = currentTime;
Video.highlightAnnotation( $video, currentTime );
}, 1000 );
} );
},
// Create a Vimeo player
makeVimeoPlayer( $video ) {
const $iframe = $video.find( 'iframe' );
const index = $video.data( 'index' );
const iframe = $iframe[0];
const player = new Vimeo.Player( iframe );
Video.players[ index ] = player;
player.on( 'loaded', () => {
Video.playVideoAtTime( index );
// Highlight the relevant annotation
let previousTime = -1;
setInterval( async () => {
let currentTime = await player.getCurrentTime();
currentTime = Math.floor( currentTime );
if ( currentTime === previousTime ) {
return;
}
previousTime = currentTime;
Video.highlightAnnotation( $video, currentTime );
}, 1000 );
} );
},
// Play the specified video at the specified time
playVideoAtTime( index, hash ) {
// Figure out the intended video and time
if ( !Number.isInteger( index ) ) {
index = 0;
}
if ( !hash ) {
hash = window.location.hash;
}
const time = hash.substr( 1 ).split( ':' );
const minutes = time[1] ? parseInt( time[0], 10 ) : 0;
const seconds = time[1] ? parseInt( time[1], 10 ) + minutes * 60 : parseInt( time[0], 10 );
if ( !Number.isInteger( seconds ) ) {
return;
}
const $video = $( '#video-' + index );
// Play the specified video at the specified time
let player;
if ( $video.hasClass( 'youtube' ) ) {
player = Video.players[ index ];
player.seekTo( seconds );
player.playVideo();
} else if ( $video.hasClass( 'vimeo' ) ) {
player = Video.players[ index ];
player.setCurrentTime( seconds );
player.play();
} else {
player = $video.find( 'video' )[0];
player.currentTime = seconds;
player.play();
}
// Center the video
$video.find( '.template-video-content' )[0].scrollIntoView( { behavior: 'smooth', block: 'center' } );
},
// Find the relevant annotation and highlight it
highlightAnnotation( $video, currentTime ) {
$video.find( '.template-video-annotations a' ).each( ( index, element ) => {
const $link = $( element );
const href = $link.attr( 'href' );
const time = href.substr( 1 ).split( ':' );
const minutes = time[1] ? parseInt( time[0], 10 ) : 0;
const seconds = time[1] ? parseInt( time[1], 10 ) + minutes * 60 : parseInt( time[0], 10 );
const $item = $link.closest( 'li' );
if ( currentTime === seconds ) {
const $annotations = $item.closest( '.template-video-annotations' );
$annotations.find( '.highlight' ).removeClass( 'highlight' );
$item.addClass( 'highlight' );
$annotations.find( '.mw-collapsible' ).hide();
if ( $item.is( ':last-child' ) ) {
$item.closest( 'ul' ).next( '.mw-collapsible' ).show();
}
return false; // Break the loop
}
} );
}
};
mw.hook( 'wikipage.content' ).add( Video.init );