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: + *

+ *   var playTypes = new minplayer.compatibility();
+ *
+ *   if (playTypes.videoOGG) {
+ *     console.log("This browser can play OGG video");
+ *   }
+ *
+ *   if (playTypes.videoH264) {
+ *     console.log("This browser can play H264 video");
+ *   }
+ *
+ *   if (playTypes.videoWEBM) {
+ *     console.log("This browser can play WebM video");
+ *   }
+ *
+ *   if (playTypes.audioOGG) {
+ *     console.log("This browser can play OGG audio");
+ *   }
+ *
+ *   if (playTypes.audioMP3) {
+ *     console.log("This browser can play MP3 audio");
+ *   }
+ *
+ *   if (playTypes.audioMP4) {
+ *     console.log("This browser can play MP4 audio");
+ *   }
+ * 
+ */ +minplayer.compatibility = function() { + var elem = null; + + // Create a video element. + elem = document.createElement('video'); + + /** Can play OGG video */ + this.videoOGG = checkPlayType(elem, 'video/ogg'); + + /** Can play H264 video */ + this.videoH264 = checkPlayType(elem, [ + 'video/mp4', + 'video/h264' + ]); + + /** Can play WEBM video */ + this.videoWEBM = checkPlayType(elem, [ + 'video/x-webm', + 'video/webm', + 'application/octet-stream' + ]); + + // Create an audio element. + elem = document.createElement('audio'); + + /** Can play audio OGG */ + this.audioOGG = checkPlayType(elem, 'audio/ogg'); + + /** Can play audio MP3 */ + this.audioMP3 = checkPlayType(elem, 'audio/mpeg'); + + /** Can play audio MP4 */ + this.audioMP4 = checkPlayType(elem, 'audio/mp4'); +}; + +if (!minplayer.playTypes) { + + /** The compatible playtypes for this browser. */ + minplayer.playTypes = new minplayer.compatibility(); +} +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** + * @constructor + * @class This class keeps track of asynchronous get requests for certain + * variables within the player. + */ +minplayer.async = function() { + + /** The final value of this asynchronous variable. */ + this.value = null; + + /** The queue of callbacks to call when this value is determined. */ + this.queue = []; +}; + +/** + * Retrieve the value of this variable. + * + * @param {function} callback The function to call when the value is determined. + * @param {function} pollValue The poll function to try and get the value every + * 1 second if the value is not set. + */ +minplayer.async.prototype.get = function(callback, pollValue) { + + // If the value is set, then immediately call the callback, otherwise, just + // add it to the queue when the variable is set. + if (this.value !== null) { + callback(this.value); + } + else { + + // Add this callback to the queue. + this.queue.push(callback); + } +}; + +/** + * Sets the value of an asynchronous value. + * + * @param {void} val The value to set. + */ +minplayer.async.prototype.set = function(val) { + + // Set the value. + this.value = val; + + // Get the callback queue length. + var i = this.queue.length; + + // Iterate through all the callbacks and call them. + if (i) { + while (i--) { + this.queue[i](val); + } + + // Reset the queue. + this.queue = []; + } +}; +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** + * @constructor + * @class This is a class used to keep track of flag states + * which is used to control the busy cursor, big play button, among other + * items in which multiple components can have an interest in hiding or + * showing a single element on the screen. + * + *

+ * Usage: + *


+ *   // Declare a flags variable.
+ *   var flags = new minplayer.flags();
+ *
+ *   // Set the flag based on two components interested in the flag.
+ *   flags.setFlag("component1", true);
+ *   flags.setFlag("component2", true);
+ *
+ *   // Print out the value of the flags. ( Prints 3 )
+ *   console.log(flags.flags);
+ *
+ *   // Now unset a single components flag.
+ *   flags.setFlag("component1", false);
+ *
+ *   // Print out the value of the flags.
+ *   console.log(flags.flags);
+ *
+ *   // Unset the other components flag.
+ *   flags.setFlag("component2", false);
+ *
+ *   // Print out the value of the flags.
+ *   console.log(flags.flags);
+ * 
+ *

+ */ +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 = '' : '>'; + this.options.elements.media.attr('src', '').empty().html(code); + } + } + + // Always call the base first on load... + minplayer.players.base.prototype.load.call(this, file); +}; + +/** + * @see minplayer.players.base#play + */ +minplayer.players.html5.prototype.play = function() { + minplayer.players.base.prototype.play.call(this); + if (this.isReady()) { + this.player.play(); + } +}; + +/** + * @see minplayer.players.base#pause + */ +minplayer.players.html5.prototype.pause = function() { + minplayer.players.base.prototype.pause.call(this); + if (this.isReady()) { + this.player.pause(); + } +}; + +/** + * @see minplayer.players.base#stop + */ +minplayer.players.html5.prototype.stop = function() { + minplayer.players.base.prototype.stop.call(this); + if (this.isReady()) { + this.player.pause(); + this.player.src = ''; + } +}; + +/** + * @see minplayer.players.base#seek + */ +minplayer.players.html5.prototype.seek = function(pos) { + minplayer.players.base.prototype.seek.call(this, pos); + if (this.isReady()) { + this.player.currentTime = pos; + } +}; + +/** + * @see minplayer.players.base#setVolume + */ +minplayer.players.html5.prototype.setVolume = function(vol) { + minplayer.players.base.prototype.setVolume.call(this, vol); + if (this.isReady()) { + this.player.volume = vol; + } +}; + +/** + * @see minplayer.players.base#getVolume + */ +minplayer.players.html5.prototype.getVolume = function(callback) { + if (this.isReady()) { + callback(this.player.volume); + } +}; + +/** + * @see minplayer.players.base#getDuration + */ +minplayer.players.html5.prototype.getDuration = function(callback) { + if (this.isReady()) { + callback(this.player.duration); + } +}; + +/** + * @see minplayer.players.base#getCurrentTime + */ +minplayer.players.html5.prototype.getCurrentTime = function(callback) { + if (this.isReady()) { + callback(this.player.currentTime); + } +}; + +/** + * @see minplayer.players.base#getBytesLoaded + */ +minplayer.players.html5.prototype.getBytesLoaded = function(callback) { + if (this.isReady()) { + var loaded = 0; + + // Check several different possibilities. + if (this.bytesLoaded.value) { + loaded = this.bytesLoaded.value; + } + else if (this.player.buffered && + this.player.buffered.length > 0 && + this.player.buffered.end && + this.player.duration) { + loaded = this.player.buffered.end(0); + } + else if (this.player.bytesTotal != undefined && + this.player.bytesTotal > 0 && + this.player.bufferedBytes != undefined) { + loaded = this.player.bufferedBytes; + } + + // Return the loaded amount. + callback(loaded); + } +}; + +/** + * @see minplayer.players.base#getBytesTotal + */ +minplayer.players.html5.prototype.getBytesTotal = function(callback) { + if (this.isReady()) { + + var total = 0; + + // Check several different possibilities. + if (this.bytesTotal.value) { + total = this.bytesTotal.value; + } + else if (this.player.buffered && + this.player.buffered.length > 0 && + this.player.buffered.end && + this.player.duration) { + total = this.player.duration; + } + else if (this.player.bytesTotal != undefined && + this.player.bytesTotal > 0 && + this.player.bufferedBytes != undefined) { + total = this.player.bytesTotal; + } + + // Return the loaded amount. + callback(total); + } +}; +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** All the media player implementations */ +minplayer.players = minplayer.players || {}; + +/** + * @constructor + * @extends minplayer.display + * @class The Flash media player class to control the flash fallback. + * + * @param {object} context The jQuery context. + * @param {object} options This components options. + */ +minplayer.players.flash = function(context, options) { + + // Derive from players base. + minplayer.players.base.call(this, context, options); +}; + +/** Derive from minplayer.players.base. */ +minplayer.players.flash.prototype = new minplayer.players.base(); + +/** Reset the constructor. */ +minplayer.players.flash.prototype.constructor = minplayer.players.flash; + +/** + * @see minplayer.players.base#getPriority + * @return {number} The priority of this media player. + */ +minplayer.players.flash.getPriority = function() { + return 0; +}; + +/** + * @see minplayer.players.base#canPlay + * @return {boolean} If this player can play this media type. + */ +minplayer.players.flash.canPlay = function(file) { + return false; +}; + +/** + * API to return the Flash player code provided params. + * + * @param {object} params The params used to populate the Flash code. + * @return {object} A Flash DOM element. + */ +minplayer.players.flash.getFlash = function(params) { + // Get the protocol. + var protocol = window.location.protocol; + if (protocol.charAt(protocol.length - 1) == ':') { + protocol = protocol.substring(0, protocol.length - 1); + } + + // Convert the flashvars object to a string... + var flashVars = jQuery.param(params.flashvars); + + // Set the codebase. + var codebase = protocol + '://fpdownload.macromedia.com'; + codebase += '/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0'; + + // Get the HTML flash object string. + var flash = ' '; + flash += ''; + flash += ''; + flash += ''; + flash += ''; + flash += ''; + flash += ''; + flash += ' 0); +}; + +/** + * @see minplayer.players.base#getPlayer + * @return {object} The media player object. + */ +minplayer.players.flash.prototype.getPlayer = function() { + // IE needs the object, everyone else just needs embed. + var object = jQuery.browser.msie ? 'object' : 'embed'; + return jQuery(object, this.display).eq(0)[0]; +}; +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** All the media player implementations */ +minplayer.players = minplayer.players || {}; + +/** + * @constructor + * @extends minplayer.display + * @class The Flash media player class to control the flash fallback. + * + * @param {object} context The jQuery context. + * @param {object} options This components options. + */ +minplayer.players.minplayer = function(context, options) { + + // Derive from players flash. + minplayer.players.flash.call(this, context, options); +}; + +/** Derive from minplayer.players.flash. */ +minplayer.players.minplayer.prototype = new minplayer.players.flash(); + +/** Reset the constructor. */ +minplayer.players.minplayer.prototype.constructor = minplayer.players.minplayer; + +/** + * Called when the Flash player is ready. + * + * @param {string} id The media player ID. + */ +window.onFlashPlayerReady = function(id) { + var media = minplayer.get(id, 'media'); + if (media) { + media.onReady(); + } +}; + +/** + * Called when the Flash player updates. + * + * @param {string} id The media player ID. + * @param {string} eventType The event type that was triggered. + */ +window.onFlashPlayerUpdate = function(id, eventType) { + var media = minplayer.get(id, 'media'); + if (media) { + media.onMediaUpdate(eventType); + } +}; + +/** + * Used to debug from the Flash player to the browser console. + * + * @param {string} debug The debug string. + */ +window.onFlashPlayerDebug = function(debug) { + minplayer.console.log(debug); +}; + +/** + * @see minplayer.players.base#getPriority + * @return {number} The priority of this media player. + */ +minplayer.players.minplayer.getPriority = function() { + return 1; +}; + +/** + * @see minplayer.players.base#canPlay + * @return {boolean} If this player can play this media type. + */ +minplayer.players.minplayer.canPlay = function(file) { + switch (file.mimetype) { + case 'video/mp4': + case 'video/x-webm': + case 'video/webm': + case 'application/octet-stream': + case 'video/quicktime': + case 'video/3gpp2': + case 'video/3gpp': + case 'application/x-shockwave-flash': + case 'audio/mpeg': + case 'audio/mp4': + case 'audio/aac': + case 'audio/vnd.wave': + case 'audio/x-ms-wma': + return true; + + default: + return false; + } +}; + +/** + * @see minplayer.players.base#create + * @return {object} The media player entity. + */ +minplayer.players.minplayer.prototype.create = function() { + minplayer.players.flash.prototype.create.call(this); + + // The flash variables for this flash player. + var flashVars = { + 'id': this.options.id, + 'debug': this.options.debug, + 'config': 'nocontrols', + 'file': this.mediaFile.path, + 'autostart': this.options.autoplay + }; + + // Return a flash media player object. + return minplayer.players.flash.getFlash({ + swf: this.options.swfplayer, + id: this.options.id + '_player', + width: this.options.width, + height: '100%', + flashvars: flashVars, + wmode: this.options.wmode + }); +}; + +/** + * Called when the Flash player has an update. + * + * @param {string} eventType The event that was triggered in the player. + */ +minplayer.players.minplayer.prototype.onMediaUpdate = function(eventType) { + switch (eventType) { + case 'mediaMeta': + this.onLoaded(); + break; + case 'mediaPlaying': + this.onPlaying(); + break; + case 'mediaPaused': + this.onPaused(); + break; + case 'mediaComplete': + this.onComplete(); + break; + } +}; + +/** + * @see minplayer.players.base#load + */ +minplayer.players.minplayer.prototype.load = function(file) { + minplayer.players.flash.prototype.load.call(this, file); + if (file && this.isReady()) { + this.player.loadMedia(file.path, file.stream); + } +}; + +/** + * @see minplayer.players.base#play + */ +minplayer.players.minplayer.prototype.play = function() { + minplayer.players.flash.prototype.play.call(this); + if (this.isReady()) { + this.player.playMedia(); + } +}; + +/** + * @see minplayer.players.base#pause + */ +minplayer.players.minplayer.prototype.pause = function() { + minplayer.players.flash.prototype.pause.call(this); + if (this.isReady()) { + this.player.pauseMedia(); + } +}; + +/** + * @see minplayer.players.base#stop + */ +minplayer.players.minplayer.prototype.stop = function() { + minplayer.players.flash.prototype.stop.call(this); + if (this.isReady()) { + this.player.stopMedia(); + } +}; + +/** + * @see minplayer.players.base#seek + */ +minplayer.players.minplayer.prototype.seek = function(pos) { + minplayer.players.flash.prototype.seek.call(this, pos); + if (this.isReady()) { + this.player.seekMedia(pos); + } +}; + +/** + * @see minplayer.players.base#setVolume + */ +minplayer.players.minplayer.prototype.setVolume = function(vol) { + minplayer.players.flash.prototype.setVolume.call(this, vol); + if (this.isReady()) { + this.player.setVolume(vol); + } +}; + +/** + * @see minplayer.players.base#getVolume + */ +minplayer.players.minplayer.prototype.getVolume = function(callback) { + if (this.isReady()) { + callback(this.player.getVolume()); + } +}; + +/** + * @see minplayer.players.flash#getDuration + */ +minplayer.players.minplayer.prototype.getDuration = function(callback) { + if (this.isReady()) { + + // Check to see if it is immediately available. + var duration = this.player.getDuration(); + if (duration) { + callback(duration); + } + else { + + // If not, then check every half second... + var _this = this; + setTimeout(function check() { + duration = _this.player.getDuration(); + if (duration) { + callback(duration); + } + else { + setTimeout(check, 500); + } + }, 500); + } + } +}; + +/** + * @see minplayer.players.base#getCurrentTime + */ +minplayer.players.minplayer.prototype.getCurrentTime = function(callback) { + if (this.isReady()) { + callback(this.player.getCurrentTime()); + } +}; + +/** + * @see minplayer.players.base#getBytesLoaded + */ +minplayer.players.minplayer.prototype.getBytesLoaded = function(callback) { + if (this.isReady()) { + callback(this.player.getMediaBytesLoaded()); + } +}; + +/** + * @see minplayer.players.base#getBytesTotal. + */ +minplayer.players.minplayer.prototype.getBytesTotal = function(callback) { + if (this.isReady()) { + callback(this.player.getMediaBytesTotal()); + } +}; +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** All the media player implementations */ +minplayer.players = minplayer.players || {}; + +/** + * @constructor + * @extends minplayer.players.base + * @class The YouTube media player. + * + * @param {object} context The jQuery context. + * @param {object} options This components options. + */ +minplayer.players.youtube = function(context, options) { + + /** The quality of the YouTube stream. */ + this.quality = 'default'; + + // Derive from players base. + minplayer.players.base.call(this, context, options); +}; + +/** Derive from minplayer.players.base. */ +minplayer.players.youtube.prototype = new minplayer.players.base(); + +/** Reset the constructor. */ +minplayer.players.youtube.prototype.constructor = minplayer.players.youtube; + +/** + * @see minplayer.players.base#getPriority + * @return {number} The priority of this media player. + */ +minplayer.players.youtube.getPriority = function() { + return 10; +}; + +/** + * @see minplayer.players.base#canPlay + * @return {boolean} If this player can play this media type. + */ +minplayer.players.youtube.canPlay = function(file) { + + // Check for the mimetype for youtube. + if (file.mimetype === 'video/youtube') { + return true; + } + + // If the path is a YouTube path, then return true. + return (file.path.search(/^http(s)?\:\/\/(www\.)?youtube\.com/i) === 0); +}; + +/** + * Return the ID for a provided media file. + * + * @param {object} file A {@link minplayer.file} object. + * @return {string} The ID for the provided media. + */ +minplayer.players.youtube.getMediaId = function(file) { + var regex = /^http[s]?\:\/\/(www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9]+)/i; + if (file.path.search(regex) === 0) { + return file.path.replace(regex, '$2'); + } + else { + return file.path; + } +}; + +/** + * Register this youtube player so that multiple players can be present + * on the same page without event collision. + */ +minplayer.players.youtube.prototype.register = function() { + + /** + * Register the standard youtube api ready callback. + */ + window.onYouTubePlayerAPIReady = function() { + + // Iterate over each media player. + jQuery.each(minplayer.get(null, 'player'), function(id, player) { + + // Make sure this is the youtube player. + if (player.currentPlayer == 'youtube') { + + // Create a new youtube player object for this instance only. + var playerId = id + '-player'; + + // Set this players media. + player.media.player = new YT.Player(playerId, { + events: { + 'onReady': function(event) { + player.media.onReady(event); + }, + 'onStateChange': function(event) { + player.media.onPlayerStateChange(event); + }, + 'onPlaybackQualityChange': function(newQuality) { + player.media.onQualityChange(newQuality); + }, + 'onError': function(errorCode) { + player.media.onError(errorCode); + } + } + }); + } + }); + } +}; + +/** + * Translates the player state for the YouTube API player. + * + * @param {number} playerState The YouTube player state. + */ +minplayer.players.youtube.prototype.setPlayerState = function(playerState) { + switch (playerState) { + case YT.PlayerState.CUED: + break; + case YT.PlayerState.BUFFERING: + this.onWaiting(); + break; + case YT.PlayerState.PAUSED: + this.onPaused(); + break; + case YT.PlayerState.PLAYING: + this.onPlaying(); + break; + case YT.PlayerState.ENDED: + this.onComplete(); + break; + default: + break; + } +}; + +/** + * Called when an error occurs. + * + * @param {string} event The onReady event that was triggered. + */ +minplayer.players.youtube.prototype.onReady = function(event) { + minplayer.players.base.prototype.onReady.call(this); + this.onLoaded(); +}; + +/** + * Checks to see if this player can be found. + * @return {bool} TRUE - Player is found, FALSE - otherwise. + */ +minplayer.players.youtube.prototype.playerFound = function() { + var iframe = this.display.find('iframe#' + this.options.id + '-player'); + return (iframe.length > 0); +}; + +/** + * Called when the player state changes. + * + * @param {object} event A JavaScript Event. + */ +minplayer.players.youtube.prototype.onPlayerStateChange = function(event) { + this.setPlayerState(event.data); +}; + +/** + * Called when the player quality changes. + * + * @param {string} newQuality The new quality for the change. + */ +minplayer.players.youtube.prototype.onQualityChange = function(newQuality) { + this.quality = newQuality; +}; + +/** + * Determines if the player should show the playloader. + * + * @return {bool} If this player implements its own playLoader. + */ +minplayer.players.youtube.prototype.hasPlayLoader = function() { + return true; +}; + +/** + * @see minplayer.players.base#create + * @return {object} The media player entity. + */ +minplayer.players.youtube.prototype.create = function() { + minplayer.players.base.prototype.create.call(this); + + // Insert the YouTube iframe API player. + var tag = document.createElement('script'); + tag.src = 'http://www.youtube.com/player_api?enablejsapi=1'; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + + // Now register this player. + this.register(); + + // Create the iframe for this player. + var iframe = document.createElement('iframe'); + iframe.setAttribute('id', this.options.id + '-player'); + iframe.setAttribute('type', 'text/html'); + iframe.setAttribute('width', '100%'); + iframe.setAttribute('height', '100%'); + iframe.setAttribute('frameborder', '0'); + + // Get the source. + var src = 'http://www.youtube.com/embed/'; + src += this.mediaFile.id + '?'; + + // Determine the origin of this script. + var origin = location.protocol; + origin += '//' + location.hostname; + origin += (location.port && ':' + location.port); + + // Add the parameters to the src. + src += jQuery.param({ + 'wmode': 'opaque', + 'controls': 0, + 'enablejsapi': 1, + 'origin': origin, + 'autoplay': this.options.autoplay, + 'loop': this.options.loop + }); + + // Set the source of the iframe. + iframe.setAttribute('src', src); + + // Return the player. + return iframe; +}; + +/** + * @see minplayer.players.base#load + */ +minplayer.players.youtube.prototype.load = function(file) { + minplayer.players.base.prototype.load.call(this, file); + if (file && this.isReady()) { + this.player.loadVideoById(file.id, 0, this.quality); + } +}; + +/** + * @see minplayer.players.base#play + */ +minplayer.players.youtube.prototype.play = function() { + minplayer.players.base.prototype.play.call(this); + if (this.isReady()) { + this.player.playVideo(); + } +}; + +/** + * @see minplayer.players.base#pause + */ +minplayer.players.youtube.prototype.pause = function() { + minplayer.players.base.prototype.pause.call(this); + if (this.isReady()) { + this.player.pauseVideo(); + } +}; + +/** + * @see minplayer.players.base#stop + */ +minplayer.players.youtube.prototype.stop = function() { + minplayer.players.base.prototype.stop.call(this); + if (this.isReady()) { + this.player.stopVideo(); + } +}; + +/** + * @see minplayer.players.base#seek + */ +minplayer.players.youtube.prototype.seek = function(pos) { + minplayer.players.base.prototype.seek.call(this, pos); + if (this.isReady()) { + this.player.seekTo(pos, true); + } +}; + +/** + * @see minplayer.players.base#setVolume + */ +minplayer.players.youtube.prototype.setVolume = function(vol) { + minplayer.players.base.prototype.setVolume.call(this, vol); + if (this.isReady()) { + this.player.setVolume(vol * 100); + } +}; + +/** + * @see minplayer.players.base#getVolume + */ +minplayer.players.youtube.prototype.getVolume = function(callback) { + if (this.isReady()) { + callback(this.player.getVolume()); + } +}; + +/** + * @see minplayer.players.base#getDuration. + */ +minplayer.players.youtube.prototype.getDuration = function(callback) { + if (this.isReady()) { + callback(this.player.getDuration()); + } +}; + +/** + * @see minplayer.players.base#getCurrentTime + */ +minplayer.players.youtube.prototype.getCurrentTime = function(callback) { + if (this.isReady()) { + callback(this.player.getCurrentTime()); + } +}; + +/** + * @see minplayer.players.base#getBytesStart. + */ +minplayer.players.youtube.prototype.getBytesStart = function(callback) { + if (this.isReady()) { + callback(this.player.getVideoStartBytes()); + } +}; + +/** + * @see minplayer.players.base#getBytesLoaded. + */ +minplayer.players.youtube.prototype.getBytesLoaded = function(callback) { + if (this.isReady()) { + callback(this.player.getVideoBytesLoaded()); + } +}; + +/** + * @see minplayer.players.base#getBytesTotal. + */ +minplayer.players.youtube.prototype.getBytesTotal = function(callback) { + if (this.isReady()) { + callback(this.player.getVideoBytesTotal()); + } +}; +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** All the media player implementations */ +minplayer.players = minplayer.players || {}; + +/** + * @constructor + * @extends minplayer.players.base + * @class The vimeo media player. + * + * @param {object} context The jQuery context. + * @param {object} options This components options. + */ +minplayer.players.vimeo = function(context, options) { + + // Derive from players base. + minplayer.players.base.call(this, context, options); +}; + +/** Derive from minplayer.players.base. */ +minplayer.players.vimeo.prototype = new minplayer.players.base(); + +/** Reset the constructor. */ +minplayer.players.vimeo.prototype.constructor = minplayer.players.vimeo; + +/** + * @see minplayer.players.base#getPriority + * @return {number} The priority of this media player. + */ +minplayer.players.vimeo.getPriority = function() { + return 10; +}; + +/** + * @see minplayer.players.base#canPlay + * @return {boolean} If this player can play this media type. + */ +minplayer.players.vimeo.canPlay = function(file) { + + // Check for the mimetype for vimeo. + if (file.mimetype === 'video/vimeo') { + return true; + } + + // If the path is a vimeo path, then return true. + return (file.path.search(/^http(s)?\:\/\/(www\.)?vimeo\.com/i) === 0); +}; + +/** + * Return the ID for a provided media file. + * + * @param {object} file A {@link minplayer.file} object. + * @return {string} The ID for the provided media. + */ +minplayer.players.vimeo.getMediaId = function(file) { + var regex = /^http[s]?\:\/\/(www\.)?vimeo\.com\/(\?v\=)?([0-9]+)/i; + if (file.path.search(regex) === 0) { + return file.path.replace(regex, '$3'); + } + else { + return file.path; + } +}; + +/** + * @see minplayer.players.base#reset + */ +minplayer.players.vimeo.prototype.reset = function() { + + // Reset the flash variables.. + minplayer.players.base.prototype.reset.call(this); +}; + +/** + * @see minplayer.players.base#create + * @return {object} The media player entity. + */ +minplayer.players.vimeo.prototype.create = function() { + minplayer.players.base.prototype.create.call(this); + + // Insert the Vimeo Froogaloop player. + var tag = document.createElement('script'); + tag.src = 'http://a.vimeocdn.com/js/froogaloop2.min.js'; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + + // Create the iframe for this player. + var iframe = document.createElement('iframe'); + iframe.setAttribute('id', this.options.id + '-player'); + iframe.setAttribute('type', 'text/html'); + iframe.setAttribute('width', '100%'); + iframe.setAttribute('height', '100%'); + iframe.setAttribute('frameborder', '0'); + + // Get the source. + var src = 'http://player.vimeo.com/video/'; + src += this.mediaFile.id + '?'; + + // Add the parameters to the src. + src += jQuery.param({ + 'wmode': 'opaque', + 'api': 1, + 'player_id': this.options.id + '-player', + 'title': 0, + 'byline': 0, + 'portrait': 0, + 'autoplay': this.options.autoplay, + 'loop': this.options.loop + }); + + // Set the source of the iframe. + iframe.setAttribute('src', src); + + // Now register this player when the froogaloop code is loaded. + var _this = this; + setTimeout(function check() { + if (window.Froogaloop) { + _this.player = window.Froogaloop(iframe); + _this.player.addEvent('ready', function() { + _this.onReady(); + }); + } + else { + setTimeout(check, 200); + } + }, 200); + + // Trigger that the load has started. + this.trigger('loadstart'); + + // Return the player. + return iframe; +}; + +/** + * @see minplayer.players.base#onReady + */ +minplayer.players.vimeo.prototype.onReady = function(player_id) { + // Store the this pointer within this context. + var _this = this; + + // Add the other listeners. + this.player.addEvent('loadProgress', function(progress) { + + // Set the duration, bytesLoaded, and bytesTotal. + _this.duration.set(parseFloat(progress.duration)); + _this.bytesLoaded.set(progress.bytesLoaded); + _this.bytesTotal.set(progress.bytesTotal); + }); + + this.player.addEvent('playProgress', function(progress) { + + // Set the duration and current time. + _this.duration.set(parseFloat(progress.duration)); + _this.currentTime.set(parseFloat(progress.seconds)); + }); + + this.player.addEvent('play', function() { + _this.onPlaying(); + }); + + this.player.addEvent('pause', function() { + _this.onPaused(); + }); + + this.player.addEvent('finish', function() { + _this.onComplete(); + }); + + minplayer.players.base.prototype.onReady.call(this); + this.onLoaded(); +}; + +/** + * Checks to see if this player can be found. + * @return {bool} TRUE - Player is found, FALSE - otherwise. + */ +minplayer.players.vimeo.prototype.playerFound = function() { + var iframe = this.display.find('iframe#' + this.options.id + '-player'); + return (iframe.length > 0); +}; + +/** + * @see minplayer.players.base#play + */ +minplayer.players.vimeo.prototype.play = function() { + minplayer.players.base.prototype.play.call(this); + if (this.isReady()) { + this.player.api('play'); + } +}; + +/** + * @see minplayer.players.base#pause + */ +minplayer.players.vimeo.prototype.pause = function() { + minplayer.players.base.prototype.pause.call(this); + if (this.isReady()) { + this.player.api('pause'); + } +}; + +/** + * @see minplayer.players.base#stop + */ +minplayer.players.vimeo.prototype.stop = function() { + minplayer.players.base.prototype.stop.call(this); + if (this.isReady()) { + this.player.api('unload'); + } +}; + +/** + * @see minplayer.players.base#seek + */ +minplayer.players.vimeo.prototype.seek = function(pos) { + minplayer.players.base.prototype.seek.call(this, pos); + if (this.isReady()) { + this.player.api('seekTo', pos); + } +}; + +/** + * @see minplayer.players.base#setVolume + */ +minplayer.players.vimeo.prototype.setVolume = function(vol) { + minplayer.players.base.prototype.setVolume.call(this, vol); + if (this.isReady()) { + this.volume.set(vol); + this.player.api('setVolume', vol); + } +}; + +/** + * @see minplayer.players.base#getVolume + */ +minplayer.players.vimeo.prototype.getVolume = function(callback) { + var _this = this; + this.player.api('getVolume', function(vol) { + callback(vol); + }); +}; + +/** + * @see minplayer.players.base#getDuration. + */ +minplayer.players.vimeo.prototype.getDuration = function(callback) { + if (this.isReady()) { + if (this.duration.value) { + callback(this.duration.value); + } + else { + this.player.api('getDuration', function(duration) { + callback(duration); + }); + } + } +}; +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** Define the controller object. */ +minplayer.controller = minplayer.controller || {}; + +/** + * @constructor + * @extends minplayer.display + * @class This is the base minplayer controller. Other controllers can derive + * from the base and either build on top of it or simply define the elements + * that this base controller uses. + * + * @param {object} context The jQuery context. + * @param {object} options This components options. + */ +minplayer.controller.base = function(context, options) { + + // Derive from display + minplayer.display.call(this, 'controller', context, options); +}; + +// Define the prototype for all controllers. +var controllersBase = minplayer.controller.base; + +/** Derive from minplayer.display. */ +minplayer.controller.base.prototype = new minplayer.display(); + +/** Reset the constructor. */ +minplayer.controller.base.prototype.constructor = minplayer.controller.base; + +/** + * A static function that will format a time value into a string time format. + * + * @param {integer} time An integer value of time. + * @return {string} A string representation of the time. + */ +minplayer.formatTime = function(time) { + time = time || 0; + var seconds = 0, minutes = 0, hour = 0, timeString = ''; + + hour = Math.floor(time / 3600); + time -= (hour * 3600); + minutes = Math.floor(time / 60); + time -= (minutes * 60); + seconds = Math.floor(time % 60); + + if (hour) { + timeString += String(hour); + timeString += ':'; + } + + timeString += (minutes >= 10) ? String(minutes) : ('0' + String(minutes)); + timeString += ':'; + timeString += (seconds >= 10) ? String(seconds) : ('0' + String(seconds)); + return {time: timeString, units: ''}; +}; + +/** + * @see minplayer.display#getElements + * @return {object} The elements defined by this display. + */ +minplayer.controller.base.prototype.getElements = function() { + var elements = minplayer.display.prototype.getElements.call(this); + return jQuery.extend(elements, { + play: null, + pause: null, + fullscreen: null, + seek: null, + progress: null, + volume: null, + timer: null + }); +}; + +/** + * @see minplayer.plugin#construct + */ +minplayer.controller.base.prototype.construct = function() { + + // Call the minplayer plugin constructor. + minplayer.display.prototype.construct.call(this); + + // If they have a fullscreen button. + if (this.elements.fullscreen) { + + // Bind to the click event. + this.elements.fullscreen.bind('click', {obj: this}, function(event) { + var isFull = event.data.obj.elements.player.hasClass('fullscreen'); + if (isFull) { + event.data.obj.elements.player.removeClass('fullscreen'); + } + else { + event.data.obj.elements.player.addClass('fullscreen'); + } + event.data.obj.trigger('fullscreen', !isFull); + }).css({'pointer' : 'hand'}); + } + + // Keep track of if we are dragging... + this.dragging = false; + + // If they have a seek bar. + if (this.elements.seek) { + + // Create the seek bar slider control. + this.seekBar = this.elements.seek.slider({ + range: 'min' + }); + } + + // If they have a volume bar. + if (this.elements.volume) { + + // Create the volume bar slider control. + this.volumeBar = this.elements.volume.slider({ + range: 'min', + orientation: 'vertical' + }); + } + + // Get the media plugin. + this.get('media', function(media) { + + var _this = this; + + // If they have a pause button + if (this.elements.pause) { + + // Bind to the click on this button. + this.elements.pause.unbind().bind('click', {obj: this}, function(event) { + event.preventDefault(); + event.data.obj.playPause(false, media); + }); + + // Bind to the pause event of the media. + media.bind('pause', {obj: this}, function(event) { + event.data.obj.setPlayPause(true); + }); + } + + // If they have a play button + if (this.elements.play) { + + // Bind to the click on this button. + this.elements.play.unbind().bind('click', {obj: this}, function(event) { + event.preventDefault(); + event.data.obj.playPause(true, media); + }); + + // Bind to the play event of the media. + media.bind('playing', {obj: this}, function(event) { + event.data.obj.setPlayPause(false); + }); + } + + // If they have a duration, then trigger on duration change. + if (this.elements.duration) { + + // Bind to the duration change event. + media.bind('durationchange', {obj: this}, function(event, data) { + event.data.obj.setTimeString('duration', data.duration); + }); + + // Set the timestring to the duration. + media.getDuration(function(duration) { + _this.setTimeString('duration', duration); + }); + } + + // If they have a progress element. + if (this.elements.progress) { + + // Bind to the progress event. + media.bind('progress', {obj: this}, function(event, data) { + var percent = data.total ? (data.loaded / data.total) * 100 : 0; + event.data.obj.elements.progress.width(percent + '%'); + }); + } + + // If they have a seek bar or timer, bind to the timeupdate. + if (this.seekBar || this.elements.timer) { + + // Bind to the time update event. + media.bind('timeupdate', {obj: this}, function(event, data) { + if (!event.data.obj.dragging) { + var value = 0; + if (data.duration) { + value = (data.currentTime / data.duration) * 100; + } + + // Update the seek bar if it exists. + if (event.data.obj.seekBar) { + event.data.obj.seekBar.slider('option', 'value', value); + } + + event.data.obj.setTimeString('timer', data.currentTime); + } + }); + } + + // If they have a seekBar element. + if (this.seekBar) { + + // Register the events for the control bar to control the media. + this.seekBar.slider({ + start: function(event, ui) { + _this.dragging = true; + }, + stop: function(event, ui) { + _this.dragging = false; + media.getDuration(function(duration) { + media.seek((ui.value / 100) * duration); + }); + }, + slide: function(event, ui) { + media.getDuration(function(duration) { + var time = (ui.value / 100) * duration; + if (!_this.dragging) { + media.seek(time); + } + _this.setTimeString('timer', time); + }); + } + }); + } + + // Setup the volume bar. + if (this.volumeBar) { + + // Create the slider. + this.volumeBar.slider({ + slide: function(event, ui) { + media.setVolume(ui.value / 100); + } + }); + + media.bind('volumeupdate', {obj: this}, function(event, vol) { + event.data.obj.volumeBar.slider('option', 'value', (vol * 100)); + }); + + // Set the volume to match that of the player. + media.getVolume(function(vol) { + _this.volumeBar.slider('option', 'value', (vol * 100)); + }); + } + }); + + // We are now ready. + this.ready(); +}; + +/** + * Sets the play and pause state of the control bar. + * + * @param {boolean} state TRUE - Show Play, FALSE - Show Pause. + */ +minplayer.controller.base.prototype.setPlayPause = function(state) { + var css = ''; + if (this.elements.play) { + css = state ? 'inherit' : 'none'; + this.elements.play.css('display', css); + } + if (this.elements.pause) { + css = state ? 'none' : 'inherit'; + this.elements.pause.css('display', css); + } +}; + +/** + * Plays or pauses the media. + * + * @param {bool} state true => play, false => pause. + * @param {object} media The media player object. + */ +minplayer.controller.base.prototype.playPause = function(state, media) { + var type = state ? 'play' : 'pause'; + this.display.trigger(type); + this.setPlayPause(!state); + if (media) { + media[type](); + } +}; + +/** + * Sets the time string on the control bar. + * + * @param {string} element The name of the element to set. + * @param {number} time The total time amount to set. + */ +minplayer.controller.base.prototype.setTimeString = function(element, time) { + if (this.elements[element]) { + this.elements[element].text(minplayer.formatTime(time).time); + } +}; diff --git a/core/modules/file/player/flash/README.txt b/core/modules/file/player/flash/README.txt new file mode 100755 index 0000000000000000000000000000000000000000..e3515881730ad190d14670facf9558c0fdf77bc8 --- /dev/null +++ b/core/modules/file/player/flash/README.txt @@ -0,0 +1,12 @@ +The minplayer is a GPLv3, minimalistic, skinable, plugin based Flash media player. + +In addition to being a minimal Flash player, it was built with a solid API so that any +JavaScript widget can communicate to this player easily and effectively controlling it +from outside sources. + +This player was written with several objectives. + + - Small and lightweight + - Solid plugin based system to easily build on top of this player without recompiling. + - Complete JavaScript API. + - Configurable using external XML files. \ No newline at end of file diff --git a/core/modules/file/player/flash/config/config.xml b/core/modules/file/player/flash/config/config.xml new file mode 100644 index 0000000000000000000000000000000000000000..8192ae4fd72388949c0158cb9dd5842b3c7d063c --- /dev/null +++ b/core/modules/file/player/flash/config/config.xml @@ -0,0 +1,39 @@ + + + + + + com.mediafront.display.media.MediaPlayer + + + skins/%skin/mediaPlayer.swf + mediaPlayer + mediaPlayer + true + + + com.mediafront.display.media.controls.PlayLoader + skins/%skin/playLoader.swf + playLoader + playLoader + true + + mediaPlayer + + + + com.mediafront.display.media.controls.ControlBar + skins/%skin/controlBar.swf + controlBar + controlBar + true + + mediaPlayer + + + + diff --git a/core/modules/file/player/flash/config/nocontrols.xml b/core/modules/file/player/flash/config/nocontrols.xml new file mode 100644 index 0000000000000000000000000000000000000000..28fef65fcdc201f3a9e875d1bb44296be4e44380 --- /dev/null +++ b/core/modules/file/player/flash/config/nocontrols.xml @@ -0,0 +1,39 @@ + + + + + + com.mediafront.display.media.MediaPlayer + + + skins/%skin/mediaPlayer.swf + mediaPlayer + mediaPlayer + true + + + com.mediafront.display.media.controls.PlayLoader + skins/%skin/playLoader.swf + playLoader + playLoader + false + + mediaPlayer + + + + com.mediafront.display.media.controls.ControlBar + skins/%skin/controlBar.swf + controlBar + controlBar + false + + mediaPlayer + + + + \ No newline at end of file diff --git a/core/modules/file/player/flash/minplayer.swf b/core/modules/file/player/flash/minplayer.swf new file mode 100644 index 0000000000000000000000000000000000000000..3d27d51b321ca95a5e43e921d64f60c27145a019 GIT binary patch literal 16064 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 G*Ekup$I9^l literal 0 HcmV?d00001 diff --git a/core/modules/file/player/flash/skins/default/controlBar.swf b/core/modules/file/player/flash/skins/default/controlBar.swf new file mode 100644 index 0000000000000000000000000000000000000000..95e2653b63bfe847e9f4cbfda5124e30fdb15e7f GIT binary patch literal 13678 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 literal 0 HcmV?d00001 diff --git a/core/modules/file/player/flash/skins/default/playLoader.swf b/core/modules/file/player/flash/skins/default/playLoader.swf new file mode 100644 index 0000000000000000000000000000000000000000..521c405e9a4532b283c777b5a7a15f6d096ffe00 GIT binary patch literal 2723 zcmV;U3S9L=S5pdM8UO%zoUND(bQ4t?fM@cWJo==~v`q`753mT$lNJ)%LTN}Tn60IC zkwunHGm~^A$%L6nn+ht5C@el!9x5wOaXH|+96VK5g%uRsg7);`@u(oKVBPIGyY0aq zI28o#-kGFL3SokF)8wCf|NGzn|L41ZZ*nIHNmoKp@?Hqaf+1UKCImr`rb(m_R7&G6 z>k^w?8}xd7j1~0C@|sxIZ`JEtT3U21W*tSl^+t=uqBofICQ}ixC}LWDEEX*CG5LAr zTHb?=V4O7RXGzMZ<@#776=2Kq^1^O|UjL|DAEU!?s*%t+DX%_=`SnJfLC^UCM%GH2 zz*vf|rzlT(1!twTdoZR+TUj?#doNA72!;V6Fi)xeX6vZ=gbjSkO$MW($Y3fm7S|hR zTTL^q#j}bGC02tWYHr9fG}S?{7>==6)MRs!(Nu4=SWPA?a1=FpjBR*2g_EwEPZ7T<}btH*fjYgZfqGV=;VYbOo zGTUgh%&I816x-%k7F!lrY<63z{+6jD-f+qp;2?*+;lIFJ)YK7g6ivEGP|vns?=^-C zEnjG3IYrpr8mhunY+qn1HX1CUP_ykNCcDXAQBq;D&z@zj7i=79&Q|PCgE?(|g)_Y^M89tePvzXHZ$^a9`v8+#jDg>RZ;|F_;j% zWZpA)Zt=uj^F}A)mtTicYSQUuL-DTNsQ~ZmLU_s8cn1aX%H!dcjI(;L z307~l0NxF;@$M7E`>_Dt=5gU|jfppqDu`DR53lqVyqY^^%;Eh}5U)c3@21#z_Y30v zKmczr9^PX@cz49cTPuiH6c4X#oYnjP#yf3&*G%_|&0P=lo$CEr_0lJ=u9a;YG%Y_qyQTTO)vXb8Ng%2;w~{fR~Ji_mmLc zL$QB37zOdhtzP+f@d{RNtpMIC*Z)>!iDzx^x4(-|gDeej~&9;=-Ypt*ex!?!wwV&#Lv% zzJ6PE%Qq{uOQ#H-Jhe$OVaeL`jP#kzc%1Fy1M7OxU+froRhomfrb?UaEwzy>Clrh84QkZ&^++BK_-U5 zS!1**jx;5arZm!&MVd&YDUUQ2k*1PwatFo|AQPYnvbD0$C15o8{Q%s>Z(Ht|32 zoXGuZ*8AJ5)~=j;<-Yc|x@n8o0zTYP2}0F_fh4E_GC|OvH^QM9DT$lsLFJpF-6tTZ(6aoA3sTWKxO;EfK>!4q z@yDHT!x3n*9K_)23%-}Xq!yEfkKgFIxTtTna%qt#oJevg5fKOxPDITmA_fCFTh1Q5 z;P+sy^8+kP`M^*rHzWhhbN+Jn@&ylt;RFpGPk&9you0uf1=4XB3nXc#NL==tfVYvL zRXD~ptqd6_@(-cFDWBo|DjZZZNmP1Be-hWdhc{UXOcERz>8mHbgopGI*<;77(PK)| zN3ta5As-@Sp$G}l^puh8<&0wEqDl`;eNe`12N(XZ~xyPT4q=#f%N0<@JDwgt(Buvv3%_N08L6Dda^Moy3 zz%4;D%23ZuP+nE2=_POyQ-s<+f(_3G$@)MV?$Is<9E>g%gL(a+7Y@s`GA!W@duBo0 z)JRiD_}t+3t5y5EhJ8#>PE)tkEs2lr7*SCwlp< zH!Z)CU%;g#&NfL~IH}?l3klNQ#LAlZ3mk-7B@9@XBn-#xTxHeqPoAcrFe=TIph%`7 z13^=zV8@YXqFJa0%|>(3iRdJBGO9&$(L6LCEkLKBQ_;K7LUbB>w??6vu9=}J(&#jL zjX{$lhT(iUtdPi*s)R&Q5|W&fIw37xjb>zKX|i*q6DLj9=H}%W=>0f7?{I0=y@D zn+}#rU|0cxk5b%ir~>X`SgDeA8@j6@&;w?$;;wGPiiCzlLqn3GA=%K7VrWP;G)yow zNWlAkXkzHqBnIZU{;G{a9vLg>tG4 zUX|7DLc&%^#0m*pAtP2GlvE;Cs+RJhz}uzn*~7qGkd^Rfu=^oF+s$jpTZMbkzD|FyHqvVBA}OtSrO3fSivK#q`-V` d8n;P{z%CxOF$2xmZB`e(&GO$2{{vbxBLN>EO<@24 literal 0 HcmV?d00001 diff --git a/core/modules/file/player/index.html b/core/modules/file/player/index.html new file mode 100644 index 0000000000000000000000000000000000000000..b00b10dde79f9ce3b949b2aed61af00b7e0e1446 --- /dev/null +++ b/core/modules/file/player/index.html @@ -0,0 +1,63 @@ + + + + minPlayer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

minPlayer

+

Below are some examples of the minPlayer in action. It is creating a common API to interact with all different types of players, including Vimeo and YouTube.

+

h264 Example

+
+ +
+

YouTube Example

+
+ +
+

Two YouTube Players on same page

+
+ +
+

Vimeo Example

+
+ +
+ + diff --git a/core/modules/file/player/makefile b/core/modules/file/player/makefile new file mode 100644 index 0000000000000000000000000000000000000000..7d6976656dafa1cc04af18b220d1cd64da572ea6 --- /dev/null +++ b/core/modules/file/player/makefile @@ -0,0 +1,67 @@ +# To run this makefile, you must do the following. +# +# 1.) Download http://closure-compiler.googlecode.com/files/compiler-latest.zip +# and place compiler.jar within the tools directory. +# +# 2.) Install closure-linter tool at by following +# http://code.google.com/closure/utilities/docs/linter_howto.html +# +# 3.) Download the JSDoc toolkit found at +# http://code.google.com/p/jsdoc-toolkit and place the jsdoc-toolkit +# directory within the tools directory. + +# Create the list of files +files = src/minplayer.compatibility.js\ + src/minplayer.async.js\ + src/minplayer.flags.js\ + src/minplayer.plugin.js\ + src/minplayer.display.js\ + src/minplayer.js\ + src/minplayer.image.js\ + src/minplayer.file.js\ + src/minplayer.playLoader.base.js\ + src/minplayer.players.base.js\ + src/minplayer.players.html5.js\ + src/minplayer.players.flash.js\ + src/minplayer.players.minplayer.js\ + src/minplayer.players.youtube.js\ + src/minplayer.players.vimeo.js\ + src/minplayer.controller.base.js + +.DEFAULT_GOAL := all + +all: jslint js jsdoc + +# Perform a jsLint on all the files. +jslint: ${files} + gjslint $^ + +# Create an aggregated js file and a compressed js file. +js: ${files} + @echo "Generating aggregated bin/minplayer.js file" + @cat > bin/minplayer.js $^ + @echo "Generating compressed bin/minplayer.compressed file" + @java -jar tools/compiler.jar --js bin/minplayer.js --js_output_file bin/minplayer.compressed.js + +# Create the documentation from source code. +jsdoc: ${files} + @echo "Generating documetation." + @java -jar tools/jsdoc-toolkit/jsrun.jar tools/jsdoc-toolkit/app/run.js -a -t=tools/jsdoc-toolkit/templates/jsdoc -d=doc $^ + +# Fix the js style on all the files. +fixjsstyle: ${files} + fixjsstyle $^ + +# Install the necessary tools. +tools: + apt-get install python-setuptools + apt-get install unzip + wget http://closure-compiler.googlecode.com/files/compiler-latest.zip -P tools + unzip tools/compiler-latest.zip -d tools + rm tools/compiler-latest.zip tools/COPYING tools/README + easy_install http://closure-linter.googlecode.com/files/closure_linter-latest.tar.gz + wget http://jsdoc-toolkit.googlecode.com/files/jsdoc_toolkit-2.4.0.zip -P tools + unzip tools/jsdoc_toolkit-2.4.0.zip -d tools + mv tools/jsdoc_toolkit-2.4.0/jsdoc-toolkit tools/jsdoc-toolkit + rm -rd tools/jsdoc_toolkit-2.4.0 + rm tools/jsdoc_toolkit-2.4.0.zip diff --git a/core/modules/file/player/src/minplayer.async.js b/core/modules/file/player/src/minplayer.async.js new file mode 100644 index 0000000000000000000000000000000000000000..656aa11031b9b919faaf55b7035b378889596c44 --- /dev/null +++ b/core/modules/file/player/src/minplayer.async.js @@ -0,0 +1,61 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** + * @constructor + * @class This class keeps track of asynchronous get requests for certain + * variables within the player. + */ +minplayer.async = function() { + + /** The final value of this asynchronous variable. */ + this.value = null; + + /** The queue of callbacks to call when this value is determined. */ + this.queue = []; +}; + +/** + * Retrieve the value of this variable. + * + * @param {function} callback The function to call when the value is determined. + * @param {function} pollValue The poll function to try and get the value every + * 1 second if the value is not set. + */ +minplayer.async.prototype.get = function(callback, pollValue) { + + // If the value is set, then immediately call the callback, otherwise, just + // add it to the queue when the variable is set. + if (this.value !== null) { + callback(this.value); + } + else { + + // Add this callback to the queue. + this.queue.push(callback); + } +}; + +/** + * Sets the value of an asynchronous value. + * + * @param {void} val The value to set. + */ +minplayer.async.prototype.set = function(val) { + + // Set the value. + this.value = val; + + // Get the callback queue length. + var i = this.queue.length; + + // Iterate through all the callbacks and call them. + if (i) { + while (i--) { + this.queue[i](val); + } + + // Reset the queue. + this.queue = []; + } +}; diff --git a/core/modules/file/player/src/minplayer.compatibility.js b/core/modules/file/player/src/minplayer.compatibility.js new file mode 100644 index 0000000000000000000000000000000000000000..0a0dbf71f8274a291cd1b6beba4eca4506a421ab --- /dev/null +++ b/core/modules/file/player/src/minplayer.compatibility.js @@ -0,0 +1,101 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +// Private function to check a single element's play type. +function checkPlayType(elem, playType) { + if ((typeof elem.canPlayType) === 'function') { + if (typeof playType === 'object') { + var i = playType.length; + var mimetype = ''; + while (i--) { + mimetype = checkPlayType(elem, playType[i]); + if (!!mimetype) { + break; + } + } + return mimetype; + } + else { + var canPlay = elem.canPlayType(playType); + if (('no' !== canPlay) && ('' !== canPlay)) { + return playType; + } + } + } + return ''; +} + +/** + * @constructor + * @class This class is used to define the types of media that can be played + * within the browser. + *

+ * Usage: + *


+ *   var playTypes = new minplayer.compatibility();
+ *
+ *   if (playTypes.videoOGG) {
+ *     console.log("This browser can play OGG video");
+ *   }
+ *
+ *   if (playTypes.videoH264) {
+ *     console.log("This browser can play H264 video");
+ *   }
+ *
+ *   if (playTypes.videoWEBM) {
+ *     console.log("This browser can play WebM video");
+ *   }
+ *
+ *   if (playTypes.audioOGG) {
+ *     console.log("This browser can play OGG audio");
+ *   }
+ *
+ *   if (playTypes.audioMP3) {
+ *     console.log("This browser can play MP3 audio");
+ *   }
+ *
+ *   if (playTypes.audioMP4) {
+ *     console.log("This browser can play MP4 audio");
+ *   }
+ * 
+ */ +minplayer.compatibility = function() { + var elem = null; + + // Create a video element. + elem = document.createElement('video'); + + /** Can play OGG video */ + this.videoOGG = checkPlayType(elem, 'video/ogg'); + + /** Can play H264 video */ + this.videoH264 = checkPlayType(elem, [ + 'video/mp4', + 'video/h264' + ]); + + /** Can play WEBM video */ + this.videoWEBM = checkPlayType(elem, [ + 'video/x-webm', + 'video/webm', + 'application/octet-stream' + ]); + + // Create an audio element. + elem = document.createElement('audio'); + + /** Can play audio OGG */ + this.audioOGG = checkPlayType(elem, 'audio/ogg'); + + /** Can play audio MP3 */ + this.audioMP3 = checkPlayType(elem, 'audio/mpeg'); + + /** Can play audio MP4 */ + this.audioMP4 = checkPlayType(elem, 'audio/mp4'); +}; + +if (!minplayer.playTypes) { + + /** The compatible playtypes for this browser. */ + minplayer.playTypes = new minplayer.compatibility(); +} diff --git a/core/modules/file/player/src/minplayer.controller.base.js b/core/modules/file/player/src/minplayer.controller.base.js new file mode 100644 index 0000000000000000000000000000000000000000..944095433dc15a002f0ead9d3887c22284060995 --- /dev/null +++ b/core/modules/file/player/src/minplayer.controller.base.js @@ -0,0 +1,295 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** Define the controller object. */ +minplayer.controller = minplayer.controller || {}; + +/** + * @constructor + * @extends minplayer.display + * @class This is the base minplayer controller. Other controllers can derive + * from the base and either build on top of it or simply define the elements + * that this base controller uses. + * + * @param {object} context The jQuery context. + * @param {object} options This components options. + */ +minplayer.controller.base = function(context, options) { + + // Derive from display + minplayer.display.call(this, 'controller', context, options); +}; + +// Define the prototype for all controllers. +var controllersBase = minplayer.controller.base; + +/** Derive from minplayer.display. */ +minplayer.controller.base.prototype = new minplayer.display(); + +/** Reset the constructor. */ +minplayer.controller.base.prototype.constructor = minplayer.controller.base; + +/** + * A static function that will format a time value into a string time format. + * + * @param {integer} time An integer value of time. + * @return {string} A string representation of the time. + */ +minplayer.formatTime = function(time) { + time = time || 0; + var seconds = 0, minutes = 0, hour = 0, timeString = ''; + + hour = Math.floor(time / 3600); + time -= (hour * 3600); + minutes = Math.floor(time / 60); + time -= (minutes * 60); + seconds = Math.floor(time % 60); + + if (hour) { + timeString += String(hour); + timeString += ':'; + } + + timeString += (minutes >= 10) ? String(minutes) : ('0' + String(minutes)); + timeString += ':'; + timeString += (seconds >= 10) ? String(seconds) : ('0' + String(seconds)); + return {time: timeString, units: ''}; +}; + +/** + * @see minplayer.display#getElements + * @return {object} The elements defined by this display. + */ +minplayer.controller.base.prototype.getElements = function() { + var elements = minplayer.display.prototype.getElements.call(this); + return jQuery.extend(elements, { + play: null, + pause: null, + fullscreen: null, + seek: null, + progress: null, + volume: null, + timer: null + }); +}; + +/** + * @see minplayer.plugin#construct + */ +minplayer.controller.base.prototype.construct = function() { + + // Call the minplayer plugin constructor. + minplayer.display.prototype.construct.call(this); + + // If they have a fullscreen button. + if (this.elements.fullscreen) { + + // Bind to the click event. + this.elements.fullscreen.bind('click', {obj: this}, function(event) { + var isFull = event.data.obj.elements.player.hasClass('fullscreen'); + if (isFull) { + event.data.obj.elements.player.removeClass('fullscreen'); + } + else { + event.data.obj.elements.player.addClass('fullscreen'); + } + event.data.obj.trigger('fullscreen', !isFull); + }).css({'pointer' : 'hand'}); + } + + // Keep track of if we are dragging... + this.dragging = false; + + // If they have a seek bar. + if (this.elements.seek) { + + // Create the seek bar slider control. + this.seekBar = this.elements.seek.slider({ + range: 'min' + }); + } + + // If they have a volume bar. + if (this.elements.volume) { + + // Create the volume bar slider control. + this.volumeBar = this.elements.volume.slider({ + range: 'min', + orientation: 'vertical' + }); + } + + // Get the media plugin. + this.get('media', function(media) { + + var _this = this; + + // If they have a pause button + if (this.elements.pause) { + + // Bind to the click on this button. + this.elements.pause.unbind().bind('click', {obj: this}, function(event) { + event.preventDefault(); + event.data.obj.playPause(false, media); + }); + + // Bind to the pause event of the media. + media.bind('pause', {obj: this}, function(event) { + event.data.obj.setPlayPause(true); + }); + } + + // If they have a play button + if (this.elements.play) { + + // Bind to the click on this button. + this.elements.play.unbind().bind('click', {obj: this}, function(event) { + event.preventDefault(); + event.data.obj.playPause(true, media); + }); + + // Bind to the play event of the media. + media.bind('playing', {obj: this}, function(event) { + event.data.obj.setPlayPause(false); + }); + } + + // If they have a duration, then trigger on duration change. + if (this.elements.duration) { + + // Bind to the duration change event. + media.bind('durationchange', {obj: this}, function(event, data) { + event.data.obj.setTimeString('duration', data.duration); + }); + + // Set the timestring to the duration. + media.getDuration(function(duration) { + _this.setTimeString('duration', duration); + }); + } + + // If they have a progress element. + if (this.elements.progress) { + + // Bind to the progress event. + media.bind('progress', {obj: this}, function(event, data) { + var percent = data.total ? (data.loaded / data.total) * 100 : 0; + event.data.obj.elements.progress.width(percent + '%'); + }); + } + + // If they have a seek bar or timer, bind to the timeupdate. + if (this.seekBar || this.elements.timer) { + + // Bind to the time update event. + media.bind('timeupdate', {obj: this}, function(event, data) { + if (!event.data.obj.dragging) { + var value = 0; + if (data.duration) { + value = (data.currentTime / data.duration) * 100; + } + + // Update the seek bar if it exists. + if (event.data.obj.seekBar) { + event.data.obj.seekBar.slider('option', 'value', value); + } + + event.data.obj.setTimeString('timer', data.currentTime); + } + }); + } + + // If they have a seekBar element. + if (this.seekBar) { + + // Register the events for the control bar to control the media. + this.seekBar.slider({ + start: function(event, ui) { + _this.dragging = true; + }, + stop: function(event, ui) { + _this.dragging = false; + media.getDuration(function(duration) { + media.seek((ui.value / 100) * duration); + }); + }, + slide: function(event, ui) { + media.getDuration(function(duration) { + var time = (ui.value / 100) * duration; + if (!_this.dragging) { + media.seek(time); + } + _this.setTimeString('timer', time); + }); + } + }); + } + + // Setup the volume bar. + if (this.volumeBar) { + + // Create the slider. + this.volumeBar.slider({ + slide: function(event, ui) { + media.setVolume(ui.value / 100); + } + }); + + media.bind('volumeupdate', {obj: this}, function(event, vol) { + event.data.obj.volumeBar.slider('option', 'value', (vol * 100)); + }); + + // Set the volume to match that of the player. + media.getVolume(function(vol) { + _this.volumeBar.slider('option', 'value', (vol * 100)); + }); + } + }); + + // We are now ready. + this.ready(); +}; + +/** + * Sets the play and pause state of the control bar. + * + * @param {boolean} state TRUE - Show Play, FALSE - Show Pause. + */ +minplayer.controller.base.prototype.setPlayPause = function(state) { + var css = ''; + if (this.elements.play) { + css = state ? 'inherit' : 'none'; + this.elements.play.css('display', css); + } + if (this.elements.pause) { + css = state ? 'none' : 'inherit'; + this.elements.pause.css('display', css); + } +}; + +/** + * Plays or pauses the media. + * + * @param {bool} state true => play, false => pause. + * @param {object} media The media player object. + */ +minplayer.controller.base.prototype.playPause = function(state, media) { + var type = state ? 'play' : 'pause'; + this.display.trigger(type); + this.setPlayPause(!state); + if (media) { + media[type](); + } +}; + +/** + * Sets the time string on the control bar. + * + * @param {string} element The name of the element to set. + * @param {number} time The total time amount to set. + */ +minplayer.controller.base.prototype.setTimeString = function(element, time) { + if (this.elements[element]) { + this.elements[element].text(minplayer.formatTime(time).time); + } +}; diff --git a/core/modules/file/player/src/minplayer.display.js b/core/modules/file/player/src/minplayer.display.js new file mode 100644 index 0000000000000000000000000000000000000000..57b0c81c436f2eb1ae66000b2d26de24c424a090 --- /dev/null +++ b/core/modules/file/player/src/minplayer.display.js @@ -0,0 +1,128 @@ +/** 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); +}; diff --git a/core/modules/file/player/src/minplayer.file.js b/core/modules/file/player/src/minplayer.file.js new file mode 100644 index 0000000000000000000000000000000000000000..a0b7fa2f00b322793777311ffe8d4636089e59e2 --- /dev/null +++ b/core/modules/file/player/src/minplayer.file.js @@ -0,0 +1,165 @@ +/** 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) : ''; +}; diff --git a/core/modules/file/player/src/minplayer.flags.js b/core/modules/file/player/src/minplayer.flags.js new file mode 100644 index 0000000000000000000000000000000000000000..4d97c3765a5f7dd592d917cc0543b6e484137cb4 --- /dev/null +++ b/core/modules/file/player/src/minplayer.flags.js @@ -0,0 +1,71 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** + * @constructor + * @class This is a class used to keep track of flag states + * which is used to control the busy cursor, big play button, among other + * items in which multiple components can have an interest in hiding or + * showing a single element on the screen. + * + *

+ * Usage: + *


+ *   // Declare a flags variable.
+ *   var flags = new minplayer.flags();
+ *
+ *   // Set the flag based on two components interested in the flag.
+ *   flags.setFlag("component1", true);
+ *   flags.setFlag("component2", true);
+ *
+ *   // Print out the value of the flags. ( Prints 3 )
+ *   console.log(flags.flags);
+ *
+ *   // Now unset a single components flag.
+ *   flags.setFlag("component1", false);
+ *
+ *   // Print out the value of the flags.
+ *   console.log(flags.flags);
+ *
+ *   // Unset the other components flag.
+ *   flags.setFlag("component2", false);
+ *
+ *   // Print out the value of the flags.
+ *   console.log(flags.flags);
+ * 
+ *

+ */ +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]); + } +}; diff --git a/core/modules/file/player/src/minplayer.image.js b/core/modules/file/player/src/minplayer.image.js new file mode 100644 index 0000000000000000000000000000000000000000..d367e8955be1b05e2c0bb5b1500ae26ccf12ad0a --- /dev/null +++ b/core/modules/file/player/src/minplayer.image.js @@ -0,0 +1,143 @@ +/** 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(); +}; diff --git a/core/modules/file/player/src/minplayer.js b/core/modules/file/player/src/minplayer.js new file mode 100644 index 0000000000000000000000000000000000000000..68ac83533c011055b995c64918b94c8ce241e0e5 --- /dev/null +++ b/core/modules/file/player/src/minplayer.js @@ -0,0 +1,346 @@ +// 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(); + }); +}; diff --git a/core/modules/file/player/src/minplayer.playLoader.base.js b/core/modules/file/player/src/minplayer.playLoader.base.js new file mode 100644 index 0000000000000000000000000000000000000000..ca6729d6ec4639f1205ca685c04575bdafe7aa6b --- /dev/null +++ b/core/modules/file/player/src/minplayer.playLoader.base.js @@ -0,0 +1,182 @@ +/** 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(); + } +}; diff --git a/core/modules/file/player/src/minplayer.players.base.js b/core/modules/file/player/src/minplayer.players.base.js new file mode 100644 index 0000000000000000000000000000000000000000..99db1b64c7efe90718b563d15ad37164840f1239 --- /dev/null +++ b/core/modules/file/player/src/minplayer.players.base.js @@ -0,0 +1,563 @@ +/** 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); +}; diff --git a/core/modules/file/player/src/minplayer.players.flash.js b/core/modules/file/player/src/minplayer.players.flash.js new file mode 100644 index 0000000000000000000000000000000000000000..a6fbbb3fd98b01963fa07dfbbc21d756dc4af7e1 --- /dev/null +++ b/core/modules/file/player/src/minplayer.players.flash.js @@ -0,0 +1,107 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** All the media player implementations */ +minplayer.players = minplayer.players || {}; + +/** + * @constructor + * @extends minplayer.display + * @class The Flash media player class to control the flash fallback. + * + * @param {object} context The jQuery context. + * @param {object} options This components options. + */ +minplayer.players.flash = function(context, options) { + + // Derive from players base. + minplayer.players.base.call(this, context, options); +}; + +/** Derive from minplayer.players.base. */ +minplayer.players.flash.prototype = new minplayer.players.base(); + +/** Reset the constructor. */ +minplayer.players.flash.prototype.constructor = minplayer.players.flash; + +/** + * @see minplayer.players.base#getPriority + * @return {number} The priority of this media player. + */ +minplayer.players.flash.getPriority = function() { + return 0; +}; + +/** + * @see minplayer.players.base#canPlay + * @return {boolean} If this player can play this media type. + */ +minplayer.players.flash.canPlay = function(file) { + return false; +}; + +/** + * API to return the Flash player code provided params. + * + * @param {object} params The params used to populate the Flash code. + * @return {object} A Flash DOM element. + */ +minplayer.players.flash.getFlash = function(params) { + // Get the protocol. + var protocol = window.location.protocol; + if (protocol.charAt(protocol.length - 1) == ':') { + protocol = protocol.substring(0, protocol.length - 1); + } + + // Convert the flashvars object to a string... + var flashVars = jQuery.param(params.flashvars); + + // Set the codebase. + var codebase = protocol + '://fpdownload.macromedia.com'; + codebase += '/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0'; + + // Get the HTML flash object string. + var flash = ' '; + flash += ''; + flash += ''; + flash += ''; + flash += ''; + flash += ''; + flash += ''; + flash += ' 0); +}; + +/** + * @see minplayer.players.base#getPlayer + * @return {object} The media player object. + */ +minplayer.players.flash.prototype.getPlayer = function() { + // IE needs the object, everyone else just needs embed. + var object = jQuery.browser.msie ? 'object' : 'embed'; + return jQuery(object, this.display).eq(0)[0]; +}; diff --git a/core/modules/file/player/src/minplayer.players.html5.js b/core/modules/file/player/src/minplayer.players.html5.js new file mode 100644 index 0000000000000000000000000000000000000000..9bd9a32994268ae890ff876fa1de300bb3e9dd16 --- /dev/null +++ b/core/modules/file/player/src/minplayer.players.html5.js @@ -0,0 +1,312 @@ +/** 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 = '' : '>'; + this.options.elements.media.attr('src', '').empty().html(code); + } + } + + // Always call the base first on load... + minplayer.players.base.prototype.load.call(this, file); +}; + +/** + * @see minplayer.players.base#play + */ +minplayer.players.html5.prototype.play = function() { + minplayer.players.base.prototype.play.call(this); + if (this.isReady()) { + this.player.play(); + } +}; + +/** + * @see minplayer.players.base#pause + */ +minplayer.players.html5.prototype.pause = function() { + minplayer.players.base.prototype.pause.call(this); + if (this.isReady()) { + this.player.pause(); + } +}; + +/** + * @see minplayer.players.base#stop + */ +minplayer.players.html5.prototype.stop = function() { + minplayer.players.base.prototype.stop.call(this); + if (this.isReady()) { + this.player.pause(); + this.player.src = ''; + } +}; + +/** + * @see minplayer.players.base#seek + */ +minplayer.players.html5.prototype.seek = function(pos) { + minplayer.players.base.prototype.seek.call(this, pos); + if (this.isReady()) { + this.player.currentTime = pos; + } +}; + +/** + * @see minplayer.players.base#setVolume + */ +minplayer.players.html5.prototype.setVolume = function(vol) { + minplayer.players.base.prototype.setVolume.call(this, vol); + if (this.isReady()) { + this.player.volume = vol; + } +}; + +/** + * @see minplayer.players.base#getVolume + */ +minplayer.players.html5.prototype.getVolume = function(callback) { + if (this.isReady()) { + callback(this.player.volume); + } +}; + +/** + * @see minplayer.players.base#getDuration + */ +minplayer.players.html5.prototype.getDuration = function(callback) { + if (this.isReady()) { + callback(this.player.duration); + } +}; + +/** + * @see minplayer.players.base#getCurrentTime + */ +minplayer.players.html5.prototype.getCurrentTime = function(callback) { + if (this.isReady()) { + callback(this.player.currentTime); + } +}; + +/** + * @see minplayer.players.base#getBytesLoaded + */ +minplayer.players.html5.prototype.getBytesLoaded = function(callback) { + if (this.isReady()) { + var loaded = 0; + + // Check several different possibilities. + if (this.bytesLoaded.value) { + loaded = this.bytesLoaded.value; + } + else if (this.player.buffered && + this.player.buffered.length > 0 && + this.player.buffered.end && + this.player.duration) { + loaded = this.player.buffered.end(0); + } + else if (this.player.bytesTotal != undefined && + this.player.bytesTotal > 0 && + this.player.bufferedBytes != undefined) { + loaded = this.player.bufferedBytes; + } + + // Return the loaded amount. + callback(loaded); + } +}; + +/** + * @see minplayer.players.base#getBytesTotal + */ +minplayer.players.html5.prototype.getBytesTotal = function(callback) { + if (this.isReady()) { + + var total = 0; + + // Check several different possibilities. + if (this.bytesTotal.value) { + total = this.bytesTotal.value; + } + else if (this.player.buffered && + this.player.buffered.length > 0 && + this.player.buffered.end && + this.player.duration) { + total = this.player.duration; + } + else if (this.player.bytesTotal != undefined && + this.player.bytesTotal > 0 && + this.player.bufferedBytes != undefined) { + total = this.player.bytesTotal; + } + + // Return the loaded amount. + callback(total); + } +}; diff --git a/core/modules/file/player/src/minplayer.players.minplayer.js b/core/modules/file/player/src/minplayer.players.minplayer.js new file mode 100644 index 0000000000000000000000000000000000000000..5b339a20fe846affd289220edc4ed943e0553151 --- /dev/null +++ b/core/modules/file/player/src/minplayer.players.minplayer.js @@ -0,0 +1,266 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** All the media player implementations */ +minplayer.players = minplayer.players || {}; + +/** + * @constructor + * @extends minplayer.display + * @class The Flash media player class to control the flash fallback. + * + * @param {object} context The jQuery context. + * @param {object} options This components options. + */ +minplayer.players.minplayer = function(context, options) { + + // Derive from players flash. + minplayer.players.flash.call(this, context, options); +}; + +/** Derive from minplayer.players.flash. */ +minplayer.players.minplayer.prototype = new minplayer.players.flash(); + +/** Reset the constructor. */ +minplayer.players.minplayer.prototype.constructor = minplayer.players.minplayer; + +/** + * Called when the Flash player is ready. + * + * @param {string} id The media player ID. + */ +window.onFlashPlayerReady = function(id) { + var media = minplayer.get(id, 'media'); + if (media) { + media.onReady(); + } +}; + +/** + * Called when the Flash player updates. + * + * @param {string} id The media player ID. + * @param {string} eventType The event type that was triggered. + */ +window.onFlashPlayerUpdate = function(id, eventType) { + var media = minplayer.get(id, 'media'); + if (media) { + media.onMediaUpdate(eventType); + } +}; + +/** + * Used to debug from the Flash player to the browser console. + * + * @param {string} debug The debug string. + */ +window.onFlashPlayerDebug = function(debug) { + minplayer.console.log(debug); +}; + +/** + * @see minplayer.players.base#getPriority + * @return {number} The priority of this media player. + */ +minplayer.players.minplayer.getPriority = function() { + return 1; +}; + +/** + * @see minplayer.players.base#canPlay + * @return {boolean} If this player can play this media type. + */ +minplayer.players.minplayer.canPlay = function(file) { + switch (file.mimetype) { + case 'video/mp4': + case 'video/x-webm': + case 'video/webm': + case 'application/octet-stream': + case 'video/quicktime': + case 'video/3gpp2': + case 'video/3gpp': + case 'application/x-shockwave-flash': + case 'audio/mpeg': + case 'audio/mp4': + case 'audio/aac': + case 'audio/vnd.wave': + case 'audio/x-ms-wma': + return true; + + default: + return false; + } +}; + +/** + * @see minplayer.players.base#create + * @return {object} The media player entity. + */ +minplayer.players.minplayer.prototype.create = function() { + minplayer.players.flash.prototype.create.call(this); + + // The flash variables for this flash player. + var flashVars = { + 'id': this.options.id, + 'debug': this.options.debug, + 'config': 'nocontrols', + 'file': this.mediaFile.path, + 'autostart': this.options.autoplay + }; + + // Return a flash media player object. + return minplayer.players.flash.getFlash({ + swf: this.options.swfplayer, + id: this.options.id + '_player', + width: this.options.width, + height: '100%', + flashvars: flashVars, + wmode: this.options.wmode + }); +}; + +/** + * Called when the Flash player has an update. + * + * @param {string} eventType The event that was triggered in the player. + */ +minplayer.players.minplayer.prototype.onMediaUpdate = function(eventType) { + switch (eventType) { + case 'mediaMeta': + this.onLoaded(); + break; + case 'mediaPlaying': + this.onPlaying(); + break; + case 'mediaPaused': + this.onPaused(); + break; + case 'mediaComplete': + this.onComplete(); + break; + } +}; + +/** + * @see minplayer.players.base#load + */ +minplayer.players.minplayer.prototype.load = function(file) { + minplayer.players.flash.prototype.load.call(this, file); + if (file && this.isReady()) { + this.player.loadMedia(file.path, file.stream); + } +}; + +/** + * @see minplayer.players.base#play + */ +minplayer.players.minplayer.prototype.play = function() { + minplayer.players.flash.prototype.play.call(this); + if (this.isReady()) { + this.player.playMedia(); + } +}; + +/** + * @see minplayer.players.base#pause + */ +minplayer.players.minplayer.prototype.pause = function() { + minplayer.players.flash.prototype.pause.call(this); + if (this.isReady()) { + this.player.pauseMedia(); + } +}; + +/** + * @see minplayer.players.base#stop + */ +minplayer.players.minplayer.prototype.stop = function() { + minplayer.players.flash.prototype.stop.call(this); + if (this.isReady()) { + this.player.stopMedia(); + } +}; + +/** + * @see minplayer.players.base#seek + */ +minplayer.players.minplayer.prototype.seek = function(pos) { + minplayer.players.flash.prototype.seek.call(this, pos); + if (this.isReady()) { + this.player.seekMedia(pos); + } +}; + +/** + * @see minplayer.players.base#setVolume + */ +minplayer.players.minplayer.prototype.setVolume = function(vol) { + minplayer.players.flash.prototype.setVolume.call(this, vol); + if (this.isReady()) { + this.player.setVolume(vol); + } +}; + +/** + * @see minplayer.players.base#getVolume + */ +minplayer.players.minplayer.prototype.getVolume = function(callback) { + if (this.isReady()) { + callback(this.player.getVolume()); + } +}; + +/** + * @see minplayer.players.flash#getDuration + */ +minplayer.players.minplayer.prototype.getDuration = function(callback) { + if (this.isReady()) { + + // Check to see if it is immediately available. + var duration = this.player.getDuration(); + if (duration) { + callback(duration); + } + else { + + // If not, then check every half second... + var _this = this; + setTimeout(function check() { + duration = _this.player.getDuration(); + if (duration) { + callback(duration); + } + else { + setTimeout(check, 500); + } + }, 500); + } + } +}; + +/** + * @see minplayer.players.base#getCurrentTime + */ +minplayer.players.minplayer.prototype.getCurrentTime = function(callback) { + if (this.isReady()) { + callback(this.player.getCurrentTime()); + } +}; + +/** + * @see minplayer.players.base#getBytesLoaded + */ +minplayer.players.minplayer.prototype.getBytesLoaded = function(callback) { + if (this.isReady()) { + callback(this.player.getMediaBytesLoaded()); + } +}; + +/** + * @see minplayer.players.base#getBytesTotal. + */ +minplayer.players.minplayer.prototype.getBytesTotal = function(callback) { + if (this.isReady()) { + callback(this.player.getMediaBytesTotal()); + } +}; diff --git a/core/modules/file/player/src/minplayer.players.vimeo.js b/core/modules/file/player/src/minplayer.players.vimeo.js new file mode 100644 index 0000000000000000000000000000000000000000..2db10d08e45a5a7d37785e5fdcd577ead31c8916 --- /dev/null +++ b/core/modules/file/player/src/minplayer.players.vimeo.js @@ -0,0 +1,259 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** All the media player implementations */ +minplayer.players = minplayer.players || {}; + +/** + * @constructor + * @extends minplayer.players.base + * @class The vimeo media player. + * + * @param {object} context The jQuery context. + * @param {object} options This components options. + */ +minplayer.players.vimeo = function(context, options) { + + // Derive from players base. + minplayer.players.base.call(this, context, options); +}; + +/** Derive from minplayer.players.base. */ +minplayer.players.vimeo.prototype = new minplayer.players.base(); + +/** Reset the constructor. */ +minplayer.players.vimeo.prototype.constructor = minplayer.players.vimeo; + +/** + * @see minplayer.players.base#getPriority + * @return {number} The priority of this media player. + */ +minplayer.players.vimeo.getPriority = function() { + return 10; +}; + +/** + * @see minplayer.players.base#canPlay + * @return {boolean} If this player can play this media type. + */ +minplayer.players.vimeo.canPlay = function(file) { + + // Check for the mimetype for vimeo. + if (file.mimetype === 'video/vimeo') { + return true; + } + + // If the path is a vimeo path, then return true. + return (file.path.search(/^http(s)?\:\/\/(www\.)?vimeo\.com/i) === 0); +}; + +/** + * Return the ID for a provided media file. + * + * @param {object} file A {@link minplayer.file} object. + * @return {string} The ID for the provided media. + */ +minplayer.players.vimeo.getMediaId = function(file) { + var regex = /^http[s]?\:\/\/(www\.)?vimeo\.com\/(\?v\=)?([0-9]+)/i; + if (file.path.search(regex) === 0) { + return file.path.replace(regex, '$3'); + } + else { + return file.path; + } +}; + +/** + * @see minplayer.players.base#reset + */ +minplayer.players.vimeo.prototype.reset = function() { + + // Reset the flash variables.. + minplayer.players.base.prototype.reset.call(this); +}; + +/** + * @see minplayer.players.base#create + * @return {object} The media player entity. + */ +minplayer.players.vimeo.prototype.create = function() { + minplayer.players.base.prototype.create.call(this); + + // Insert the Vimeo Froogaloop player. + var tag = document.createElement('script'); + tag.src = 'http://a.vimeocdn.com/js/froogaloop2.min.js'; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + + // Create the iframe for this player. + var iframe = document.createElement('iframe'); + iframe.setAttribute('id', this.options.id + '-player'); + iframe.setAttribute('type', 'text/html'); + iframe.setAttribute('width', '100%'); + iframe.setAttribute('height', '100%'); + iframe.setAttribute('frameborder', '0'); + + // Get the source. + var src = 'http://player.vimeo.com/video/'; + src += this.mediaFile.id + '?'; + + // Add the parameters to the src. + src += jQuery.param({ + 'wmode': 'opaque', + 'api': 1, + 'player_id': this.options.id + '-player', + 'title': 0, + 'byline': 0, + 'portrait': 0, + 'autoplay': this.options.autoplay, + 'loop': this.options.loop + }); + + // Set the source of the iframe. + iframe.setAttribute('src', src); + + // Now register this player when the froogaloop code is loaded. + var _this = this; + setTimeout(function check() { + if (window.Froogaloop) { + _this.player = window.Froogaloop(iframe); + _this.player.addEvent('ready', function() { + _this.onReady(); + }); + } + else { + setTimeout(check, 200); + } + }, 200); + + // Trigger that the load has started. + this.trigger('loadstart'); + + // Return the player. + return iframe; +}; + +/** + * @see minplayer.players.base#onReady + */ +minplayer.players.vimeo.prototype.onReady = function(player_id) { + // Store the this pointer within this context. + var _this = this; + + // Add the other listeners. + this.player.addEvent('loadProgress', function(progress) { + + // Set the duration, bytesLoaded, and bytesTotal. + _this.duration.set(parseFloat(progress.duration)); + _this.bytesLoaded.set(progress.bytesLoaded); + _this.bytesTotal.set(progress.bytesTotal); + }); + + this.player.addEvent('playProgress', function(progress) { + + // Set the duration and current time. + _this.duration.set(parseFloat(progress.duration)); + _this.currentTime.set(parseFloat(progress.seconds)); + }); + + this.player.addEvent('play', function() { + _this.onPlaying(); + }); + + this.player.addEvent('pause', function() { + _this.onPaused(); + }); + + this.player.addEvent('finish', function() { + _this.onComplete(); + }); + + minplayer.players.base.prototype.onReady.call(this); + this.onLoaded(); +}; + +/** + * Checks to see if this player can be found. + * @return {bool} TRUE - Player is found, FALSE - otherwise. + */ +minplayer.players.vimeo.prototype.playerFound = function() { + var iframe = this.display.find('iframe#' + this.options.id + '-player'); + return (iframe.length > 0); +}; + +/** + * @see minplayer.players.base#play + */ +minplayer.players.vimeo.prototype.play = function() { + minplayer.players.base.prototype.play.call(this); + if (this.isReady()) { + this.player.api('play'); + } +}; + +/** + * @see minplayer.players.base#pause + */ +minplayer.players.vimeo.prototype.pause = function() { + minplayer.players.base.prototype.pause.call(this); + if (this.isReady()) { + this.player.api('pause'); + } +}; + +/** + * @see minplayer.players.base#stop + */ +minplayer.players.vimeo.prototype.stop = function() { + minplayer.players.base.prototype.stop.call(this); + if (this.isReady()) { + this.player.api('unload'); + } +}; + +/** + * @see minplayer.players.base#seek + */ +minplayer.players.vimeo.prototype.seek = function(pos) { + minplayer.players.base.prototype.seek.call(this, pos); + if (this.isReady()) { + this.player.api('seekTo', pos); + } +}; + +/** + * @see minplayer.players.base#setVolume + */ +minplayer.players.vimeo.prototype.setVolume = function(vol) { + minplayer.players.base.prototype.setVolume.call(this, vol); + if (this.isReady()) { + this.volume.set(vol); + this.player.api('setVolume', vol); + } +}; + +/** + * @see minplayer.players.base#getVolume + */ +minplayer.players.vimeo.prototype.getVolume = function(callback) { + var _this = this; + this.player.api('getVolume', function(vol) { + callback(vol); + }); +}; + +/** + * @see minplayer.players.base#getDuration. + */ +minplayer.players.vimeo.prototype.getDuration = function(callback) { + if (this.isReady()) { + if (this.duration.value) { + callback(this.duration.value); + } + else { + this.player.api('getDuration', function(duration) { + callback(duration); + }); + } + } +}; diff --git a/core/modules/file/player/src/minplayer.players.youtube.js b/core/modules/file/player/src/minplayer.players.youtube.js new file mode 100644 index 0000000000000000000000000000000000000000..f3b98ec19ca80a5c32dd34144fbbaee181dbfd08 --- /dev/null +++ b/core/modules/file/player/src/minplayer.players.youtube.js @@ -0,0 +1,345 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +/** All the media player implementations */ +minplayer.players = minplayer.players || {}; + +/** + * @constructor + * @extends minplayer.players.base + * @class The YouTube media player. + * + * @param {object} context The jQuery context. + * @param {object} options This components options. + */ +minplayer.players.youtube = function(context, options) { + + /** The quality of the YouTube stream. */ + this.quality = 'default'; + + // Derive from players base. + minplayer.players.base.call(this, context, options); +}; + +/** Derive from minplayer.players.base. */ +minplayer.players.youtube.prototype = new minplayer.players.base(); + +/** Reset the constructor. */ +minplayer.players.youtube.prototype.constructor = minplayer.players.youtube; + +/** + * @see minplayer.players.base#getPriority + * @return {number} The priority of this media player. + */ +minplayer.players.youtube.getPriority = function() { + return 10; +}; + +/** + * @see minplayer.players.base#canPlay + * @return {boolean} If this player can play this media type. + */ +minplayer.players.youtube.canPlay = function(file) { + + // Check for the mimetype for youtube. + if (file.mimetype === 'video/youtube') { + return true; + } + + // If the path is a YouTube path, then return true. + return (file.path.search(/^http(s)?\:\/\/(www\.)?youtube\.com/i) === 0); +}; + +/** + * Return the ID for a provided media file. + * + * @param {object} file A {@link minplayer.file} object. + * @return {string} The ID for the provided media. + */ +minplayer.players.youtube.getMediaId = function(file) { + var regex = /^http[s]?\:\/\/(www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9]+)/i; + if (file.path.search(regex) === 0) { + return file.path.replace(regex, '$2'); + } + else { + return file.path; + } +}; + +/** + * Register this youtube player so that multiple players can be present + * on the same page without event collision. + */ +minplayer.players.youtube.prototype.register = function() { + + /** + * Register the standard youtube api ready callback. + */ + window.onYouTubePlayerAPIReady = function() { + + // Iterate over each media player. + jQuery.each(minplayer.get(null, 'player'), function(id, player) { + + // Make sure this is the youtube player. + if (player.currentPlayer == 'youtube') { + + // Create a new youtube player object for this instance only. + var playerId = id + '-player'; + + // Set this players media. + player.media.player = new YT.Player(playerId, { + events: { + 'onReady': function(event) { + player.media.onReady(event); + }, + 'onStateChange': function(event) { + player.media.onPlayerStateChange(event); + }, + 'onPlaybackQualityChange': function(newQuality) { + player.media.onQualityChange(newQuality); + }, + 'onError': function(errorCode) { + player.media.onError(errorCode); + } + } + }); + } + }); + } +}; + +/** + * Translates the player state for the YouTube API player. + * + * @param {number} playerState The YouTube player state. + */ +minplayer.players.youtube.prototype.setPlayerState = function(playerState) { + switch (playerState) { + case YT.PlayerState.CUED: + break; + case YT.PlayerState.BUFFERING: + this.onWaiting(); + break; + case YT.PlayerState.PAUSED: + this.onPaused(); + break; + case YT.PlayerState.PLAYING: + this.onPlaying(); + break; + case YT.PlayerState.ENDED: + this.onComplete(); + break; + default: + break; + } +}; + +/** + * Called when an error occurs. + * + * @param {string} event The onReady event that was triggered. + */ +minplayer.players.youtube.prototype.onReady = function(event) { + minplayer.players.base.prototype.onReady.call(this); + this.onLoaded(); +}; + +/** + * Checks to see if this player can be found. + * @return {bool} TRUE - Player is found, FALSE - otherwise. + */ +minplayer.players.youtube.prototype.playerFound = function() { + var iframe = this.display.find('iframe#' + this.options.id + '-player'); + return (iframe.length > 0); +}; + +/** + * Called when the player state changes. + * + * @param {object} event A JavaScript Event. + */ +minplayer.players.youtube.prototype.onPlayerStateChange = function(event) { + this.setPlayerState(event.data); +}; + +/** + * Called when the player quality changes. + * + * @param {string} newQuality The new quality for the change. + */ +minplayer.players.youtube.prototype.onQualityChange = function(newQuality) { + this.quality = newQuality; +}; + +/** + * Determines if the player should show the playloader. + * + * @return {bool} If this player implements its own playLoader. + */ +minplayer.players.youtube.prototype.hasPlayLoader = function() { + return true; +}; + +/** + * @see minplayer.players.base#create + * @return {object} The media player entity. + */ +minplayer.players.youtube.prototype.create = function() { + minplayer.players.base.prototype.create.call(this); + + // Insert the YouTube iframe API player. + var tag = document.createElement('script'); + tag.src = 'http://www.youtube.com/player_api?enablejsapi=1'; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + + // Now register this player. + this.register(); + + // Create the iframe for this player. + var iframe = document.createElement('iframe'); + iframe.setAttribute('id', this.options.id + '-player'); + iframe.setAttribute('type', 'text/html'); + iframe.setAttribute('width', '100%'); + iframe.setAttribute('height', '100%'); + iframe.setAttribute('frameborder', '0'); + + // Get the source. + var src = 'http://www.youtube.com/embed/'; + src += this.mediaFile.id + '?'; + + // Determine the origin of this script. + var origin = location.protocol; + origin += '//' + location.hostname; + origin += (location.port && ':' + location.port); + + // Add the parameters to the src. + src += jQuery.param({ + 'wmode': 'opaque', + 'controls': 0, + 'enablejsapi': 1, + 'origin': origin, + 'autoplay': this.options.autoplay, + 'loop': this.options.loop + }); + + // Set the source of the iframe. + iframe.setAttribute('src', src); + + // Return the player. + return iframe; +}; + +/** + * @see minplayer.players.base#load + */ +minplayer.players.youtube.prototype.load = function(file) { + minplayer.players.base.prototype.load.call(this, file); + if (file && this.isReady()) { + this.player.loadVideoById(file.id, 0, this.quality); + } +}; + +/** + * @see minplayer.players.base#play + */ +minplayer.players.youtube.prototype.play = function() { + minplayer.players.base.prototype.play.call(this); + if (this.isReady()) { + this.player.playVideo(); + } +}; + +/** + * @see minplayer.players.base#pause + */ +minplayer.players.youtube.prototype.pause = function() { + minplayer.players.base.prototype.pause.call(this); + if (this.isReady()) { + this.player.pauseVideo(); + } +}; + +/** + * @see minplayer.players.base#stop + */ +minplayer.players.youtube.prototype.stop = function() { + minplayer.players.base.prototype.stop.call(this); + if (this.isReady()) { + this.player.stopVideo(); + } +}; + +/** + * @see minplayer.players.base#seek + */ +minplayer.players.youtube.prototype.seek = function(pos) { + minplayer.players.base.prototype.seek.call(this, pos); + if (this.isReady()) { + this.player.seekTo(pos, true); + } +}; + +/** + * @see minplayer.players.base#setVolume + */ +minplayer.players.youtube.prototype.setVolume = function(vol) { + minplayer.players.base.prototype.setVolume.call(this, vol); + if (this.isReady()) { + this.player.setVolume(vol * 100); + } +}; + +/** + * @see minplayer.players.base#getVolume + */ +minplayer.players.youtube.prototype.getVolume = function(callback) { + if (this.isReady()) { + callback(this.player.getVolume()); + } +}; + +/** + * @see minplayer.players.base#getDuration. + */ +minplayer.players.youtube.prototype.getDuration = function(callback) { + if (this.isReady()) { + callback(this.player.getDuration()); + } +}; + +/** + * @see minplayer.players.base#getCurrentTime + */ +minplayer.players.youtube.prototype.getCurrentTime = function(callback) { + if (this.isReady()) { + callback(this.player.getCurrentTime()); + } +}; + +/** + * @see minplayer.players.base#getBytesStart. + */ +minplayer.players.youtube.prototype.getBytesStart = function(callback) { + if (this.isReady()) { + callback(this.player.getVideoStartBytes()); + } +}; + +/** + * @see minplayer.players.base#getBytesLoaded. + */ +minplayer.players.youtube.prototype.getBytesLoaded = function(callback) { + if (this.isReady()) { + callback(this.player.getVideoBytesLoaded()); + } +}; + +/** + * @see minplayer.players.base#getBytesTotal. + */ +minplayer.players.youtube.prototype.getBytesTotal = function(callback) { + if (this.isReady()) { + callback(this.player.getVideoBytesTotal()); + } +}; diff --git a/core/modules/file/player/src/minplayer.plugin.js b/core/modules/file/player/src/minplayer.plugin.js new file mode 100644 index 0000000000000000000000000000000000000000..70df312f7f966c5f3b3477ab007d7b6ffffdd9ab --- /dev/null +++ b/core/modules/file/player/src/minplayer.plugin.js @@ -0,0 +1,519 @@ +/** 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); + } + } + } +}; diff --git a/core/modules/file/player/templates/default/css/images/loader.gif b/core/modules/file/player/templates/default/css/images/loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..5e2d16730415c52b18d085af4b497b6f9d009666 GIT binary patch literal 404 zcmZ?wbhEHb)Mnsj_{hL8bLPynw6yyAdLXIzpWDwhB-q(8z|~04fSHkjfkE+~lygyP zVo7R>LV0FMhC*UiVnt4VVv1g7URpkb;!hS%E}$wMAZ7p=$iQUO(#LUn+RYoypO11$ z-wXSEy7hrsTSVl-H1*nhv-U0LN!Rtf-hN8&*OARX?RG3sF!B)vT7qee?Gd0c3jB%5 z8)vl~UsLmWM&iBiJt;G0%s;iz%h{_yX9s)s*^O&os_<=MOcjyJkN8yEcgpYbqggsh zLC4o)HxcA81E7g3XJtj-$$!MpCA_`zVP(&fD+^Za;#S?BYU=U)B{nl)pM z=^29__fs!F%>}1{rUgj{_y7X^AaBzH8nLCT~T0GlW3ke@guXq zfA_^ wK;v0jZoG1NY?~D}vUUX6%XV=xH83zUeEFs*^2YV9Fwj~CPgg&ebxsLQ0FS3aNdN!< literal 0 HcmV?d00001 diff --git a/core/modules/file/player/templates/default/css/images/play-icon.png b/core/modules/file/player/templates/default/css/images/play-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..71a34135e4e77734e6a80e0bc72ec81e067e65e0 GIT binary patch literal 375 zcmV--0f_#IP);htG zAZDNjh8qk7X+V4rh$jQFG}T-T1#Cc^1H`wX7D!OT;9p2$oKOp3J`knE;O|)EU=}613XX*KtAZgWdRcf5f8GU6ly^` zx&=(I1Wlm@6;KOWKo)?^WcmO9Keci(%m>UsT*LC=!v~Py7YYsi3&e|oxCw~wg9KQR zB*+CTfw&%sZz2n_P+;IDke7k@GB!za4Bibj@Dx5pq!@f0h|7TZ2(@zFSs-=;Vk=?| zC1fxt?1F&U1nPZ~0Jc;CirrRd@*x|*3V*fWqX5Vlc>s`_Kw}iU2@>0RY83 VIH3}Z+zef9{?Cx%cR-sS`Y{p|J5{a4ssZ?rrI-TzL2V4sj z;5OT~?*@ax30|HSyou!zQ8~)GHHRyCpX3-k;p68b#E6zw%y6t z%l`#4=ujq;dFeRL2++M)-%SkvKJhEk4~N6?d_Mn1`XmrfCX-G@FcbS2G>49tiQU5Q zMZ=~%fCp&`ByfPtPl+e=7T(@UYa!W;t4wOIv=WZz47vuSk+A~o0sI6njn|CM;60X% z`vFa{+(@@6p=&)Z)g9x2)j?Q{#ZgYh5~Mi$G-_a@8}x^9CHuX0u+vD<#*z8P#hUi$^V& z%hhRYDHIA7!C%m4H6^&y!%KR#eTzP7-)#B^Q{y-~tZG4h=vx)&Idoq8Be7V_WhL9` zA5(AEfPbK`D%pUW=qP$o0lcR5UO=PjotBfRrFmNIN=x13+aTBWli3_?r;yyP9<)lf zJ7}+K>=`Rac=-t3)7m}Q;6F$2^l05d2Q}LPGr;lk)kJ5})-o~qR$S2j4YFPI12)fU z8Bb2dCDet6N&`-y_U9p*C)>YnnYCGeroROk08v!bv_qh#qyPW_07*qoM6N<$f=>=J A&;S4c literal 0 HcmV?d00001 diff --git a/core/modules/file/player/templates/default/css/images/volume-mute-icon.png b/core/modules/file/player/templates/default/css/images/volume-mute-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..23979e323b2989b87cdea7527c913e0d532f7411 GIT binary patch literal 331 zcmV-R0kr;!P)RO!c1@j^msbD;YA_=);R#IJdO)fZh&_;m zeYLc-wosJwfCgLwVoxA59f)7R97VN42x#~$AO*^WtAO|){xky&3Uct(t5;d@$uo{t dO)vluU;xlmeHH0N4ZZ*X002ovPDHLkV1lGVf71W} literal 0 HcmV?d00001 diff --git a/core/modules/file/player/templates/default/css/media_player_default.css b/core/modules/file/player/templates/default/css/media_player_default.css new file mode 100644 index 0000000000000000000000000000000000000000..77b8fb2398597607d4a8e74f0249fde702aa7768 --- /dev/null +++ b/core/modules/file/player/templates/default/css/media_player_default.css @@ -0,0 +1,475 @@ + +/* base styles */ +.media-player {} +.media-player-play, .media-player-volume-button { + cursor: pointer; +} +.media-player-timer { + cursor: default; +} + +.media-player { + position: relative; + background:#000; + border:1px solid #333; + font-family:"Trebuchet MS", Helvetica, sans-serif; + -moz-box-shadow:0px 5px 10px #333;/*no-important moz*/ + -webkit-box-shadow:0px 5px 10px #333;/*no-important chrome*/ +} + +.media-player .media-player-error { + display:none; + position: absolute; + top: 80%; + left: 50%; + width: 320px; + height: 40px; + line-height: 40px; + margin: -20px 0 0 -160px; + text-align: center; + vertical-align: center; + border: none; + z-index:1000; + color: #fff; + opacity: 0.9; + border-radius: 10px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + background: rgba(23, 35, 34, 0.9); + box-shadow: 2px 2px 4px #000; + -webkit-box-shadow: 2px 2px 4px #000; + -moz-box-shadow: 2px 2px 4px #000; +} + +.media-player .media-player-display { + background-color:#000; + width:100%; + height:100%; +} + +.media-player .media-player-preview { + width:100%; + height:100%; + position:absolute; + z-index:1; +} + +.media-player .media-player-preview.has-preview { + background-color: rgb(0, 0, 0); +} + +.media-player .media-player-play-loader { + width:100%; + height:100%; + position:absolute; + z-index:2; + background: rgb(0, 0, 0); + background: rgba(0, 0, 0, 0.3); + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#55000000, endColorstr=#55000000); + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#55000000, endColorstr=#55000000)"; +} + +.media-player .media-player-play-loader .media-player-loader { + width:42px; + height:10px; + position: absolute; + top: 50%; + left: 50%; + margin: -5px 0 0 -21px; + text-align:center; + vertical-align:center; + background: url(images/loader.gif) no-repeat; +} + +/* Big play button */ +.media-player .media-player-play-loader .media-player-big-play { + position: absolute; + top: 50%; + left: 50%; + width: 80px; + height: 80px; + margin: -40px 0 0 -40px; + text-align: center; + vertical-align: center; + cursor: pointer !important; + border: none; + opacity: 0.9; + z-index:3; + border-radius: 10px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + background: rgba(23, 35, 34, 0.746094); + box-shadow: 2px 2px 4px #000; + -webkit-box-shadow: 2px 2px 4px #000; + -moz-box-shadow: 2px 2px 4px #000; +} + +.media-player .media-player-play-loader .media-player-big-play span { + margin: 22px 0 0 48px; + border-left: 36px solid white; + border-top: 18px solid transparent; + border-bottom: 18px solid transparent; +} + +.media-player .media-player-play-loader .media-player-big-play span { + display: block; + font-size: 0; + line-height: 0; + width: 0; + height: 0; + margin: 20px 0 0 23px; + border-left: 40px solid white; + border-top: 20px solid transparent; + border-bottom: 20px solid transparent; +} + +.media-player.fullscreen, .media-player.fullscreen .media-player-display, .media-player.fullscreen .media-player-play-loader { + position: fixed; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + z-index:1000; + width: 100% !important; + height: 100% !important; +} + +.media-player.fullscreen .media-player-play-loader { + z-index:1001; +} + +.media-player .media-player-controls { + position: absolute; + display: block; + z-index:3; + bottom:0px; + left:0px; + right:0px; + height:30px; + background-color:rgba(0,0,0,0.5); +} + +.media-player-controls-left { + float:left; + width:30px; + height:26px; + margin: 2px 0; + border-right:1px solid #eee; +} + +.media-player.fullscreen .media-player-controls-left { + width:40px; + border:none; +} + +.media-player-controls-right { + float:right; + width:120px; + height:30px; +} + +.media-player.fullscreen .media-player-controls-right { + width:110px; +} + +.media-player-controls-mid { + position:absolute; + left:40px; + right:130px; + height:30px; +} + +.media-player.fullscreen .media-player-controls-mid { + left:50px; +} + +.media-player .media-player-play, .media-player .media-player-volume, .media-player .media-player-timer { + float: left; +} + +/* play, pause */ +.media-player .media-player-play, .media-player .media-player-pause { + display: block; + width: 22px; + height: 22px; + margin:2px 0 0 4px; + opacity: 0.7; + -moz-transition: all 0.2s ease-in-out; /* Firefox */ + -webkit-transition: all 0.2s ease-in-out; /* Safari and Chrome */ + -o-transition: all 0.2s ease-in-out; /* Opera */ + transition: all 0.2s ease-in-out; +} + +.media-player .media-player-play:hover, .media-player .media-player-pause:hover { + opacity: 1; +} + +.media-player .media-player-play { + background: url(images/play-icon.png) no-repeat; +} + +.media-player .media-player-pause { + background: url(images/pause-icon.png) no-repeat; + display:none; +} + +/* seek */ +.media-player .media-player-seek { + position:relative; + height: 10px; + margin-top:9px; + border: 1px solid #494949; + -moz-border-radius:4px; + -webkit-border-radius:4px; + border-radius:4px; + background: #535353; + background-image: -moz-linear-gradient(top, #535353, #333333); + background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #535353),color-stop(1, #333333)); + box-shadow: inset 0 -3px 3px #333333; +} + +.media-player .media-player-seek .ui-slider-handle { + width: 15px; + height: 15px; + border: 1px solid #333; + top: -4px; + z-index:20px; + -moz-border-radius:10px; + -webkit-border-radius:10px; + border-radius:10px; + background: #e6e6e6; + background-image: -moz-linear-gradient(top, #e6e6e6, #d5d5d5); + background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #e6e6e6),color-stop(1, #d5d5d5)); + box-shadow: inset 0 -3px 3px #d5d5d5; +} + +.media-player .media-player-seek .ui-slider-handle.ui-state-hover { + background: #fff; +} + +.media-player .media-player-seek .ui-slider-range { + -moz-border-radius:15px; + -webkit-border-radius:15px; + z-index:10px; + border-radius:15px; + background: #4cbae8; + background-image: -moz-linear-gradient(top, #4cbae8, #39a2ce); + background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #4cbae8),color-stop(1, #39a2ce)); + box-shadow: inset 0 -3px 3px #39a2ce; +} + +.media-player .media-player-progress { + -moz-border-radius:15px; + -webkit-border-radius:15px; + z-index:8px; + width:0px; + height:10px; + border-radius:15px; + background: #266580; + background-image: -moz-linear-gradient(top, #266580, #153A4A); + background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #266580),color-stop(1, #153A4A)); + box-shadow: inset 0 -3px 3px #153A4A; +} + +/* timer */ +.media-player .media-player-timer { + position:relative; + float:left; + color: #999; + margin-top:3px; + padding-right:6px; + font-size: 16px; + font-weight: bold; + border-right:1px solid #eee; +} + +.media-player.fullscreen .media-player-timer { + border:none; + margin-top:4px; +} + +/* volume */ +.media-player .media-player-volume { + position: absolute; + right:33px; + bottom:4px; + float:right; + overflow: hidden; + width: 20px; + height: 30px; + color: #fff; + padding: 0px 10px; + -moz-transition: all 0.1s ease-in-out; /* Firefox */ + -webkit-transition: all 0.1s ease-in-out; /* Safari and Chrome */ + -o-transition: all 0.2s ease-in-out; /* Opera */ + transition: all 0.1s ease-in-out; +} + +.media-player.fullscreen .media-player-volume { + right:40px; + bottom:13px; +} + +.media-player .media-player-volume:hover { + height: 135px; + padding-top: 5px; +} + + +.media-player .media-player-volume:hover .media-player-volume-slider { + position: relative; + visibility: visible; + opacity: 1; +} + +.media-player .media-player-volume-slider { + position: relative; + height: 100px; + width: 7px; + left: 4px; + visiblity: hidden; + opacity: 0; + border: 1px solid #444; + -moz-border-radius:15px; + -webkit-border-radius:15px; + border-radius:15px; + background: #535353; + background-image: -moz-linear-gradient(top, #535353, #333333); + background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #535353),color-stop(1, #333333)); + box-shadow: inset 0 3px 3px #333333; + -moz-transition: all 0.1s ease-in-out; /* Firefox */ + -webkit-transition: all 0.1s ease-in-out; /* Safari and Chrome */ + -o-transition: all 0.1s ease-in-out; /* Opera */ + transition: all 0.1s ease-in-out; +} + +.media-player .media-player-volume-slider .ui-slider-handle { + width: 12px; + height: 12px; + left: -4px; + margin-bottom:-0.6em; + margin-left:0; + border: 1px solid #333; + -moz-border-radius:10px; + -webkit-border-radius:10px; + border-radius:10px; + background: #e6e6e6; + background-image: -moz-linear-gradient(top, #e6e6e6, #d5d5d5); + background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #e6e6e6),color-stop(1, #d5d5d5)); + + box-shadow: inset 0 3px 3px #d5d5d5; +} + +.media-player .media-player-volume-slider .ui-slider-handle.ui-state-hover { + background: #fff; +} + +.media-player .media-player-volume-slider .ui-slider-range { + -moz-border-radius:15px; + -webkit-border-radius:15px; + border-radius:15px; + background: #e6e6e6; + background-image: -moz-linear-gradient(top, #e6e6e6, #d5d5d5); + background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #e6e6e6),color-stop(1, #d5d5d5)); + box-shadow: inset 0 3px 3px #d5d5d5; +} + +/* volume button */ + +.media-player .media-player-volume-button { + position: absolute; + bottom: 0px; + display: block; + width: 22px; + height: 22px; + background: url(images/volume-full-icon.png) no-repeat; + text-indent: -9999px; + opacity: 0.8; +} + +.media-player .media-player-volume-button:hover { + opacity: 1; +} + +.media-player .media-player-volume-mute { + background: url(images/volume-mute-icon.png) no-repeat; +} + +/* Fullscreen button */ +.media-player .media-player-fullscreen { + position: absolute; + right:6px; + bottom:7px; + width: 22px; + height: 14px; + border:1px solid #aaa; +} + +.media-player.fullscreen .media-player-fullscreen { + right:14px; + bottom:16px; +} + +.media-player .media-player-fullscreen-inner, .media-player.fullscreen .media-player-fullscreen:hover .media-player-fullscreen-inner { + position:absolute; + bottom:0; + width:16px; + height:8px; + background-color:#aaa; + -moz-transition: all 0.1s ease-in-out; /* Firefox */ + -webkit-transition: all 0.1s ease-in-out; /* Safari and Chrome */ + -o-transition: all 0.1s ease-in-out; /* Opera */ + transition: all 0.1s ease-in-out; +} + +.media-player .media-player-fullscreen:hover .media-player-fullscreen-inner, .media-player.fullscreen .media-player-fullscreen-inner { + width:20px; + height:12px; +} + +.media-player.fullscreen .media-player-controls { + position: absolute; + z-index:1002; + width:500px; + left: 50%; + bottom:10px; + margin: 0 0 0 -260px; + padding: 10px; + border: 1px solid #2E2E2E; + -moz-border-radius: 5px; /* FF1+ */ + -webkit-border-radius: 5px; /* Saf3+, Chrome */ + border-radius: 5px; /* Opera 10.5, IE 9 */ + background: #000000; + background-image: -moz-linear-gradient(top, #313131, #000000); /* FF3.6 */ + background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #313131),color-stop(1, #000000)); /* Saf4+, Chrome */ + box-shadow: inset 0 15px 35px #535353; + -moz-transition: all 0.1s ease-in-out; /* Firefox */ + -webkit-transition: all 0.1s ease-in-out; /* Safari and Chrome */ + -o-transition: all 0.1s ease-in-out; /* Opera */ + transition: all 0.1s ease-in-out; +} + + +/* needed jquery ui styles + * using these, we don't depend on jQuery UI's stylsheet +*/ +.ui-slider-handle { + position: absolute; + z-index: 4; + display: block; + margin-left:-0.6em; + cursor: default; + outline: none; +} + +.ui-slider-range { + display:block; + width:100%; + height:100%; + left:0; + bottom: 0; + border:0 none; + position:absolute; + z-index:3; +} \ No newline at end of file diff --git a/core/modules/file/player/templates/default/js/minplayer.controller.default.js b/core/modules/file/player/templates/default/js/minplayer.controller.default.js new file mode 100644 index 0000000000000000000000000000000000000000..57c0b357769af4de82967519d1dd63423aafaeb7 --- /dev/null +++ b/core/modules/file/player/templates/default/js/minplayer.controller.default.js @@ -0,0 +1,71 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +// Define the controller object. +minplayer.controller = minplayer.controller || {}; + +/** + * Constructor for the minplayer.controller + */ +minplayer.controller["default"] = function(context, options) { + + // Derive from base controller + minplayer.controller.base.call(this, context, options); +}; + +/** Derive from controller.base. */ +minplayer.controller["default"].prototype = new minplayer.controller.base(); +minplayer.controller["default"].prototype.constructor = minplayer.controller["default"]; + +/** + * Return the display for this plugin. + */ +minplayer.controller["default"].prototype.getDisplay = function(context, options) { + + // See if we need to build out the controller. + if (context.build) { + + // Prepend the control template. + context.prepend('\ +
\ +
\ +
\ + \ + \ +
\ +
\ +
00:00
\ +
\ +
\ +
\ +
\ +
\ + \ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
'); + } + + return jQuery('.media-player-controls', context); +} + +// Return the elements +minplayer.controller["default"].prototype.getElements = function() { + var elements = minplayer.controller.base.prototype.getElements.call(this); + var timer = jQuery(".media-player-timer", this.display); + return jQuery.extend(elements, { + play: jQuery(".media-player-play", this.display), + pause: jQuery(".media-player-pause", this.display), + fullscreen: jQuery(".media-player-fullscreen", this.display), + seek: jQuery(".media-player-seek", this.display), + progress: jQuery(".media-player-progress", this.display), + volume: jQuery(".media-player-volume-slider", this.display), + timer:timer, + duration:timer + }); +}; diff --git a/core/modules/file/player/templates/default/js/minplayer.default.js b/core/modules/file/player/templates/default/js/minplayer.default.js new file mode 100644 index 0000000000000000000000000000000000000000..a46af70485cb3709bd8e28f459c36d664b1348e9 --- /dev/null +++ b/core/modules/file/player/templates/default/js/minplayer.default.js @@ -0,0 +1,60 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +// Default player. +minplayer["default"] = function(context, options) { + + // Derive from minplayer. + minplayer.call(this, context, options); +}; + +/** + * Define this template prototype. + */ +minplayer["default"].prototype = new minplayer(); +minplayer["default"].prototype.constructor = minplayer["default"]; + +/** + * Return the display for this plugin. + */ +minplayer["default"].prototype.getDisplay = function(context, options) { + + // Convert the context to jQuery object. + context = jQuery(context); + + // If the tag is video or audio, then build out the player. + var tag = context.get(0).tagName.toLowerCase(); + if (tag == 'video' || tag == 'audio') { + + // Build out the player provided the base tag. + context = context.attr({ + 'id': options.id + '-player', + 'class': 'media-player-media' + }) + .wrap(jQuery(document.createElement('div')).attr({ + 'class': 'media-player-display' + })).parent('.media-player-display') + .wrap(jQuery(document.createElement('div')).attr({ + 'id': options.id, + 'class': 'media-player' + })).parent('.media-player'); + + // Mark a flag that says this display needs to be built. + context.build = true; + } + + return context; +} + +// Get the elements for this player. +minplayer["default"].prototype.getElements = function() { + var elements = minplayer.prototype.getElements.call(this); + + // Return the jQuery elements. + return jQuery.extend(elements, { + player:this.display, + display:jQuery(".media-player-display", this.display), + media:jQuery("#" + this.options.id + "-player", this.display), + error:jQuery('.media-player-error', this.display) + }); +}; diff --git a/core/modules/file/player/templates/default/js/minplayer.playLoader.default.js b/core/modules/file/player/templates/default/js/minplayer.playLoader.default.js new file mode 100644 index 0000000000000000000000000000000000000000..facf2ec1620debf74099818eaf75184a59f3b585 --- /dev/null +++ b/core/modules/file/player/templates/default/js/minplayer.playLoader.default.js @@ -0,0 +1,46 @@ +/** The minplayer namespace. */ +var minplayer = minplayer || {}; + +// Define the busy object. +minplayer.playLoader = minplayer.playLoader || {}; + +// constructor. +minplayer.playLoader["default"] = function(context, options) { + + // Derive from busy.base + minplayer.playLoader.base.call(this, context, options); +}; + +// Define the prototype for all controllers. +minplayer.playLoader["default"].prototype = new minplayer.playLoader.base(); +minplayer.playLoader["default"].prototype.constructor = minplayer.playLoader["default"]; + +/** + * Return the display for this plugin. + */ +minplayer.playLoader["default"].prototype.getDisplay = function(context, options) { + + // See if we need to build out the controller. + if (context.build) { + + // Prepend the playloader template. + context.prepend('\ +
\ +
\ +
 
\ +
\ +
'); + } + + return jQuery('.media-player-play-loader', context); +} + +// Return the elements +minplayer.playLoader["default"].prototype.getElements = function() { + var elements = minplayer.playLoader.base.prototype.getElements.call(this); + return jQuery.extend(elements, { + busy:jQuery(".media-player-loader", this.display), + bigPlay:jQuery(".media-player-big-play", this.display), + preview:jQuery(".media-player-preview", this.display) + }); +}; diff --git a/core/modules/file/player/templates/default/media_player_default.tpl.php b/core/modules/file/player/templates/default/media_player_default.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..ac8e226c7067db78e8eb11898bfd17037ab973f3 --- /dev/null +++ b/core/modules/file/player/templates/default/media_player_default.tpl.php @@ -0,0 +1,32 @@ +
+
+
+
+ + +
+
+
00:00
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
 
+
+
+
+ +
+
\ No newline at end of file diff --git a/core/modules/file/player/tools/README.md b/core/modules/file/player/tools/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cca552e58f066b0b04b6e1a1364ac7d672f0486d --- /dev/null +++ b/core/modules/file/player/tools/README.md @@ -0,0 +1,5 @@ +This is a tools folder used to run the makefile. You can easily get all the +necessary tools by navigating to the player folder within Terminal, and then +type the following command. + + sudo make -B tools