Jump to content

MediaWiki:Gadget-Video.js

From Appropedia

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 );
Cookies help us deliver our services. By using our services, you agree to our use of cookies.