User:Valerio Bozzolan/PlayerGoodies.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
/**
 * This is a MediaWiki gadget to extend the functionalities of
 * the default player.
 *
 * Some features:
 *  - allow to alert when closing a page while a file is playing
 *  - allow to check a file as already listened/seen
 *  - allow to restart from the previous point
 *
 */

/**
 * Eventually initialize the plugin
 * 
 * This allows to inherit users' configuration
 */
window.PlayerGoodies = window.PlayerGoodies || {};

/**
 * Wrapper for the gadget
 */
( function( mw, $, PlayerGoodies ) {

	/**
	 * Default configuration for the PlayerGoodies plugin
	 */
	var DEFAULT_CONFIG = {
		
	};

	// try to inherit user's configuration or just initialize an empty one
	PlayerGoodies.config = PlayerGoodies.config || {};

	// merge the user configuration with the default configuration
	$.extend( PlayerGoodies.config, DEFAULT_CONFIG );


	/*
	 * UTILITIES
	 */

	/**
	 * Event fired when the player is ended
	 */
	function onPlayerEnded() {
		alert("ended");
	}

	/**
	 * Event fired when the player is paused
	 */
	function onPlayerPaused() {
		alert("ended" );
	}

	/**
	 * Event fired when the player is played
	 */
	function onPlayerPlay() {
		alert("play");
	}


	/*
	 * CLASSES
	 */

	/**
	 * Constructor for a PlayerGoodieFile
	 *
	 * Remember to call .init()
	 */
	var PlayerGoodieFile = function() {
		// this.$playBtn
		// this.$container
		// this.$audio
	};

	/**
	 * Initialize an audio element
	 */
	PlayerGoodieFile.init = function() {

		// eventually find the container
		if( !this.$container ) {

			// no play button no party
			if( !this.$playBtn || !this.$playBtn.length ) {
				throw 'missing this.$playBtn - oh nose!';
			}

			this.$container = this.$playBtn.closest( '.mediaContainer' );
		}

		// eventually find the audio file
		if( !this.$audio ) {
			this.$audio = this.$container.find( 'audio' );
		}

		// no audio no party
		var $audio = this.$audio;
		if( !$audio || !$audio.length ) {
			throw 'missing this.$audio - oh-nose!';
		}

		// register some events on the <audio> element
		$audio.on( 'play',   PlayerGoodie.onPlay   );
		$audio.on( 'ended',  PlayerGoodie.onEnded  );
		$audio.on( 'paused', PlayerGoodie.onPaused );
	};

	/**
	 * Trigger the internal play callback
	 */
	PlayerGoodieFile.prototype.triggerInternalPlay = function() {

		onPlayerPlay.bind( this.$audio )();
	};

	/*
	 * CORE
	 */

	/**
	 * Check if the page has an audio file
	 *
	 * @return {boolean}
	 */
	PlayerGoodies.hasAudioFile = function() {
		return $( 'audio' ).length > 0;
	};

	/**
	 * Check if the page has a video file
	 *
	 * @return {boolean}
	 */
	PlayerGoodies.hasVideoFile = function() {
		return $( 'video' ).length > 0;
	};

	/**
	 * Check if the page has a multimedia file
	 *
	 * @return {boolean}
	 */
	PlayerGoodies.hasMultimediaFile = function() {
		return PlayerGoodies.hasVideoFile() || PlayerGoodies.hasAudioFile();
	};

	/**
	 * Initialize and run the plugin
	 */ 
	PlayerGoodies.init = function() {

		// check if it has sense to start
		if( !PlayerGoodies.hasMultimediaFile() ) {
			return;
		}

		// do not proceed with weird pages
		if( mw.config.get( 'wgArticleId' ) < 0 ) {
			return;
		}

		// require missing modules
		mw.loader.using( 'mediawiki.api', function () {

			// now we can start the party
			PlayerGoodies.run();
		} );
	};

	/**
	 * Start the party
	 *
	 * Workflow:
	 *   1. find all the audio players in the page
	 *   2. eventually obtain page IDs from page titles
	 *
	 * This method assumes that all the needed dependencies are present.
	 */
	PlayerGoodies.run = function() {

		// array of players in the page
		//  fileTitle => PlayerGoodie
		var playerGoodiesByTitle = {};

		/**
		 * Find all the "Play" buttons
		 *
		 * Note that, only after clicking this button the first time,
		 * MediaWiki will spawn the <audio> tag.
		 */
		$( '.mediaContainer .play-btn' ).click( function() {

			// the play button
			var $playBtn = $(this);

			// do not initialize twice
			if( !$playBtn.data( 'PlayerGoodiesInitializedFromPlayBtn' ) ) {

				$playBtn.data( 'PlayerGoodiesInitializedFromPlayBtn', true );

				/**
				 * Note that, before clicking the play button,
				 * there was not any <audio> tag.
				 *
				 * MediaWiki will spawn that damn <audio> tag
				 * just after clicking that fake play button.
				 *
				 * So, just wait.
				 *
				 * This is a very stupid hack. Please fix this
				 * if you have the time to investigate how that
				 * MediaWiki component works. I really don't know.
				 */
				setTimeout( function() {

					/**
					 * Note that few clock cycles before, the user clicked
					 * on the "Play" icon. Anyway, we were not able to fire
					 * our damn 'play' callback because the <audio>
					 * element, even if now is playing, was created just now
					 * without any callback.
					 *
					 * Let's init the element, attach the callbacks, and 
					 * trigger our 'play' callback manually.
					 */
					var p = new PlayerGoodieFile();
					p.$playBtn = $playBtn;
					p.init();

					// as said, trigger our play event manually
					p.triggerInternalPlay();
				}, 1 );
			}
		} );

		// preoad all the page ids
		PlayerGoodies.preloadPageIDs();
	};

	/**
	 * Preload all the page IDs from the known page titles
	 *
	 * This method should be called after the 'run()' method.
	 */
	PlayerGoodies.preloadPageIDs = function() {

		// array of titles we d
		var titlesWithoutId = [];
		var missingTitle = [];

		// loop all the players in the page
		var playerGoodies = PlayerGoodies.playerGoodies;
		var playerGoodie;
		for( var i in playerGoodies ) {
			playerGoodie = playerGoodies[ i ];
			if( playerGoodie.id === undefined ) {

				// add this title in the list
				titlesWithoutId.push( playerGoodie.title );
				missingTitle.push( playerGoodie );
			} 
		}

		if( playerGoodies.length ) {
			// ..todo
		}
	};

	/**
	 * Set some data about a page
	 *
	 * It returns a Premise object
	 *
	 * @param {int} pageId
	 * @param {object} data
	 */
	PlayerGoodies.savePageData = function( pageId, data ) {
		// TODO: create savePageDataInUserSubpage( pageId, data );
		return this.savePageDataInLocalStorage( pageId, data );
	};

	/**
	 * Save some data about a page in the browser's local storage
	 *
	 * @param {int} pageId
	 * @param {object} dataPage
	 * @return {Promise}
	 */
	PlayerGoodies.savePageDataInLocalStorage = function ( pageId, dataPage ) {

		// everything OK
		var ok = true;

		// local storage option name
		var optionName = 'mwPlayerGoodiesGadgetData';

		// check existing data (it's a JSON string)
		var dataJSON = mw.storage.get( optionName ) || '{}';

		// parse existing data
		var data = JSON.parse( dataJSON ) || {};

		// check existing data about pages
		var dataPages = data.dataPages || {};

		// check existing page data
		var dataPageOld = [ pageId ] || {};

		// extends the existing data with the new data
		dataPages[ pageId ] = $.extends( {}, dataPageOld, dataPage );

		// encapsulate again the data as JSON
		var newDataJSON = JSON.stringify( data );

		// save
		mw.storage.set( optionName, newDataJSON );

		/**
		 * Return a dummy promise with the fresh data
		 *
		 * The Promise is not very useful here, but 
		 * see also savePageDataInUserSubpage().
		 */
		return new Promise( function (resolve, reject ) {
			resolve( data );
		} );
	};

	/**
	 * Prepare the page
	 */
	PlayerGoodies.init();

} )( mw, jQuery, window.PlayerGoodies );