diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc
index a46ed1a8a70caca2205e3668531279e1f50aafda..a9428c4656d4195b4db2ca4b840479570f38b700 100644
--- a/core/modules/file/file.field.inc
+++ b/core/modules/file/file.field.inc
@@ -403,10 +403,117 @@ function file_field_formatter_info() {
'label' => t('URL to file'),
'field types' => array('file'),
),
+ 'file_player' => array(
+ 'label' => t('Media Player'),
+ 'description' => t('Play this file within a Media Player.'),
+ 'field types' => array('file', 'text'),
+ 'settings' => array(
+ 'template' => 'default',
+ 'preload' => TRUE,
+ 'autoplay' => FALSE,
+ 'loop' => FALSE,
+ 'width' => '100%',
+ 'height' => '400px',
+ 'volume' => 80,
+ 'sources' => FALSE,
+ 'debug' => FALSE
+ )
+ )
);
}
/**
+ * Implements hook_field_formatter_settings_form
+ */
+function file_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+ $element = array();
+ if ($display['type'] == 'file_player') {
+
+ // Get the player information and templates.
+ $info = file_media_player_info();
+ $templates = array_keys($info['templates']);
+ $templates = array_combine($templates, $templates);
+
+ $element['template'] = array(
+ '#title' => t('Template'),
+ '#type' => 'select',
+ '#options' => $templates,
+ '#default_value' => $settings['template']
+ );
+
+ $element['preload'] = array(
+ '#title' => t('Preload'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['preload']
+ );
+
+ $element['autoplay'] = array(
+ '#title' => t('Autoplay'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['autoplay']
+ );
+
+ $element['loop'] = array(
+ '#title' => t('Loop'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['loop']
+ );
+
+ $element['width'] = array(
+ '#title' => t('Width'),
+ '#type' => 'textfield',
+ '#default_value' => $settings['width']
+ );
+
+ $element['height'] = array(
+ '#title' => t('Height'),
+ '#type' => 'textfield',
+ '#default_value' => $settings['height']
+ );
+
+ $element['volume'] = array(
+ '#title' => t('Initial Volume (0 - 100)'),
+ '#type' => 'textfield',
+ '#default_value' => $settings['volume']
+ );
+
+ $element['sources'] = array(
+ '#title' => t('Allow multiple sources'),
+ '#description' => t('Checking this will turn multiple instances of files into multiple sources within the media element.'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['sources']
+ );
+
+ $element['debug'] = array(
+ '#title' => t('Debug Mode'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['debug']
+ );
+ }
+ return $element;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary
+ */
+function file_field_formatter_settings_summary($field, $instance, $view_mode) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+ $summary = '';
+ if ($display['type'] == 'file_player') {
+ $header = array('Setting', 'Value');
+ $rows = array();
+ foreach ($settings as $name => $value) {
+ $rows[] = array($name, $value);
+ }
+ $summary = theme('table', array('header' => $header, 'rows' => $rows));
+ }
+ return $summary;
+}
+
+/**
* Implements hook_field_widget_info().
*/
function file_field_widget_info() {
@@ -1001,6 +1108,56 @@ function file_field_formatter_view($entity_type, $entity, $field, $instance, $la
);
}
break;
+
+ case 'file_player':
+ // Get the display settings.
+ $settings = $display['settings'];
+
+ // Get the ID for this media player.
+ $id = 'player-' . drupal_clean_css_identifier($field['field_name']);
+
+ // If they wish to show all sources within a single media element.
+ if ($settings['sources']) {
+
+ // Get the media tag.
+ $mediatag = '';
+ foreach ($items as $delta => $item) {
+ if ($mediatag = file_get_media_type((object)$item)) {
+ break;
+ }
+ }
+
+ // If the mediatag exists, then theme the player.
+ if ($mediatag) {
+ $settings['id'] = $id;
+ $element[$delta] = array(
+ '#theme' => 'media_player',
+ '#tag' => $mediatag,
+ '#attributes' => file_player_get_attributes($settings),
+ '#settings' => $settings,
+ '#sources' => $items
+ );
+ }
+ }
+ else {
+
+ // Iterate through all the items.
+ foreach ($items as $delta => $item) {
+
+ // Get the media tag.
+ if ($mediatag = file_get_media_type((object)$item)) {
+ $settings['id'] = $id . '-' . $delta;
+ $element[$delta] = array(
+ '#theme' => 'media_player',
+ '#tag' => $mediatag,
+ '#attributes' => file_player_get_attributes($settings),
+ '#settings' => $settings,
+ '#sources' => array($item)
+ );
+ }
+ }
+ }
+ break;
}
return $element;
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 2141cae59f13108e5797f5a4836f7e9ce4daa72f..271f4e7ccc72f3f768584cf10f71047428d05bd7 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -86,7 +86,7 @@ function file_element_info() {
* Implements hook_theme().
*/
function file_theme() {
- return array(
+ $themes = array(
// file.module.
'file_link' => array(
'variables' => array('file' => NULL, 'icon_directory' => NULL),
@@ -111,10 +111,363 @@ function file_theme() {
'file_upload_help' => array(
'variables' => array('description' => NULL, 'upload_validators' => NULL),
),
+
+ // media player theme
+ 'media_player' => array(
+ 'render element' => 'element',
+ )
+ );
+
+ // Register the player templates
+ $info = file_get_player_info();
+ foreach ($info['templates'] as $name => $info) {
+ $themes['media_player_' . $name] = array(
+ 'template' => 'media_player_' . $name,
+ 'variables' => array('params' => NULL),
+ 'path' => $info['path']
+ );
+ }
+
+ // Return the themes.
+ return $themes;
+}
+
+/**
+ * Implements hook_library_info().
+ */
+function file_library_info() {
+ $path = drupal_get_path('module', 'file') . '/player';
+ return array(
+ 'mediaplayer' => array(
+ 'title' => 'Media Player',
+ 'version' => '0.1',
+ 'js' => array(
+ $path . '/bin/minplayer.compressed.js' => array('group' => JS_LIBRARY)
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.slider')
+ )
+ ),
+ 'mediaplayer_debug' => array(
+ 'title' => 'Media Player (Debug Mode)',
+ 'version' => '0.1',
+ 'js' => array(
+ $path . '/src/minplayer.compatibility.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.flags.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.async.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.plugin.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.display.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.image.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.file.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.playLoader.base.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.players.base.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.players.html5.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.players.flash.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.players.minplayer.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.players.youtube.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.players.vimeo.js' => array('group' => JS_LIBRARY),
+ $path . '/src/minplayer.controller.base.js' => array('group' => JS_LIBRARY)
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.slider')
+ )
+ )
+ );
+}
+
+/**
+ * Returns all of the media player information.
+ */
+function file_get_player_info() {
+
+ // Implement hook_media_player_info
+ $cache = cache();
+ $info = $cache->get('media_player_info');
+ if ($info) {
+ return $info->data;
+ }
+ else {
+ // Invoke all media_player_info and then set the cache.
+ $player_info = module_invoke_all('media_player_info');
+ $cache->set('media_player_info', $player_info);
+ return $player_info;
+ }
+}
+
+/**
+ * Implements hook_media_player_info
+ */
+function file_media_player_info() {
+ $path = drupal_get_path('module', 'file') . '/player/templates';
+ return array(
+ 'plugins' => array(),
+ 'templates' => array(
+ 'default' => array(
+ 'path' => $path . '/default',
+ 'js' => array(
+ $path . '/default/js/minplayer.playLoader.default.js' => array('group' => JS_DEFAULT),
+ $path . '/default/js/minplayer.controller.default.js' => array('group' => JS_DEFAULT),
+ $path . '/default/js/minplayer.default.js' => array('group' => JS_DEFAULT)
+ ),
+ 'css' => array(
+ $path . '/default/css/media_player_default.css' => array('group' => CSS_DEFAULT)
+ )
+ )
+ )
+ );
+}
+
+/**
+ * Returns the player settings.
+ */
+function file_player_settings() {
+ return array(
+ "id" => 'player',
+ "controller" => 'default',
+ "template" => 'default',
+ "swfplayer" => '',
+ "wmode" => 'transparent',
+ "preload" => true,
+ "autoplay" => false,
+ "loop" => false,
+ "width" => '100%',
+ "height" => '400px',
+ "debug" => false,
+ "volume" => 80,
+ "files" => array(),
+ "file" => '',
+ "preview" => '',
+ "attributes" => array()
);
}
/**
+ * Register a new media player in JavaScript.
+ */
+function file_register_player($settings, $attributes) {
+ $playerId = $settings['id'];
+ file_player_add_resources($settings['template'], $settings['debug']);
+ $attributes = drupal_json_encode($attributes);
+ $settings = array_intersect_key($settings, file_player_settings());
+ $settings = trim(drupal_json_encode($settings), '{}');
+ $swfplayer = url(drupal_get_path('module', 'file') . '/player/flash/minplayer.swf');
+ drupal_add_js("
+ jQuery(function() {
+ jQuery('#{$playerId}').minplayer({
+ id:'#{$playerId}',
+ attributes:{$attributes},
+ {$settings},
+ swfplayer:'{$swfplayer}'
+ });
+ });
+ ", 'inline');
+}
+
+/**
+ * Theme a media player.
+ */
+function theme_media_player($variables) {
+
+ // Get the element for this player.
+ if (isset($variables['element'])) {
+ $element = &$variables['element'];
+ }
+ else {
+ $element &$variables;
+ }
+
+ // Get the settings.
+ $settings = $element['#settings'];
+ $attributes = $element['#attributes'];
+
+ // Check to make sure there are sources.
+ if (empty($element['#sources'])) {
+ return 'No media sources provided';
+ }
+
+ // Set the value.
+ $element['#value'] = '';
+
+ // Iterate through each of the sources and create a source for that file.
+ foreach ($element['#sources'] as $delta => $file) {
+
+ // Make sure this is an object.
+ $file = (object)$file;
+
+ // Gets the source of this media.
+ if ($source = file_get_source($file)) {
+
+ // Get the source attributes.
+ $source_attributes = array('src' => $source);
+
+ // Add the sources to the #value of the media tag.
+ $element['#value'] .= theme('html_tag', array(
+ 'element' => array(
+ '#tag' => 'source',
+ '#attributes' => $source_attributes
+ )
+ ));
+ }
+ }
+
+ // Add some variables that the template needs.
+ $variables['player'] = theme('html_tag', $variables);
+ $variables['settings'] = $settings;
+
+ // Register the media player in JavaScript.
+ file_register_player($settings, $attributes);
+
+ // Return the theme for our media player.
+ return theme('media_player_' . $settings['template'], $variables);
+}
+
+/**
+ * Returns the media source provided a field.
+ *
+ * @param object A Drupal file object.
+ */
+function file_get_source($file) {
+ if ($file) {
+ if (isset($file->uri)) {
+ return file_create_url($file->uri);
+ }
+ else if (!empty($file->value)) {
+ return $file->value;
+ }
+ }
+ return '';
+}
+
+/**
+ * Returns the extension provided a file object.
+ *
+ * @param object A Drupal file object.
+ * @return string The file extension.
+ */
+function file_get_extension($file) {
+
+ // Get the file source.
+ if ($source = file_get_source($file)) {
+ return drupal_strtolower(drupal_substr($source, strrpos($source, '.') + 1));
+ }
+
+ return '';
+}
+
+/**
+ * Return the media type provided a Drupal file object.
+ *
+ * @param object A Drupal file object.
+ * @return string 'video', 'audio', or '' if none.
+ */
+function file_get_media_type($file) {
+
+ // First try the filemime.
+ if (isset($file->filemime)) {
+ if (strpos($file->filemime, 'video/') === 0) {
+ return 'video';
+ }
+ if (strpos($file->filemime, 'audio/') === 0) {
+ return 'audio';
+ }
+ }
+
+ // Next try the extension.
+ if ($ext = file_get_extension($file)) {
+
+ // Determine if the extension is a "video" type.
+ if (in_array($ext, array('swf', 'mov', 'mp4', 'm4v', 'flv', 'f4v', 'ogg', 'ogv', '3g2', 'webm'))) {
+ return 'video';
+ }
+
+ // Determine if the extension is an "audio" type.
+ if (in_array($ext, array('mp3', 'oga', 'wav', 'aif', 'm4a', 'aac'))) {
+ return 'audio';
+ }
+ }
+
+ // Return video if value is set, nothing otherwise.
+ return !empty($file->value) ? 'video' : '';
+}
+
+/**
+ * Returns the settings for this video or audio element.
+ */
+function file_player_get_attributes($settings) {
+ $attributes = array();
+ $element_settings = array('preload', 'autoplay', 'loop');
+ foreach ($settings as $name => $value) {
+ if ($value && in_array($name, $element_settings)) {
+ $attributes[$name] = NULL;
+ }
+ }
+
+ // Set the ID, width and height.
+ $attributes['id'] = $settings['id'] . '-player';
+ $attributes['width'] = '100%';
+ $attributes['height'] = '100%';
+ return $attributes;
+}
+
+/**
+ * Adds the media player resources to the view.
+ */
+function file_player_add_resources($template, $debug) {
+ static $resources_added = FALSE, $template_added = array();
+
+ // Get the player information.
+ $info = file_media_player_info();
+
+ if (!$resources_added) {
+
+ // Add the media player library.
+ drupal_add_library('file', $debug ? 'mediaplayer_debug' : 'mediaplayer');
+
+ // Iterate through all the plugins...
+ foreach ($info['plugins'] as $plugin) {
+
+ // Include all of the css and js files.
+ if ($plugin['js']) {
+ foreach ($plugin['js'] as $file => $options) {
+ drupal_add_js($file, $options);
+ }
+ }
+ if ($plugin['css']) {
+ foreach ($plugin['css'] as $file => $options) {
+ drupal_add_css($file, $options);
+ }
+ }
+ }
+ }
+
+ // Get the templates...
+ $templates = $info['templates'];
+
+ // If this template exists, then...
+ if (!isset($template_added[$template]) && isset($templates[$template])) {
+
+ // Statically cache this so we won't add it again.
+ $template_added[$template] = TRUE;
+
+ // Store the template info.
+ $template = $templates[$template];
+
+ // Include all of the template files.
+ if ($template['js']) {
+ foreach ($template['js'] as $file => $options) {
+ drupal_add_js($file, $options);
+ }
+ }
+ if ($template['css']) {
+ foreach ($template['css'] as $file => $options) {
+ drupal_add_css($file, $options);
+ }
+ }
+ }
+}
+
+/**
* Implements hook_file_download().
*
* This function takes an extra parameter $field_type so that it may
diff --git a/core/modules/file/player/README.md b/core/modules/file/player/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..63034555b62fbae8e252698d3e1e138fd89d40f7
--- /dev/null
+++ b/core/modules/file/player/README.md
@@ -0,0 +1,122 @@
+minPlayer - Because less IS more.
+===================================
+
+The goal of this project is to provide a slim, well documented, object oriented,
+plugin-based "core" media player in which other players and libraries can build
+on top of. It is written using object oriented JavaScript and is continuously
+integrated using JSLint, JSDoc, and Google Closure.
+
+Multiple players - One single API.
+-----------------------------------
+It also allows for hot-swappable 3rd party API players by providing a common
+API for all of these players so that they can be utilized in the same manner.
+This means that once you develop for the minPlayer, one can easily bring in a
+different player an your code will still function as it would for all the
+others. Out of the box, this player provides a common API for YouTube, Vimeo,
+HTML5, and Flash with more on the way.
+
+Everything is a plugin
+-----------------------------------
+Since this is a plugin-based media player, every displayable class must derive
+from the plugin class, thereby, making it a plugin. This includes the media
+player itself. This plugin system is highly flexible to be able to handle
+just about any type of plugin imaginable, and allows for every plugin to have
+direct dependeny-injected control over any other plugin within the media player.
+
+Complete User Interface & Business Logic separation
+-----------------------------------
+One common complaint for many media solutions out there is that they hijack the
+DOM and build out their own controls to provide consistency amongst different
+browsers. They do this, however, within the core player which completely binds
+the user interface to the business logic of the media player. The minPlayer
+takes a different approach by keeping ALL user interface functionality within
+the "templates" directory, where each template essentially derives from the base
+Business logic classes only to provide the user interface aspects of that control.
+This allows for easy templating of the media player besides just overriding the
+CSS like current media solutions do today.
+
+No "Features"!
+-----------------------------------
+I am pleased to say that this media player does NOT have many features, and this
+is on purpose. Since this is a "core" player, it does not have any features
+other than what is critical in presenting your media. Any additional "bling"
+will be added to this player from different repositories and from different
+players that extend this "core" functionality. This methodology will keep this
+"core" media solution lean & highly functional.
+
+API
+-----------------------------------
+The API for minPlayer is very simple. It revolves around a single API that is
+able to retrieve any plugin even before that plugin is created. By doing this,
+you can have complete control over any plugin within the minPlayer. This API
+is simply called
+
+```
+minplayer.get();
+```
+
+This API can take up to three different argument with each argument providing
+different usage. The code docs for this function are as follows...
+
+```
+/**
+ * The main API for minPlayer.
+ *
+ * Provided that this function takes three parameters, there are 8 different
+ * ways to use this api.
+ *
+ * id (0x100) - You want a specific player.
+ * plugin (0x010) - You want a specific plugin.
+ * callback (0x001) - You only want it when it is ready.
+ *
+ * 000 - You want all plugins from all players, ready or not.
+ *
+ * var instances = minplayer.get();
+ *
+ * 001 - You want all plugins from all players, but only when ready.
+ *
+ * minplayer.get(function(plugin) {
+ * // Code goes here.
+ * });
+ *
+ * 010 - You want a specific plugin from all players, ready or not...
+ *
+ * var medias = minplayer.get(null, 'media');
+ *
+ * 011 - You want a specific plugin from all players, but only when ready.
+ *
+ * minplayer.get('player', function(player) {
+ * // Code goes here.
+ * });
+ *
+ * 100 - You want all plugins from a specific player, ready or not.
+ *
+ * var plugins = minplayer.get('player_id');
+ *
+ * 101 - You want all plugins from a specific player, but only when ready.
+ *
+ * minplayer.get('player_id', null, function(plugin) {
+ * // Code goes here.
+ * });
+ *
+ * 110 - You want a specific plugin from a specific player, ready or not.
+ *
+ * var plugin = minplayer.get('player_id', 'media');
+ *
+ * 111 - You want a specific plugin from a specific player, only when ready.
+ *
+ * minplayer.get('player_id', 'media', function(media) {
+ * // Code goes here.
+ * });
+ *
+ * @this The context in which this function was called.
+ * @param {string} id The ID of the widget to get the plugins from.
+ * @param {string} plugin The name of the plugin.
+ * @param {function} callback Called when the plugin is ready.
+ * @return {object} The plugin object if it is immediately available.
+ */
+minplayer.get = function(id, plugin, callback) {
+};
+```
+
+Thanks and enjoy minPlayer.
\ No newline at end of file
diff --git a/core/modules/file/player/bin/minplayer.compressed.js b/core/modules/file/player/bin/minplayer.compressed.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b70e432fbe919fab15e319f3952c015a8150aec
--- /dev/null
+++ b/core/modules/file/player/bin/minplayer.compressed.js
@@ -0,0 +1,95 @@
+var minplayer=minplayer||{};function checkPlayType(a,b){if("function"===typeof a.canPlayType){if("object"===typeof b){for(var c=b.length,d="";c--&&!(d=checkPlayType(a,b[c])););return d}c=a.canPlayType(b);if("no"!==c&&""!==c)return b}return""}
+minplayer.compatibility=function(){var a=null,a=document.createElement("video");this.videoOGG=checkPlayType(a,"video/ogg");this.videoH264=checkPlayType(a,["video/mp4","video/h264"]);this.videoWEBM=checkPlayType(a,["video/x-webm","video/webm","application/octet-stream"]);a=document.createElement("audio");this.audioOGG=checkPlayType(a,"audio/ogg");this.audioMP3=checkPlayType(a,"audio/mpeg");this.audioMP4=checkPlayType(a,"audio/mp4")};minplayer.playTypes||(minplayer.playTypes=new minplayer.compatibility);
+minplayer=minplayer||{};minplayer.async=function(){this.value=null;this.queue=[]};minplayer.async.prototype.get=function(a){null!==this.value?a(this.value):this.queue.push(a)};minplayer.async.prototype.set=function(a){this.value=a;var b=this.queue.length;if(b){for(;b--;)this.queue[b](a);this.queue=[]}};minplayer=minplayer||{};minplayer.flags=function(){this.flag=0;this.ids={};this.numFlags=0};
+minplayer.flags.prototype.setFlag=function(a,b){this.ids.hasOwnProperty(a)||(this.ids[a]=this.numFlags,this.numFlags++);this.flag=b?this.flag|1<a?(c.height=b.height,c.width=Math.floor(b.height*a)):(c.height=Math.floor(b.width/a),c.width=b.width),c.x=Math.floor((b.width-c.width)/2),c.y=Math.floor((b.height-c.height)/2));return c};minplayer.display.prototype.getElements=function(){return{}};minplayer.display.prototype.isValid=function(){return 0b&&(a=d,b=f)});return a};
+minplayer.file.prototype.getPriority=function(){var a=1;this.player&&(a=minplayer.players[this.player].getPriority());switch(this.mimetype){case "video/x-webm":case "video/webm":case "application/octet-stream":return 10*a;case "video/mp4":case "audio/mp4":case "audio/mpeg":return 9*a;case "video/ogg":case "audio/ogg":case "video/quicktime":return 8*a;default:return 5*a}};minplayer.file.prototype.getFileExtension=function(){return this.path.substring(this.path.lastIndexOf(".")+1).toLowerCase()};
+minplayer.file.prototype.getMimeType=function(){switch(this.extension){case "mp4":case "m4v":case "flv":case "f4v":return"video/mp4";case "webm":return"video/webm";case "ogg":case "ogv":return"video/ogg";case "3g2":return"video/3gpp2";case "3gpp":case "3gp":return"video/3gpp";case "mov":return"video/quicktime";case "swf":return"application/x-shockwave-flash";case "oga":return"audio/ogg";case "mp3":return"audio/mpeg";case "m4a":case "f4a":return"audio/mp4";case "aac":return"audio/aac";case "wav":return"audio/vnd.wave";
+case "wma":return"audio/x-ms-wma";default:return"unknown"}};minplayer.file.prototype.getType=function(){switch(this.mimetype){case "video/mp4":case "video/webm":case "application/octet-stream":case "video/x-webm":case "video/ogg":case "video/3gpp2":case "video/3gpp":case "video/quicktime":return"video";case "audio/mp3":case "audio/mp4":case "audio/ogg":case "audio/mpeg":return"audio";default:return""}};
+minplayer.file.prototype.getId=function(){var a=minplayer.players[this.player];return a&&a.getMediaId?a.getMediaId(this):""};minplayer=minplayer||{};minplayer.playLoader=minplayer.playLoader||{};minplayer.playLoader.base=function(a,b){this.busy=new minplayer.flags;this.bigPlay=new minplayer.flags;this.preview=null;minplayer.display.call(this,"playLoader",a,b)};minplayer.playLoader.base.prototype=new minplayer.display;minplayer.playLoader.base.prototype.constructor=minplayer.playLoader.base;
+minplayer.playLoader.base.prototype.construct=function(){minplayer.display.prototype.construct.call(this);this.get("media",function(a){a.hasPlayLoader()?(this.elements.busy&&this.elements.busy.unbind().hide(),this.elements.bigPlay&&this.elements.bigPlay.unbind().hide(),this.display.unbind().hide()):(this.loadPreview(),this.elements.bigPlay&&this.elements.bigPlay.unbind().bind("click",function(b){b.preventDefault();jQuery(this).hide();a.play()}),a.unbind("loadstart").bind("loadstart",{obj:this},function(a){a.data.obj.busy.setFlag("media",
+!0);a.data.obj.bigPlay.setFlag("media",!0);a.data.obj.preview&&a.data.obj.elements.preview.show();a.data.obj.checkVisibility()}),a.bind("waiting",{obj:this},function(a){a.data.obj.busy.setFlag("media",!0);a.data.obj.checkVisibility()}),a.bind("loadeddata",{obj:this},function(a){a.data.obj.busy.setFlag("media",!1);a.data.obj.checkVisibility()}),a.bind("playing",{obj:this},function(a){a.data.obj.busy.setFlag("media",!1);a.data.obj.bigPlay.setFlag("media",!1);a.data.obj.preview&&a.data.obj.elements.preview.hide();
+a.data.obj.checkVisibility()}),a.bind("pause",{obj:this},function(a){a.data.obj.bigPlay.setFlag("media",!0);a.data.obj.checkVisibility()}))});this.ready()};
+minplayer.playLoader.base.prototype.loadPreview=function(){this.elements.preview&&(this.options.preview||(this.options.preview=this.elements.media.attr("poster")),this.elements.media.attr("poster",""),this.options.preview?(this.elements.preview.addClass("has-preview").show(),this.preview=new minplayer.image(this.elements.preview,this.options),this.preview.load(this.options.preview)):this.elements.preview.hide())};
+minplayer.playLoader.base.prototype.checkVisibility=function(){this.busy.flag?this.elements.busy.show():this.elements.busy.hide();this.bigPlay.flag?this.elements.bigPlay.show():this.elements.bigPlay.hide();(this.bigPlay.flag||this.busy.flag)&&this.display.show();!this.bigPlay.flag&&!this.busy.flag&&this.display.hide()};minplayer=minplayer||{};minplayer.players=minplayer.players||{};minplayer.players.base=function(a,b){minplayer.display.call(this,"media",a,b)};minplayer.players.base.prototype=new minplayer.display;
+minplayer.players.base.prototype.constructor=minplayer.players.base;minplayer.players.base.getPriority=function(){return 0};minplayer.players.base.getMediaId=function(){return""};minplayer.players.base.canPlay=function(){return!1};
+minplayer.players.base.prototype.construct=function(){minplayer.display.prototype.construct.call(this);this.reset();this.mediaFile=this.options.file;this.playerFound()||(this.elements.media&&this.elements.media.remove(),this.display.html(this.create()));this.player=this.getPlayer();var a=this;jQuery(document).bind("click",function(b){a.hasFocus=0==jQuery(b.target).closest("#"+a.options.id).length?!1:!0});jQuery(document).bind("keydown",{obj:this},function(a){if(a.data.obj.hasFocus)switch(a.preventDefault(),
+a.keyCode){case 32:case 179:a.data.obj.playing?a.data.obj.pause():a.data.obj.play();break;case 38:a.data.obj.setVolumeRelative(0.1);break;case 40:a.data.obj.setVolumeRelative(-0.1);break;case 37:case 227:a.data.obj.seekRelative(-0.05);break;case 39:case 228:a.data.obj.seekRelative(0.05)}})};minplayer.players.base.prototype.destroy=function(){minplayer.plugin.prototype.destroy.call(this);this.reset()};
+minplayer.players.base.prototype.reset=function(){this.playerReady=!1;this.duration=new minplayer.async;this.currentTime=new minplayer.async;this.bytesLoaded=new minplayer.async;this.bytesTotal=new minplayer.async;this.bytesStart=new minplayer.async;this.volume=new minplayer.async;this.loading=this.playing=this.hasFocus=!1;this.player&&jQuery(this.player).unbind()};minplayer.players.base.prototype.poll=function(a){var b=this;setTimeout(function d(){a.call(b)&&setTimeout(d,1E3)},1E3)};
+minplayer.players.base.prototype.onReady=function(){var a=this;this.playerReady=!0;this.setVolume(this.options.volume/100);this.loading=!0;this.poll(function(){a.loading&&a.getBytesLoaded(function(b){a.getBytesTotal(function(c){if(b||c){var d=0;a.getBytesStart(function(a){d=a});a.trigger("progress",{loaded:b,total:c,start:d});b>=c&&(a.loading=!1)}})});return a.loading});this.ready();this.trigger("loadstart")};
+minplayer.players.base.prototype.onPlaying=function(){var a=this;this.trigger("playing");this.playing=this.hasFocus=!0;this.poll(function(){a.playing&&a.getCurrentTime(function(b){a.getDuration(function(c){b=parseFloat(b);c=parseFloat(c);(b||c)&&a.trigger("timeupdate",{currentTime:b,duration:c})})});return a.playing})};minplayer.players.base.prototype.onPaused=function(){this.trigger("pause");this.playing=this.hasFocus=!1};
+minplayer.players.base.prototype.onComplete=function(){this.hasFocus=this.loading=this.playing=!1;this.trigger("ended")};minplayer.players.base.prototype.onLoaded=function(){this.trigger("loadeddata")};minplayer.players.base.prototype.onWaiting=function(){this.trigger("waiting")};minplayer.players.base.prototype.onError=function(a){this.hasFocus=!1;this.trigger("error",a)};minplayer.players.base.prototype.isReady=function(){return this.player&&this.playerReady};
+minplayer.players.base.prototype.hasPlayLoader=function(){return!1};minplayer.players.base.prototype.playerFound=function(){return!1};minplayer.players.base.prototype.create=function(){this.reset();return null};minplayer.players.base.prototype.getPlayer=function(){return this.player};minplayer.players.base.prototype.load=function(a){a&&(this.reset(),this.mediaFile=a)};minplayer.players.base.prototype.play=function(){};minplayer.players.base.prototype.pause=function(){};
+minplayer.players.base.prototype.stop=function(){this.hasFocus=this.loading=this.playing=!1};minplayer.players.base.prototype.seekRelative=function(a){var b=this;this.getCurrentTime(function(c){b.getDuration(function(d){if(d){var e=0,e=-1a?c/d+parseFloat(a):(c+parseFloat(a))/d;b.seek(e)}})})};minplayer.players.base.prototype.seek=function(){};minplayer.players.base.prototype.setVolumeRelative=function(a){var b=this;this.getVolume(function(c){c+=parseFloat(a);c=0>c?0:c;b.setVolume(1':">");this.options.elements.media.attr("src","").empty().html(b)}minplayer.players.base.prototype.load.call(this,a)};minplayer.players.html5.prototype.play=function(){minplayer.players.base.prototype.play.call(this);this.isReady()&&this.player.play()};
+minplayer.players.html5.prototype.pause=function(){minplayer.players.base.prototype.pause.call(this);this.isReady()&&this.player.pause()};minplayer.players.html5.prototype.stop=function(){minplayer.players.base.prototype.stop.call(this);this.isReady()&&(this.player.pause(),this.player.src="")};minplayer.players.html5.prototype.seek=function(a){minplayer.players.base.prototype.seek.call(this,a);this.isReady()&&(this.player.currentTime=a)};
+minplayer.players.html5.prototype.setVolume=function(a){minplayer.players.base.prototype.setVolume.call(this,a);this.isReady()&&(this.player.volume=a)};minplayer.players.html5.prototype.getVolume=function(a){this.isReady()&&a(this.player.volume)};minplayer.players.html5.prototype.getDuration=function(a){this.isReady()&&a(this.player.duration)};minplayer.players.html5.prototype.getCurrentTime=function(a){this.isReady()&&a(this.player.currentTime)};
+minplayer.players.html5.prototype.getBytesLoaded=function(a){if(this.isReady()){var b=0;this.bytesLoaded.value?b=this.bytesLoaded.value:this.player.buffered&&0 ';d=d+''+
+('');d+='';d=d+''+('')+(''};minplayer.players.flash.prototype.playerFound=function(){return 0
+ * Usage:
+ *
+ */
+minplayer.flags = function() {
+
+ /** The flag. */
+ this.flag = 0;
+
+ /** Id map to reference id with the flag index. */
+ this.ids = {};
+
+ /** The number of flags. */
+ this.numFlags = 0;
+};
+
+/**
+ * Sets a flag based on boolean logic operators.
+ *
+ * @param {string} id The id of the controller interested in this flag.
+ * @param {boolean} value The value of this flag ( true or false ).
+ */
+minplayer.flags.prototype.setFlag = function(id, value) {
+
+ // Define this id if it isn't present.
+ if (!this.ids.hasOwnProperty(id)) {
+ this.ids[id] = this.numFlags;
+ this.numFlags++;
+ }
+
+ // Use binary operations to keep track of the flag state
+ if (value) {
+ this.flag |= (1 << this.ids[id]);
+ }
+ else {
+ this.flag &= ~(1 << this.ids[id]);
+ }
+};
+/** The minplayer namespace. */
+minplayer = minplayer || {};
+
+/** Static array to keep track of all plugins. */
+minplayer.plugins = minplayer.plugins || {};
+
+/** Static array to keep track of queues. */
+minplayer.queue = minplayer.queue || [];
+
+/** Mutex lock to keep multiple triggers from occuring. */
+minplayer.lock = false;
+
+/**
+ * @constructor
+ * @class The base class for all plugins.
+ *
+ * @param {string} name The name of this plugin.
+ * @param {object} context The jQuery context.
+ * @param {object} options This components options.
+ */
+minplayer.plugin = function(name, context, options) {
+
+ /** The name of this plugin. */
+ this.name = name;
+
+ /** The ready flag. */
+ this.pluginReady = false;
+
+ /** The options for this plugin. */
+ this.options = options;
+
+ /** The event queue. */
+ this.queue = {};
+
+ /** Keep track of already triggered events. */
+ this.triggered = {};
+
+ /** Create a queue lock. */
+ this.lock = false;
+
+ // Only call the constructor if we have a context.
+ if (context) {
+
+ // Construct this plugin.
+ this.construct();
+ }
+};
+
+/**
+ * The constructor which is called once the context is set.
+ * Any class deriving from the plugin class should place all context
+ * dependant functionality within this function instead of the standard
+ * constructor function since it is called on object derivation as well
+ * as object creation.
+ */
+minplayer.plugin.prototype.construct = function() {
+
+ // Adds this as a plugin.
+ this.addPlugin();
+};
+
+/**
+ * Destructor.
+ */
+minplayer.plugin.prototype.destroy = function() {
+
+ // Unbind all events.
+ this.unbind();
+};
+
+/**
+ * Loads all of the available plugins.
+ */
+minplayer.plugin.prototype.loadPlugins = function() {
+
+ // Get all the plugins to load.
+ var instance = '';
+
+ // Iterate through all the plugins.
+ for (var name in this.options.plugins) {
+
+ // Only load if it does not already exist.
+ if (!minplayer.plugins[this.options.id][name]) {
+
+ // Get the instance name from the setting.
+ instance = this.options.plugins[name];
+
+ // If this object exists.
+ if (minplayer[name][instance]) {
+
+ // Declare a new object.
+ new minplayer[name][instance](this.display, this.options);
+ }
+ }
+ }
+};
+
+/**
+ * Plugins should call this method when they are ready.
+ */
+minplayer.plugin.prototype.ready = function() {
+
+ // Keep this plugin from triggering multiple ready events.
+ if (!this.pluginReady) {
+
+ // Set the ready flag.
+ this.pluginReady = true;
+
+ // Now trigger that I am ready.
+ this.trigger('ready');
+
+ // Check the queue.
+ this.checkQueue();
+ }
+};
+
+/**
+ * Adds a new plugin to this player.
+ *
+ * @param {string} name The name of this plugin.
+ * @param {object} plugin A new plugin object, derived from media.plugin.
+ */
+minplayer.plugin.prototype.addPlugin = function(name, plugin) {
+ name = name || this.name;
+ plugin = plugin || this;
+
+ // Make sure the plugin is valid.
+ if (plugin.isValid()) {
+
+ // If the plugins for this instance do not exist.
+ if (!minplayer.plugins[this.options.id]) {
+
+ // Initialize the plugins.
+ minplayer.plugins[this.options.id] = {};
+ }
+
+ // Add this plugin.
+ minplayer.plugins[this.options.id][name] = plugin;
+ }
+};
+
+/**
+ * Gets a plugin by name and calls callback when it is ready.
+ *
+ * @param {string} plugin The plugin of the plugin.
+ * @param {function} callback Called when the plugin is ready.
+ * @return {object} The plugin if no callback is provided.
+ */
+minplayer.plugin.prototype.get = function(plugin, callback) {
+
+ // If they pass just a callback, then return all plugins when ready.
+ if (typeof plugin === 'function') {
+ callback = plugin;
+ plugin = null;
+ }
+
+ // Return the minplayer.get equivalent.
+ return minplayer.get.call(this, this.options.id, plugin, callback);
+};
+
+/**
+ * Check the queue and execute it.
+ */
+minplayer.plugin.prototype.checkQueue = function() {
+
+ // Initialize our variables.
+ var q = null, i = 0, check = false, newqueue = [];
+
+ // Set the lock.
+ minplayer.lock = true;
+
+ // Iterate through all the queues.
+ var length = minplayer.queue.length;
+ for (i = 0; i < length; i++) {
+
+ // Get the queue.
+ q = minplayer.queue[i];
+
+ // Now check to see if this queue is about us.
+ check = !q.id && !q.plugin;
+ check |= (q.plugin == this.name) && (!q.id || (q.id == this.options.id));
+
+ // If the check passes...
+ if (check) {
+ check = minplayer.bind.call(
+ q.context,
+ q.event,
+ this.options.id,
+ this.name,
+ q.callback
+ );
+ }
+
+ // Add the queue back if it doesn't check out.
+ if (!check) {
+
+ // Add this back to the queue.
+ newqueue.push(q);
+ }
+ }
+
+ // Set the old queue to the new queue.
+ minplayer.queue = newqueue;
+
+ // Release the lock.
+ minplayer.lock = false;
+};
+
+/**
+ * Trigger a media event.
+ *
+ * @param {string} type The event type.
+ * @param {object} data The event data object.
+ * @return {object} The plugin object.
+ */
+minplayer.plugin.prototype.trigger = function(type, data) {
+ data = data || {};
+ data.plugin = this;
+
+ // Add this to our triggered array.
+ this.triggered[type] = data;
+
+ // Check to make sure the queue for this type exists.
+ if (this.queue[type]) {
+
+ var i = 0, queue = {};
+
+ // Iterate through all the callbacks in this queue.
+ for (i in this.queue[type]) {
+
+ // Setup the event object, and call the callback.
+ queue = this.queue[type][i];
+ queue.callback({target: this, data: queue.data}, data);
+ }
+ }
+
+ // Return the plugin object.
+ return this;
+};
+
+/**
+ * Bind to a media event.
+ *
+ * @param {string} type The event type.
+ * @param {object} data The data to bind with the event.
+ * @param {function} fn The callback function.
+ * @return {object} The plugin object.
+ **/
+minplayer.plugin.prototype.bind = function(type, data, fn) {
+
+ // Allow the data to be the callback.
+ if (typeof data === 'function') {
+ fn = data;
+ data = null;
+ }
+
+ // You must bind to a specific event and have a callback.
+ if (!type || !fn) {
+ return;
+ }
+
+ // Initialize the queue for this type.
+ this.queue[type] = this.queue[type] || [];
+
+ // Unbind any existing equivalent events.
+ this.unbind(type, fn);
+
+ // Now add this event to the queue.
+ this.queue[type].push({
+ callback: fn,
+ data: data
+ });
+
+ // Now see if this event has already been triggered.
+ if (this.triggered[type]) {
+
+ // Go ahead and trigger the event.
+ fn({target: this, data: data}, this.triggered[type]);
+ }
+
+ // Return the plugin.
+ return this;
+};
+
+/**
+ * Unbind a media event.
+ *
+ * @param {string} type The event type.
+ * @param {function} fn The callback function.
+ * @return {object} The plugin object.
+ **/
+minplayer.plugin.prototype.unbind = function(type, fn) {
+
+ // If this is locked then try again after 10ms.
+ if (this.lock) {
+ var _this = this;
+ setTimeout(function() {
+ _this.unbind(type, fn);
+ }, 10);
+ }
+
+ // Set the lock.
+ this.lock = true;
+
+ if (!type) {
+ this.queue = {};
+ }
+ else if (!fn) {
+ this.queue[type] = [];
+ }
+ else {
+ // Iterate through all the callbacks and search for equal callbacks.
+ var i = 0, queue = {};
+ for (i in this.queue[type]) {
+ if (this.queue[type][i].callback === fn) {
+ queue = this.queue[type].splice(1, 1);
+ delete queue;
+ }
+ }
+ }
+
+ // Reset the lock.
+ this.lock = false;
+
+ // Return the plugin.
+ return this;
+};
+
+/**
+ * Adds an item to the queue.
+ *
+ * @param {object} context The context which this is called within.
+ * @param {string} event The event to trigger on.
+ * @param {string} id The player ID.
+ * @param {string} plugin The name of the plugin.
+ * @param {function} callback Called when the event occurs.
+ */
+minplayer.addQueue = function(context, event, id, plugin, callback) {
+
+ // See if it is locked...
+ if (!minplayer.lock) {
+ minplayer.queue.push({
+ context: context,
+ id: id,
+ event: event,
+ plugin: plugin,
+ callback: callback
+ });
+ }
+ else {
+
+ // If so, then try again after 10 milliseconds.
+ setTimeout(function() {
+ minplayer.addQueue(context, id, event, plugin, callback);
+ }, 10);
+ }
+};
+
+/**
+ * Binds an event to a plugin instance, and if it doesn't exist, then caches
+ * it for a later time.
+ *
+ * @param {string} event The event to trigger on.
+ * @param {string} id The player ID.
+ * @param {string} plugin The name of the plugin.
+ * @param {function} callback Called when the event occurs.
+ * @return {boolean} If the bind was successful.
+ * @this The object in context who called this method.
+ */
+minplayer.bind = function(event, id, plugin, callback) {
+
+ // If no callback exists, then just return false.
+ if (!callback) {
+ return false;
+ }
+
+ // Get the plugins.
+ var inst = minplayer.plugins;
+
+ // See if this plugin exists.
+ if (inst[id][plugin]) {
+
+ // If so, then bind the event to this plugin.
+ inst[id][plugin].bind(event, {context: this}, function(event, data) {
+ callback.call(event.data.context, data.plugin);
+ });
+ return true;
+ }
+
+ // If not, then add it to the queue to bind later.
+ minplayer.addQueue(this, event, id, plugin, callback);
+
+ // Return that this wasn't handled.
+ return false;
+};
+
+/**
+ * The main API for minPlayer.
+ *
+ * Provided that this function takes three parameters, there are 8 different
+ * ways to use this api.
+ *
+ * id (0x100) - You want a specific player.
+ * plugin (0x010) - You want a specific plugin.
+ * callback (0x001) - You only want it when it is ready.
+ *
+ * 000 - You want all plugins from all players, ready or not.
+ *
+ * var plugins = minplayer.get();
+ *
+ * 001 - You want all plugins from all players, but only when ready.
+ *
+ * minplayer.get(function(plugin) {
+ * // Code goes here.
+ * });
+ *
+ * 010 - You want a specific plugin from all players, ready or not...
+ *
+ * var medias = minplayer.get(null, 'media');
+ *
+ * 011 - You want a specific plugin from all players, but only when ready.
+ *
+ * minplayer.get('player', function(player) {
+ * // Code goes here.
+ * });
+ *
+ * 100 - You want all plugins from a specific player, ready or not.
+ *
+ * var plugins = minplayer.get('player_id');
+ *
+ * 101 - You want all plugins from a specific player, but only when ready.
+ *
+ * minplayer.get('player_id', null, function(plugin) {
+ * // Code goes here.
+ * });
+ *
+ * 110 - You want a specific plugin from a specific player, ready or not.
+ *
+ * var plugin = minplayer.get('player_id', 'media');
+ *
+ * 111 - You want a specific plugin from a specific player, only when ready.
+ *
+ * minplayer.get('player_id', 'media', function(media) {
+ * // Code goes here.
+ * });
+ *
+ * @this The context in which this function was called.
+ * @param {string} id The ID of the widget to get the plugins from.
+ * @param {string} plugin The name of the plugin.
+ * @param {function} callback Called when the plugin is ready.
+ * @return {object} The plugin object if it is immediately available.
+ */
+minplayer.get = function(id, plugin, callback) {
+
+ // Normalize the arguments for a better interface.
+ if (typeof id === 'function') {
+ callback = id;
+ plugin = id = null;
+ }
+
+ if (typeof plugin === 'function') {
+ callback = plugin;
+ plugin = id;
+ id = null;
+ }
+
+ // Make sure the callback is a callback.
+ callback = (typeof callback === 'function') ? callback : null;
+
+ // Get the plugins.
+ var plugins = minplayer.plugins;
+
+ // 0x000
+ if (!id && !plugin && !callback) {
+ return plugins;
+ }
+ // 0x100
+ else if (id && !plugin && !callback) {
+ return plugins[id];
+ }
+ // 0x110
+ else if (id && plugin && !callback) {
+ return plugins[id][plugin];
+ }
+ // 0x111
+ else if (id && plugin && callback) {
+ minplayer.bind.call(this, 'ready', id, plugin, callback);
+ }
+ // 0x011
+ else if (!id && plugin && callback) {
+ for (var id in plugins) {
+ minplayer.bind.call(this, 'ready', id, plugin, callback);
+ }
+ }
+ // 0x101
+ else if (id && !plugin && callback) {
+ for (var plugin in plugins[id]) {
+ minplayer.bind.call(this, 'ready', id, plugin, callback);
+ }
+ }
+ // 0x010
+ else if (!id && plugin && !callback) {
+ var plugin_types = {};
+ for (var id in plugins) {
+ if (plugins.hasOwnProperty(id) && plugins[id].hasOwnProperty(plugin)) {
+ plugin_types[id] = plugins[id][plugin];
+ }
+ }
+ return plugin_types;
+ }
+ // 0x001
+ else {
+ for (var id in plugins) {
+ for (var plugin in plugins[id]) {
+ minplayer.bind.call(this, 'ready', id, plugin, callback);
+ }
+ }
+ }
+};
+/** The minplayer namespace. */
+minplayer = minplayer || {};
+
+/**
+ * @constructor
+ * @extends minplayer.plugin
+ * @class Base class used to provide the display and options for any component
+ * deriving from this class. Components who derive are expected to provide
+ * the elements that they define by implementing the getElements method.
+ *
+ * @param {string} name The name of this plugin.
+ * @param {object} context The jQuery context this component resides.
+ * @param {object} options The options for this component.
+ */
+minplayer.display = function(name, context, options) {
+
+ // See if we allow resize on this display.
+ this.allowResize = false;
+
+ if (context) {
+
+ // Set the display.
+ this.display = this.getDisplay(context, options);
+ }
+
+ // Derive from plugin
+ minplayer.plugin.call(this, name, context, options);
+};
+
+/** Derive from minplayer.plugin. */
+minplayer.display.prototype = new minplayer.plugin();
+
+/** Reset the constructor. */
+minplayer.display.prototype.constructor = minplayer.display;
+
+/**
+ * Returns the display for this component.
+ *
+ * @param {object} context The original context.
+ * @param {object} options The options for this component.
+ * @return {object} The jQuery context for this display.
+ */
+minplayer.display.prototype.getDisplay = function(context, options) {
+ return jQuery(context);
+};
+
+/**
+ * @see minplayer.plugin.construct
+ */
+minplayer.display.prototype.construct = function() {
+
+ // Call the plugin constructor.
+ minplayer.plugin.prototype.construct.call(this);
+
+ // Extend all display elements.
+ this.options.elements = this.options.elements || {};
+ jQuery.extend(this.options.elements, this.getElements());
+ this.elements = this.options.elements;
+
+ // Only do this if they allow resize for this display.
+ if (this.allowResize) {
+
+ // Set the resize timeout and this pointer.
+ var resizeTimeout = 0;
+ var _this = this;
+
+ // Add a handler to trigger a resize event.
+ jQuery(window).resize(function() {
+ clearTimeout(resizeTimeout);
+ resizeTimeout = setTimeout(function() {
+ _this.onResize();
+ }, 200);
+ });
+ }
+};
+
+/**
+ * Called when the window resizes.
+ */
+minplayer.display.prototype.onResize = function() {
+};
+
+/**
+ * Returns a scaled rectangle provided a ratio and the container rect.
+ *
+ * @param {number} ratio The width/height ratio of what is being scaled.
+ * @param {object} rect The bounding rectangle for scaling.
+ * @return {object} The Rectangle object of the scaled rectangle.
+ */
+minplayer.display.prototype.getScaledRect = function(ratio, rect) {
+ var scaledRect = {};
+ scaledRect.x = rect.x ? rect.x : 0;
+ scaledRect.y = rect.y ? rect.y : 0;
+ scaledRect.width = rect.width ? rect.width : 0;
+ scaledRect.height = rect.height ? rect.height : 0;
+ if (ratio) {
+ if ((rect.width / rect.height) > ratio) {
+ scaledRect.height = rect.height;
+ scaledRect.width = Math.floor(rect.height * ratio);
+ }
+ else {
+ scaledRect.height = Math.floor(rect.width / ratio);
+ scaledRect.width = rect.width;
+ }
+ scaledRect.x = Math.floor((rect.width - scaledRect.width) / 2);
+ scaledRect.y = Math.floor((rect.height - scaledRect.height) / 2);
+ }
+ return scaledRect;
+};
+
+/**
+ * Returns all the jQuery elements that this component uses.
+ *
+ * @return {object} An object which defines all the jQuery elements that
+ * this component uses.
+ */
+minplayer.display.prototype.getElements = function() {
+ return {};
+};
+
+/**
+ * Returns if this component is valid and exists within the DOM.
+ *
+ * @return {boolean} TRUE if the plugin display is valid.
+ */
+minplayer.display.prototype.isValid = function() {
+ return (this.display.length > 0);
+};
+// Add a way to instanciate using jQuery prototype.
+if (!jQuery.fn.minplayer) {
+
+ /**
+ * @constructor
+ *
+ * Define a jQuery minplayer prototype.
+ *
+ * @param {object} options The options for this jQuery prototype.
+ * @return {Array} jQuery object.
+ */
+ jQuery.fn.minplayer = function(options) {
+ return jQuery(this).each(function() {
+ options = options || {};
+ options.id = options.id || $(this).attr('id') || Math.random();
+ if (!minplayer.plugins[options.id]) {
+ var template = options.template || 'default';
+ if (minplayer[template]) {
+ new minplayer[template](jQuery(this), options);
+ }
+ else {
+ new minplayer(jQuery(this), options);
+ }
+ }
+ });
+ };
+}
+
+/**
+ * @constructor
+ * @extends minplayer.display
+ * @class The core media player class which governs the media player
+ * functionality.
+ *
+ *
Usage:
+ *
+ *
+ * // Create a media player.
+ * var player = jQuery("#player").minplayer({
+ *
+ * });
+ *
+ *
+ *
+ *
+ * @param {object} context The jQuery context.
+ * @param {object} options This components options.
+ */
+minplayer = jQuery.extend(function(context, options) {
+
+ // Make sure we provide default options...
+ options = jQuery.extend({
+ id: 'player',
+ swfplayer: '',
+ wmode: 'transparent',
+ preload: true,
+ autoplay: false,
+ loop: false,
+ width: '100%',
+ height: '350px',
+ debug: false,
+ volume: 80,
+ files: [],
+ file: '',
+ preview: '',
+ attributes: {}
+ }, options);
+
+ // Setup the plugins.
+ options.plugins = jQuery.extend({
+ controller: 'default',
+ playLoader: 'default'
+ }, options.plugins);
+
+ // Derive from display
+ minplayer.display.call(this, 'player', context, options);
+}, minplayer);
+
+/** Derive from minplayer.display. */
+minplayer.prototype = new minplayer.display();
+
+/** Reset the constructor. */
+minplayer.prototype.constructor = minplayer;
+
+/**
+ * Define a way to debug.
+ */
+minplayer.console = console || {log: function(data) {}};
+
+/**
+ * @see minplayer.plugin.construct
+ */
+minplayer.prototype.construct = function() {
+
+ // Call the minplayer display constructor.
+ minplayer.display.prototype.construct.call(this);
+
+ // Load the plugins.
+ this.loadPlugins();
+
+ /** Variable to store the current media player. */
+ this.currentPlayer = 'html5';
+
+ // Add key events to the window.
+ this.addKeyEvents();
+
+ // Now load these files.
+ this.load(this.getFiles());
+
+ // Add the player events.
+ this.addEvents();
+
+ // The player is ready.
+ this.ready();
+};
+
+/**
+ * We need to bind to events we are interested in.
+ */
+minplayer.prototype.addEvents = function() {
+ var _this = this;
+ minplayer.get.call(this, this.options.id, null, function(plugin) {
+
+ // Bind to the error event.
+ plugin.bind('error', function(event, data) {
+
+ // If an error occurs within the html5 media player, then try
+ // to fall back to the flash player.
+ if (_this.currentPlayer == 'html5') {
+ _this.options.file.player = 'minplayer';
+ _this.loadPlayer();
+ }
+ else {
+ _this.error(data);
+ }
+ });
+
+ // Bind to the fullscreen event.
+ plugin.bind('fullscreen', function(event, data) {
+ _this.resize();
+ });
+ });
+};
+
+/**
+ * Sets an error on the player.
+ *
+ * @param {string} error The error to display on the player.
+ */
+minplayer.prototype.error = function(error) {
+ error = error || '';
+ if (this.elements.error) {
+
+ // Set the error text.
+ this.elements.error.text(error);
+ if (error) {
+ this.elements.error.show();
+ }
+ else {
+ this.elements.error.hide();
+ }
+ }
+};
+
+/**
+ * Adds key events to the player.
+ */
+minplayer.prototype.addKeyEvents = function() {
+
+ // Bind to key events...
+ jQuery(document).bind('keydown', {obj: this}, function(e) {
+ switch (e.keyCode) {
+ case 113: // ESC
+ case 27: // Q
+ e.data.obj.display.removeClass('fullscreen');
+ break;
+ }
+ });
+};
+
+/**
+ * Returns all the media files available for this player.
+ *
+ * @return {array} All the media files for this player.
+ */
+minplayer.prototype.getFiles = function() {
+ var files = [];
+ var mediaSrc = null;
+
+ // Get the files involved...
+ if (this.elements.media) {
+ mediaSrc = this.elements.media.attr('src');
+ if (mediaSrc) {
+ files.push({'path': mediaSrc});
+ }
+ jQuery('source', this.elements.media).each(function() {
+ files.push({
+ 'path': jQuery(this).attr('src'),
+ 'mimetype': jQuery(this).attr('type'),
+ 'codecs': jQuery(this).attr('codecs')
+ });
+ });
+ }
+
+ return files;
+};
+
+/**
+ * Returns the full media player object.
+ * @param {array} files An array of files to chose from.
+ * @return {object} The best media file to play in the current browser.
+ */
+minplayer.prototype.getMediaFile = function(files) {
+
+ // If there are no files then return null.
+ if (!files) {
+ return null;
+ }
+
+ // If the file is a single string, then return the file object.
+ if (typeof files === 'string') {
+ return new minplayer.file({'path': files});
+ }
+
+ // If the file is already a file object then just return.
+ if (files.path) {
+ return new minplayer.file(files);
+ }
+
+ // Add the files and get the best player to play.
+ var i = files.length, bestPriority = 0, mFile = null, file = null;
+ while (i--) {
+ file = files[i];
+
+ // Get the minplayer file object.
+ if (typeof file === 'string') {
+ file = new minplayer.file({'path': file});
+ }
+ else {
+ file = new minplayer.file(file);
+ }
+
+ // Determine the best file for this browser.
+ if (file.priority > bestPriority) {
+ mFile = file;
+ }
+ }
+
+ // Return the best minplayer file.
+ return mFile;
+};
+
+/**
+ * Loads a media player based on the current file.
+ */
+minplayer.prototype.loadPlayer = function() {
+
+ // Do nothing if there isn't a file.
+ if (!this.options.file) {
+ this.error('No media found.');
+ return;
+ }
+
+ if (!this.options.file.player) {
+ this.error('Cannot play media: ' + this.options.file.mimetype);
+ return;
+ }
+
+ // Reset the error.
+ this.error();
+
+ // Only destroy if the current player is different than the new player.
+ var player = this.options.file.player.toString();
+
+ // If there isn't media or if the players are different.
+ if (!this.media || (player !== this.currentPlayer)) {
+
+ // Set the current media player.
+ this.currentPlayer = player;
+
+ // Do nothing if we don't have a display.
+ if (!this.elements.display) {
+ this.error('No media display found.');
+ return;
+ }
+
+ // Store the queue.
+ var queue = this.media ? this.media.queue : {};
+
+ // Destroy the current media.
+ if (this.media) {
+ this.media.destroy();
+ }
+
+ // Get the class name and create the new player.
+ pClass = minplayer.players[this.options.file.player];
+
+ // Create the new media player.
+ this.media = new pClass(this.elements.display, this.options);
+
+ // Restore the queue.
+ this.media.queue = queue;
+
+ // Now get the media when it is ready.
+ this.get('media', function(media) {
+
+ // Load the media.
+ media.load();
+ });
+ }
+ // If the media object already exists...
+ else if (this.media) {
+
+ // Now load the different media file.
+ this.media.load(this.options.file);
+ }
+};
+
+/**
+ * Load a set of files or a single file for the media player.
+ *
+ * @param {array} files An array of files to chose from to load.
+ */
+minplayer.prototype.load = function(files) {
+
+ // Set the id and class.
+ var id = '', pClass = '';
+
+ // If no file was provided, then get it.
+ this.options.files = files || this.options.files;
+ this.options.file = this.getMediaFile(this.options.files);
+
+ // Now load the player.
+ this.loadPlayer();
+};
+
+/**
+ * Called when the player is resized.
+ */
+minplayer.prototype.resize = function() {
+
+ // Call onRezie for each plugin.
+ this.get(function(plugin) {
+ plugin.onResize();
+ });
+};
+/** The minplayer namespace. */
+var minplayer = minplayer || {};
+
+/**
+ * @constructor
+ * @class A class to easily handle images.
+ * @param {object} context The jQuery context.
+ * @param {object} options This components options.
+ */
+minplayer.image = function(context, options) {
+
+ // Determine if the image is loaded.
+ this.loaded = false;
+
+ // The image loader.
+ this.loader = null;
+
+ // The ratio of the image.
+ this.ratio = 0;
+
+ // The image element.
+ this.img = null;
+
+ // Derive from display
+ minplayer.display.call(this, 'image', context, options);
+};
+
+/** Derive from minplayer.display. */
+minplayer.image.prototype = new minplayer.display();
+
+/** Reset the constructor. */
+minplayer.image.prototype.constructor = minplayer.image;
+
+/**
+ * @see minplayer.plugin.construct
+ */
+minplayer.image.prototype.construct = function() {
+
+ // Say we need to resize.
+ this.allowResize = true;
+
+ // Call the media display constructor.
+ minplayer.display.prototype.construct.call(this);
+
+ // Set the container to not show any overflow...
+ this.display.css('overflow', 'hidden');
+
+ // Create the image loader.
+ var _this = this;
+
+ /** The loader for the image. */
+ this.loader = new Image();
+
+ /** Register for when the image is loaded within the loader. */
+ this.loader.onload = function() {
+ _this.loaded = true;
+ _this.ratio = (_this.loader.width / _this.loader.height);
+ _this.resize();
+ _this.trigger('loaded');
+ };
+
+ // We are now ready.
+ this.ready();
+};
+
+/**
+ * Loads an image.
+ *
+ * @param {string} src The source of the image to load.
+ */
+minplayer.image.prototype.load = function(src) {
+
+ // First clear the previous image.
+ this.clear(function() {
+
+ // Create the new image, and append to the display.
+ this.img = jQuery(document.createElement('img')).attr({src: ''}).hide();
+ this.display.append(this.img);
+ this.loader.src = src;
+ });
+};
+
+/**
+ * Clears an image.
+ *
+ * @param {function} callback Called when the image is done clearing.
+ */
+minplayer.image.prototype.clear = function(callback) {
+ this.loaded = false;
+ if (this.img) {
+ var _this = this;
+ this.img.fadeOut(function() {
+ _this.img.attr('src', '');
+ _this.loader.src = '';
+ $(this).remove();
+ callback.call(_this);
+ });
+ }
+ else {
+ callback.call(this);
+ }
+};
+
+/**
+ * Resize the image provided a width and height or nothing.
+ *
+ * @param {integer} width (optional) The width of the container.
+ * @param {integer} height (optional) The height of the container.
+ */
+minplayer.image.prototype.resize = function(width, height) {
+ width = width || this.display.width();
+ height = height || this.display.height();
+ if (width && height && this.loaded) {
+
+ // Get the scaled rectangle.
+ var rect = this.getScaledRect(this.ratio, {
+ width: width,
+ height: height
+ });
+
+ // Now set this image to the new size.
+ if (this.img) {
+ this.img.attr('src', this.loader.src).css({
+ marginLeft: rect.x,
+ marginTop: rect.y,
+ width: rect.width,
+ height: rect.height
+ });
+ }
+
+ // Show the container.
+ this.img.fadeIn();
+ }
+};
+
+/**
+ * @see minplayer.display#onResize
+ */
+minplayer.image.prototype.onResize = function() {
+
+ // Resize the image to fit.
+ this.resize();
+};
+/** The minplayer namespace. */
+var minplayer = minplayer || {};
+
+/**
+ * @constructor
+ * @class A wrapper class used to provide all the data necessary to control an
+ * individual file within this media player.
+ *
+ * @param {object} file A media file object with minimal required information.
+ */
+minplayer.file = function(file) {
+ this.duration = file.duration || 0;
+ this.bytesTotal = file.bytesTotal || 0;
+ this.quality = file.quality || 0;
+ this.stream = file.stream || '';
+ this.path = file.path || '';
+ this.codecs = file.codecs || '';
+
+ // These should be provided, but just in case...
+ this.extension = file.extension || this.getFileExtension();
+ this.mimetype = file.mimetype || file.filemime || this.getMimeType();
+ this.type = file.type || this.getType();
+
+ // Fail safe to try and guess the mimetype and media type.
+ if (!this.type) {
+ this.mimetype = this.getMimeType();
+ this.type = this.getType();
+ }
+
+ // Get the player.
+ this.player = file.player || this.getBestPlayer();
+ this.priority = file.priority || this.getPriority();
+ this.id = file.id || this.getId();
+};
+
+/**
+ * Returns the best player for the job.
+ *
+ * @return {string} The best player to play the media file.
+ */
+minplayer.file.prototype.getBestPlayer = function() {
+ var bestplayer = null, bestpriority = 0, _this = this;
+ jQuery.each(minplayer.players, function(name, player) {
+ var priority = player.getPriority();
+ if (player.canPlay(_this) && (priority > bestpriority)) {
+ bestplayer = name;
+ bestpriority = priority;
+ }
+ });
+ return bestplayer;
+};
+
+/**
+ * The priority of this file is determined by the priority of the best
+ * player multiplied by the priority of the mimetype.
+ *
+ * @return {integer} The priority of the media file.
+ */
+minplayer.file.prototype.getPriority = function() {
+ var priority = 1;
+ if (this.player) {
+ priority = minplayer.players[this.player].getPriority();
+ }
+ switch (this.mimetype) {
+ case 'video/x-webm':
+ case 'video/webm':
+ case 'application/octet-stream':
+ return priority * 10;
+ case 'video/mp4':
+ case 'audio/mp4':
+ case 'audio/mpeg':
+ return priority * 9;
+ case 'video/ogg':
+ case 'audio/ogg':
+ case 'video/quicktime':
+ return priority * 8;
+ default:
+ return priority * 5;
+ }
+};
+
+/**
+ * Returns the file extension of the file path.
+ *
+ * @return {string} The file extension.
+ */
+minplayer.file.prototype.getFileExtension = function() {
+ return this.path.substring(this.path.lastIndexOf('.') + 1).toLowerCase();
+};
+
+/**
+ * Returns the proper mimetype based off of the extension.
+ *
+ * @return {string} The mimetype of the file based off of extension.
+ */
+minplayer.file.prototype.getMimeType = function() {
+ switch (this.extension) {
+ case 'mp4': case 'm4v': case 'flv': case 'f4v':
+ return 'video/mp4';
+ case'webm':
+ return 'video/webm';
+ case 'ogg': case 'ogv':
+ return 'video/ogg';
+ case '3g2':
+ return 'video/3gpp2';
+ case '3gpp':
+ case '3gp':
+ return 'video/3gpp';
+ case 'mov':
+ return 'video/quicktime';
+ case'swf':
+ return 'application/x-shockwave-flash';
+ case 'oga':
+ return 'audio/ogg';
+ case 'mp3':
+ return 'audio/mpeg';
+ case 'm4a': case 'f4a':
+ return 'audio/mp4';
+ case 'aac':
+ return 'audio/aac';
+ case 'wav':
+ return 'audio/vnd.wave';
+ case 'wma':
+ return 'audio/x-ms-wma';
+ default:
+ return 'unknown';
+ }
+};
+
+/**
+ * The type of media this is: video or audio.
+ *
+ * @return {string} "video" or "audio" based on what the type of media this
+ * is.
+ */
+minplayer.file.prototype.getType = function() {
+ switch (this.mimetype) {
+ case 'video/mp4':
+ case 'video/webm':
+ case 'application/octet-stream':
+ case 'video/x-webm':
+ case 'video/ogg':
+ case 'video/3gpp2':
+ case 'video/3gpp':
+ case 'video/quicktime':
+ return 'video';
+ case 'audio/mp3':
+ case 'audio/mp4':
+ case 'audio/ogg':
+ case 'audio/mpeg':
+ return 'audio';
+ default:
+ return '';
+ }
+};
+
+/**
+ * Returns the ID for this media file.
+ *
+ * @return {string} The id for this media file which is provided by the player.
+ */
+minplayer.file.prototype.getId = function() {
+ var player = minplayer.players[this.player];
+ return (player && player.getMediaId) ? player.getMediaId(this) : '';
+};
+/** The minplayer namespace. */
+var minplayer = minplayer || {};
+
+/** Define the playLoader object. */
+minplayer.playLoader = minplayer.playLoader || {};
+
+/**
+ * @constructor
+ * @extends minplayer.display
+ * @class The play loader base class, which is used to control the busy
+ * cursor, big play button, and the opaque background which shows when the
+ * player is paused.
+ *
+ * @param {object} context The jQuery context.
+ * @param {object} options This components options.
+ */
+minplayer.playLoader.base = function(context, options) {
+
+ // Define the flags that control the busy cursor.
+ this.busy = new minplayer.flags();
+
+ // Define the flags that control the big play button.
+ this.bigPlay = new minplayer.flags();
+
+ /** The preview image. */
+ this.preview = null;
+
+ // Derive from display
+ minplayer.display.call(this, 'playLoader', context, options);
+};
+
+/** Derive from minplayer.display. */
+minplayer.playLoader.base.prototype = new minplayer.display();
+
+/** Reset the constructor. */
+minplayer.playLoader.base.prototype.constructor = minplayer.playLoader.base;
+
+/**
+ * The constructor.
+ */
+minplayer.playLoader.base.prototype.construct = function() {
+
+ // Call the media display constructor.
+ minplayer.display.prototype.construct.call(this);
+
+ // Get the media plugin.
+ this.get('media', function(media) {
+
+ // Only bind if this player does not have its own play loader.
+ if (!media.hasPlayLoader()) {
+
+ // Load the preview image.
+ this.loadPreview();
+
+ // Trigger a play event when someone clicks on the controller.
+ if (this.elements.bigPlay) {
+ this.elements.bigPlay.unbind().bind('click', function(event) {
+ event.preventDefault();
+ jQuery(this).hide();
+ media.play();
+ });
+ }
+
+ // Bind to the player events to control the play loader.
+ media.unbind('loadstart').bind('loadstart', {obj: this}, function(event) {
+ event.data.obj.busy.setFlag('media', true);
+ event.data.obj.bigPlay.setFlag('media', true);
+ if (event.data.obj.preview) {
+ event.data.obj.elements.preview.show();
+ }
+ event.data.obj.checkVisibility();
+ });
+ media.bind('waiting', {obj: this}, function(event) {
+ event.data.obj.busy.setFlag('media', true);
+ event.data.obj.checkVisibility();
+ });
+ media.bind('loadeddata', {obj: this}, function(event) {
+ event.data.obj.busy.setFlag('media', false);
+ event.data.obj.checkVisibility();
+ });
+ media.bind('playing', {obj: this}, function(event) {
+ event.data.obj.busy.setFlag('media', false);
+ event.data.obj.bigPlay.setFlag('media', false);
+ if (event.data.obj.preview) {
+ event.data.obj.elements.preview.hide();
+ }
+ event.data.obj.checkVisibility();
+ });
+ media.bind('pause', {obj: this}, function(event) {
+ event.data.obj.bigPlay.setFlag('media', true);
+ event.data.obj.checkVisibility();
+ });
+ }
+ else {
+
+ // Hide the busy cursor.
+ if (this.elements.busy) {
+ this.elements.busy.unbind().hide();
+ }
+
+ // Hide the big play button.
+ if (this.elements.bigPlay) {
+ this.elements.bigPlay.unbind().hide();
+ }
+
+ // Hide the display.
+ this.display.unbind().hide();
+ }
+ });
+
+ // We are now ready.
+ this.ready();
+};
+
+/**
+ * Loads the preview image.
+ */
+minplayer.playLoader.base.prototype.loadPreview = function() {
+
+ // If the preview element exists.
+ if (this.elements.preview) {
+
+ // Get the poster image.
+ if (!this.options.preview) {
+ this.options.preview = this.elements.media.attr('poster');
+ }
+
+ // Reset the media's poster image.
+ this.elements.media.attr('poster', '');
+
+ // If there is a preview to show...
+ if (this.options.preview) {
+
+ // Say that this has a preview.
+ this.elements.preview.addClass('has-preview').show();
+
+ // Create a new preview image.
+ this.preview = new minplayer.image(this.elements.preview, this.options);
+
+ // Create the image.
+ this.preview.load(this.options.preview);
+ }
+ else {
+
+ // Hide the preview.
+ this.elements.preview.hide();
+ }
+ }
+};
+
+/**
+ * Hide or show certain elements based on the state of the busy and big play
+ * button.
+ */
+minplayer.playLoader.base.prototype.checkVisibility = function() {
+
+ // Hide or show the busy cursor based on the flags.
+ if (this.busy.flag) {
+ this.elements.busy.show();
+ }
+ else {
+ this.elements.busy.hide();
+ }
+
+ // Hide or show the big play button based on the flags.
+ if (this.bigPlay.flag) {
+ this.elements.bigPlay.show();
+ }
+ else {
+ this.elements.bigPlay.hide();
+ }
+
+ // Show the control either flag is set.
+ if (this.bigPlay.flag || this.busy.flag) {
+ this.display.show();
+ }
+
+ // Hide the whole control if both flags are 0.
+ if (!this.bigPlay.flag && !this.busy.flag) {
+ this.display.hide();
+ }
+};
+/** The minplayer namespace. */
+var minplayer = minplayer || {};
+
+/** All the media player implementations */
+minplayer.players = minplayer.players || {};
+
+/**
+ * @constructor
+ * @extends minplayer.display
+ * @class The base media player class where all media players derive from.
+ *
+ * @param {object} context The jQuery context.
+ * @param {object} options This components options.
+ */
+minplayer.players.base = function(context, options) {
+
+ // Derive from display
+ minplayer.display.call(this, 'media', context, options);
+};
+
+/** Derive from minplayer.display. */
+minplayer.players.base.prototype = new minplayer.display();
+
+/** Reset the constructor. */
+minplayer.players.base.prototype.constructor = minplayer.players.base;
+
+/**
+ * Get the priority of this media player.
+ *
+ * @return {number} The priority of this media player.
+ */
+minplayer.players.base.getPriority = function() {
+ return 0;
+};
+
+/**
+ * Returns the ID for the media being played.
+ *
+ * @param {object} file A {@link minplayer.file} object.
+ * @return {string} The ID for the provided media.
+ */
+minplayer.players.base.getMediaId = function(file) {
+ return '';
+};
+
+/**
+ * Determine if we can play the media file.
+ *
+ * @param {object} file A {@link minplayer.file} object.
+ * @return {boolean} If this player can play this media type.
+ */
+minplayer.players.base.canPlay = function(file) {
+ return false;
+};
+
+/**
+ * @see minplayer.plugin.construct
+ * @this minplayer.players.base
+ */
+minplayer.players.base.prototype.construct = function() {
+
+ // Call the media display constructor.
+ minplayer.display.prototype.construct.call(this);
+
+ // Reset the variables to initial state.
+ this.reset();
+
+ /** The currently loaded media file. */
+ this.mediaFile = this.options.file;
+
+ // Get the player display object.
+ if (!this.playerFound()) {
+
+ // Remove the media element if found
+ if (this.elements.media) {
+ this.elements.media.remove();
+ }
+
+ // Create a new media player element.
+ this.display.html(this.create());
+ }
+
+ // Get the player object...
+ this.player = this.getPlayer();
+
+ // Set the focus of the element based on if they click in or outside of it.
+ var _this = this;
+ jQuery(document).bind('click', function(e) {
+ if (jQuery(e.target).closest('#' + _this.options.id).length == 0) {
+ _this.hasFocus = false;
+ }
+ else {
+ _this.hasFocus = true;
+ }
+ });
+
+ // Bind to key events...
+ jQuery(document).bind('keydown', {obj: this}, function(e) {
+ if (e.data.obj.hasFocus) {
+ e.preventDefault();
+ switch (e.keyCode) {
+ case 32: // SPACE
+ case 179: // GOOGLE play/pause button.
+ if (e.data.obj.playing) {
+ e.data.obj.pause();
+ }
+ else {
+ e.data.obj.play();
+ }
+ break;
+ case 38: // UP
+ e.data.obj.setVolumeRelative(0.1);
+ break;
+ case 40: // DOWN
+ e.data.obj.setVolumeRelative(-0.1);
+ break;
+ case 37: // LEFT
+ case 227: // GOOGLE TV REW
+ e.data.obj.seekRelative(-0.05);
+ break;
+ case 39: // RIGHT
+ case 228: // GOOGLE TV FW
+ e.data.obj.seekRelative(0.05);
+ break;
+ }
+ }
+ });
+};
+
+/**
+ * @see minplayer.plugin.destroy.
+ */
+minplayer.players.base.prototype.destroy = function() {
+ minplayer.plugin.prototype.destroy.call(this);
+
+ // Reset the player.
+ this.reset();
+};
+
+/**
+ * Resets all variables.
+ */
+minplayer.players.base.prototype.reset = function() {
+
+ // Reset the ready flag.
+ this.playerReady = false;
+
+ // The duration of the player.
+ this.duration = new minplayer.async();
+
+ // The current play time of the player.
+ this.currentTime = new minplayer.async();
+
+ // The amount of bytes loaded in the player.
+ this.bytesLoaded = new minplayer.async();
+
+ // The total amount of bytes for the media.
+ this.bytesTotal = new minplayer.async();
+
+ // The bytes that the download started with.
+ this.bytesStart = new minplayer.async();
+
+ // The current volume of the player.
+ this.volume = new minplayer.async();
+
+ // Reset focus.
+ this.hasFocus = false;
+
+ // We are not playing.
+ this.playing = false;
+
+ // We are not loading.
+ this.loading = false;
+
+ // If the player exists, then unbind all events.
+ if (this.player) {
+ jQuery(this.player).unbind();
+ }
+};
+
+/**
+ * Create a polling timer.
+ * @param {function} callback The function to call when you poll.
+ */
+minplayer.players.base.prototype.poll = function(callback) {
+ var _this = this;
+ setTimeout(function later() {
+ if (callback.call(_this)) {
+ setTimeout(later, 1000);
+ }
+ }, 1000);
+};
+
+/**
+ * Called when the player is ready to recieve events and commands.
+ */
+minplayer.players.base.prototype.onReady = function() {
+ // Store the this pointer.
+ var _this = this;
+
+ // Set the ready flag.
+ this.playerReady = true;
+
+ // Set the volume to the default.
+ this.setVolume(this.options.volume / 100);
+
+ // Setup the progress interval.
+ this.loading = true;
+
+ // Create a poll to get the progress.
+ this.poll(function() {
+
+ // Only do this if the play interval is set.
+ if (_this.loading) {
+
+ // Get the bytes loaded asynchronously.
+ _this.getBytesLoaded(function(bytesLoaded) {
+
+ // Get the bytes total asynchronously.
+ _this.getBytesTotal(function(bytesTotal) {
+
+ // Trigger an event about the progress.
+ if (bytesLoaded || bytesTotal) {
+
+ // Get the bytes start, but don't require it.
+ var bytesStart = 0;
+ _this.getBytesStart(function(val) {
+ bytesStart = val;
+ });
+
+ // Trigger a progress event.
+ _this.trigger('progress', {
+ loaded: bytesLoaded,
+ total: bytesTotal,
+ start: bytesStart
+ });
+
+ // Say we are not longer loading if they are equal.
+ if (bytesLoaded >= bytesTotal) {
+ _this.loading = false;
+ }
+ }
+ });
+ });
+ }
+
+ return _this.loading;
+ });
+
+ // We are now ready.
+ this.ready();
+
+ // Trigger that the load has started.
+ this.trigger('loadstart');
+};
+
+/**
+ * Should be called when the media is playing.
+ */
+minplayer.players.base.prototype.onPlaying = function() {
+ // Store the this pointer.
+ var _this = this;
+
+ // Trigger an event that we are playing.
+ this.trigger('playing');
+
+ // Say that this player has focus.
+ this.hasFocus = true;
+
+ // Set the playInterval to true.
+ this.playing = true;
+
+ // Create a poll to get the timeupate.
+ this.poll(function() {
+
+ // Only do this if the play interval is set.
+ if (_this.playing) {
+
+ // Get the current time asyncrhonously.
+ _this.getCurrentTime(function(currentTime) {
+
+ // Get the duration asynchronously.
+ _this.getDuration(function(duration) {
+
+ // Convert these to floats.
+ currentTime = parseFloat(currentTime);
+ duration = parseFloat(duration);
+
+ // Trigger an event about the progress.
+ if (currentTime || duration) {
+
+ // Trigger an update event.
+ _this.trigger('timeupdate', {
+ currentTime: currentTime,
+ duration: duration
+ });
+ }
+ });
+ });
+ }
+
+ return _this.playing;
+ });
+};
+
+/**
+ * Should be called when the media is paused.
+ */
+minplayer.players.base.prototype.onPaused = function() {
+
+ // Trigger an event that we are paused.
+ this.trigger('pause');
+
+ // Remove focus.
+ this.hasFocus = false;
+
+ // Say we are not playing.
+ this.playing = false;
+};
+
+/**
+ * Should be called when the media is complete.
+ */
+minplayer.players.base.prototype.onComplete = function() {
+ // Stop the intervals.
+ this.playing = false;
+ this.loading = false;
+ this.hasFocus = false;
+ this.trigger('ended');
+};
+
+/**
+ * Should be called when the media is done loading.
+ */
+minplayer.players.base.prototype.onLoaded = function() {
+ this.trigger('loadeddata');
+};
+
+/**
+ * Should be called when the player is waiting.
+ */
+minplayer.players.base.prototype.onWaiting = function() {
+ this.trigger('waiting');
+};
+
+/**
+ * Called when an error occurs.
+ *
+ * @param {string} errorCode The error that was triggered.
+ */
+minplayer.players.base.prototype.onError = function(errorCode) {
+ this.hasFocus = false;
+ this.trigger('error', errorCode);
+};
+
+/**
+ * @see minplayer.players.base#isReady
+ * @return {boolean} Checks to see if the Flash is ready.
+ */
+minplayer.players.base.prototype.isReady = function() {
+
+ // Return that the player is set and the ready flag is good.
+ return (this.player && this.playerReady);
+};
+
+/**
+ * Determines if the player should show the playloader.
+ *
+ * @return {bool} If this player implements its own playLoader.
+ */
+minplayer.players.base.prototype.hasPlayLoader = function() {
+ return false;
+};
+
+/**
+ * Returns if the media player is already within the DOM.
+ *
+ * @return {boolean} TRUE - if the player is in the DOM, FALSE otherwise.
+ */
+minplayer.players.base.prototype.playerFound = function() {
+ return false;
+};
+
+/**
+ * Creates the media player and inserts it in the DOM.
+ *
+ * @return {object} The media player entity.
+ */
+minplayer.players.base.prototype.create = function() {
+ this.reset();
+ return null;
+};
+
+/**
+ * Returns the media player object.
+ *
+ * @return {object} The media player object.
+ */
+minplayer.players.base.prototype.getPlayer = function() {
+ return this.player;
+};
+
+/**
+ * Loads a new media player.
+ *
+ * @param {object} file A {@link minplayer.file} object.
+ */
+minplayer.players.base.prototype.load = function(file) {
+
+ // Store the media file for future lookup.
+ if (file) {
+ this.reset();
+ this.mediaFile = file;
+ }
+};
+
+/**
+ * Play the loaded media file.
+ */
+minplayer.players.base.prototype.play = function() {
+};
+
+/**
+ * Pause the loaded media file.
+ */
+minplayer.players.base.prototype.pause = function() {
+};
+
+/**
+ * Stop the loaded media file.
+ */
+minplayer.players.base.prototype.stop = function() {
+ this.playing = false;
+ this.loading = false;
+ this.hasFocus = false;
+};
+
+/**
+ * Seeks to relative position.
+ *
+ * @param {number} pos Relative position. -1 to 1 (percent), > 1 (seconds).
+ */
+minplayer.players.base.prototype.seekRelative = function(pos) {
+
+ // Get the current time asyncrhonously.
+ var _this = this;
+ this.getCurrentTime(function(currentTime) {
+
+ // Get the duration asynchronously.
+ _this.getDuration(function(duration) {
+
+ // Only do this if we have a duration.
+ if (duration) {
+
+ // Get the position.
+ var seekPos = 0;
+ if ((pos > -1) && (pos < 1)) {
+ seekPos = (currentTime / duration) + parseFloat(pos);
+ }
+ else {
+ seekPos = (currentTime + parseFloat(pos)) / duration;
+ }
+
+ // Set the seek value.
+ _this.seek(seekPos);
+ }
+ });
+ });
+};
+
+/**
+ * Seek the loaded media.
+ *
+ * @param {number} pos The position to seek the minplayer. 0 to 1.
+ */
+minplayer.players.base.prototype.seek = function(pos) {
+};
+
+/**
+ * Set the volume of the loaded minplayer.
+ *
+ * @param {number} vol -1 to 1 - The relative amount to increase or decrease.
+ */
+minplayer.players.base.prototype.setVolumeRelative = function(vol) {
+
+ // Get the volume
+ var _this = this;
+ this.getVolume(function(newVol) {
+ newVol += parseFloat(vol);
+ newVol = (newVol < 0) ? 0 : newVol;
+ newVol = (newVol > 1) ? 1 : newVol;
+ _this.setVolume(newVol);
+ });
+};
+
+/**
+ * Set the volume of the loaded minplayer.
+ *
+ * @param {number} vol The volume to set the media. 0 to 1.
+ */
+minplayer.players.base.prototype.setVolume = function(vol) {
+ this.trigger('volumeupdate', vol);
+};
+
+/**
+ * Get the volume from the loaded media.
+ *
+ * @param {function} callback Called when the volume is determined.
+ * @return {number} The volume of the media; 0 to 1.
+ */
+minplayer.players.base.prototype.getVolume = function(callback) {
+ return this.volume.get(callback);
+};
+
+/**
+ * Get the current time for the media being played.
+ *
+ * @param {function} callback Called when the time is determined.
+ * @return {number} The volume of the media; 0 to 1.
+ */
+minplayer.players.base.prototype.getCurrentTime = function(callback) {
+ return this.currentTime.get(callback);
+};
+
+/**
+ * Return the duration of the loaded media.
+ *
+ * @param {function} callback Called when the duration is determined.
+ * @return {number} The duration of the loaded media.
+ */
+minplayer.players.base.prototype.getDuration = function(callback) {
+ return this.duration.get(callback);
+};
+
+/**
+ * Return the start bytes for the loaded media.
+ *
+ * @param {function} callback Called when the start bytes is determined.
+ * @return {int} The bytes that were started.
+ */
+minplayer.players.base.prototype.getBytesStart = function(callback) {
+ return this.bytesStart.get(callback);
+};
+
+/**
+ * Return the bytes of media loaded.
+ *
+ * @param {function} callback Called when the bytes loaded is determined.
+ * @return {int} The amount of bytes loaded.
+ */
+minplayer.players.base.prototype.getBytesLoaded = function(callback) {
+ return this.bytesLoaded.get(callback);
+};
+
+/**
+ * Return the total amount of bytes.
+ *
+ * @param {function} callback Called when the bytes total is determined.
+ * @return {int} The total amount of bytes for this media.
+ */
+minplayer.players.base.prototype.getBytesTotal = function(callback) {
+ return this.bytesTotal.get(callback);
+};
+/** The minplayer namespace. */
+var minplayer = minplayer || {};
+
+/** All the media player implementations */
+minplayer.players = minplayer.players || {};
+
+/**
+ * @constructor
+ * @extends minplayer.display
+ * @class The HTML5 media player implementation.
+ *
+ * @param {object} context The jQuery context.
+ * @param {object} options This components options.
+ */
+minplayer.players.html5 = function(context, options) {
+
+ // Derive players base.
+ minplayer.players.base.call(this, context, options);
+};
+
+/** Derive from minplayer.players.base. */
+minplayer.players.html5.prototype = new minplayer.players.base();
+
+/** Reset the constructor. */
+minplayer.players.html5.prototype.constructor = minplayer.players.html5;
+
+/**
+ * @see minplayer.players.base#getPriority
+ * @return {number} The priority of this media player.
+ */
+minplayer.players.html5.getPriority = function() {
+ return 10;
+};
+
+/**
+ * @see minplayer.players.base#canPlay
+ * @return {boolean} If this player can play this media type.
+ */
+minplayer.players.html5.canPlay = function(file) {
+ switch (file.mimetype) {
+ case 'video/ogg':
+ return !!minplayer.playTypes.videoOGG;
+ case 'video/mp4':
+ return !!minplayer.playTypes.videoH264;
+ case 'video/x-webm':
+ case 'video/webm':
+ case 'application/octet-stream':
+ return !!minplayer.playTypes.videoWEBM;
+ case 'audio/ogg':
+ return !!minplayer.playTypes.audioOGG;
+ case 'audio/mpeg':
+ return !!minplayer.playTypes.audioMP3;
+ case 'audio/mp4':
+ return !!minplayer.playTypes.audioMP4;
+ default:
+ return false;
+ }
+};
+
+/**
+ * @see minplayer.plugin.construct
+ */
+minplayer.players.html5.prototype.construct = function() {
+
+ // Call base constructor.
+ minplayer.players.base.prototype.construct.call(this);
+
+ // Store the this pointer...
+ var _this = this;
+
+ // For the HTML5 player, we will just pass events along...
+ if (this.player) {
+
+ this.player.addEventListener('abort', function() {
+ _this.trigger('abort');
+ }, false);
+ this.player.addEventListener('loadstart', function() {
+ _this.onReady();
+ }, false);
+ this.player.addEventListener('loadeddata', function() {
+ _this.onLoaded();
+ }, false);
+ this.player.addEventListener('loadedmetadata', function() {
+ _this.onLoaded();
+ }, false);
+ this.player.addEventListener('canplaythrough', function() {
+ _this.onLoaded();
+ }, false);
+ this.player.addEventListener('ended', function() {
+ _this.onComplete();
+ }, false);
+ this.player.addEventListener('pause', function() {
+ _this.onPaused();
+ }, false);
+ this.player.addEventListener('play', function() {
+ _this.onPlaying();
+ }, false);
+ this.player.addEventListener('playing', function() {
+ _this.onPlaying();
+ }, false);
+ this.player.addEventListener('error', function() {
+ _this.trigger('error', 'An error occured - ' + this.error.code);
+ }, false);
+ this.player.addEventListener('waiting', function() {
+ _this.onWaiting();
+ }, false);
+ this.player.addEventListener('durationchange', function() {
+ _this.duration.set(this.duration);
+ _this.trigger('durationchange', {duration: this.duration});
+ }, false);
+ this.player.addEventListener('progress', function(event) {
+ _this.bytesTotal.set(event.total);
+ _this.bytesLoaded.set(event.loaded);
+ }, false);
+ }
+};
+
+/**
+ * @see minplayer.players.base#playerFound
+ * @return {boolean} TRUE - if the player is in the DOM, FALSE otherwise.
+ */
+minplayer.players.html5.prototype.playerFound = function() {
+ return (this.display.find(this.mediaFile.type).length > 0);
+};
+
+/**
+ * @see minplayer.players.base#create
+ * @return {object} The media player entity.
+ */
+minplayer.players.html5.prototype.create = function() {
+ minplayer.players.base.prototype.create.call(this);
+ var element = jQuery(document.createElement(this.mediaFile.type))
+ .attr(this.options.attributes)
+ .append(
+ jQuery(document.createElement('source')).attr({
+ 'src': this.mediaFile.path
+ })
+ );
+
+ // Fix the fluid width and height.
+ element.eq(0)[0].setAttribute('width', '100%');
+ element.eq(0)[0].setAttribute('height', '100%');
+ return element;
+};
+
+/**
+ * @see minplayer.players.base#getPlayer
+ * @return {object} The media player object.
+ */
+minplayer.players.html5.prototype.getPlayer = function() {
+ return this.options.elements.media.eq(0)[0];
+};
+
+/**
+ * @see minplayer.players.base#load
+ */
+minplayer.players.html5.prototype.load = function(file) {
+
+ if (file && this.isReady()) {
+
+ // Get the current source.
+ var src = this.options.elements.media.attr('src');
+
+ // If the source is different.
+ if (src != file.path) {
+
+ // Change the source...
+ var code = '