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)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
 * This script interacts with [[Template:Video]]
 * 
 * Most of the complexity of this script stems from the fact that it's designed
 * to handle videos from YouTube, Vimeo, Commons or Appropedia
 * 
 * [[Category:Template script pages]]
 */
window.TemplateVideo = {

	init: function () {
		var $videos = $( '.template-video' );

		// Give each video an id and an index
		$videos.each( function ( index ) {
			$( this ).attr( {
				'id': 'video-' + index,
				'data-video-index': index
			} );
		} );

		// Replace plain timestamps for links
		$videos.find( '.template-video-annotations' ).html( function ( 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', function () {
			var $link = $( this );
			var $video = $link.closest( '.template-video' );
			var index = $video.data( 'video-index' );
			var hash = $link.attr( 'href' );
			if ( $video.find( 'iframe, video' ).length ) {
				TemplateVideo.playVideoAtTime( index, hash );
			} else if ( $video.hasClass( 'youtube' ) ) {
				TemplateVideo.loadYouTubeVideo.bind( this )();
			} else if ( $video.hasClass( 'vimeo' ) ) {
				TemplateVideo.loadVimeoVideo.bind( this )();
			}
		} );

		// Add event listeners to highlight the relevant annotation
		$videos.filter( '.appropedia, .commons' ).each( function () {
			var $video = $( this );
			var player = $video.find( 'video' )[0];
			var previousTime = -1;
			player.addEventListener( 'timeupdate', function () {
				var currentTime = player.currentTime;
				currentTime = Math.floor( currentTime );
				if ( currentTime === previousTime ) {
					return;
				}
				previousTime = currentTime;
				TemplateVideo.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', TemplateVideo.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', TemplateVideo.loadVimeoVideo );
	},

	isYouTubeLoaded: false,
	loadYouTubeVideo: function () {
		var $video = $( this ).closest( '.template-video' );

		// Make the iframe
		var params = new URLSearchParams( {
			autoplay: 1,
			enablejsapi: 1,
			rel: 0,
			origin: window.location.origin
		} );
		var id = $video.data( 'video-id' );
		var url = 'https://www.youtube.com/embed/' + id + '?' + params.toString();
		var $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 ( TemplateVideo.isYouTubeLoaded === false ) {
			$.getScript( 'https://www.youtube.com/iframe_api' );
			window.onYouTubeIframeAPIReady = function () {
				TemplateVideo.isYouTubeLoaded = true;
				TemplateVideo.makeYouTubePlayer( $video );
			};	
		} else {
			TemplateVideo.makeYouTubePlayer( $video );
		}
	},

	isVimeoLoaded: false,
	loadVimeoVideo: function () {
		var $video = $( this ).closest( '.template-video' );

		// Make the iframe
		var id = $video.data( 'video-id' );
		var url = 'https://player.vimeo.com/video/' + id;
		var $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 ( TemplateVideo.isVimeoLoaded === false ) {
			$.getScript( 'https://player.vimeo.com/api/player.js' ).done( function () {
				TemplateVideo.isVimeoLoaded = true;
				TemplateVideo.makeVimeoPlayer( $video );
			} );
		} else {
			TemplateVideo.makeVimeoPlayer( $video );
		}
	},

	// This will hold both YouTube and Vimeo players
	players: [],

	// Create a YouTube player
	makeYouTubePlayer: function ( $video ) {
		var $iframe = $video.find( 'iframe' );
		var index = $video.data( 'video-index' );
		var id = 'video-iframe-' + index;
		$iframe.attr( 'id', id );
		var player = new YT.Player( id, { origin: window.location.origin } );
		TemplateVideo.players[ index ] = player;

		player.addEventListener( 'onReady', function () {
			TemplateVideo.playVideoAtTime( index );

			// Highlight the relevant annotation
			var previousTime = -1;
			setInterval( function () {
				var currentTime = player.getCurrentTime();
				currentTime = Math.floor( currentTime );
				if ( currentTime === previousTime ) {
					return;
				}
				previousTime = currentTime;
				TemplateVideo.highlightAnnotation( $video, currentTime );
			}, 1000 );
		} );
	},

	// Create a Vimeo player
	makeVimeoPlayer: function ( $video ) {
		var $iframe = $video.find( 'iframe' );
		var index = $video.data( 'video-index' );
		var iframe = $iframe[0];
		var player = new Vimeo.Player( iframe );
		TemplateVideo.players[ index ] = player;

		player.on( 'loaded', function () {
			TemplateVideo.playVideoAtTime( index );

			// Highlight the relevant annotation
			var previousTime = -1;
			setInterval( function () {
				player.getCurrentTime().then( function ( currentTime ) {
					currentTime = Math.floor( currentTime );
					if ( currentTime === previousTime ) {
						return;
					}
					previousTime = currentTime;
					TemplateVideo.highlightAnnotation( $video, currentTime );
				} );
			}, 1000 );
		} );
	},

	// Play the specified video at the specified time
	playVideoAtTime: function ( index, hash ) {

		// Figure out the intended video and time
		if ( !Number.isInteger( index ) ) {
			index = 0;
		}
		if ( !hash ) {
			hash = window.location.hash;
		}
		var time = hash.substr( 1 ).split( ':' );
		var minutes = time[1] ? parseInt( time[0], 10 ) : 0;
		var seconds = time[1] ? parseInt( time[1], 10 ) + minutes * 60 : parseInt( time[0], 10 );
		if ( !Number.isInteger( seconds ) ) {
			return;
		}
		var $video = $( '#video-' + index );

		// Play the specified video at the specified time
		var player;
		if ( $video.hasClass( 'youtube' ) ) {
			player = TemplateVideo.players[ index ];
			player.seekTo( seconds );
			player.playVideo();
		} else if ( $video.hasClass( 'vimeo' ) ) {
			player = TemplateVideo.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: function ( $video, currentTime ) {
		$video.find( '.template-video-annotations a' ).each( function () {
			var $link = $( this );
			var href = $link.attr( 'href' );
			var time = href.substr( 1 ).split( ':' );
			var minutes = time[1] ? parseInt( time[0], 10 ) : 0;
			var seconds = time[1] ? parseInt( time[1], 10 ) + minutes * 60 : parseInt( time[0], 10 );
			var $item = $link.closest( 'li' );
			if ( currentTime === seconds ) {
				var $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
			}
		} );
	}
};

$( TemplateVideo.init );
Cookies help us deliver our services. By using our services, you agree to our use of cookies.