diff --git a/README.txt b/README.txt index a4ca9b2..be95ad6 100644 --- a/README.txt +++ b/README.txt @@ -86,7 +86,7 @@ to the YouTube's video page in the field, and the video will be rendered. ALTER PLAYER IN CODE ----------------------------------------------- The player can be modified programmatically by using an available hook -(hook_jw_player_build_player_alter). See jw_player.api.php for details. +(hook_jw_player_alter). See jw_player.api.php for details. PLAYER STYLING @@ -116,32 +116,32 @@ capable of doing so. If the Server does not support this the player will always start at the beginning. -LEGACY VS. IMPROVED THEMING +ENABLE PLAYER API AND REMOVE INLINE JAVASCRIPT ----------------------------------------------- -The module by default is set to use Legacy Theming. This uses a traditional -templating approach with jw_player.tpl.php, and renders JW Player's JavaScript -in an inline format. This does not follow Drupal's recommended methodology of -using #attached to include JavaScript, and relies on a template_preprocess -function for making modifications. This causes some limitations, including -potential problems for sites using JavaScript aggregation and compression -modules such as AdvAgg. For sites that find the current functionality -acceptable, it is recommended to continue to use Legacy Theming. - -An alternative now available on the JW Player general settings page -(admin/config/media/jw_player/settings) is "Improved Theming". Improved -Theming takes advantage of the best approach caching and JavaScript -aggregation. It also provides a hook for changing player options, and an -API for building custom players in code. It is recommended that new sites -and sites wanting improved functionality choose the Improved Theming option. - -Please note, if a site changes from Legacy to Improved, updates to templates, -presets, and field configuration may be necessary. +In JW Player general settings (admin/config/media/jw_player/settings) +is an option to enable Player API and remove inline JavaScript. + +If this option is selected, an API will be made available for building +custom players in code (see notes below). Also, JavaScript will be removed +from inline and rendered using #attached. Using this approach ensures +optimal JavaScript aggregation, compression, and caching. + +Deciding to not enable the option provides legacy support for JW Player. +When disabled, the module will use traditional templating with +jw_player.tpl.php, and render JW Player's JavaScript in inline format. +In this mode, the API is not available for building custom players. Sites +that find the current functionality acceptable can continue to run the +JW Player module without enabling this new feature. + +Please note, if a site changes from disabling to enabling the option, +there will be required updates necessary to templates, presets, and field +configurations. API USAGE ----------------------------------------------- -With the Improved Theming option, building a player is as simple as creating -an array with player data, and calling drupal_render. +With the Player API option enabled, building a player is as simple as creating +a renderable array with player data. It is a very powerful approach for creating JW Player rendered content on your site, as it is possible to pull information from multiple sources, feeds, and @@ -161,13 +161,12 @@ The required information to be passed into the API is as follows: $player (array) This is an associative array of all features to be included. Several examples -are included to show how to build a single player, a playlist with multiple files, -and a playlist that includes fallback source videos (for example, if mp4 cannot be -played, video falls back to flv). +are included to show how to build a single player, a playlist with multiple +files, and a playlist that includes fallback source videos (for example, if +mp4 cannot be played, video falls back to flv). -The player can be rendered using drupal_render($player). See jw_player.api.php -for more details. If the array does not follow the correct format, the content -will not be loaded. Examples are presented below. +See jw_player.api.php for more details. If the array does not follow the correct +format, the content will not be loaded. Examples are presented below. // How to structure a player with one file. $player_example = array( @@ -177,11 +176,14 @@ $player_example = array( '#html_id' => 'my_custom_player', // Options: player, playlist, and sources (required) '#player_type' => 'player', + // JW Player cloud library URL that defines a style for the player to be + // rendered. If added, this overrides any option set for '#preset' (optional). + '#player_library_url' => 'http://content.jwplatform.com/libraries/iHRbcWAq.js', // Machine name of preset to apply. If included, '#options' array is not required. // If both are used, #options array will override preset settings. '#preset' => 'my_saved_preset', // Machine name of image style to apply to each preview image. - 'image_style' => 'large', + '#image_style' => 'large', // Associative array including files, titles, and images to render. '#files' => array( array( @@ -235,7 +237,7 @@ $playlist_example = array( '#html_id' => 'my_custom_playlist', '#player_type' => 'playlist', '#preset' => 'my_video_image_style', - 'image_style' => 'large', + '#image_style' => 'large', // Associative array including files, titles, and images to render. '#files' => array( array( @@ -285,8 +287,8 @@ $sources_example = array( '#type' => 'jw_player', '#html_id' => 'my_custom_sources', '#player_type' => 'sources', - // No extra #options settings, so only preset settings are used. - '#preset' => 'my_saved_preset', + // No extra #options settings, so only cloud library settings are used. + '#player_library_url' => 'http://content.jwplatform.com/libraries/xHDbcsAp.js', // Fallback sources are structured as an array within the playlist array. // Include files in the order in which they should be processed. '#files' => array( diff --git a/includes/jw_player.build.inc b/includes/jw_player.build.inc deleted file mode 100644 index a7911ab..0000000 --- a/includes/jw_player.build.inc +++ /dev/null @@ -1,323 +0,0 @@ -uri) : $preset_settings['skin']; - } - else { - if (!isset($preset_settings['skin']) || empty($preset_settings['skin'])) { - unset($preset_settings['skin']); - } - // Reset skin into an array, since JW7 allows for different skin options. - else { - $skin = $preset_settings['skin']; - unset($preset_settings['skin']); - $preset_settings['skin'] = array(); - $preset_settings['skin']['name'] = $skin; - } - } - // Sharing sites settings. - if (isset($preset_settings['sharing']) && $preset_settings['sharing']) { - $preset_settings['sharing'] = array(); - $preset_settings['sharing']['heading'] = $preset_settings['sharing_heading']; - unset($preset_settings['sharing_heading']); - $selected_sharing = array(); - foreach ($preset_settings['sharing_sites_show'] as $sharing_site) { - if (!empty($sharing_site)) { - $selected_sharing[$sharing_site] = $preset_settings['sharing_sites_order'][$sharing_site]['weight']; - } - } - asort($selected_sharing); - foreach ($selected_sharing as $key => $value) { - $preset_settings['sharing']['sites'][] = $key; - } - } - // Once we're done, remove the extra 'sharing_sites' arrays if they are set. - if (isset($preset_settings['sharing_sites_show'])) { - unset($preset_settings['sharing_sites_show']); - } - if (isset($preset_settings['sharing_sites_order'])) { - unset($preset_settings['sharing_sites_order']); - } - } - } - // Merge presets into default settings. - return array_merge($default_settings, $preset_settings); -} - -/** - * Build a formatted array that can be used to render a JW Player player. - * - * @param string $field_type - * Type of field where content is stored (video, audio, link_field, text) - * @param string $entity_type - * The entity type of the entity, such as 'node' or 'user'. - * @param object $entity - * Object defining the entity where the player will be rendered. - * @param array $items - * Array of field items to be rendered. - * @param array $settings - * Array of display settings for the field item to be rendered. - * - * @return array - * A formatted array that can be passed to jw_player_build_player for rendering. - */ -function jw_player_build_element($field_type, $entity_type, $entity, $items, $settings) { - $element = element_info('jw_player'); - $files = array(); - switch ($settings['player_type']) { - case 'player': - default: - $files[] = array( - 'file' => jw_player_build_file($field_type, $items[0]), - 'title' => jw_player_build_title($field_type, $items[0]), - 'image' => jw_player_build_preview_image($entity, 0, $settings), - ); - break; - - case 'playlist': - foreach ($items as $delta => $item) { - $files[] = array( - 'file' => jw_player_build_file($field_type, $items[$delta]), - 'title' => jw_player_build_title($field_type, $items[$delta]), - 'image' => jw_player_build_preview_image($entity, $delta, $settings), - ); - } - break; - - case 'sources': - $files[] = array( - 'sources' => jw_player_build_sources($field_type, $items), - 'title' => jw_player_build_title($field_type, $items[0]), - 'image' => jw_player_build_preview_image($entity, 0, $settings), - ); - break; - } - - // Used as a 'marker' in pre_render to identify if this is - // a player generated from a field, or from an API build. - list($id) = entity_extract_ids($entity_type, $entity); - $element['entity_id'] = $id; - - $element['#html_id'] = jw_player_build_player_id($items, $settings); - $element['#player_type'] = $settings['player_type']; - if (!empty($settings['jwplayer_preset'])) { - $element['#preset'] = $settings['jwplayer_preset']; - } - if (!empty($settings['preview_image_style'])) { - $element['#image_style'] = $settings['preview_image_style']; - } - $element['#files'] = $files; - return $element; -} - -/* - * Build custom JW Player id. - */ -function jw_player_build_player_id($items, $settings) { - // @todo Consider using standard approach when caching patches are committed. - // https://www.drupal.org/node/2637552 - // https://www.drupal.org/node/2643188 - $item_id = ''; - // If item being rendered is a file, video, or audio. - if (isset($items[0]['fid'])) { - $item_id = $items[0]['fid']; - } - // If item being rendered is a link_field. - else if (isset($items[0]['url'])) { - $item_id = $items[0]['url']; - } - // If item being rendered is a text. - else if (isset($items[0]['value'])) { - $item_id = $items[0]['value']; - } - $item_id = jw_player_remove_special_chars($item_id); - // Cannot completely guarantee uniqueness, but adding different settings should help. - $suffix = md5($settings['player_type'] . '_' . $item_id . '_' . $settings['jwplayer_preset']); - return drupal_html_id('jwplayer-' . $suffix); -} - -/* - * Helper function to remove special characters and URL prefixes from ids. - */ -function jw_player_remove_special_chars($id) { - $find = array('/[^A-Za-z0-9\-\_]/', '/^(https?)/i', '/^(www)/i'); - $replace = array('', '', ''); - return preg_replace($find, $replace, $id); -} - -/* - * Build JW Player file. - * Support for file, video, text, and link field types. - */ -function jw_player_build_file($field_type, $item) { - switch ($field_type) { - case 'file': - case 'video': - $file = file_create_url($item['uri']); - break; - case 'text': - $file = file_create_url($item['value']); - break; - case 'link_field': - $file = file_create_url($item['url']); - break; - default: - $file = file_create_url($item['uri']); - break; - } - return preg_replace('/^https?:/i', '', $file); -} - -/* - * Build JW Player file title. - * Support for file, video, text, and link field types. - */ -function jw_player_build_title($field_type, $item) { - switch ($field_type) { - case 'file': - case 'video': - return isset($item['description']) ? $item['description'] : ''; - case 'link_field': - return isset($item['title']) ? $item['title'] : ''; - case 'text': - default: - return ''; - } -} - -/* - * Build JW Player preview image. - */ -function jw_player_build_preview_image($entity, $delta, $settings) { - if (isset($settings['preview_image_field']) && !empty($settings['preview_image_field'])) { - $split = explode('|', $settings['preview_image_field']); - $preview_image_entity_type = explode(':', $split[0]); - - if ($images = field_get_items($preview_image_entity_type[0], $entity, $split[1])) { - if (isset($images[$delta]['uri'])) { - // Return image with applied image style. - if (!empty($settings['preview_image_style'])) { - return image_style_url($settings['preview_image_style'], $images[$delta]['uri']); - } - // If no image style is set, return the original image. - else { - return file_create_url($images[$delta]['uri']); - } - } - } - } - return ''; -} - -/* - * Recursive function to loop through files, find those - * set as 'image', and apply an image style to them. - */ -function jw_player_apply_preview_image_style(&$files, $image_style) { - global $base_url; - $dir_path = file_stream_wrapper_get_instance_by_scheme(file_default_scheme())->getDirectoryPath(); - $path_to_files = $base_url . '/' . $dir_path; - foreach ($files as $key => &$value) { - if ($key === 'image' && !is_array($value) && !empty($value)) { - // Check if $value is in URI format. - preg_match('/^(public|private):\/\/(.*)/i', $value, $matches); - if (!isset($matches[1])) { - // If not URI, check if the URL is the same as this site. - if (stripos($value, $path_to_files) !== FALSE) { - // If URL is the same as the site, build a URI. - // If URL is an external site, skip applying image style. - $uri = file_build_uri(str_replace($path_to_files, '', $value)); - $value = image_style_url($image_style, $uri); - } - } - // If already in URI format, create styled image. - else { - $value = image_style_url($image_style, $value); - } - } - else if (is_array($value)) { - jw_player_apply_preview_image_style($value, $image_style); - } - } -} - -/* - * Build JW Player sources file array. - */ -function jw_player_build_sources($field_type, $items) { - $sources = array(); - foreach ($items as $delta => $item) { - $sources[] = array('file' => jw_player_build_file($field_type, $items[$delta])); - } - return $sources; -} diff --git a/js/jw_player.drupal.js b/js/jw_player.drupal.js deleted file mode 100644 index 2f38958..0000000 --- a/js/jw_player.drupal.js +++ /dev/null @@ -1,21 +0,0 @@ -(function ($) { - - Drupal.behaviors.jw_player = { - attach: function (context, settings) { - - $(settings.jw_player).each(function () { - // Load an object with all of our player options - var options = {}; - for (var option_key in this.options) { - if (this.options.hasOwnProperty(option_key)) { - options[option_key] = this.options[option_key]; - } - } - - jwplayer(this.html_id).setup(options); - }); - - } - }; - -})(jQuery); diff --git a/js/jw_player.seek.js b/js/jw_player.seek.js deleted file mode 100644 index 2f93678..0000000 --- a/js/jw_player.seek.js +++ /dev/null @@ -1,16 +0,0 @@ -(function ($) { - - /** - * Check for seeking parameters in the url and trigger a seek. - */ - Drupal.behaviors.JWPlayerSeek = { - attach: function(context) { - var seek = decodeURI((RegExp('seek=(.+?)(&|$)').exec(location.search)||[,null])[1]); - var url_player_id = decodeURI((RegExp('#(.+?)(&|$)').exec(location.hash)||[,null])[1]); - if (seek > 0 && $('#' + url_player_id).size() > 0) { - jwplayer(url_player_id).seek(seek); - } - } - }; - -})(jQuery); diff --git a/jw_player.admin.inc b/jw_player.admin.inc index 5040330..eb3c814 100644 --- a/jw_player.admin.inc +++ b/jw_player.admin.inc @@ -127,19 +127,11 @@ function jw_player_settings_form($form) { ), ); - $form['#attached']['js'] = array( - drupal_get_path('module', 'jw_player') . '/js/jw_player.admin.js', - ); - - $form['jw_player_theming'] = array( - '#type' => 'radios', - '#title' => t('Theming options'), - '#options' => array( - 'legacy' => t('Legacy theming (recommended for existing sites)'), - 'improved' => t('Improved theming (recommended for new sites and those wanting improved functionality)'), - ), - '#default_value' => variable_get('jw_player_theming', 'legacy'), - '#description' => t('Legacy theming uses traditional templating and inline JavaScript, and may have JavaScript aggregation/compression issues. Improved theming provides a hook to change player options, build players in code, and is the best approach for caching and aggregation. Note: if changing from Legacy to Improved, updates to templates, presets, and field configuration may be necessary.'), + $form['jw_player_api'] = array( + '#title' => t('Enable Player API and Remove Inline JavaScript'), + '#type' => 'checkbox', + '#description' => t('Select to create custom players using the module API, modify players via hook, and improve JavaScript aggregation/compression. Note: will require updates to existing templates, presets, and field configurations.'), + '#default_value' => variable_get('jw_player_api', FALSE), '#states' => array( 'visible' => array( 'select[name="jw_player_version"]' => array('value' => '7'), @@ -147,6 +139,10 @@ function jw_player_settings_form($form) { ), ); + $form['#attached']['js'] = array( + drupal_get_path('module', 'jw_player') . '/js/jw_player.admin.js', + ); + $form['#validate'][] = 'jw_player_settings_form_validate'; return system_settings_form($form); diff --git a/jw_player.api.php b/jw_player.api.php index 923471c..a006656 100644 --- a/jw_player.api.php +++ b/jw_player.api.php @@ -55,11 +55,27 @@ * ); * @endcode * - * Once you have generated a player object, you can run drupal_render() on it - * to turn it into HTML: + * Once you have generated a player renderable array, it can be passed back for + * display. For example, if displaying a player in a block, use hook_block_view + * to return the renderable array to the content of the block's body. * * @code - * $output = drupal_render($player); + * function my_module_block_view($delta = '') { + * $block = array(); + * if ($delta == 'my_video_block') { + * $block['content'] = array( + * '#type' => 'jw_player', + * '#html_id' => 'my_custom_player', + * '#player_type' => 'player', + * '#files' => array( + * array( + * 'file' => 'http://mysite.com/sites/all/files/video2.mp4' + * ) + * ), + * ); + * } + * return $block; + * } * @endcode * * There are many properties available for the player types (player, playlist, @@ -74,9 +90,11 @@ * * @param $player * The chart renderable. Passed in by reference. - * @param $html_id + * @param $id * The player identifier, pulled from the $player['#id'] property (machine - * name of the player to be rendered). + * name of the player to be rendered). The player id can be found by using + * Firebug or Chrome Developer Tools to inspect the HTML generated for the + * player, and replacing dashes with underscores. */ function hook_jw_player_alter(&$player, $id) { if ($id === 'my_playlist') { @@ -86,18 +104,6 @@ function hook_jw_player_alter(&$player, $id) { } /** - * Alter an individual player before it's rendered. - * - * Same as hook_jw_player_alter(), only including the $id in the function - * name instead of being passed in as an argument. - * - * @see hook_jw_player_alter() - */ -function hook_jw_player_PLAYER_ID_alter(&$player) { - -} - -/** * Implements hook_jw_player_plugin_info(). * * @return array Associative array of plugins keyed by actual plugin id diff --git a/jw_player.install b/jw_player.install index 8cddd8e..cad32b4 100644 --- a/jw_player.install +++ b/jw_player.install @@ -55,6 +55,16 @@ function jw_player_schema() { } /** + * Implements hook_install(). + */ +function jw_player_install() { + variable_set('jw_player_version', 7); + variable_set('jw_player_hosting', 'cloud'); + variable_set('jw_player_api', TRUE); + drupal_set_message(t('JW Player version has been set to 7, hosting type to Cloud, and Player API enabled.')); +} + +/** * Implements hook_uninstall(). */ function jw_player_uninstall() { @@ -64,6 +74,7 @@ function jw_player_uninstall() { variable_del('jw_player_key'); variable_del('jw_player_key_7'); variable_del('jw_player_cloud_player_default'); + variable_del('jw_player_api'); } /** @@ -142,18 +153,6 @@ function jw_player_update_7002() { } /** - * Default new installations to JW Player version 7 and improved theming. - */ -function jw_player_update_7004() { - if (!variable_get('jw_player_version', FALSE)) { - variable_set('jw_player_version', 7); - variable_set('jw_player_hosting', 'cloud'); - variable_set('jw_player_theming', 'improved'); - drupal_set_message(t('JW Player version has been set to 7, hosting type to Cloud, and theming options set to Improved.')); - } -} - -/** * Set JW Player hosting based on currently saved keys and URLs. */ function jw_player_update_7003() { diff --git a/jw_player.module b/jw_player.module index 01b645c..6360e4d 100644 --- a/jw_player.module +++ b/jw_player.module @@ -7,8 +7,6 @@ define('JW_PLAYER_DEFAULT_PLAYLIST_SIZE', 200); define('JW_PLAYER_DEFAULT_PLAYLIST_POSITION', 'right'); -module_load_include('inc', 'jw_player', 'includes/jw_player.build'); - /** * Implements hook_menu(). */ @@ -42,33 +40,31 @@ function jw_player_permission() { * Implements hook_theme(). */ function jw_player_theme() { - if (!jw_player_use_legacy_theming()) { - $themes['jw_player_player'] = array( + return array( + 'jw_player_player' => array( 'render element' => 'element', 'file' => 'theme/jw_player.theme.inc', - ); - } - else { - $themes['jw_player'] = array( + ), + 'jw_player' => array( 'variables' => array( - 'file' => NULL, - 'sources' => NULL, - 'playlist' => NULL, - 'preset' => '', - 'image' => '', + 'file' => NULL, // deprecated + 'sources' => NULL, // deprecated + 'playlist' => NULL, // deprecated + 'preset' => '', // deprecated + 'image' => '', // deprecated 'html_id' => NULL, - 'options' => array(), - 'playlist_size' => JW_PLAYER_DEFAULT_PLAYLIST_SIZE, - 'playlist_position' => JW_PLAYER_DEFAULT_PLAYLIST_POSITION, + 'options' => array(), // deprecated + 'playlist_size' => JW_PLAYER_DEFAULT_PLAYLIST_SIZE, // deprecated + 'playlist_position' => JW_PLAYER_DEFAULT_PLAYLIST_POSITION, // deprecated + 'attributes' => array(), ), 'template' => 'theme/jw_player', - ); - } - $themes['jw_player_sharing_sites_order'] = array( - 'render element' => 'element', - 'file' => 'theme/jw_player.theme.inc', + ), + 'jw_player_sharing_sites_order' => array( + 'render element' => 'element', + 'file' => 'theme/jw_player.theme.inc', + ), ); - return $themes; } /** @@ -76,13 +72,14 @@ function jw_player_theme() { */ function jw_player_element_info() { $types['jw_player'] = array( - '#type' => 'jw_player', '#html_id' => '', - '#id' => '', // machine name '#player_type' => NULL, '#theme' => 'jw_player_player', '#tag' => 'div', - '#attributes' => NULL, + '#attributes' => array( + 'class' => array('jwplayer-video'), + ), + '#player_library_url' => NULL, '#preset' => '', '#image_style' => '', '#files' => array(), @@ -97,8 +94,8 @@ function jw_player_element_info() { * Implements hook_field_formatter_info(). */ function jw_player_field_formatter_info() { - if (!jw_player_use_legacy_theming()) { - $formatters['jw_player'] = array( + $formatters = array( + 'jw_player' => array( 'label' => t('JW Player'), 'field types' => array('file', 'video', 'link_field', 'text'), 'settings' => array( @@ -108,45 +105,28 @@ function jw_player_field_formatter_info() { 'preview_image_style' => NULL, 'player_type' => 'jwplayer', ), - ); - } - else { - // @todo Remove in future and consolidate to single JW Player formatter. - // In JW Player 7, all styling differences are set through CSS, and options - // can be generated in one array (see code in 'jw_player_build_player'). - $formatters = array( - 'jw_player' => array( - 'label' => t('JW Player'), - 'field types' => array('file', 'video', 'link_field', 'text'), - 'settings' => array( - 'jwplayer_preset' => '', - 'check_support' => FALSE, - 'preview_image_field' => NULL, - 'preview_image_style' => NULL, - ), - ), + ), - 'jw_player_playlist' => array( - 'label' => t('JW Player playlist (for JW Player 6 and earlier)'), - 'field types' => array('file', 'video'), - 'settings' => array( - 'jwplayer_preset' => '', - 'check_support' => FALSE, - 'playlist_size' => JW_PLAYER_DEFAULT_PLAYLIST_SIZE, - 'playlist_position' => JW_PLAYER_DEFAULT_PLAYLIST_POSITION, - ), + 'jw_player_playlist' => array( + 'label' => t('JW Player playlist (deprecated)'), + 'field types' => array('file', 'video'), + 'settings' => array( + 'jwplayer_preset' => '', + 'check_support' => FALSE, + 'playlist_size' => JW_PLAYER_DEFAULT_PLAYLIST_SIZE, + 'playlist_position' => JW_PLAYER_DEFAULT_PLAYLIST_POSITION, ), + ), - 'jw_player_sources' => array( - 'label' => t('JW Player sources (for JW Player 6 and earlier)'), - 'field types' => array('file', 'video'), - 'settings' => array( - 'jwplayer_preset' => '', - 'check_support' => FALSE, - ), + 'jw_player_sources' => array( + 'label' => t('JW Player sources (deprecated)'), + 'field types' => array('file', 'video'), + 'settings' => array( + 'jwplayer_preset' => '', + 'check_support' => FALSE, ), - ); - } + ), + ); return $formatters; } @@ -163,7 +143,7 @@ function jw_player_field_formatter_settings_form($field, $instance, $view_mode, // If no, prompt to create a preset. if (!empty($presets)) { $options = array(); - if (!jw_player_use_legacy_theming()) { + if (jw_player_api_enabled()) { $element['player_type'] = array( '#title' => t('Select player type'), '#type' => 'select', @@ -298,7 +278,7 @@ function jw_player_field_formatter_settings_summary($field, $instance, $view_mod $preset_settings = jw_player_preset_settings($settings['jwplayer_preset']); // Formatted preset name and player type. $summary[] = $preset_settings['name']; - if (!jw_player_use_legacy_theming()) { + if (jw_player_api_enabled()) { $player = drupal_ucfirst($settings['player_type']); $summary[] = t('Type: @player_type', array('@player_type' => $player)); } @@ -415,19 +395,16 @@ function jw_player_preset_settings($preset_machine_name) { } // Enabled options. $enabled = array(); - if (isset($preset_settings['autostart']) && $preset_settings['autostart']) { + if ($preset_settings['autostart']) { $enabled[] = t('Autostart'); } - if (isset($preset_settings['mute']) && $preset_settings['mute']) { + if ($preset_settings['mute']) { $enabled[] = t('Mute'); } - if (isset($preset_settings['repeat']) && $preset_settings['repeat']) { - $enabled[] = t('Repeat'); - } - if (isset($preset_settings['controls']) && $preset_settings['controls']) { + if ($preset_settings['controls']) { $enabled[] = t('Controls'); } - if (isset($preset_settings['sharing']) && $preset_settings['sharing']) { + if ($preset_settings['sharing']) { $enabled[] = t('Sharing'); } if (!empty($enabled)) { @@ -497,7 +474,7 @@ function jw_player_field_formatter_view($entity_type, $entity, $field, $instance } if (count($items) > 0) { - if (!jw_player_use_legacy_theming()) { + if (jw_player_api_enabled()) { $element[0] = jw_player_build_element($field['type'], $entity_type, $entity, $items, $settings); } else { @@ -508,7 +485,7 @@ function jw_player_field_formatter_view($entity_type, $entity, $field, $instance } /** - * Return renderable array for a player with inline JavaScript (legacy theming support). + * Return renderable array for a player with inline JavaScript (deprecated). * * @param string $type * String defining type of JW Player player to be rendered. @@ -545,7 +522,7 @@ function jw_player_build_legacy_element($type, $entity_type, $entity, $items, $s // the entire theme functions output is cached. // Prefixing the id makes sure that the id does not start with a // invalid numeric value. - $id = isset($item['fid']) ? $items['fid'] . $settings['jwplayer_preset'] : '-' . md5(rand()); + $id = isset($items['fid']) ? $items['fid'] . $settings['jwplayer_preset'] : '-' . md5(rand()); $element[$delta] = array( '#theme' => 'jw_player', '#file' => $item, @@ -662,53 +639,90 @@ function jw_player_preset_load($machine_name = NULL) { */ function jw_player_default_settings() { $defaults = &drupal_static(__FUNCTION__); + if (!isset($defaults)) { $defaults = array( 'width' => '640', 'height' => '480', 'autostart' => FALSE, + 'controls' => TRUE, + 'controlbar' => 'bottom', // deprecated ); - if (!jw_player_use_legacy_theming()) { - $defaults['controls'] = TRUE; - } - else { - $defaults['controlbar'] = 'bottom'; - } } + return $defaults; } /** - * Main #pre_render callback to expand a player element. + * #pre_render callback to expand a player element. + * + * @param array $element + * The JW Player element array. + * + * @return array + * Modified element array, with attached JavaScript and player options. */ function jw_player_pre_render_element($element) { - // Build preset options array based on selected preset, and override - // with any values passed in through the #options array. - if (isset($element['#preset'])) { + // Add player type as class. + $element['#attributes']['class'][] = $element['#player_type']; + + // Check if #preset is empty. + if (!empty($element['#preset'])) { + // Add preset as class. + $element['#attributes']['class'][] = $element['#preset']; + // Load #preset option settings. $options = jw_player_build_preset_options($element['#preset']); - $element['#options'] = array_merge($options, $element['#options']); + // Check if #preset has a player library URL. + if (isset($options['player_library_url'])) { + // Attach player library URL as JavaScript. + $element['#player_library_url'] = $options['player_library_url']; + $element['#attached']['js'] = array( + $element['#player_library_url'] => array( + 'type' => 'external', + ), + ); + } + // If #preset does not have a player library URL, build #options array. + else { + // Merge #preset options and any overrides into a single array. + $element['#options'] = array_merge($options, $element['#options']); + } + } + // #preset can be empty during API usage. + else if (!empty($element['#player_library_url'])) { + // If #player_library_url is has been set in API, check that it is valid. + preg_match(jw_player_library_url_regex(), $element['#player_library_url'], $matches); + // If valid, #attach the #player_library_url as JavaScript. + if (isset($matches[2])) { + $element['#attached']['js'] = array( + $element['#player_library_url'] => array( + 'type' => 'external', + ), + ); + } + // If #player_library_url is not set, use the default cloud player URL. + else { + $element['#attached']['library'][] = array('jw_player', 'jwplayer'); + } + } + // If both #preset and #player_library_url are empty, use the + // default cloud player URL (this can only happen via API usage). + else { + $element['#attached']['library'][] = array('jw_player', 'jwplayer'); } - // Only for use with the API, not hook_field_formatter_view. + // Only during API usage, not hook_field_formatter_view. if (!isset($element['entity_id'])) { // Remove any special characters that may have been added. $element['#html_id'] = jw_player_remove_special_chars($element['#html_id']); - // Images added to the API may be unpredictable formats, so perform formatting. + // Images added to API may be in unpredictable formats, so perform formatting. if (isset($element['#image_style'])) { jw_player_apply_preview_image_style($element['#files'], $element['#image_style']); } } - // Allow individual players to be altered. - $alter_hooks = array('jw_player'); - // Create machine name by replacing dash with underscore. - $element['#id'] = str_replace('-', '_', $element['#html_id']); - $alter_hooks[] = 'jw_player_' . $element['#id']; - drupal_alter($alter_hooks, $element, $element['#id']); - - $settings['html_id'] = $element['#html_id']; - // If player type is playlist or sources, shift the #files to include 'playlist'. - // Once done, merge #files and #options into single array to send to JW Player. + // If player type is playlist or sources, shift the #files to include + // 'playlist'. Once done, merge #files and #options into $settings array. if ($element['#player_type'] != 'player') { $shift['playlist'] = $element['#files']; $settings['options'] = array_merge($shift, $element['#options']); @@ -717,178 +731,599 @@ function jw_player_pre_render_element($element) { $settings['options'] = array_merge($element['#files'][0], $element['#options']); } + // Pass html_id and options to JavaScript. + $settings['html_id'] = $element['#html_id']; $element['#attached']['js'][] = array( 'data' => array('jw_player' => array($settings)), 'type' => 'setting', ); - // Load the JW Player library and supporting Drupal functions. + // Create machine name by replacing dashes with underscores. + // Used to access players via hook_jw_player_alter(). See jw_player.api.php. + $element['#id'] = str_replace('-', '_', $element['#html_id']); + + // Load the basic JW Player library and supporting Drupal functions. $element['#attached']['library'][] = array('jw_player', 'jwplayer'); - $element['#attached']['library'][] = array('jw_player', 'jw_player_drupal'); return $element; } /** - * Process variables for jw_player.tpl.php. - * Support for Legacy Theming. Not used by Improved Theming. + * Process variables for jw_player.tpl.php (deprecated). */ function template_preprocess_jw_player(&$variables) { - if (jw_player_use_legacy_theming()) { - //In some instances an object is passed so convert to an array. - if(is_object($variables['file'])) { - $variables['file'] = (array) $variables['file']; - } + // If using newer approach, return from function. + if (jw_player_api_enabled()) { + return; + } - // Load defaults as the starting point. - $default_settings = jw_player_default_settings(); - - // Load preset if set. - $preset_settings = array(); - if (!empty($variables['preset'])) { - $preset = jw_player_preset_load($variables['preset']); - // Additional check to ensure that the preset has actually loaded. This - // prevents problems where a preset has been deleted but a field is still - // configured to use it. - if (!empty($preset)) { - $preset_settings = $preset['settings']; - if (!empty($preset_settings['responsive'])) { - unset($preset_settings['height']); - $preset_settings['width'] = $preset_settings['width'] . '%'; - } - else { - unset($preset_settings['aspectratio']); - } + //In some instances an object is passed so convert to an array. + if(is_object($variables['file'])) { + $variables['file'] = (array) $variables['file']; + } + + // Load defaults as the starting point. + $default_settings = jw_player_default_settings(); + + // Load preset if set. + $preset_settings = array(); + if (!empty($variables['preset'])) { + $preset = jw_player_preset_load($variables['preset']); + // Additional check to ensure that the preset has actually loaded. This + // prevents problems where a preset has been deleted but a field is still + // configured to use it. + if (!empty($preset)) { + $preset_settings = $preset['settings']; + if (!empty($preset_settings['responsive'])) { + unset($preset_settings['height']); + $preset_settings['width'] = $preset_settings['width'] . '%'; + } + else { + unset($preset_settings['aspectratio']); } } + } - // Get any preset override options that were sent through the formatter or - // theme call. - $options = array(); - if (isset($variables['options'])) { - $options = $variables['options']; - unset($variables['options']); - } + // Get any preset override options that were sent through the formatter or + // theme call. + $options = array(); + if (isset($variables['options'])) { + $options = $variables['options']; + unset($variables['options']); + } - // Merge all variables together. Preset settings take priority over defaults, - // variables passed directly to the theme function take priority over both. - $variables = array_merge($default_settings, $preset_settings, $options, $variables); + // Merge all variables together. Preset settings take priority over defaults, + // variables passed directly to the theme function take priority over both. + $variables = array_merge($default_settings, $preset_settings, $options, $variables); - // The html_id should have been previously set. But we need to make - // sure we do have one. - if (!isset($variables['html_id'])) { - $id = isset($variables['file']['fid']) ? $variables['file']['fid'] . $variables['preset'] : '-' . md5(rand()); - $variables['html_id'] = drupal_html_id('jwplayer' . $id); - } + // The html_id should have been previously set. But we need to make + // sure we do have one. + if (!isset($variables['html_id'])) { + $id = isset($variables['file']['fid']) ? $variables['file']['fid'] . $variables['preset'] : '-' . md5(rand()); + $variables['html_id'] = drupal_html_id('jwplayer' . $id); + } - // Check if there is one or multiple files. If one file then we set 'file', if - // there are multiple files we set 'levels'. Note that levels is used for both - // multiple video formats as well as for adaptive bitrates. - if (isset($variables['sources'])) { - $variables['config']['playlist'][0] = array(); - $variables['config']['playlist'][0]['image'] = file_create_url($variables['image']['uri']); - foreach ($variables['sources'] as $key => $source) { - $file = isset($source['uri']) ? $source['uri'] : (isset($source['url']) ? $source['url'] : $source['value']); - $variables['config']['playlist'][0]['sources'][$key]['file'] = file_create_url($file); - if (isset($source['getid3']) && $source['getid3']['width'] > 0 && $source['getid3']['height'] > 0) { - $variables['config']['playlist'][0]['sources'][$key]['width'] = $source['getid3']['width']; - $variables['config']['playlist'][0]['sources'][$key]['height'] = $source['getid3']['height']; - } - elseif (isset($source['field_width']) && isset($source['field_height'])) { - $variables['config']['playlist'][0]['sources'][$key]['width'] = $source['field_width'][LANGUAGE_NONE][0]['value']; - $variables['config']['playlist'][0]['sources'][$key]['height'] = $source['field_height'][LANGUAGE_NONE][0]['value']; - } + // Check if there is one or multiple files. If one file then we set 'file', if + // there are multiple files we set 'levels'. Note that levels is used for both + // multiple video formats as well as for adaptive bitrates. + if (isset($variables['sources'])) { + $variables['config']['playlist'][0] = array(); + $variables['config']['playlist'][0]['image'] = file_create_url($variables['image']['uri']); + foreach ($variables['sources'] as $key => $source) { + $file = isset($source['uri']) ? $source['uri'] : (isset($source['url']) ? $source['url'] : $source['value']); + $variables['config']['playlist'][0]['sources'][$key]['file'] = file_create_url($file); + if (isset($source['getid3']) && $source['getid3']['width'] > 0 && $source['getid3']['height'] > 0) { + $variables['config']['playlist'][0]['sources'][$key]['width'] = $source['getid3']['width']; + $variables['config']['playlist'][0]['sources'][$key]['height'] = $source['getid3']['height']; + } + elseif (isset($source['field_width']) && isset($source['field_height'])) { + $variables['config']['playlist'][0]['sources'][$key]['width'] = $source['field_width'][LANGUAGE_NONE][0]['value']; + $variables['config']['playlist'][0]['sources'][$key]['height'] = $source['field_height'][LANGUAGE_NONE][0]['value']; } } + } - elseif (isset($variables['playlist'])) { - $variables['config']['playlist'] = array(); - foreach ($variables['playlist'] as $key => $source) { - $file = isset($source['uri']) ? $source['uri'] : (isset($source['url']) ? $source['url'] : $source['value']); - $variables['config']['playlist'][$key]['file'] = file_create_url($file); - $variables['config']['playlist'][$key]['image'] = 'none'; - if (!empty($source['description'])) { - $variables['config']['playlist'][$key]['title'] = $source['description']; - } - else { - $pathinfo = pathinfo($file); - $variables['config']['playlist'][$key]['title'] = $pathinfo['filename']; - } + elseif (isset($variables['playlist'])) { + $variables['config']['playlist'] = array(); + foreach ($variables['playlist'] as $key => $source) { + $file = isset($source['uri']) ? $source['uri'] : (isset($source['url']) ? $source['url'] : $source['value']); + $variables['config']['playlist'][$key]['file'] = file_create_url($file); + $variables['config']['playlist'][$key]['image'] = 'none'; + if (!empty($source['description'])) { + $variables['config']['playlist'][$key]['title'] = $source['description']; } + else { + $pathinfo = pathinfo($file); + $variables['config']['playlist'][$key]['title'] = $pathinfo['filename']; + } + } - $variables['config']['listbar'] = array( - 'position' => !empty($variables['playlist_position']) ? $variables['playlist_position'] : JW_PLAYER_DEFAULT_PLAYLIST_POSITION, - 'size' => !empty($variables['playlist_size']) ? $variables['playlist_size'] : JW_PLAYER_DEFAULT_PLAYLIST_POSITION, - ); + $variables['config']['listbar'] = array( + 'position' => !empty($variables['playlist_position']) ? $variables['playlist_position'] : JW_PLAYER_DEFAULT_PLAYLIST_POSITION, + 'size' => !empty($variables['playlist_size']) ? $variables['playlist_size'] : JW_PLAYER_DEFAULT_PLAYLIST_POSITION, + ); + } + + else { + $vars_file = $variables['file']; + $file = isset($vars_file['uri']) ? $vars_file['uri'] : (isset($vars_file['url']) ? $vars_file['url'] : $vars_file['value']); + $variables['config']['file'] = file_create_url($file); + } + + if (jw_player_use_legacy()) { + // Resolve skin url. + $skin = !empty($variables['skin']) ? jw_player_skins($variables['skin']) : NULL; + $variables['skin'] = is_string($skin) ? file_create_url($skin->uri) : $variables['skin']; + } + + // Outside of the cloud player check, has to work then as well. + if (!empty($variables['image'])) { + $variables['config']['image'] = $variables['image']; + } + + // Don't apply preset config in case of cloud hosted players. + if (!variable_get('jw_player_cloud_player_default', FALSE)) { + + // Copy player variables into their own array to be set as JavaScript + // configuration. + // @todo Bad smell here. Refactoring needed. + + $config_variables = array( + 'width', + 'height', + 'playlist.position', + 'playlist.size', + 'skin', + 'stretching', + 'aspectratio', + ); + foreach ($config_variables as $key) { + if (!empty($variables[$key])) { + $variables['config'][$key] = $variables[$key]; + } } - else { - $vars_file = $variables['file']; - $file = isset($vars_file['uri']) ? $vars_file['uri'] : (isset($vars_file['url']) ? $vars_file['url'] : $vars_file['value']); - $variables['config']['file'] = file_create_url($file); + if (isset($variables['autostart']) && $variables['autostart']) { + $variables['config']['autostart'] = 'true'; } - if (jw_player_use_legacy()) { - // Resolve skin url. - $skin = !empty($variables['skin']) ? jw_player_skins($variables['skin']) : NULL; - $variables['skin'] = is_string($skin) ? file_create_url($skin->uri) : $variables['skin']; + if (isset($variables['controls']) && !$variables['controls']) { + $variables['config']['controls'] = 'false'; } - // Outside of the cloud player check, has to work then as well. - if (!empty($variables['image'])) { - $variables['config']['image'] = $variables['image']; + // If the preset has the primary mode set, modify the modes array so that it + // comes first. + if (isset($variables['primary']) && $variables['primary'] == 'flash') { + $variables['config']['primary'] = 'flash'; } + } +} - // Don't apply preset config in case of cloud hosted players. - if (!variable_get('jw_player_cloud_player_default', FALSE)) { - - // Copy player variables into their own array to be set as JavaScript - // configuration. - // @todo Bad smell here. Refactoring needed. - - $config_variables = array( - 'width', - 'height', - 'playlist.position', - 'playlist.size', - 'skin', - 'stretching', - 'aspectratio', - ); - foreach ($config_variables as $key) { - if (!empty($variables[$key])) { - $variables['config'][$key] = $variables[$key]; +/** + * Implements hook_process_HOOK() (deprecated). + */ +function jw_player_process_jw_player(&$variables) { + // If using newer approach, return from function. + if (jw_player_api_enabled()) { + return; + } + + $variables['jw_player_inline_js_code'] = stripslashes(json_encode($variables['config'])); + // Load the JW Player libraries (and integration into Drupal). + drupal_add_library('jw_player', 'jwplayer'); +} + +/** + * Create preset options array by merging default and preset settings. + * + * @param string $preset_machine_name + * Preset for which an options array should be build. + * + * @return array + * Merge array of default preset values, and values stored for the preset + * passed in to the function. + */ +function jw_player_build_preset_options($preset_machine_name) { + $default_settings = jw_player_default_settings(); + $preset_settings = array(); + + if (!empty($preset_machine_name)) { + $preset = jw_player_preset_load($preset_machine_name); + if (!empty($preset)) { + $preset_settings = $preset['settings']; + // No longer need preset source. + unset($preset_settings['preset_source']); + // Controlbar is no longer an option in JW Player 7. + if (!jw_player_use_legacy()) { + unset($default_settings['controlbar']); + } + // Primary setting: legacy support for value. + if (isset($preset_settings['primary']) && $preset_settings['primary'] != 'flash') { + unset($preset_settings['primary']); + } + // Responsive setting: responsive or fixed. + // Responsive needs width and aspectratio; fixed needs height and width. + if (!empty($preset_settings['responsive'])) { + unset($preset_settings['height']); + unset($default_settings['height']); + $preset_settings['width'] = $preset_settings['width'] . '%'; + } + else { + unset($preset_settings['aspectratio']); + } + // Once we're done, remove the 'responsive' value. + unset($preset_settings['responsive']); + // Autostart setting. + if (!empty($preset_settings['autostart'])) { + $preset_settings['autostart'] = true; + } + else { + $preset_settings['autostart'] = false; + } + // Mute setting. + if (!empty($preset_settings['mute'])) { + $preset_settings['mute'] = true; + } + else { + $preset_settings['mute'] = false; + } + // Repeat setting. + if (!empty($preset_settings['repeat'])) { + $preset_settings['repeat'] = true; + } + else { + $preset_settings['repeat'] = false; + } + // Controls setting. + if (!empty($preset_settings['controls'])) { + $preset_settings['controls'] = true; + } + else { + $preset_settings['controls'] = false; + } + // Skin setting. + if (jw_player_use_legacy()) { + $skin = !empty($preset_settings['skin']) ? jw_player_skins($preset_settings['skin']) : NULL; + $preset_settings['skin'] = is_object($skin) ? file_create_url($skin->uri) : $preset_settings['skin']; + } + else { + if (!isset($preset_settings['skin']) || empty($preset_settings['skin'])) { + unset($preset_settings['skin']); + } + // Reset skin into an array, since JW7 allows for different skin options. + else { + $skin = $preset_settings['skin']; + unset($preset_settings['skin']); + $preset_settings['skin'] = array(); + $preset_settings['skin']['name'] = $skin; } } + // Sharing sites settings. + if (!empty($preset_settings['sharing'])) { + $preset_settings['sharing'] = array(); + $preset_settings['sharing']['heading'] = $preset_settings['sharing_heading']; + unset($preset_settings['sharing_heading']); + $selected_sharing = array(); + foreach ($preset_settings['sharing_sites_show'] as $sharing_site) { + if (!empty($sharing_site)) { + $selected_sharing[$sharing_site] = $preset_settings['sharing_sites_order'][$sharing_site]['weight']; + } + } + asort($selected_sharing); + foreach ($selected_sharing as $key => $value) { + $preset_settings['sharing']['sites'][] = $key; + } + } + // Once we're done, remove the extra 'sharing_sites' arrays if they are set. + if (isset($preset_settings['sharing_sites_show'])) { + unset($preset_settings['sharing_sites_show']); + } + if (isset($preset_settings['sharing_sites_order'])) { + unset($preset_settings['sharing_sites_order']); + } + } + } + // Merge presets into default settings. + return array_merge($default_settings, $preset_settings); +} - if (isset($variables['autostart']) && $variables['autostart']) { - $variables['config']['autostart'] = 'true'; +/** + * Build a formatted array that can be used to render a JW Player player. + * + * @param string $field_type + * Type of field where content is stored (video, audio, link_field, text) + * @param string $entity_type + * The entity type of the entity, such as 'node' or 'user'. + * @param object $entity + * Object defining the entity where the player will be rendered. + * @param array $items + * Array of field items to be rendered. + * @param array $settings + * Array of display settings for the field item to be rendered. + * + * @return array + * A formatted array that can be passed to jw_player_build_player for rendering. + */ +function jw_player_build_element($field_type, $entity_type, $entity, $items, $settings) { + $element = element_info('jw_player'); + $files = array(); + switch ($settings['player_type']) { + case 'player': + default: + $files[] = array( + 'file' => jw_player_create_file_url($field_type, $items[0]), + 'title' => jw_player_build_title($field_type, $items[0]), + 'image' => jw_player_build_preview_image($entity, 0, $settings), + ); + break; + + case 'playlist': + foreach ($items as $delta => $item) { + $files[] = array( + 'file' => jw_player_create_file_url($field_type, $items[$delta]), + 'title' => jw_player_build_title($field_type, $items[$delta]), + 'image' => jw_player_build_preview_image($entity, $delta, $settings), + ); } + break; + + case 'sources': + $files[] = array( + 'sources' => jw_player_build_sources($field_type, $items), + 'title' => jw_player_build_title($field_type, $items[0]), + 'image' => jw_player_build_preview_image($entity, 0, $settings), + ); + break; + } - if (isset($variables['controls']) && !$variables['controls']) { - $variables['config']['controls'] = 'false'; + // Used as a 'marker' in pre_render to identify if this is + // a player generated from a field, or from an API build. + list($id) = entity_extract_ids($entity_type, $entity); + $element['entity_id'] = $id; + + $element['#html_id'] = jw_player_build_player_id($items, $settings); + $element['#player_type'] = $settings['player_type']; + //$element['#attributes']['class'][] = $settings['player_type']; + if (!empty($settings['jwplayer_preset'])) { + $element['#preset'] = $settings['jwplayer_preset']; + //$element['#attributes']['class'][] = $settings['jwplayer_preset']; + } + if (!empty($settings['preview_image_style'])) { + $element['#image_style'] = $settings['preview_image_style']; + } + $element['#files'] = $files; + return $element; +} + +/** + * Build a custom JW Player id to be set as the player's html id. Also can be + * referenced via hook_jw_player_alter. + * + * @param array $items + * Array of field items to be rendered. + * @param array $settings + * Array of display settings for the field item to be rendered. + * + * @return string + * The id of the current item's player, formatted in the following structure: + * - 1st component: 'jwplayer' + * - 2nd component: player type, such as 'player', 'playlist', or 'sources' + * - 3rd component: unique id of the item to be rendered (fid for file, video, + * or audio, url for link field, or value for text field. + * - 4th component: machine name of the JW Player preset + * - Example: 'jwplayer-player-32-640x480' + * + * @see hook_jw_player_alter() + */ +function jw_player_build_player_id($items, $settings) { + // @todo Consider using standard approach when caching patches are committed. + // https://www.drupal.org/node/2637552 + // https://www.drupal.org/node/2643188 + $item_id = ''; + // If item being rendered is a file, video, or audio. + if (isset($items[0]['fid'])) { + $item_id = $items[0]['fid']; + } + // If item being rendered is a link_field. + else if (isset($items[0]['url'])) { + $item_id = $items[0]['url']; + } + // If item being rendered is a text field. + else if (isset($items[0]['value'])) { + $item_id = $items[0]['value']; + } + $item_id = jw_player_remove_special_chars($item_id); + // Cannot completely guarantee uniqueness, but adding different settings + // should help. + $suffix = $settings['player_type'] . '_' . $item_id . '_' . $settings['jwplayer_preset']; + // If $suffix is extra long, use md5. + if (strlen($suffix) > 32) { + $suffix = md5($suffix); + } + return drupal_html_id('jwplayer-' . $suffix); +} + +/** + * Remove special characters and URL prefixes from ids. Especially important + * for API usage, and formatting link and text fields for JW Player. + * + * @param string $id + * Item identification stored by Drupal, or passed through API usage. + * - For file, video, and audio field content, id is 'fid'. + * - For link field content, id is 'url'. + * - For text field content, id is 'value'. + * + * @return string + * Unique string containing no special characters, or URL prefixes (http, + * https, www, etc.) + */ +function jw_player_remove_special_chars($id) { + $find = array('/[^A-Za-z0-9\-\_]/', '/^(https?)/i', '/^(www)/i'); + $replace = array('', '', ''); + return preg_replace($find, $replace, $id); +} + +/** + * Create file URL to be passed to JW Player. + * + * @param string $field_type + * The type of field that JW Player is formatting. Options include file, + * video, text, and link. + * @param array $item + * The field item to be rendered. + * + * @return string + * File URL to be sent to JW Player for rendering, with protocol removed. + */ +function jw_player_create_file_url($field_type, $item) { + switch ($field_type) { + case 'file': + case 'video': + $file = file_create_url($item['uri']); + break; + case 'text': + $file = file_create_url($item['value']); + break; + case 'link_field': + $file = file_create_url($item['url']); + break; + default: + $file = file_create_url($item['uri']); + break; + } + return preg_replace('/^https?:/i', '', $file); +} + +/** + * Build file title to be passed to JW Player. + * + * @param string $field_type + * The type of field that JW Player is formatting. Options include file, + * video, text, and link. + * @param array $item + * The field item to be rendered. + * + * @return string + * File title to be sent to JW Player for rendering. + */ +function jw_player_build_title($field_type, $item) { + switch ($field_type) { + case 'file': + case 'video': + return isset($item['description']) ? $item['description'] : ''; + case 'link_field': + return isset($item['title']) ? $item['title'] : ''; + case 'text': + default: + return ''; + } +} + +/** + * Build image URL to be passed to JW Player and displayed prior to file playback. + * + * @param object $entity + * The entity containing the data to be displayed. + * @param integer $delta + * The specific image among a set of images to be processed. + * @param array $settings + * Array of display settings for the field item to be rendered. + * + * @return string + * URL for the image to be displayed. If an image style has been set for + * the field, it will be applied. + */ +function jw_player_build_preview_image($entity, $delta, $settings) { + if (!empty($settings['preview_image_field'])) { + $split = explode('|', $settings['preview_image_field']); + $preview_image_entity_type = explode(':', $split[0]); + + if ($images = field_get_items($preview_image_entity_type[0], $entity, $split[1])) { + if (isset($images[$delta]['uri'])) { + // Return image with applied image style. + if (!empty($settings['preview_image_style'])) { + return image_style_url($settings['preview_image_style'], $images[$delta]['uri']); + } + // If no image style is set, return the original image. + else { + return file_create_url($images[$delta]['uri']); + } } + } + } + return ''; +} + +/* + * Recursive function to loop through files, find those + * set as 'image', and apply an image style to them. + */ - // If the preset has the primary mode set, modify the modes array so that it - // comes first. - if (isset($variables['primary']) && $variables['primary'] == 'flash') { - $variables['config']['primary'] = 'flash'; +/** + * Recursive function to loop through files, find those set as 'image', and + * apply an image style to them. Specifically for use with the module API, + * since we do not know the format used to include image file URLs in the array. + * + * @param array $files + * The #files array created for the JW Player element. + * @param string $image_style + * Machine name of the image style to be applied to each image. + * + * @return array $files + * The modified $files parameter is returned, with each image file URL + * replaced by a version that is relative to the image style applied. + */ +function jw_player_apply_preview_image_style(&$files, $image_style) { + global $base_url; + $dir_path = file_stream_wrapper_get_instance_by_scheme(file_default_scheme())->getDirectoryPath(); + $path_to_files = $base_url . '/' . $dir_path; + foreach ($files as $key => &$value) { + if ($key === 'image' && !is_array($value) && !empty($value)) { + // Check if $value is in URI format. + preg_match('/^(public|private):\/\/(.*)/i', $value, $matches); + if (!isset($matches[1])) { + // If not URI, check if the URL is the same as this site. + if (stripos($value, $path_to_files) !== FALSE) { + // If URL is the same as the site, build a URI. + // If URL is an external site, skip applying image style. + $uri = file_build_uri(str_replace($path_to_files, '', $value)); + $value = image_style_url($image_style, $uri); + } + } + // If already in URI format, create styled image. + else { + $value = image_style_url($image_style, $value); } } + else if (is_array($value)) { + jw_player_apply_preview_image_style($value, $image_style); + } } } /** - * Implements hook_process_HOOK(). + * Build JW Player sources file array. + * + * @param string $field_type + * The type of field that JW Player is formatting. Options include file, + * video, text, and link. + * @param array $items + * Array of field items to be rendered. + * + * @return array $sources + * An array of files that will serve as fallback in the case where files + * cannot be properly rendered. */ -function jw_player_process_jw_player(&$variables) { - $variables['jw_player_inline_js_code'] = stripslashes(json_encode($variables['config'])); - // Load the JW Player libraries (and integration into Drupal). - drupal_add_library('jw_player', 'jwplayer'); +function jw_player_build_sources($field_type, $items) { + $sources = array(); + foreach ($items as $delta => $item) { + $sources[] = array('file' => jw_player_create_file_url($field_type, $items[$delta])); + } + return $sources; } /** - * Implements hook_libraries_info() + * Implements hook_libraries_info(). */ function jw_player_libraries_info() { $libraries = array( @@ -903,7 +1338,7 @@ function jw_player_libraries_info() { ), 'integration files' => array( 'jw_player' => array( - 'js' => array('js/jw_player.seek.js') + 'js' => array('jw_player_seek.js') ), ), 'callbacks' => array( @@ -956,34 +1391,73 @@ function jw_player_library() { 'js' => array(), ); - if ($cloud_hosted_default = variable_get('jw_player_cloud_player_default', FALSE)) { - // Cloud hosted player, use external JavaScript. - $libraries['jwplayer']['js'][$cloud_hosted_default] = array( - 'type' => 'external', - ); - } - else if ($info['installed']) { - // Self hosted player, use files from library definition. - if (!empty($info['files']['js'])) { - foreach ($info['files']['js'] as $data => $option) { - if (is_numeric($data)) { - $option = "{$info['library path']}/{$option}"; + // Cloud-hosted option. + if (variable_get('jw_player_hosting', 'self') == 'cloud') { + $default_player_url = variable_get('jw_player_cloud_player_default', FALSE); + // If not legacy, add default and each preset url as a separate library. + if (jw_player_api_enabled()) { + // Add default as possible library to #attach during pre-render. + $libraries['jwplayer_cloud_default'] = array( + 'title' => 'JW Player Cloud Library Player (Default)', + 'website' => $info['vendor url'], + 'version' => $info['version'], + 'js' => array( + $default_player_url => array( + 'type' => 'external', + ), + ), + ); + // Add presets with 'jwplayer' source as possible libraries to #attach. + if ($presets = jw_player_load_preset_settings()) { + foreach ($presets as $preset_name => $preset) { + if (isset($preset['preset_source']) && $preset['preset_source'] == 'jwplayer') { + $libraries['jwplayer_' . $preset_name] = array( + 'title' => 'JW Player Cloud Library Player (Preset: ' . $preset['preset_name'] . ')', + 'website' => $info['vendor url'], + 'version' => $info['version'], + 'js' => array( + $preset['player_library_url'] => array( + 'type' => 'external', + ), + ), + ); + } } - else if (empty($option['type']) || $option['type'] == 'file') { - $data = "{$info['library path']}/{$data}"; + } + } + // If legacy, add default player as external JavaScript. + else { + $libraries['jwplayer']['js'][$default_player_url] = array( + 'type' => 'external', + ); + } + } + // Self-hosted option. + else { + if ($info['installed']) { + // Use files from library definition for self-hosted player. + if (!empty($info['files']['js'])) { + foreach ($info['files']['js'] as $data => $option) { + if (is_numeric($data)) { + $option = "{$info['library path']}/{$option}"; + } + else if (empty($option['type']) || $option['type'] == 'file') { + $data = "{$info['library path']}/{$data}"; + } + $libraries['jwplayer']['js'][$data] = $option; } - $libraries['jwplayer']['js'][$data] = $option; } } + // Key is only required for self-hosting option. + if ($key = jw_player_get_key()) { + $libraries['jwplayer']['js']['jwplayer.key=' . drupal_json_encode((string) $key)] = array( + 'type' => 'inline', + 'scope' => 'header', + 'weight' => 5 + ); + } } - if ($key = jw_player_get_key()) { - $libraries['jwplayer']['js']['jwplayer.key=' . drupal_json_encode($key)] = array( - 'type' => 'inline', - 'scope' => 'header', - 'weight' => 5 - ); - } // Use integration files from library definition. foreach ($info['integration files'] as $module => $files) { foreach (array_keys($files) as $type) { @@ -999,19 +1473,6 @@ function jw_player_library() { } } } - - $libraries['jw_player_drupal'] = array( - 'title' => t('jQuery support for rendering JW Player in Drupal.'), - 'version' => VERSION, - 'js' => array( - drupal_get_path('module', 'jw_player') . '/js/jw_player.drupal.js' => array( - 'type' => 'file', - 'weight' => -10, - 'group' => JS_DEFAULT, - 'scope' => 'footer', - ), - ), - ); return $libraries; } @@ -1128,13 +1589,13 @@ function jw_player_use_legacy() { } /** - * Checks whether legacy theming has been selected. + * Checks whether API is available. * * @return bool - * TRUE if legacy theming is set, FALSE otherwise. + * TRUE if API is set, FALSE otherwise. */ -function jw_player_use_legacy_theming() { - return variable_get('jw_player_theming', 'legacy') == 'legacy'; +function jw_player_api_enabled() { + return variable_get('jw_player_api', FALSE); } /** @@ -1148,6 +1609,32 @@ function jw_player_get_key() { } /** + * Return all preset settings defined in Drupal's JW Player configuration. + * + * @return array + * An array containing preset names and preset settings. + */ +function jw_player_load_preset_settings() { + $presets = &drupal_static(__FUNCTION__, NULL); + + if (!isset($presets)) { + $query = db_select('jwplayer_preset', 'p'); + $result = $query + ->fields('p') + ->execute(); + + $preset_info = $result->fetchAllAssoc('machine_name'); + $presets = array(); + foreach ($preset_info as $preset) { + $presets["{$preset->machine_name}"] = unserialize($preset->settings); + $presets["{$preset->machine_name}"]['preset_name'] = $preset->preset_name; + } + } + + return $presets; +} + +/** * Return regex to check JW Player library URL format. * * @return string diff --git a/jw_player_seek.js b/jw_player_seek.js new file mode 100644 index 0000000..2f93678 --- /dev/null +++ b/jw_player_seek.js @@ -0,0 +1,16 @@ +(function ($) { + + /** + * Check for seeking parameters in the url and trigger a seek. + */ + Drupal.behaviors.JWPlayerSeek = { + attach: function(context) { + var seek = decodeURI((RegExp('seek=(.+?)(&|$)').exec(location.search)||[,null])[1]); + var url_player_id = decodeURI((RegExp('#(.+?)(&|$)').exec(location.hash)||[,null])[1]); + if (seek > 0 && $('#' + url_player_id).size() > 0) { + jwplayer(url_player_id).seek(seek); + } + } + }; + +})(jQuery); diff --git a/plugins/export_ui/jw_player_ctools_export_ui.inc b/plugins/export_ui/jw_player_ctools_export_ui.inc index 93e73ca..bede631 100644 --- a/plugins/export_ui/jw_player_ctools_export_ui.inc +++ b/plugins/export_ui/jw_player_ctools_export_ui.inc @@ -273,18 +273,6 @@ function jw_player_ctools_export_ui_form(&$form, &$form_state) { ), ); - $form['settings']['repeat'] = array( - '#type' => 'checkbox', - '#title' => t('Repeat'), - '#description' => t('Set the player to loop content after a playlist completes.'), - '#default_value' => !empty($settings['repeat']) ? $settings['repeat'] : FALSE, - '#states' => array( - 'visible' => array( - ':input[name="settings[preset_source]"]' => array('value' => 'drupal'), - ), - ), - ); - $form['settings']['controls'] = array( '#title' => t('Controls'), '#type' => 'checkbox', diff --git a/theme/jw_player.theme.inc b/theme/jw_player.theme.inc index 5b1a684..0219842 100644 --- a/theme/jw_player.theme.inc +++ b/theme/jw_player.theme.inc @@ -8,17 +8,24 @@ */ /** - * Output the player renderable as a string. + * Returns HTML for JW Players. + * + * @param $variables + * An associative array containing: + * - element: A render element representing the player. + * + * @ingroup themeable */ function theme_jw_player_player($variables) { $element = $variables['element']; $attributes = $element['#attributes']; $attributes['id'] = $element['#html_id']; - $attributes['class'][] = 'jwplayer-video'; - $output = '