Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

MDL-26697 multiple media filtering fixes and improvements

Bug fixes:
    * fixed broken flash resizing via URL
    * upgraded Flowplayer
    * fixed invalid context in format_text()
    * all media related CSS moved from themes to filter and resources
    * fixed automatic pdf resizing in resources

Changes:
    * reworked filter_mediaplugin system settings - grouped by player type instead of individual extensions, added more information
    * improved regex url matching
    * removed old unused players, Eolas fix and UFO embedding
    * image embedding moved to filter_urltolink
    * new Flowplayer embedding API
    * accessibility and compatibility tweaks in Flowplayer
    * SWF embedding now works only in trusted texts, it is now enabled by default (works everywhere if "Allow EMBED and OBJECT tags" enabled)
    * new default video width and height

New features:
    * automatic Flash video resizing using information from video metadata
    * Flash HD video support (*.f4v)
    * Flash video embedding with HTML5 fallback - compatible with iOS and other mobile devices
    * Vimeo embedding
    * no-cookie YouTube site supported
    * HTML 5 audio and video with multiple source URLs and QuickTime fallback
    * more video and audio extensions in filelib.php
    * MP3 player colours customisable via CSS in themes
    * nomediaplugin class in a tag prevents media embedding
  • Loading branch information...
commit fcd2cbaf1f99750ce1a44780ae1dad24f21078b7 1 parent c096042
@skodak skodak authored
Showing with 3,147 additions and 1,255 deletions.
  1. +47 −0 filter/mediaplugin/db/upgrade.php
  2. +0 −80 filter/mediaplugin/eolas_fix.js
  3. +707 −285 filter/mediaplugin/filter.php
  4. +38 −22 filter/mediaplugin/filtersettings.php
  5. BIN  filter/mediaplugin/flowplayer.audio.swf
  6. BIN  filter/mediaplugin/flowplayer.controls.swf
  7. +0 −4 filter/mediaplugin/flvplayer.README.txt
  8. BIN  filter/mediaplugin/flvplayer.fla.zip
  9. BIN  filter/mediaplugin/flvplayer.swf
  10. +24 −4 filter/mediaplugin/lang/en/filter_mediaplugin.php
  11. BIN  filter/mediaplugin/mp3player.fla.zip
  12. BIN  filter/mediaplugin/mp3player.swf
  13. +14 −0 filter/mediaplugin/styles.css
  14. +1 −1  filter/mediaplugin/version.php
  15. +26 −0 filter/urltolink/filter.php
  16. +5 −0 filter/urltolink/filtersettings.php
  17. +3 −1 filter/urltolink/lang/en/filter_urltolink.php
  18. +6 −0 lib/filelib.php
  19. +0 −24 lib/flowplayer.js
  20. +350 −0 lib/flowplayer/README.txt
  21. +1,577 −0 lib/flowplayer/flowplayer-3.2.6.js
  22. BIN  lib/flowplayer/flowplayer-3.2.7.swf
  23. BIN  lib/flowplayer/flowplayer.audio-3.2.2.swf
  24. BIN  lib/flowplayer/flowplayer.controls-3.2.5.swf
  25. +11 −0 lib/flowplayer/readme_moodle.txt
  26. +159 −72 lib/javascript-static.js
  27. +2 −18 lib/outputlib.php
  28. +3 −16 lib/outputrenderers.php
  29. +1 −0  lib/outputrequirementslib.php
  30. +79 −63 lib/resourcelib.php
  31. +5 −5 lib/thirdpartylibs.xml
  32. +0 −340 lib/ufo.js
  33. +4 −29 lib/weblib.php
  34. +3 −1 mod/lesson/locallib.php
  35. +2 −0  mod/lesson/styles.css
  36. +11 −9 mod/resource/locallib.php
  37. +1 −11 mod/resource/styles.css
  38. +1 −1  mod/url/lang/en/url.php
  39. +4 −2 mod/url/locallib.php
  40. +1 −0  mod/url/styles.css
  41. +0 −14 theme/anomaly/config.php
  42. +0 −8 theme/arialist/config.php
  43. +3 −8 theme/base/style/core.css
  44. +0 −13 theme/binarius/config.php
  45. +0 −12 theme/boxxie/config.php
  46. +17 −29 theme/brick/config.php
  47. +0 −13 theme/canvas/config.php
  48. +0 −14 theme/formal_white/config.php
  49. +0 −13 theme/formfactor/config.php
  50. +0 −12 theme/fusion/config.php
  51. +0 −12 theme/leatherbound/config.php
  52. +0 −12 theme/magazine/config.php
  53. +1 −14 theme/nimble/config.php
  54. +0 −12 theme/nonzero/config.php
  55. +15 −27 theme/overlay/config.php
  56. +21 −33 theme/serenity/config.php
  57. +0 −12 theme/sky_high/config.php
  58. +5 −9 theme/standard/style/core.css
View
47 filter/mediaplugin/db/upgrade.php
@@ -0,0 +1,47 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Manual authentication plugin upgrade code
+ *
+ * @package filter
+ * @subpackage mediaplugin
+ * @copyright 2011 Petr Skoda (http://skodak.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * @param int $oldversion the version we are upgrading from
+ * @return bool result
+ */
+function xmldb_filter_mediaplugin_upgrade($oldversion) {
+ global $CFG, $DB, $OUTPUT;
+
+ if ($oldversion < 2011030900) {
+ unset_config('filter_mediaplugin_enable_img'); // migrated to filter_urltolink
+ unset_config('filter_mediaplugin_enable_ram'); // --> rm
+ unset_config('filter_mediaplugin_enable_rpm'); // --> rm
+ unset_config('filter_mediaplugin_enable_ogg'); // --> html5audio
+ unset_config('filter_mediaplugin_enable_ogv'); // --> html5video
+ unset_config('filter_mediaplugin_enable_avi'); // --> wmp
+ unset_config('filter_mediaplugin_enable_wmv'); // --> wmp
+ unset_config('filter_mediaplugin_enable_mov'); // --> qt
+ unset_config('filter_mediaplugin_enable_mpg'); // --> qt
+ upgrade_plugin_savepoint(true, 2011030900, 'filter', 'mediaplugin');
+ }
+
+ return true;
+}
View
80 filter/mediaplugin/eolas_fix.js
@@ -1,80 +0,0 @@
-// Documentation & updates available at:
-// http://codecentre.eplica.is/js/eolasfix/test.htm
-
-(function( Eolas_Fixed,
- win, doc,
- getElementsByTagName,
- outerHTML,
- parentNode,
- tags,
- elmsToRemoveOnload,
- x,
- is_ie,
- y,z,elm,childNode,HTML,dummy,eolasfix)
-{
- // run only once!
- if (win[Eolas_Fixed]) return;
- win[Eolas_Fixed] = 1;
-
- eolasfix = function ()
- {
- // for each tag name specified in Array t
- while (tags[++x])
- {
- // find all elements of that type in the document
- // loop through the elements
- y = 0;
- while (elm = doc[getElementsByTagName](tags[x])[y++])
- {
- if (is_ie)
- {
- HTML = '>';
- z = 0;
- // <param> elements don't show up in innerHTML IE
- // so we need to collect their outerHTML.
- while (childNode = elm.childNodes[z++])
- HTML += childNode[outerHTML];
-
- // create a 'dummy' element
- dummy = doc.createElement('i');
- // inject it next to `elm`,
- elm[parentNode].insertBefore(dummy, elm);
- // and turn it into an `elm` clone
- dummy[outerHTML] = elm[outerHTML].replace(/>/, HTML);
- // increment y to skip over it
- y++;
-
- // then hide the original elm
- elm.style.display = 'none';
- // and save it in 'The List of Elements to Remove Later'.
- elmsToRemoveOnload[elmsToRemoveOnload.length] = elm;
- }
- else
- {
- elm[outerHTML] = elm[outerHTML];
- }
- }
- }
- };
-
- // For IE run the fix straight away (because the defer="defer"
- // attribute has delayed execution until the DOM has loaded).
- // Then assign a window.onload event to purge the old elements.
- is_ie && !eolasfix() && win.attachEvent('onload', function(){
- x=0;
- while(elm = elmsToRemoveOnload[x++])
- elm[parentNode].removeChild(elm);
- });
- // For Opera set an `DOMContentLoaded` event to run the fix.
- win.opera && doc.addEventListener('DOMContentLoaded', eolasfix, 0);
-
-})( '__Eolas_Fixed',
- window, document,
- 'getElementsByTagName',
- 'outerHTML',
- 'parentNode',
- ['object','embed','applet'],
- [],
- -1 /*@cc_on,1 @*/
- );
-
View
992 filter/mediaplugin/filter.php
@@ -1,5 +1,4 @@
<?php
-
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -31,18 +30,46 @@
require_once($CFG->libdir.'/filelib.php');
+if (!defined('FILTER_MEDIAPLUGIN_VIDEO_WIDTH')) {
+ /**
+ * Default media width, some plugins may use automatic sizes or accept resize parameters.
+ * This can be defined in config.php.
+ */
+ define('FILTER_MEDIAPLUGIN_VIDEO_WIDTH', 400);
+}
+
+if (!defined('FILTER_MEDIAPLUGIN_VIDEO_HEIGHT')) {
+ /**
+ * Default video height, plugins that know aspect ration
+ * should calculate it themselves using the FILTER_MEDIAPLUGIN_VIDEO_HEIGHT
+ * This can be defined in config.php.
+ */
+ define('FILTER_MEDIAPLUGIN_VIDEO_HEIGHT', 300);
+}
+
+
+//TODO: we should use /u modifier in regex, unfortunately it may not work properly on some misconfigured servers, see lib/filter/urltolink/filter.php ...
+
+//TODO: we should migrate to proper config_plugin settings ...
+
+
+/**
+ * Automatic media embedding filter class.
+ *
+ * It is highly recommended to configure servers to be compatible with our slasharguments,
+ * otherwise the "?d=600x400" may not work.
+ *
+ * @package filter
+ * @subpackage mediaplugin
+ * @copyright 2004 onwards Martin Dougiamas {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
class filter_mediaplugin extends moodle_text_filter {
- private $eolas_fix_applied = false;
+
function filter($text, array $options = array()) {
- global $CFG, $PAGE;
- // You should never modify parameters passed to a method or function, it's BAD practice. Create a copy instead.
- // The reason is that you must always be able to refer to the original parameter that was passed.
- // For this reason, I changed $text = preg_replace(..,..,$text) into $newtext = preg.... (NICOLAS CONNAULT)
- // Thanks to Pablo Etcheverry for pointing this out! MDL-10177
-
- // We're using the UFO technique for flash to attain XHTML Strict 1.0
- // See: http://www.bobbyvandersluis.com/ufo/
- if (!is_string($text)) {
+ global $CFG;
+
+ if (!is_string($text) or empty($text)) {
// non string data can not be filtered anyway
return $text;
}
@@ -51,421 +78,816 @@ function filter($text, array $options = array()) {
// if not present nothing can match
return $text;
}
- $newtext = $text; // fullclone is slow and not needed here
- if (!empty($CFG->filter_mediaplugin_enable_mp3)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.mp3)"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_mp3_callback', $newtext);
+ $newtext = $text; // we need to return the original value if regex fails!
+
+ // YouTube and Vimeo are great because the files are not served by Moodle server
+
+ if (!empty($CFG->filter_mediaplugin_enable_youtube)) {
+ $search = '/<a\s[^>]*href="(https?:\/\/www\.youtube(-nocookie)?\.com)\/watch\?v=([a-z0-9\-_]+)[^>]*>([^>]*)<\/a>/is';
+ $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_callback', $newtext);
+
+ $search = '/<a\s[^>]*href="(https?:\/\/www\.youtube(-nocookie)?\.com)\/v\/([a-z0-9\-_]*)[^>]+>([^>]*)<\/a>/is';
+ $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_callback', $newtext);
+
+ $search = '/<a\s[^>]*href="(https?:\/\/www\.youtube(-nocookie)?\.com)\/view_play_list\?p=([a-z0-9\-_]+)[^>]*>([^>]*)<\/a>/is';
+ $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_playlist_callback', $newtext);
+
+ $search = '/<a\s[^>]*href="(https?:\/\/www\.youtube(-nocookie)?\.com)\/p\/([a-z0-9\-_]*)[^>]+>([^>]*)<\/a>/is';
+ $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_playlist_callback', $newtext);
+ }
+
+ if (!empty($CFG->filter_mediaplugin_enable_vimeo)) {
+ $search = '/<a\s[^>]*href="http:\/\/vimeo\.com\/([0-9]+)"[^>]*>([^>]*)<\/a>/is';
+ $newtext = preg_replace_callback($search, 'filter_mediaplugin_vimeo_callback', $newtext);
+ }
+
+
+ // HTML 5 audio and video tags are the future! If only if vendors decided to use just one audio and video format...
+
+ if (!empty($CFG->filter_mediaplugin_enable_html5audio)) {
+ $search = '/<a\s[^>]*href="([^"#\?]+\.(ogg|oga|aac|m4a)([#\?][^"]*)?)"[^>]*>([^>]*)<\/a>/is';
+ $newtext = preg_replace_callback($search, 'filter_mediaplugin_html5audio_callback', $newtext);
}
- if (!empty($CFG->filter_mediaplugin_enable_ogg)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.ogg)"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_ogg_callback', $newtext);
+ if (!empty($CFG->filter_mediaplugin_enable_html5video)) {
+ $search = '/<a\s[^>]*href="([^"#\?]+\.(m4v|webm|ogv|mp4)([#\?][^"]*)?)"[^>]*>([^>]*)<\/a>/is';
+ $newtext = preg_replace_callback($search, 'filter_mediaplugin_html5video_callback', $newtext);
}
- if (!empty($CFG->filter_mediaplugin_enable_ogv)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.ogv)"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_ogv_callback', $newtext);
+
+ // Flash stuff
+
+ if (!empty($CFG->filter_mediaplugin_enable_mp3)) {
+ $search = '/<a\s[^>]*href="([^"#\?]+\.mp3)"[^>]*>([^>]*)<\/a>/is';
+ $newtext = preg_replace_callback($search, 'filter_mediaplugin_mp3_callback', $newtext);
}
- if (!empty($CFG->filter_mediaplugin_enable_swf)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.swf)(\?d=([\d]{1,3}%?)x([\d]{1,3}%?))?"[^>]*>.*?<\/a>/is';
+ if ((!empty($options['noclean']) or !empty($CFG->allowobjectembed)) and !empty($CFG->filter_mediaplugin_enable_swf)) {
+ $search = '/<a\s[^>]*href="([^"#\?]+\.swf)([#\?]d=([\d]{1,4}%?)x([\d]{1,4}%?))?"[^>]*>([^>]*)<\/a>/is';
$newtext = preg_replace_callback($search, 'filter_mediaplugin_swf_callback', $newtext);
}
if (!empty($CFG->filter_mediaplugin_enable_flv)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.flv)(\?d=([\d]{1,3}%?)x([\d]{1,3}%?))?"[^>]*>.*?<\/a>/is';
+ $search = '/<a\s[^>]*href="([^"#\?]+\.(flv|f4v)([#\?][^"]*)?)"[^>]*>([^>]*)<\/a>/is';
$newtext = preg_replace_callback($search, 'filter_mediaplugin_flv_callback', $newtext);
}
- if (!empty($CFG->filter_mediaplugin_enable_mov)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.mov)(\?d=([\d]{1,3}%?)x([\d]{1,3}%?))?"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_qt_callback', $newtext);
-
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.mp4)(\?d=([\d]{1,4}%?)x([\d]{1,4}%?))?"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_qt_callback', $newtext);
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.m4v)(\?d=([\d]{1,4}%?)x([\d]{1,4}%?))?"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_qt_callback', $newtext);
+ // The rest of legacy formats - these should not be used if possible
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.m4a)(\?d=([\d]{1,4}%?)x([\d]{1,4}%?))?"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_qt_callback', $newtext);
- }
-
- if (!empty($CFG->filter_mediaplugin_enable_wmv)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.wmv)(\?d=([\d]{1,3}%?)x([\d]{1,3}%?))?"[^>]*>.*?<\/a>/is';
+ if (!empty($CFG->filter_mediaplugin_enable_wmp)) {
+ $search = '/<a\s[^>]*href="([^"#\?]+\.(wmv|avi))(\?d=([\d]{1,4}%?)x([\d]{1,4}%?))?"[^>]*>([^>]*)<\/a>/is';
$newtext = preg_replace_callback($search, 'filter_mediaplugin_wmp_callback', $newtext);
}
- if (!empty($CFG->filter_mediaplugin_enable_mpg)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.mpe?g)(\?d=([\d]{1,3}%?)x([\d]{1,3}%?))?"[^>]*>.*?<\/a>/is';
+ if (!empty($CFG->filter_mediaplugin_enable_qt)) {
+ // HTML5 filtering may steal mpeg 4 formats
+ $search = '/<a\s[^>]*href="([^"#\?]+\.(mpg|mpeg|mov|mp4|m4v|m4a))(\?d=([\d]{1,4}%?)x([\d]{1,4}%?))?"[^>]*>([^>]*)<\/a>/is';
$newtext = preg_replace_callback($search, 'filter_mediaplugin_qt_callback', $newtext);
}
- if (!empty($CFG->filter_mediaplugin_enable_avi)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.avi)(\?d=([\d]{1,3}%?)x([\d]{1,3}%?))?"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_wmp_callback', $newtext);
- }
+ if (!empty($CFG->filter_mediaplugin_enable_rm)) {
+ // hopefully nobody is using this any more!!
+ // rpm is redhat packaging format these days, it is better to prevent these in default installs
- if (!empty($CFG->filter_mediaplugin_enable_ram)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.ram)"[^>]*>.*?<\/a>/is';
+ $search = '/<a\s[^>]*href="([^"#\?]+\.(ra|ram|rm|rv))"[^>]*>([^>]*)<\/a>/is';
$newtext = preg_replace_callback($search, 'filter_mediaplugin_real_callback', $newtext);
}
- if (!empty($CFG->filter_mediaplugin_enable_rpm)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.rpm)"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_real_callback', $newtext);
- }
- if (!empty($CFG->filter_mediaplugin_enable_rm)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.rm)"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_real_callback', $newtext);
+ if (empty($newtext) or $newtext === $text) {
+ // error or not filtered
+ unset($newtext);
+ return $text;
}
- if (!empty($CFG->filter_mediaplugin_enable_youtube)) {
- $search = '/<a(\s+[^>]+?)?\s+href="(([^"]+youtube\.com)\/watch\?v=([A-Za-z0-9\-_]+))[^>]*>(.*?)<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_callback', $newtext);
- $search = '/<a(\s+[^>]+?)?\s+href="(([^"]+youtube\.com)\/v\/([A-Za-z0-9\-_]*))[^>]+>(.*?)<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_callback', $newtext);
+ return $newtext;
+ }
+}
- $search = '/<a(\s+[^>]+?)?\s+href="((([^"]+)youtube\.com)\/view_play_list\?p=([A-Za-z0-9\-_]+))[^>]*>(.*?)<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_youtube_playlist_callback', $newtext);
- }
- if (!empty($CFG->filter_mediaplugin_enable_img)) {
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.jpg)"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_img_callback', $newtext);
+///===========================
+/// utility functions
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.png)"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_img_callback', $newtext);
- $search = '/<a(\s+[^>]+?)?\s+href="([^"]+\.gif)"[^>]*>.*?<\/a>/is';
- $newtext = preg_replace_callback($search, 'filter_mediaplugin_img_callback', $newtext);
+/**
+ * Parse list of alternative URLs
+ * @param string $url urls separated with '#', size specified as ?d=640x480 or #d=640x480
+ * @return array (urls, width, height)
+ */
+function filter_mediaplugin_parse_alternatives($url, $defaultwidth = 0, $defaultheight = 0) {
+ $urls = explode('#', $url);
+ $width = $defaultwidth;
+ $height = $defaultheight;
+ $returnurls = array();
+
+ foreach ($urls as $url) {
+ $matches = null;
+
+ if (preg_match('/^d=([\d]{1,4}%?)x([\d]{1,4}%?)$/i', $url, $matches)) { // #d=640x480
+ $width = $matches[1];
+ $height = $matches[2];
+ continue;
}
-
- if (empty($newtext) or $newtext === $text) {
- // error or not filtered
- unset($newtext);
- return $text;
+ if (preg_match('/\?d=([\d]{1,4}%?)x([\d]{1,4}%?)$/i', $url, $matches)) { // old style file.ext?d=640x480
+ $width = $matches[1];
+ $height = $matches[2];
+ $url = str_replace($matches[0], '', $url);
}
- if (!$this->eolas_fix_applied) {
- $PAGE->requires->js('/filter/mediaplugin/eolas_fix.js');
- $this->eolas_fix_applied = true;
+ $url = str_replace('&amp;', '&', $url);
+ $url = clean_param($url, PARAM_URL);
+ if (empty($url)) {
+ continue;
}
- return $newtext;
+ $returnurls[] = $url;
+ }
+
+ return array($returnurls, $width, $height);
+}
+
+/**
+ * Should the current tag be ignored in this filter?
+ * @param string $tag
+ * @return bool
+ */
+function filter_mediaplugin_ignore($tag) {
+ if (preg_match('/class="[^"]*nomediaplugin/i', $tag)) {
+ return true;
+ } else {
+ false;
}
}
///===========================
/// callback filter functions
-function filter_mediaplugin_mp3_callback($link) {
- global $CFG, $OUTPUT, $PAGE;
- $c = $OUTPUT->filter_mediaplugin_colors(); // You can set this up in your theme/xxx/config.php
+/**
+ * Replace audio links with audio tag.
+ *
+ * @param array $link
+ * @return string
+ */
+function filter_mediaplugin_html5audio_callback(array $link) {
+ global $CFG;
- static $count = 0;
- $count++;
- $id = 'filter_mp3_'.time().$count; //we need something unique because it might be stored in text cache
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
+ $info = trim($link[4]);
+ if (empty($info) or strpos($info, 'http') === 0) {
+ $info = get_string('fallbackaudio', 'filter_mediaplugin');
+ }
+
+ list($urls, $ignorewidth, $ignoredheight) = filter_mediaplugin_parse_alternatives($link[1]);
- $url = addslashes_js($link[2]);
+ $fallbackurl = null;
+ $fallbackmime = null;
+ $sources = array();
+ $fallbacklink = null;
- $playerpath = $CFG->wwwroot.'/filter/mediaplugin/mp3player.swf';
- $audioplayerpath = $CFG->wwwroot .'/filter/mediaplugin/flowplayer.audio.swf';
- $colors = explode('&', $c);
- $playercolors = array();
- foreach ($colors as $color) {
- $color = explode('=', $color);
- $playercolors[$color[0]] = $color[1];
+ foreach ($urls as $url) {
+ $mimetype = mimeinfo('type', $url);
+ if (strpos($mimetype, 'audio/') !== 0) {
+ continue;
+ }
+ $sources[] = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype));
+
+ if ($fallbacklink === null) {
+ $fallbacklink = html_writer::link($url.'#', $info); // the extra '#' prevents linking in mp3 filter bellow
+ }
+ if ($fallbackurl === null) {
+ if ($mimetype === 'audio/mp3' or $mimetype === 'audio/aac') {
+ $fallbackurl = str_replace('&', '&amp;', $url);
+ $fallbackmime = $mimetype;
+ }
+ }
+ }
+ if (!$sources) {
+ return $link[0];
}
- $output = <<<OET
- <span class="mediaplugin mediaplugin_mp3" id="$id"></span>
- <noscript><div>
- <object width="100" height="15" id="nonjsmp3plugin" name="undefined" data="$playerpath" type="application/x-shockwave-flash">
- <param name="movie" value="$playerpath" />
- <param name="allowfullscreen" value="false" />
- <param name="allowscriptaccess" value="always" />
- <param name="flashvars" value='config={"plugins": {"controls": {
- "fullscreen": false,
- "height": 15,
- "autoHide": false,
- "all": false,
- "play": true,
- "pause": true,
- "scrubber": true
- },
- "audio": {"url": "$audioplayerpath"}
- },
- "clip":{"url":"$url",
- "autoPlay": false},
- "content":{"url":"$playerpath"}}}' />
- </object>
- </div></noscript>
+ if ($fallbackmime !== null) {
+ // fallback to quicktime
+ $fallback = <<<OET
+<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab" width="200" height="20">
+ <param name="pluginspage" value="http://www.apple.com/quicktime/download/" />
+ <param name="src" value="$fallbackurl" />
+ <param name="controller" value="true" />
+ <param name="loop" value="false" />
+ <param name="autoplay" value="false" />
+ <param name="autostart" value="false" />
+ <param name="scale" value="aspect" />
+ $fallbacklink
+<!--[if !IE]>-->
+ <object data="$fallbackurl" type="$fallbackmime" width="200" height="20">
+ <param name="src" value="$fallbackurl" />
+ <param name="pluginurl" value="http://www.apple.com/quicktime/download/" />
+ <param name="controller" value="true" />
+ <param name="loop" value="false" />
+ <param name="autoplay" value="false" />
+ <param name="autostart" value="false" />
+ <param name="scale" value="aspect" />
+ $fallbacklink
+ </object>
+<!--<![endif]-->
+</object>
OET;
+ } else {
+ $fallback = $fallbacklink;
+ }
- $jsoutput = create_flowplayer($id, $url, 'mp3', $playercolors);
- $output .= $jsoutput;
+ $sources = implode("\n", $sources);
+ $title = s($info);
+
+ // audio players are supposed to be inline elements
+ $output = <<<OET
+<audio controls="true" width="200" class="mediaplugin mediaplugin_html5audio" preload="no" title="$title">
+$sources
+$fallback
+</audio>
+OET;
return $output;
}
-function filter_mediaplugin_ogg_callback($link) {
- global $CFG, $OUTPUT, $PAGE;
+/**
+ * Replace ogg video links with video tag.
+ *
+ * Please note this is not going to work in all browsers,
+ * it is also not xhtml strict.
+ *
+ * @param array $link
+ * @return string
+ */
+function filter_mediaplugin_html5video_callback(array $link) {
- static $count = 0;
- $count++;
- $id = 'filter_ogg_'.time().$count; //we need something unique because it might be stored in text cache
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
+ $info = trim($link[4]);
+ if (empty($info) or strpos($info, 'http') === 0) {
+ $info = get_string('fallbackvideo', 'filter_mediaplugin');
+ }
+
+ list($urls, $width, $height) = filter_mediaplugin_parse_alternatives($link[1], FILTER_MEDIAPLUGIN_VIDEO_WIDTH, 0);
+
+ $fallbackurl = null;
+ $fallbackmime = null;
+ $sources = array();
+ $fallbacklink = null;
+
+ foreach ($urls as $url) {
+ $mimetype = mimeinfo('type', $url);
+ if (strpos($mimetype, 'video/') !== 0) {
+ continue;
+ }
+ $source = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype));
+ if ($mimetype === 'video/mp4') {
+ // better add m4v as first source, it might be a bit more compatible with problematic browsers
+ array_unshift($sources, $source);
+ } else {
+ $sources[] = $source;
+ }
+
+ if ($fallbacklink === null) {
+ $fallbacklink = html_writer::link($url.'#', $info); // the extra '#' prevents linking in mp3 filter bellow
+ }
+ if ($fallbackurl === null) {
+ if ($mimetype === 'video/mp4') {
+ $fallbackurl = str_replace('&', '&amp;', $url);
+ $fallbackmime = $mimetype;
+ }
+ }
+ }
+ if (!$sources) {
+ return $link[0];
+ }
+
+ if ($fallbackmime !== null) {
+ $qtheight = ($height == 0) ? FILTER_MEDIAPLUGIN_VIDEO_HEIGHT : ($height + 15);
+ // fallback to quicktime
+ $fallback = <<<OET
+<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab" width="$width" height="$qtheight">
+ <param name="pluginspage" value="http://www.apple.com/quicktime/download/" />
+ <param name="src" value="$fallbackurl" />
+ <param name="controller" value="true" />
+ <param name="loop" value="false" />
+ <param name="autoplay" value="false" />
+ <param name="autostart" value="false" />
+ <param name="scale" value="aspect" />
+ $fallbacklink
+<!--[if !IE]>-->
+ <object data="$fallbackurl" type="$fallbackmime" width="$width" height="$qtheight">
+ <param name="src" value="$fallbackurl" />
+ <param name="pluginurl" value="http://www.apple.com/quicktime/download/" />
+ <param name="controller" value="true" />
+ <param name="loop" value="false" />
+ <param name="autoplay" value="false" />
+ <param name="autostart" value="false" />
+ <param name="scale" value="aspect" />
+ $fallbacklink
+ </object>
+<!--<![endif]-->
+</object>
+OET;
+ } else {
+ $fallback = $fallbacklink;
+ }
+
+ $sources = implode("\n", $sources);
+ $title = s($info);
+
+ if (empty($height)) {
+ // automatic height
+ $size = "width=\"$width\"";
+ } else {
+ $size = "width=\"$width\" height=\"$height\"";
+ }
- $url = $link[2];
- $printlink = html_writer::link($url, get_string('oggaudio', 'filter_mediaplugin'));
- $unsupportedplugins = get_string('unsupportedplugins', 'filter_mediaplugin', $printlink);
$output = <<<OET
- <audio id="$id" src="$url" controls="true" width="100">
- $unsupportedplugins
- </audio>
+<span class="mediaplugin mediaplugin_html5video">
+<video controls="true" $size preload="metadata" title="$title">
+$sources
+$fallback
+</video>
+</span>
OET;
return $output;
}
-function filter_mediaplugin_ogv_callback($link) {
- global $CFG, $OUTPUT, $PAGE;
-
+/**
+ * Replace mp3 links with small audio player.
+ *
+ * @param $link
+ * @return string
+ */
+function filter_mediaplugin_mp3_callback($link) {
static $count = 0;
+
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
$count++;
- $id = 'filter_ogv_'.time().$count; //we need something unique because it might be stored in text cache
+ $id = 'filter_mp3_'.time().'_'.$count; //we need something unique because it might be stored in text cache
- $url = $link[2];
- $printlink = html_writer::link($url, get_string('ogvvideo', 'filter_mediaplugin'));
- $unsupportedplugins = get_string('unsupportedplugins', 'filter_mediaplugin', $printlink);
- $output = <<<OET
- <video id="$id" src="$url" controls="true" width="600" >
- $unsupportedplugins
- </video>
-OET;
+ $url = $link[1];
+ $rawurl = str_replace('&amp;', '&', $url);
+
+ $info = trim($link[2]);
+ if (empty($info) or strpos($info, 'http') === 0) {
+ $info = get_string('mp3audio', 'filter_mediaplugin');
+
+ }
+ $printlink = html_writer::link($rawurl, $info, array('class'=>'mediafallbacklink'));
+
+ //note: when flash or javascript not available only the $printlink is displayed,
+ // audio players are supposed to be inline elements
+
+ $output = html_writer::tag('span', $printlink, array('id'=>$id, 'class'=>'mediaplugin mediaplugin_mp3'));
+ $output .= html_writer::script(js_writer::function_call('M.util.add_audio_player', array($id, $rawurl, true))); // we can not use standard JS init because this may be cached
return $output;
}
+/**
+ * Replace swf links with embedded flash objects.
+ *
+ * Please note this is not a secure and is recommended to be disabled on production systems.
+ *
+ * @deprecated
+ * @param $link
+ * @return string
+ */
function filter_mediaplugin_swf_callback($link) {
- global $PAGE;
- static $count = 0;
- $count++;
- $id = 'filter_swf_'.time().$count; //we need something unique because it might be stored in text cache
- $width = empty($link[4]) ? '400' : $link[4];
- $height = empty($link[5]) ? '300' : $link[5];
- $url = $link[2];
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
+ $width = empty($link[3]) ? FILTER_MEDIAPLUGIN_VIDEO_WIDTH : $link[3];
+ $height = empty($link[4]) ? FILTER_MEDIAPLUGIN_VIDEO_HEIGHT : $link[4];
- $args = Array();
- $args['movie'] = $url;
- $args['width'] = $width;
- $args['height'] = $height;
- $args['majorversion'] = 6;
- $args['build'] = 40;
- $args['allowscriptaccess'] = 'never';
- $args['quality'] = 'high';
+ $url = $link[1];
+ $rawurl = str_replace('&amp;', '&', $url);
- $jsoutput = create_ufo_inline($id, $args);
+ $info = trim($link[5]);
+ if (empty($info) or strpos($info, 'http') === 0) {
+ $info = get_string('flashanimation', 'filter_mediaplugin');
+
+ }
+ $printlink = html_writer::link($rawurl, $info, array('class'=>'mediafallbacklink'));
- $output = $link[0].'<span class="mediaplugin mediaplugin_swf" id="'.$id.'">('.get_string('flashanimation', 'filter_mediaplugin').')</span>'.$jsoutput;
+ $output = <<<OET
+<span class="mediaplugin mediaplugin_swf">
+ <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="$width" height="$height">
+ <param name="movie" value="$url" />
+ <param name="autoplay" value="true" />
+ <param name="loop" value="true" />
+ <param name="controller" value="true" />
+ <param name="scale" value="aspect" />
+ <param name="base" value="." />
+ <param name="allowscriptaccess" value="never" />
+<!--[if !IE]>-->
+ <object type="application/x-shockwave-flash" data="$url" width="$width" height="$height">
+ <param name="controller" value="true" />
+ <param name="autoplay" value="true" />
+ <param name="loop" value="true" />
+ <param name="scale" value="aspect" />
+ <param name="base" value="." />
+ <param name="allowscriptaccess" value="never" />
+<!--<![endif]-->
+$printlink
+<!--[if !IE]>-->
+ </object>
+<!--<![endif]-->
+ </object>
+</span>
+OET;
return $output;
+
}
+/**
+ * Replace flv links with flow player.
+ *
+ * @param $link
+ * @return string
+ */
function filter_mediaplugin_flv_callback($link) {
- global $CFG, $PAGE;
-
static $count = 0;
+
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
$count++;
- $id = 'filter_flv_'.time().$count; //we need something unique because it might be stored in text cache
-
- // note: in 1.9.x this used to be 480x360
- $width = empty($link[4]) ? '800' : $link[4];
- $height = empty($link[5]) ? '600' : $link[5];
- $url = addslashes_js($link[2]);
-
- $playerpath = $CFG->wwwroot.'/filter/mediaplugin/flvplayer.swf';
-
- $output = <<<EOT
- <span class="mediaplugin mediaplugin_flv" id="$id"></span>
- <noscript><div>
- <object width="$width" height="$height" id="undefined" name="undefined" data="$playerpath" type="application/x-shockwave-flash">
- <param name="movie" value="$playerpath" />
- <param name="allowfullscreen" value="true" />
- <param name="allowscriptaccess" value="always" />
- <param name="flashvars" value='config={"clip":{"url":"$url",
- "autoPlay": false},
- "content":{"url":"$playerpath"}}}' />
- </object>
- </div></noscript>
-EOT;
+ $id = 'filter_flv_'.time().'_'.$count; //we need something unique because it might be stored in text cache
+
+ list($urls, $width, $height) = filter_mediaplugin_parse_alternatives($link[1], 0, 0);
+
+ $autosize = false;
+ if (!$width and !$height) {
+ $width = FILTER_MEDIAPLUGIN_VIDEO_WIDTH;
+ $height = FILTER_MEDIAPLUGIN_VIDEO_HEIGHT;
+ $autosize = true;
+ }
+
+ $flashurl = null;
+ $sources = array();
+
+ foreach ($urls as $url) {
+ $mimetype = mimeinfo('type', $url);
+ if (strpos($mimetype, 'video/') !== 0) {
+ continue;
+ }
+ $source = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype));
+ if ($mimetype === 'video/mp4') {
+ // better add m4v as first source, it might be a bit more compatible with problematic browsers
+ array_unshift($sources, $source);
+ } else {
+ $sources[] = $source;
+ }
+
+ if ($flashurl === null) {
+ $flashurl = str_replace('&', '&amp;', $url);
+ }
+ }
+ if (!$sources) {
+ return $link[0];
+ }
+
+ $info = trim($link[4]);
+ if (empty($info) or strpos($info, 'http') === 0) {
+ $info = get_string('fallbackvideo', 'filter_mediaplugin');
+ }
+ $printlink = html_writer::link($flashurl.'#', $info, array('class'=>'mediafallbacklink')); // the '#' prevents the QT filter
+
+ $title = s($info);
+
+ if (count($sources) > 1) {
+ $sources = implode("\n", $sources);
+
+ // html 5 fallback
+ $printlink = <<<OET
+<video controls="true" width="$width" height="$height" preload="metadata" title="$title">
+$sources
+$printlink
+</video>
+<noscript><br />
+$printlink
+</noscript>
+OET;
+ }
+
+ // note: no need to print "this is flv link" because it is printed automatically if JS or Flash not available
+
+ $output = html_writer::tag('span', $printlink, array('id'=>$id, 'class'=>'mediaplugin mediaplugin_flv'));
+ $output .= html_writer::script(js_writer::function_call('M.util.add_video_player', array($id, $flashurl, $width, $height, $autosize))); // we can not use standard JS init because this may be cached
- $jsoutput = create_flowplayer($id, $url, 'flv');
- $output .= $jsoutput;
return $output;
}
-function filter_mediaplugin_real_callback($link, $autostart=false) {
- $url = $link[2];
- $jsurl = addslashes_js($link[2]);
- $mimetype = mimeinfo('type', $url);
- $autostart = $autostart ? 'true' : 'false';
-
-// embed kept for now see MDL-8674
- return $link[0].
-'<span class="mediaplugin mediaplugin_real">
-<script type="text/javascript">
-//<![CDATA[
-document.write(\'<object classid="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA" width="240" height="180">\\
- <param name="src" value="'.$jsurl.'" />\\
- <param name="autostart" value="'.$autostart.'" />\\
- <param name="controls" value="imagewindow" />\\
- <param name="console" value="video" />\\
- <param name="loop" value="true" />\\
- <embed src="'.$jsurl.'" width="240" height="180" loop="true" type="'.$mimetype.'" controls="imagewindow" console="video" autostart="'.$autostart.'" />\\
- </object><br />\\
- <object classid="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA" width="240" height="30">\\
- <param name="src" value="'.$jsurl.'" />\\
- <param name="autostart" value="'.$autostart.'" />\\
- <param name="controls" value="ControlPanel" />\\
- <param name="console" value="video" />\\
- <embed src="'.$jsurl.'" width="240" height="30" controls="ControlPanel" type="'.$mimetype.'" console="video" autostart="'.$autostart.'" />\\
- </object>\');
-//]]>
-</script></span>';
+/**
+ * Replace real media links with real player.
+ *
+ * Note: hopefully nobody is using this obsolete format any more.
+ *
+ * @deprectated
+ * @param $link
+ * @return string
+ */
+function filter_mediaplugin_real_callback($link) {
+
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
+ $url = $link[1];
+ $rawurl = str_replace('&amp;', '&', $url);
+
+ //Note: the size is hardcoded intentionally because this does not work anyway!
+
+ $width = FILTER_MEDIAPLUGIN_VIDEO_WIDTH;
+ $height = FILTER_MEDIAPLUGIN_VIDEO_HEIGHT;
+
+ $info = trim($link[3]);
+ if (empty($info) or strpos($info, 'http') === 0) {
+ $info = get_string('fallbackvideo', 'filter_mediaplugin');
+ }
+ $printlink = html_writer::link($rawurl, $info, array('class'=>'mediafallbacklink'));
+
+ return <<<OET
+<span class="mediaplugin mediaplugin_real">
+ $printlink <br />
+ <object title="$info" classid="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA" data="$url" width="$width" height="$height"">
+ <param name="src" value="$url" />
+ <param name="controls" value="All" />
+<!--[if !IE]>-->
+ <object title="$info" type="audio/x-pn-realaudio-plugin" data="$url" width="$width" height="$height">
+ <param name="src" value="$url" />
+ <param name="controls" value="All" />
+<!--<![endif]-->
+<!--[if !IE]>-->
+ </object>
+<!--<![endif]-->
+ </object>
+</span>
+OET;
}
/**
- * Change links to Youtube into embedded Youtube videos
+ * Change links to YouTube into embedded YouTube videos
+ *
+ * Note: resizing via url is not supported, user can click the fullscreen button instead
+ *
+ * @param $link
+ * @return string
*/
-function filter_mediaplugin_youtube_callback($link, $autostart=false) {
- $site = s($link[3]);
- $param = $link[4]; // video id
- $info = s(strip_tags($link[5]));
-
-
- return '<object title="'.$info.'"
- class="mediaplugin mediaplugin_youtube" type="application/x-shockwave-flash"
- data="'.$site.'/v/'.$param.'&amp;fs=1&amp;rel=0" width="400" height="320">'.
- '<param name="movie" value="'.$site.'/v/'.$param.'&amp;fs=1&amp;rel=0" />'.
- '<param name="FlashVars" value="playerMode=embedded" />'.
- '<param name="wmode" value="transparent" />'.
- '<param name="allowFullScreen" value="true" />'.
- '</object>';
+function filter_mediaplugin_youtube_callback($link) {
+ global $CFG;
+
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
+ $site = $link[1];
+ $videoid = $link[3];
+
+ $info = trim($link[4]);
+ if (empty($info) or strpos($info, 'http') === 0) {
+ $info = get_string('siteyoutube', 'filter_mediaplugin');
+ }
+ $info = s($info);
+
+ $width = FILTER_MEDIAPLUGIN_VIDEO_WIDTH;
+ $height = FILTER_MEDIAPLUGIN_VIDEO_HEIGHT;
+
+ if (false and empty($CFG->xmlstrictheaders)) {
+ // TODO: remove this once iframe playback starts to work properly in iPads
+ return <<<OET
+<iframe title="$info" width="$width" height="$height" src="$site/embed/$videoid?rel=0" frameborder="0" allowfullscreen></iframe>
+OET;
+ }
+
+ //NOTE: we can not use any link fallback because it breaks built-in player on iOS devices
+
+ $output = <<<OET
+<span class="mediaplugin mediaplugin_youtube">
+<object title="$info" type="application/x-shockwave-flash" data="$site/v/$videoid&amp;fs=1&amp;rel=0" width="$width" height="$height">
+ <param name="movie" value="$site/v/$videoid&amp;fs=1&amp;rel=0" />
+ <param name="FlashVars" value="playerMode=embedded" />
+ <param name="allowFullScreen" value="true" />
+</object>
+</span>
+OET;
+
+ return $output;
}
/**
- * Change Youtube playlist into embedded Youtube playlist videos
+ * Change YouTube playlist into embedded YouTube playlist videos
+ *
+ * Note: resizing via url is not supported, user can click the fullscreen button instead
+ *
+ * @param $link
+ * @return string
*/
-function filter_mediaplugin_youtube_playlist_callback($link, $autostart=false) {
-
- $site = s($link[4]);
- $param = $link[5]; // playlist id
- $info = s(strip_tags($link[6]));
-
- return '<object title="'.$info.'"
- class="mediaplugin mediaplugin_youtube" type="application/x-shockwave-flash"
- data="'.$site.'youtube.com/p/'.$param.'&amp;fs=1&amp;rel=0" width="400" height="320">'.
- '<param name="movie" value="'.$site.'youtube.com/p/'.$param.'&amp;fs=1&amp;rel=0" />'.
- '<param name="FlashVars" value="playerMode=embedded" />'.
- '<param name="wmode" value="transparent" />'.
- '<param name="allowFullScreen" value="true" />'.
- '</object>';
+function filter_mediaplugin_youtube_playlist_callback($link) {
+ global $CFG;
+
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
+ $site = $link[1];
+ $playlist = $link[3];
+
+ $info = trim($link[4]);
+ if (empty($info) or strpos($info, 'http') === 0) {
+ $info = get_string('siteyoutube', 'filter_mediaplugin');
+ }
+ $printlink = html_writer::link("$site/view_play_list\?p=$playlist", $info, array('class'=>'mediafallbacklink'));
+ $info = s($info);
+
+ $width = FILTER_MEDIAPLUGIN_VIDEO_WIDTH;
+ $height = FILTER_MEDIAPLUGIN_VIDEO_HEIGHT;
+
+ // TODO: iframe HTML 5 video not implemented and object does work on iOS devices
+
+ $output = <<<OET
+<span class="mediaplugin mediaplugin_youtube">
+<object title="$info" type="application/x-shockwave-flash" data="$site/p/$playlist&amp;fs=1&amp;rel=0" width="$width" height="$height">
+ <param name="movie" value="$site/v/$playlist&amp;fs=1&amp;rel=0" />
+ <param name="FlashVars" value="playerMode=embedded" />
+ <param name="allowFullScreen" value="true" />
+$printlink</object>
+</span>
+OET;
+
+ return $output;
}
/**
- * Change links to images into embedded images
+ * Change links to Vimeo into embedded Vimeo videos
+ *
+ * @param $link
+ * @return string
*/
-function filter_mediaplugin_img_callback($link, $autostart=false) {
- $url = $link[2];
- $info = s(strip_tags($link[2]));
- return '<img class="mediaplugin mediaplugin_img" alt="" title="'.$info.'" src="'.$url.'" />';
+function filter_mediaplugin_vimeo_callback($link) {
+ global $CFG;
+
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
+ $videoid = $link[1];
+ $info = s(strip_tags($link[2]));
+
+ //Note: resizing via url is not supported, user can click the fullscreen button instead
+ // iframe embedding is not xhtml strict but it is the only option that seems to work on most devices
+
+ $width = FILTER_MEDIAPLUGIN_VIDEO_WIDTH;
+ $height = FILTER_MEDIAPLUGIN_VIDEO_HEIGHT;
+
+ $output = <<<OET
+<span class="mediaplugin mediaplugin_vimeo">
+<iframe title="$info" src="http://player.vimeo.com/video/$videoid" width="$width" height="$height" frameborder="0"></iframe>
+</span>
+OET;
+
+ return $output;
}
/**
* Embed video using window media player if available
+ *
+ * This does not work much outside of IE, hopefully not many ppl use it these days.
+ *
+ * @param $link
+ * @return string
*/
-function filter_mediaplugin_wmp_callback($link, $autostart=false) {
- $url = $link[2];
+function filter_mediaplugin_wmp_callback($link) {
+
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
+ $url = $link[1];
+ $rawurl = str_replace('&amp;', '&', $url);
+
+ $info = trim($link[6]);
+ if (empty($info) or strpos($info, 'http') === 0) {
+ $info = get_string('fallbackvideo', 'filter_mediaplugin');
+ }
+ $printlink = html_writer::link($rawurl, $info, array('class'=>'mediafallbacklink'));
+
if (empty($link[4]) or empty($link[5])) {
$mpsize = '';
- $size = 'width="300" height="260"';
+ $size = 'width="'.FILTER_MEDIAPLUGIN_VIDEO_WIDTH.'" height="'.(FILTER_MEDIAPLUGIN_VIDEO_HEIGHT+64).'"';
$autosize = 'true';
} else {
- $size = 'width="'.$link[4].'" height="'.$link[5].'"';
- $mpsize = $size;
+ $size = 'width="'.$link[4].'" height="'.($link[5] + 15).'"';
+ $mpsize = 'width="'.$link[4].'" height="'.($link[5] + 64).'"';
$autosize = 'false';
}
$mimetype = mimeinfo('type', $url);
- $autostart = $autostart ? 'true' : 'false';
-
- return $link[0].
-'<span class="mediaplugin mediaplugin_wmp">
-<object classid="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6" '.$mpsize.'
- standby="Loading Microsoft(R) Windows(R) Media Player components..."
- type="application/x-oleobject">
- <param name="Filename" value="'.$url.'" />
- <param name="src" value="'.$url.'" />
- <param name="url" value="'.$url.'" />
+
+
+
+ return <<<OET
+<span class="mediaplugin mediaplugin_wmp">
+$printlink <br />
+<object classid="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6" $mpsize standby="Loading Microsoft(R) Windows(R) Media Player components..." type="application/x-oleobject">
+ <param name="Filename" value="$url" />
+ <param name="src" value="$url" />
+ <param name="url" value="$url" />
<param name="ShowControls" value="true" />
<param name="AutoRewind" value="true" />
- <param name="AutoStart" value="'.$autostart.'" />
- <param name="Autosize" value="'.$autosize.'" />
+ <param name="AutoStart" value="false" />
+ <param name="Autosize" value="$autosize" />
<param name="EnableContextMenu" value="true" />
<param name="TransparentAtStart" value="false" />
<param name="AnimationAtStart" value="false" />
<param name="ShowGotoBar" value="false" />
<param name="EnableFullScreenControls" value="true" />
+ <param name="uimode" value="full" />
<!--[if !IE]>-->
- <object data="'.$url.'" type="'.$mimetype.'" '.$size.'>
- <param name="src" value="'.$url.'" />
+ <object data="$url" type="$mimetype" $size>
+ <param name="src" value="$url" />
<param name="controller" value="true" />
- <param name="autoplay" value="'.$autostart.'" />
- <param name="autostart" value="'.$autostart.'" />
+ <param name="autoplay" value="false" />
+ <param name="autostart" value="false" />
<param name="resize" value="scale" />
</object>
<!--<![endif]-->
-</object></span>';
+</object></span>
+OET;
}
-function filter_mediaplugin_qt_callback($link, $autostart=false) {
- $url = $link[2];
+/**
+ * Replace quicktime links with quicktime player.
+ *
+ * You need to install a quicktime player, it is not available for all browsers+OS combinations.
+ *
+ * @param $link
+ * @return string
+ */
+function filter_mediaplugin_qt_callback($link) {
+
+ if (filter_mediaplugin_ignore($link[0])) {
+ return $link[0];
+ }
+
+ $url = $link[1];
+ $rawurl = str_replace('&amp;', '&', $url);
+
+ $info = trim($link[6]);
+ if (empty($info) or strpos($info, 'http') === 0) {
+ $info = get_string('fallbackvideo', 'filter_mediaplugin');
+ }
+ $printlink = html_writer::link($rawurl, $info, array('class'=>'mediafallbacklink'));
+
if (empty($link[4]) or empty($link[5])) {
- $size = 'width="440" height="315"';
+ $size = 'width="'.FILTER_MEDIAPLUGIN_VIDEO_WIDTH.'" height="'.(FILTER_MEDIAPLUGIN_VIDEO_HEIGHT+15).'"';
} else {
- $size = 'width="'.$link[4].'" height="'.$link[5].'"';
+ $size = 'width="'.$link[4].'" height="'.($link[5]+15).'"';
}
$mimetype = mimeinfo('type', $url);
- $autostart = $autostart ? 'true' : 'false';
- return $link[0].
-'<span class="mediaplugin mediaplugin_qt">
-<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
- codebase="http://www.apple.com/qtactivex/qtplugin.cab" '.$size.'>
+ // this is the safest fallback for incomplete or missing browser support for this format
+ return <<<OET
+<span class="mediaplugin mediaplugin_qt">
+$printlink <br />
+<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab" $size>
<param name="pluginspage" value="http://www.apple.com/quicktime/download/" />
- <param name="src" value="'.$url.'" />
+ <param name="src" value="$url" />
<param name="controller" value="true" />
<param name="loop" value="true" />
- <param name="autoplay" value="'.$autostart.'" />
- <param name="autostart" value="'.$autostart.'" />
+ <param name="autoplay" value="false" />
+ <param name="autostart" value="false" />
<param name="scale" value="aspect" />
<!--[if !IE]>-->
- <object data="'.$url.'" type="'.$mimetype.'" '.$size.'>
- <param name="src" value="'.$url.'" />
+ <object data="$url" type="$mimetype" $size>
+ <param name="src" value="$url" />
<param name="pluginurl" value="http://www.apple.com/quicktime/download/" />
<param name="controller" value="true" />
<param name="loop" value="true" />
- <param name="autoplay" value="'.$autostart.'" />
- <param name="autostart" value="'.$autostart.'" />
+ <param name="autoplay" value="false" />
+ <param name="autostart" value="false" />
<param name="scale" value="aspect" />
</object>
<!--<![endif]-->
-</object></span>';
+</object></span>
+OET;
}
-
View
60 filter/mediaplugin/filtersettings.php
@@ -1,34 +1,50 @@
<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Mediaplugin filter settings
+ *
+ * @package filter
+ * @subpackage mediaplugin
+ * @copyright 2017 Petr Skoda (http://skodak.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_mp3', get_string('mediapluginmp3','admin'), '', 1));
+ // External services
+ $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_youtube', get_string('siteyoutube','filter_mediaplugin'), get_string('siteyoutube_help','filter_mediaplugin'), 1));
+ $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_vimeo', get_string('sitevimeo','filter_mediaplugin'), get_string('sitevimeo_help','filter_mediaplugin'), 0));
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_swf', get_string('mediapluginswf','admin'), get_string('mediapluginswfnote','admin'), 0)); // sorry, this is a big potential security hole (skodak)
+ // these require flash
+ $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_mp3', get_string('mp3audio','filter_mediaplugin'), get_string('mp3audio_help','filter_mediaplugin'), 1));
+ $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_flv', get_string('flashvideo','filter_mediaplugin'), get_string('flashvideo_help','filter_mediaplugin'), 1));
+ $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_swf', get_string('flashanimation','filter_mediaplugin'), get_string('flashanimation_help','filter_mediaplugin'), 1));
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_mov', get_string('mediapluginmov','admin'), '', 1));
+ // HTML 5 media
+ $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_html5audio', get_string('html5audio','filter_mediaplugin'), get_string('html5audio_help','filter_mediaplugin'), 0)); // disabled because mp3 is much better choice
+ $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_html5video', get_string('html5video','filter_mediaplugin'), get_string('html5video_help','filter_mediaplugin'), 0)); // disabled because flv with html5 fallback works better
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_wmv', get_string('mediapluginwmv','admin'), '', 1));
+ // legacy players
+ $settings->add(new admin_setting_heading('legacymediaformats', get_string('legacyheading', 'filter_mediaplugin'), get_string('legacyheading_help', 'filter_mediaplugin')));
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_mpg', get_string('mediapluginmpg','admin'), '', 1));
+ $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_qt', get_string('legacyquicktime','filter_mediaplugin'), get_string('legacyquicktime_help','filter_mediaplugin'), 1));
+ $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_wmp', get_string('legacywmp','filter_mediaplugin'), get_string('legacywmp_help','filter_mediaplugin'), 1));
+ $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_rm', get_string('legacyreal','filter_mediaplugin'), get_string('legacyreal_help','filter_mediaplugin'), 1));
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_avi', get_string('mediapluginavi','admin'), '', 1));
-
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_flv', get_string('mediapluginflv','admin'), '', 1));
-
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_ram', get_string('mediapluginram','admin'), '', 1));
-
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_rpm', get_string('mediapluginrpm','admin'), '', 1));
-
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_rm', get_string('mediapluginrm','admin'), '', 1));
-
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_youtube', get_string('mediapluginyoutube','admin'), '', 1));
-
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_ogg', get_string('mediapluginogg','admin'), '', 1));
-
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_ogv', get_string('mediapluginogv','admin'), '', 1));
-
- $settings->add(new admin_setting_configcheckbox('filter_mediaplugin_enable_img', get_string('mediapluginimg','admin'), '', 1));
}
View
BIN  filter/mediaplugin/flowplayer.audio.swf
Binary file not shown
View
BIN  filter/mediaplugin/flowplayer.controls.swf
Binary file not shown
View
4 filter/mediaplugin/flvplayer.README.txt
@@ -1,4 +0,0 @@
-The FLV player is by Jeroen Wijering - www.jeroenwijering.com
-and included in Moodle by his permission under the GNU GPL version 2 or later.
-
-Thanks, Jeroen!
View
BIN  filter/mediaplugin/flvplayer.fla.zip
Binary file not shown
View
BIN  filter/mediaplugin/flvplayer.swf
Binary file not shown
View
28 filter/mediaplugin/lang/en/filter_mediaplugin.php
@@ -1,5 +1,4 @@
<?php
-
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -23,10 +22,31 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['fallbackaudio'] = 'Audio link';
+$string['fallbackvideo'] = 'Video link';
$string['filtername'] = 'Multimedia plugins';
$string['flashanimation'] = 'Flash animation';
+$string['flashanimation_help'] = 'Files with extension *.swf. For security reasons this filter is used only in trusted texts.';
$string['flashvideo'] = 'Flash video';
+$string['flashvideo_help'] = 'Files with extension *.flv and *.f4v. Plays video clips using Flowplayer, requires Flash plugin and javascript. Uses HTML 5 video fallback if multiple sources psecified.';
+$string['html5audio'] = 'HTML 5 audio';
+$string['html5audio_help'] = 'Audio files with extension *.ogg, *.acc and others. It is compatible with latest web browsers only, unfortunately there is no format that is supported by all browsers.
+Workaround is to specify fallbacks separated with # (ex: http://example.org/audio.acc#http://example.org/audio.acc#http://example.org/audio.mp3#), QuickTime player is used as a fallback for old browsers, fallback can be any audio type.';
+$string['html5video'] = 'HTML 5 video';
+$string['html5video_help'] = 'Video files with extension *.webm, *.m4v, *.ogv, *.mp4 and others. It is compatible with latest web browsers only, unfortunately there is no format that is supported by all browsers.
+Workaround is to specify fallbacks sources separated with # (ex: http://example.org/video.m4v#http://example.org/video.acc#http://example.org/video.ogv#d=640x480), QuickTime player is used as a fallback for old browsers.';
$string['mp3audio'] = 'MP3 audio';
-$string['oggaudio'] = 'OGG audio';
-$string['ogvvideo'] = 'OGV video';
-$string['unsupportedplugins'] = '(The browser does not support this type of file. {$a})';
+$string['mp3audio_help'] = 'Files with extension *.mp3. Plays audio using Flowplayer, requires Flash plugin.';
+$string['legacyquicktime'] = 'QuickTime player';
+$string['legacyquicktime_help'] = 'Files with extension *.mov, *.mp4, *.m4a, *.mp4 and *.mpg. Requires QuickTime player or codecs.';
+$string['legacyreal'] = 'Real media player';
+$string['legacyreal_help'] = 'Files with extension *.rm, *.ra, *.ram, *.rp, *.rv. Requires RealPlayer.';
+$string['legacywmp'] = 'Windows media player';
+$string['legacywmp_help'] = 'Files with extension *.avi and *.wmv. Fully compatible with Internet Explorer in Windows, may be problematic in other browsers or operating systems.';
+$string['legacyheading'] = 'Legacy media players';
+$string['legacyheading_help'] = 'Following formats are not recommended for general usage, they are usually used in intranet installation with centrally managed clients.';
+$string['sitevimeo'] = 'Vimeo';
+$string['sitevimeo_help'] = 'Vimeo video sharing site.';
+$string['siteyoutube'] = 'YouTube';
+$string['siteyoutube_help'] = 'YouTube video sharing site, video and playlist links supported.';
+
View
BIN  filter/mediaplugin/mp3player.fla.zip
Binary file not shown
View
BIN  filter/mediaplugin/mp3player.swf
Binary file not shown
View
14 filter/mediaplugin/styles.css
@@ -0,0 +1,14 @@
+/**
+ * Filters
+ */
+.mediaplugin_html5video, .mediaplugin_swf, .mediaplugin_flv, .mediaplugin_real, .mediaplugin_youtube, .mediaplugin_vimeo, .mediaplugin_wmp, .mediaplugin_qt
+ {display:block;margin-top:5px;margin-bottom:5px;text-align: center;}
+.mediaplugin.mediaplugin_mp3 object {display:inline;height:15px;width:180px;margin-left:0.5em;}
+
+
+/*
+ * mp3 player colours -this read using JS and applied to swf audio flow player
+ * see http://flowplayer.org/documentation/skinning/controlbar.html?skin=default for more color properties,
+ * any property that ends with '...Color' is supported here.
+ */
+.mp3flowplayer_backgroundColor {color: #000000};
View
2  filter/mediaplugin/version.php
@@ -26,4 +26,4 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2010070900;
+$plugin->version = 2011030900;
View
26 filter/urltolink/filter.php
@@ -139,5 +139,31 @@ protected function convert_urls_into_links(&$text) {
$ignoretags = array_reverse($ignoretags); /// Reversed so "progressive" str_replace() will solve some nesting problems.
$text = str_replace(array_keys($ignoretags),$ignoretags,$text);
}
+
+ if ($this->get_global_config('embedimages')) {
+ // now try to inject the images, this code was originally in the mediapluing filter
+ // this may be useful only if somebody relies on the fact the links in FORMAT_MOODLE get converted
+ // to URLs which in turn change to real images
+ $search = '/<a href="([^"]+\.(jpg|png|gif))" class="_blanktarget">([^>]*)<\/a>/is';
+ $text = preg_replace_callback($search, 'filter_urltolink_img_callback', $text);
+ }
}
}
+
+
+/**
+ * Change links to images into embedded images.
+ *
+ * This plugin is intended for automatic conversion of image URLs when FORMAT_MOODLE used.
+ *
+ * @param $link
+ * @return string
+ */
+function filter_urltolink_img_callback($link) {
+ if ($link[1] !== $link[3]) {
+ // this is not a link created by this filter, because the url does not match the text
+ return $link[0];
+ }
+ return '<img class="filter_urltolink_image" alt="" src="'.$link[1].'" />';
+}
+
View
5 filter/urltolink/filtersettings.php
@@ -30,4 +30,9 @@
get_string('settingformats', 'filter_urltolink'),
get_string('settingformats_desc', 'filter_urltolink'),
array(FORMAT_MOODLE => 1), format_text_menu()));
+
+ $settings->add(new admin_setting_configcheckbox('filter_urltolink/embedimages',
+ get_string('embedimages', 'filter_urltolink'),
+ get_string('embedimages_desc', 'filter_urltolink'),
+ 1));
}
View
4 filter/urltolink/lang/en/filter_urltolink.php
@@ -26,6 +26,8 @@
defined('MOODLE_INTERNAL') || die();
-$string['filtername'] = 'Convert URLs into links';
+$string['embedimages'] = 'Embed images';
+$string['embedimages_desc'] = 'Replace image urls with images in selected text formats.';
+$string['filtername'] = 'Convert URLs into links and images';
$string['settingformats'] = 'Apply to formats';
$string['settingformats_desc'] = 'The filter will be applied only if the original text was inserted in one of the selected formats.';
View
6 lib/filelib.php
@@ -1120,6 +1120,7 @@ function get_mimetypes_array() {
static $mimearray = array (
'xxx' => array ('type'=>'document/unknown', 'icon'=>'unknown'),
'3gp' => array ('type'=>'video/quicktime', 'icon'=>'video'),
+ 'aac' => array ('type'=>'audio/aac', 'icon'=>'audio'),
'ai' => array ('type'=>'application/postscript', 'icon'=>'image'),
'aif' => array ('type'=>'audio/x-aiff', 'icon'=>'audio'),
'aiff' => array ('type'=>'audio/x-aiff', 'icon'=>'audio'),
@@ -1152,6 +1153,7 @@ function get_mimetypes_array() {
'eps' => array ('type'=>'application/postscript', 'icon'=>'pdf'),
'fdf' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
'flv' => array ('type'=>'video/x-flv', 'icon'=>'video'),
+ 'f4v' => array ('type'=>'video/mp4', 'icon'=>'video'),
'gif' => array ('type'=>'image/gif', 'icon'=>'image'),
'gtar' => array ('type'=>'application/x-gtar', 'icon'=>'zip'),
'tgz' => array ('type'=>'application/g-zip', 'icon'=>'zip'),
@@ -1207,6 +1209,7 @@ function get_mimetypes_array() {
'odf' => array ('type'=>'application/vnd.oasis.opendocument.formula', 'icon'=>'odf'),
'odb' => array ('type'=>'application/vnd.oasis.opendocument.database', 'icon'=>'odb'),
'odi' => array ('type'=>'application/vnd.oasis.opendocument.image', 'icon'=>'odi'),
+ 'oga' => array ('type'=>'audio/ogg', 'icon'=>'audio'),
'ogg' => array ('type'=>'audio/ogg', 'icon'=>'audio'),
'ogv' => array ('type'=>'video/ogg', 'icon'=>'video'),
@@ -1233,8 +1236,10 @@ function get_mimetypes_array() {
'ram' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio'),
'rhb' => array ('type'=>'text/xml', 'icon'=>'xml'),
'rm' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio'),
+ 'rmvb' => array ('type'=>'application/vnd.rn-realmedia-vbr', 'icon'=>'video'),
'rtf' => array ('type'=>'text/rtf', 'icon'=>'text'),
'rtx' => array ('type'=>'text/richtext', 'icon'=>'text'),
+ 'rv' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'video'),
'sh' => array ('type'=>'application/x-sh', 'icon'=>'text'),
'sit' => array ('type'=>'application/x-stuffit', 'icon'=>'zip'),
'smi' => array ('type'=>'application/smil', 'icon'=>'text'),
@@ -1266,6 +1271,7 @@ function get_mimetypes_array() {
'tsv' => array ('type'=>'text/tab-separated-values', 'icon'=>'text'),
'txt' => array ('type'=>'text/plain', 'icon'=>'text'),
'wav' => array ('type'=>'audio/wav', 'icon'=>'audio'),
+ 'webm' => array ('type'=>'video/webm', 'icon'=>'video'),
'wmv' => array ('type'=>'video/x-ms-wmv', 'icon'=>'avi'),
'asf' => array ('type'=>'video/x-ms-asf', 'icon'=>'avi'),
'xdp' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
View
24 lib/flowplayer.js
@@ -1,24 +0,0 @@
-/*
- * flowplayer.js 3.2.3. The Flowplayer API
- *
- * Copyright 2009 Flowplayer Oy
- *
- * This file is part of Flowplayer.
- *
- * Flowplayer is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Flowplayer is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Flowplayer. If not, see <http://www.gnu.org/licenses/>.
- *
- * Date: 2010-08-10 10:12:09 +0000 (Tue, 10 Aug 2010)
- * Revision: 539
- */
-(function(){function g(o){console.log("$f.fireEvent",[].slice.call(o))}function k(q){if(!q||typeof q!="object"){return q}var o=new q.constructor();for(var p in q){if(q.hasOwnProperty(p)){o[p]=k(q[p])}}return o}function m(t,q){if(!t){return}var o,p=0,r=t.length;if(r===undefined){for(o in t){if(q.call(t[o],o,t[o])===false){break}}}else{for(var s=t[0];p<r&&q.call(s,p,s)!==false;s=t[++p]){}}return t}function c(o){return document.getElementById(o)}function i(q,p,o){if(typeof p!="object"){return q}if(q&&p){m(p,function(r,s){if(!o||typeof s!="function"){q[r]=s}})}return q}function n(s){var q=s.indexOf(".");if(q!=-1){var p=s.slice(0,q)||"*";var o=s.slice(q+1,s.length);var r=[];m(document.getElementsByTagName(p),function(){if(this.className&&this.className.indexOf(o)!=-1){r.push(this)}});return r}}function f(o){o=o||window.event;if(o.preventDefault){o.stopPropagation();o.preventDefault()}else{o.returnValue=false;o.cancelBubble=true}return false}function j(q,o,p){q[o]=q[o]||[];q[o].push(p)}function e(){return"_"+(""+Math.random()).slice(2,10)}var h=function(t,r,s){var q=this,p={},u={};q.index=r;if(typeof t=="string"){t={url:t}}i(this,t,true);m(("Begin*,Start,Pause*,Resume*,Seek*,Stop*,Finish*,LastSecond,Update,BufferFull,BufferEmpty,BufferStop").split(","),function(){var v="on"+this;if(v.indexOf("*")!=-1){v=v.slice(0,v.length-1);var w="onBefore"+v.slice(2);q[w]=function(x){j(u,w,x);return q}}q[v]=function(x){j(u,v,x);return q};if(r==-1){if(q[w]){s[w]=q[w]}if(q[v]){s[v]=q[v]}}});i(this,{onCuepoint:function(x,w){if(arguments.length==1){p.embedded=[null,x];return q}if(typeof x=="number"){x=[x]}var v=e();p[v]=[x,w];if(s.isLoaded()){s._api().fp_addCuepoints(x,r,v)}return q},update:function(w){i(q,w);if(s.isLoaded()){s._api().fp_updateClip(w,r)}var v=s.getConfig();var x=(r==-1)?v.clip:v.playlist[r];i(x,w,true)},_fireEvent:function(v,y,w,A){if(v=="onLoad"){m(p,function(B,C){if(C[0]){s._api().fp_addCuepoints(C[0],r,B)}});return false}A=A||q;if(v=="onCuepoint"){var z=p[y];if(z){return z[1].call(s,A,w)}}if(y&&"onBeforeBegin,onMetaData,onStart,onUpdate,onResume".indexOf(v)!=-1){i(A,y);if(y.metaData){if(!A.duration){A.duration=y.metaData.duration}else{A.fullDuration=y.metaData.duration}}}var x=true;m(u[v],function(){x=this.call(s,A,y,w)});return x}});if(t.onCuepoint){var o=t.onCuepoint;q.onCuepoint.apply(q,typeof o=="function"?[o]:o);delete t.onCuepoint}m(t,function(v,w){if(typeof w=="function"){j(u,v,w);delete t[v]}});if(r==-1){s.onCuepoint=this.onCuepoint}};var l=function(p,r,q,t){var o=this,s={},u=false;if(t){i(s,t)}m(r,function(v,w){if(typeof w=="function"){s[v]=w;delete r[v]}});i(this,{animate:function(y,z,x){if(!y){return o}if(typeof z=="function"){x=z;z=500}if(typeof y=="string"){var w=y;y={};y[w]=z;z=500}if(x){var v=e();s[v]=x}if(z===undefined){z=500}r=q._api().fp_animate(p,y,z,v);return o},css:function(w,x){if(x!==undefined){var v={};v[w]=x;w=v}r=q._api().fp_css(p,w);i(o,r);return o},show:function(){this.display="block";q._api().fp_showPlugin(p);return o},hide:function(){this.display="none";q._api().fp_hidePlugin(p);return o},toggle:function(){this.display=q._api().fp_togglePlugin(p);return o},fadeTo:function(y,x,w){if(typeof x=="function"){w=x;x=500}if(w){var v=e();s[v]=w}this.display=q._api().fp_fadeTo(p,y,x,v);this.opacity=y;return o},fadeIn:function(w,v){return o.fadeTo(1,w,v)},fadeOut:function(w,v){return o.fadeTo(0,w,v)},getName:function(){return p},getPlayer:function(){return q},_fireEvent:function(w,v,x){if(w=="onUpdate"){var z=q._api().fp_getPlugin(p);if(!z){return}i(o,z);delete o.methods;if(!u){m(z.methods,function(){var B=""+this;o[B]=function(){var C=[].slice.call(arguments);var D=q._api().fp_invoke(p,B,C);return D==="undefined"||D===undefined?o:D}});u=true}}var A=s[w];if(A){var y=A.apply(o,v);if(w.slice(0,1)=="_"){delete s[w]}return y}return o}})};function b(q,F,t){var w=this,v=null,C=false,u,s,E=[],y={},x={},D,r,p,B,o,z;i(w,{id:function(){return D},isLoaded:function(){return(v!==null&&v.fp_play!==undefined&&!C)},getParent:function(){return q},hide:function(G){if(G){q.style.height="0px"}if(w.isLoaded()){v.style.height="0px"}return w},show:function(){q.style.height=z+"px";if(w.isLoaded()){v.style.height=o+"px"}return w},isHidden:function(){return w.isLoaded()&&parseInt(v.style.height,10)===0},load:function(I){if(!w.isLoaded()&&w._fireEvent("onBeforeLoad")!==false){var G=function(){u=q.innerHTML;if(u&&!flashembed.isSupported(F.version)){q.innerHTML=""}flashembed(q,F,{config:t});if(I){I.cached=true;j(x,"onLoad",I)}};var H=0;m(a,function(){this.unload(function(J){if(++H==a.length){G()}})})}return w},unload:function(I){if(this.isFullscreen()&&/WebKit/i.test(navigator.userAgent)){if(I){I(false)}return w}if(u.replace(/\s/g,"")!==""){if(w._fireEvent("onBeforeUnload")===false){if(I){I(false)}return w}C=true;try{if(v){v.fp_close();w._fireEvent("onUnload")}}catch(G){}var H=function(){v=null;q.innerHTML=u;C=false;if(I){I(true)}};setTimeout(H,50)}else{if(I){I(false)}}return w},getClip:function(G){if(G===undefined){G=B}return E[G]},getCommonClip:function(){return s},getPlaylist:function(){return E},getPlugin:function(G){var I=y[G];if(!I&&w.isLoaded()){var H=w._api().fp_getPlugin(G);if(H){I=new l(G,H,w);y[G]=I}}return I},getScreen:function(){return w.getPlugin("screen")},getControls:function(){return w.getPlugin("controls")._fireEvent("onUpdate")},getLogo:function(){try{return w.getPlugin("logo")._fireEvent("onUpdate")}catch(G){}},getPlay:function(){return w.getPlugin("play")._fireEvent("onUpdate")},getConfig:function(G){return G?k(t):t},getFlashParams:function(){return F},loadPlugin:function(J,I,L,K){if(typeof L=="function"){K=L;L={}}var H=K?e():"_";w._api().fp_loadPlugin(J,I,L,H);var G={};G[H]=K;var M=new l(J,null,w,G);y[J]=M;return M},getState:function(){return w.isLoaded()?v.fp_getState():-1},play:function(H,G){var I=function(){if(H!==undefined){w._api().fp_play(H,G)}else{w._api().fp_play()}};if(w.isLoaded()){I()}else{if(C){setTimeout(function(){w.play(H,G)},50)}else{w.load(function(){I()})}}return w},getVersion:function(){var H="flowplayer.js 3.2.3";if(w.isLoaded()){var G=v.fp_getVersion();G.push(H);return G}return H},_api:function(){if(!w.isLoaded()){throw"Flowplayer "+w.id()+" not loaded when calling an API method"}return v},setClip:function(G){w.setPlaylist([G]);return w},getIndex:function(){return p}});m(("Click*,Load*,Unload*,Keypress*,Volume*,Mute*,Unmute*,PlaylistReplace,ClipAdd,Fullscreen*,FullscreenExit,Error,MouseOver,MouseOut").split(","),function(){var G="on"+this;if(G.indexOf("*")!=-1){G=G.slice(0,G.length-1);var H="onBefore"+G.slice(2);w[H]=function(I){j(x,H,I);return w}}w[G]=function(I){j(x,G,I);return w}});m(("pause,resume,mute,unmute,stop,toggle,seek,getStatus,getVolume,setVolume,getTime,isPaused,isPlaying,startBuffering,stopBuffering,isFullscreen,toggleFullscreen,reset,close,setPlaylist,addClip,playFeed,setKeyboardShortcutsEnabled,isKeyboardShortcutsEnabled").split(","),function(){var G=this;w[G]=function(I,H){if(!w.isLoaded()){return w}var J=null;if(I!==undefined&&H!==undefined){J=v["fp_"+G](I,H)}else{J=(I===undefined)?v["fp_"+G]():v["fp_"+G](I)}return J==="undefined"||J===undefined?w:J}});w._fireEvent=function(P){if(typeof P=="string"){P=[P]}var Q=P[0],N=P[1],L=P[2],K=P[3],J=0;if(t.debug){g(P)}if(!w.isLoaded()&&Q=="onLoad"&&N=="player"){v=v||c(r);o=v.clientHeight;m(E,function(){this._fireEvent("onLoad")});m(y,function(R,S){S._fireEvent("onUpdate")});s._fireEvent("onLoad")}if(Q=="onLoad"&&N!="player"){return}if(Q=="onError"){if(typeof N=="string"||(typeof N=="number"&&typeof L=="number")){N=L;L=K}}if(Q=="onContextMenu"){m(t.contextMenu[N],function(R,S){S.call(w)});return}if(Q=="onPluginEvent"||Q=="onBeforePluginEvent"){var G=N.name||N;var H=y[G];if(H){H._fireEvent("onUpdate",N);return H._fireEvent(L,P.slice(3))}return}if(Q=="onPlaylistReplace"){E=[];var M=0;m(N,function(){E.push(new h(this,M++,w))})}if(Q=="onClipAdd"){if(N.isInStream){return}N=new h(N,L,w);E.splice(L,0,N);for(J=L+1;J<E.length;J++){E[J].index++}}var O=true;if(typeof N=="number"&&N<E.length){B=N;var I=E[N];if(I){O=I._fireEvent(Q,L,K)}if(!I||O!==false){O=s._fireEvent(Q,L,K,I)}}m(x[Q],function(){O=this.call(w,N,L);if(this.cached){x[Q].splice(J,1)}if(O===false){return false}J++});return O};function A(){if($f(q)){$f(q).getParent().innerHTML="";p=$f(q).getIndex();a[p]=w}else{a.push(w);p=a.length-1}z=parseInt(q.style.height,10)||q.clientHeight;D=q.id||"fp"+e();r=F.id||D+"_api";F.id=r;t.playerId=D;if(typeof t=="string"){t={clip:{url:t}}}if(typeof t.clip=="string"){t.clip={url:t.clip}}t.clip=t.clip||{};if(q.getAttribute("href",2)&&!t.clip.url){t.clip.url=q.getAttribute("href",2)}s=new h(t.clip,-1,w);t.playlist=t.playlist||[t.clip];var H=0;m(t.playlist,function(){var J=this;if(typeof J=="object"&&J.length){J={url:""+J}}m(t.clip,function(K,L){if(L!==undefined&&J[K]===undefined&&typeof L!="function"){J[K]=L}});t.playlist[H]=J;J=new h(J,H,w);E.push(J);H++});m(t,function(J,K){if(typeof K=="function"){if(s[J]){s[J](K)}else{j(x,J,K)}delete t[J]}});m(t.plugins,function(J,K){if(K){y[J]=new l(J,K,w)}});if(!t.plugins||t.plugins.controls===undefined){y.controls=new l("controls",null,w)}y.canvas=new l("canvas",null,w);u=q.innerHTML;function I(J){if(/iPad|iPhone/.test(navigator.userAgent)&&!/.flv$/i.test(E[0].url)&&w.ipad===undefined){return true}if(!w.isLoaded()&&w._fireEvent("onBeforeClick")!==false){w.load()}return f(J)}function G(){if(u.replace(/\s/g,"")!==""){if(q.addEventListener){q.addEventListener("click",I,false)}else{if(q.attachEvent){q.attachEvent("onclick",I)}}}else{if(q.addEventListener){q.addEventListener("click",f,false)}w.load()}}setTimeout(G,0)}if(typeof q=="string"){flashembed.domReady(function(){var G=c(q);if(!G){throw"Flowplayer cannot access element: "+q}else{q=G;A()}})}else{A()}}var a=[];function d(o){this.length=o.length;this.each=function(p){m(o,p)};this.size=function(){return o.length}}window.flowplayer=window.$f=function(){var p=null;var o=arguments[0];if(!arguments.length){m(a,function(){if(this.isLoaded()){p=this;return false}});return p||a[0]}if(arguments.length==1){if(typeof o=="number"){return a[o]}else{if(o=="*"){return new d(a)}m(a,function(){if(this.id()==o.id||this.id()==o||this.getParent()==o){p=this;return false}});return p}}if(arguments.length>1){var t=arguments[1],q=(arguments.length==3)?arguments[2]:{};if(typeof t=="string"){t={src:t}}t=i({bgcolor:"#000000",version:[9,0],expressInstall:"http://static.flowplayer.org/swf/expressinstall.swf",cachebusting:true},t);if(typeof o=="string"){if(o.indexOf(".")!=-1){var s=[];m(n(o),function(){s.push(new b(this,k(t),k(q)))});return new d(s)}else{var r=c(o);return new b(r!==null?r:o,t,q)}}else{if(o){return new b(o,t,q)}}}return null};i(window.$f,{fireEvent:function(){var o=[].slice.call(arguments);var q=$f(o[0]);return q?q._fireEvent(o.slice(1)):null},addPlugin:function(o,p){b.prototype[o]=p;return $f},each:m,extend:i});if(typeof jQuery=="function"){jQuery.fn.flowplayer=function(q,p){if(!arguments.length||typeof arguments[0]=="number"){var o=[];this.each(function(){var r=$f(this);if(r){o.push(r)}});return arguments.length?o[arguments[0]]:new d(o)}return this.each(function(){$f(this,k(q),p?k(p):{})})}}})();(function(){var h=document.all,j="http://www.adobe.com/go/getflashplayer",c=typeof jQuery=="function",e=/(\d+)[^\d]+(\d+)[^\d]*(\d*)/,b={width:"100%",height:"100%",id:"_"+(""+Math.random()).slice(9),allowfullscreen:true,allowscriptaccess:"always",quality:"high",version:[3,0],onFail:null,expressInstall:null,w3c:false,cachebusting:false};if(window.attachEvent){window.attachEvent("onbeforeunload",function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){}})}function i(m,l){if(l){for(var f in l){if(l.hasOwnProperty(f)){m[f]=l[f]}}}return m}function a(f,n){var m=[];for(var l in f){if(f.hasOwnProperty(l)){m[l]=n(f[l])}}return m}window.flashembed=function(f,m,l){if(typeof f=="string"){f=document.getElementById(f.replace("#",""))}if(!f){return}if(typeof m=="string"){m={src:m}}return new d(f,i(i({},b),m),l)};var g=i(window.flashembed,{conf:b,getVersion:function(){var f;try{f=navigator.plugins["Shockwave Flash"].description.slice(16)}catch(n){try{var l=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");f=l&&l.GetVariable("$version")}catch(m){}}f=e.exec(f);return f?[f[1],f[3]]:[0,0]},asString:function(l){if(l===null||l===undefined){return null}var f=typeof l;if(f=="object"&&l.push){f="array"}switch(f){case"string":l=l.replace(new RegExp('(["\\\\])',"g"),"\\$1");l=l.replace(/^\s?(\d+\.?\d+)%/,"$1pct");return'"'+l+'"';case"array":return"["+a(l,function(o){return g.asString(o)}).join(",")+"]";case"function":return'"function()"';case"object":var m=[];for(var n in l){if(l.hasOwnProperty(n)){m.push('"'+n+'":'+g.asString(l[n]))}}return"{"+m.join(",")+"}"}return String(l).replace(/\s/g," ").replace(/\'/g,'"')},getHTML:function(o,l){o=i({},o);var n='<object width="'+o.width+'" height="'+o.height+'" id="'+o.id+'" name="'+o.id+'"';if(o.cachebusting){o.src+=((o.src.indexOf("?")!=-1?"&":"?")+Math.random())}if(o.w3c||!h){n+=' data="'+o.src+'" type="application/x-shockwave-flash"'}else{n+=' classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'}n+=">";if(o.w3c||h){n+='<param name="movie" value="'+o.src+'" />'}o.width=o.height=o.id=o.w3c=o.src=null;o.onFail=o.version=o.expressInstall=null;for(var m in o){if(o[m]){n+='<param name="'+m+'" value="'+o[m]+'" />'}}var p="";if(l){for(var f in l){if(l[f]){var q=l[f];p+=f+"="+(/function|object/.test(typeof q)?g.asString(q):q)+"&"}}p=p.slice(0,-1);n+='<param name="flashvars" value=\''+p+"' />"}n+="</object>";return n},isSupported:function(f){return k[0]>f[0]||k[0]==f[0]&&k[1]>=f[1]}});var k=g.getVersion();function d(f,n,m){if(g.isSupported(n.version)){f.innerHTML=g.getHTML(n,m)}else{if(n.expressInstall&&g.isSupported([6,65])){f.innerHTML=g.getHTML(i(n,{src:n.expressInstall}),{MMredirectURL:location.href,MMplayerType:"PlugIn",MMdoctitle:document.title})}else{if(!f.innerHTML.replace(/\s/g,"")){f.innerHTML="<h2>Flash version "+n.version+" or greater is required</h2><h3>"+(k[0]>0?"Your version is "+k:"You have no flash plugin installed")+"</h3>"+(f.tagName=="A"?"<p>Click here to download latest version</p>":"<p>Download latest version from <a href='"+j+"'>here</a></p>");if(f.tagName=="A"){f.onclick=function(){location.href=j}}}if(n.onFail){var l=n.onFail.call(this);if(typeof l=="string"){f.innerHTML=l}}}}if(h){window[n.id]=document.getElementById(n.id)}i(this,{getRoot:function(){return f},getOptions:function(){return n},getConf:function(){return m},getApi:function(){return f.firstChild}})}if(c){jQuery.tools=jQuery.tools||{version:"3.2.3"};jQuery.tools.flashembed={conf:b};jQuery.fn.flashembed=function(l,f){return this.each(function(){$(this).data("flashembed",flashembed(this,l,f))})}}})();
View
350 lib/flowplayer/README.txt
@@ -0,0 +1,350 @@
+Version history:
+
+3.2.7
+-----
+
+- Loads the new controlbar plugin version 3.2.5. No other functional changes.
+
+3.2.6
+-----
+- linkUrl should now work better with popup blockers: http://code.google.com/p/flowplayer-core/issues/detail?id=31
+- new linkWindow value "_popup" opens the linked page in a popup browser window
+- added new onClipResized event
+- Added new onUnload event, can be only listened in Flash and not triggered to JS
+- API: Added new url property to plugin objects
+Fixes:
+- it was not possible to call play() in an onFinish listener
+- fix to preserve the infoObject for custom netStream and netConnection clients in cases where the infoObject is a
+ primitive object without properties
+- does not show the error dialog in the debugger player when showErrors: false
+- fixed to correctly handle xx.ca subdomains when validating the license key
+- a custom logo is now sized correctly according to the configured size
+- does not show the buffer animation any more when the player receives the onBufferEmpty message from the netStream.
+ The animation was unnecessarily shown in some situations.
+- fixed #155. added new urlEncoding property to Clip for url ncoding ut8 urls
+
+3.2.5
+-----
+- added new scaling option 'crop' that resizes to fill all available space, cropping on top/bottom or left/right
+- improvements to RSS file parsing
+- Now displays a hand cursor when a linkUrl is used in clips
+
+3.2.4
+-----
+- new flowplayer.js version, with Apple iDevice fixes
+
+3.2.3
+-----
+- a new 'type' clip property exposed to JS
+- changed the clip type property to better work as a read-write property. Now accepts 'video', 'audio',
+ 'image' and 'api' as configuration values.
+- moved parallel rtmp connection mechanism from the RTMP plugin to Core so other plugins can use it (ie: securestreaming)
+Fixes:
+- fixed #112, wrong URL computation when using clip with relative URL on a page with a / after a # in its url
+- fixed #111, wrong behavior of pre/post roll images with duration 0
+- fixed multiple license keys logic
+Fixes:
+- correct verification of license keys in *.ca domains
+- fix to make playback to always reach end of video
+- fixed resuming of live streams
+
+3.2.2
+-----
+Fixes:
+- Now recognizes following kind of urls as audio clips: 'mp3:audiostreamname' (ulrs with mp3 prefix and no extension)
+- Now ignores the duration from metadata if we already got one. Fix required for pseudostreaming
+- Fix to reuse buffered data when replaying a clip
+
+3.2.1
+---------
+- Support for RTMP redirects (tested with Wowza loadbalancing)
+- Fixed video size when no size info available in clip metadata
+
+Fixes:
+- Fix to correctly detect if the player SWF name contains a version number and if it does also use the version number
+when it automatically loads the controls plugin.
+
+3.2.0
+-----
+- canvas, controlbar and the content plugin backgound color and border color can be now given with rgb() and rgba() CSS style syntax
+- Added onMouseOver() and onMouseOut() listener registration methods to the Flowplayer API
+- enhancements to RSS playlist. Converted parsing to E4X, yahoo media and flowplayer namespace support.
+- added feature to obtain bitrate and dimension information to a new clip custom property "bitrates" for future support for bitrate choosing.
+- added getter for playerSwfName config
+- if clip.url has the string "mp3:" in it, the clip.type will report 'audio'
+- added setKeyboardShortcutsEnabled(), addKeyListener(), removeKeyListener() to FlowplayerBase
+Fixes:
+- onSeek() was not fired when seeking while paused and when using RTMP. An extra onStart was fired too.
+- fireErrorExternal() was not working properly with an error PlayerEvent
+- countPlugins() was throwing an error when a plugin was not found
+- external swf files were not scaled properly
+- the logo was unnecessary shown when going fullscreen if logo.displayTime was being used
+- added a loadPluginWithConfig method to FlowplayerBase, accessible from javascript. Fixed double onload callback call.
+- now handles cuepoint parameters injected using the Adobe Media Encoder
+- showPlugin was not working when config.play was null
+- handles 3-part duration values included in FLV metadata, like "500.123.123"
+- player wasn't always reaching end of video
+- fixed broken buffering: false
+- fixed event dispatching when embedding flowplayer without flowplayer.js (=without playlist config field)
+- fixed safari crashes when unloading player
+- fixed scrubber behaviour with a playlist containing 2 images (or swf) in a row
+- fixed errors in logs when using an RSS playlist
+- fixed OverlayPlayButton that was showing even if it shouldn't on some cases
+- fixed wrong behavior when onBeforeFinish was returning false within playlists
+- /!\ Don't use the fadeIn / fadeOut controlbar's API while using autoHide.
+- fixed play state button with images
+- fixed splash image flickering
+
+3.1.5
+-----
+Fixes:
+- The player went to a locked state when resuming playback after a period that was long enought to send the
+netConnection to an invalid state. Now when resuming playback on an invalid connection the clip starts again from
+the beginning. This is only when using RTMP connections and does not affect progressive download playback.
+- Custom netConnect and netStream events did not pass the info object to JS listeners
+
+3.1.4
+-----
+Fixes:
+- player did not initialize if the controlbar plugin was disabled and if the play button overlay was disabled with play: null
+- works properly without cachebusting on IE
+- RSS playlist parsing now respects the isDefault attribute used in mRSS media group items
+- Fixed passing of connection arguments
+
+3.1.3
+-----
+- enhancements to RSS playlist parsing: Now skips all media:content that have unsupported types. Now the type attribute
+of the media:content element is mandatory and has to be present in the RSS file
+- Possibility to pass a RSS file name with playFeed("playlist.rss") and setPlaylist("playlist.rss") calls.
+- changes to the ConnectionProvider and URLResolver APIs
+- Now automatically uses a plugin that is called 'rtmp' for all clips that have the rtmp-protocol in their URLs.
+- Added possibility to specify all clip properties in an RSS playlist
+
+Fixes:
+- the result of URL resolvers in now cached, and the resolvers will not be used again when a clip is replayed
+- some style properties like 'backgroundGradient' had no effect in config
+- video goes tiny on Firefox: http://flowplayer.org/forum/8/23226
+- RSS playlists: The 'type' attribute value 'audio/mp3' in the media:content element caused an error.
+- Dispatches onMetadata() if an URL resolver changes the clip URL (changes to a different file)
+- error codes and error message were not properly passed to onEvent JS listeners
+
+3.1.2
+-----
+- The domain of the logo url must the same domain from where the player SWF is loaded from.
+- Fullscreen can be toggled by doublclick on the video area.
+Fixes:
+- Player was not initialized correctly when instream playlists were used and the provider used in the instream clips was defined in the common clip.
+- A separator in the Context Menu made the callbacks in the following menu items out of order. Related forum post: http://flowplayer.org/forum/8/22541
+- the width and height settings of a logo were ignored if the logo was a sWF file
+- volume control and mute/unmute were not working after an instream clip had been played
+- now possible to use RTMP for mp3 files
+- Issue 12: cuepointMultiplier was undefined in the clip object set to JS event listeners
+- Issue 14: onBeforeStop was unnecessarily fired when calling setPlaylist() and the player was not playing,
+ additionally onStop was never fired even if onBeforeStop was
+- fixed screen vertical placement problems that reappeared with 3.1.1
+- The rotating animation now has the same size and position as it has after initialized
+
+3.1.1
+-----
+- External configuration files
+- Instream playback
+- Added toggleFullscreen() the API
+- Possibility to specify controls configuration in clips
+- Seek target position is now sent in the onBeforeSeek event
+Fixes:
+- The screen size was initially too small on Firefox (Mac)
+- Did not persist a zero volume value: http://www.flowplayer.org/forum/8/18413
+
+3.1.0
+-----
+New features:
+- clip's can have urlResolvers and connectionProviders
+- Added new configuration options 'connectionCallbacks' and 'streamCallbacks'. Both accept an Array of event names as a value.
+ When these events get fired on the connection or stream object, corresponding Clip events will be fired by the player.
+ This can be used for example when firing custom events from RTMP server apps
+- Added new clip event types: 'onConnectionEvent' and 'onStreamEvent' these get fired when the predefined events happen on the connection and stream objects.
+- Added Security.allowDomain() to allow loaded plugins to script the player
+- Added addClip(clip, index) to the API, index is optional
+- Possibility to view videos without metadata, using clip.metaData: false
+- Now the player's preloader uses the rotating animation instead of a percent text to indicate the progress
+ of loading the player SWF. You can disable the aninamtion by setting buffering: false
+- calling close() now does not send the onStop event
+- Clip's custom properties are now present in the root of the clip argument in all clip events that are sent to JS.
+
+Bug fixes:
+- The preloader sometimes failed to initialize the player
+- Allow seeking while in buffering state: http://flowplayer.org/forum/8/16505
+- Replay of a RTMP stream was failing after the connection had expired
+- Security error when clicking on the screen if there is an image in the playlist loaded from a foreign domain
+- loadPlugin() was not working
+- now fullscreen works with Flash versions older than 9.0.115, in versions that do not support hardware scaling
+- replaying a RTMP stream with an image in front of the stream in the playlist was not working (video stayed hidden). Happened
+ because the server does not send metadata if replaying the same stream.
+- the scrubber is disabled if the clip is not seekable in the first frame: http://flowplayer.org/forum/8/16526
+ By default if the clip has one of following extensions (the typical flash video extensions) it is seekable
+ in the first frame: 'f4b', 'f4p', 'f4v', 'flv'. Added new clip property seekableOnBegin that can be used to override the default.
+
+3.0.6
+-----
+- added possibility to associate a linkUrl and linkWindow to the canvas
+Fixes:
+- fix for entering fullscreen for Flash versions that don't support the hardware scaled fullscreen-mode
+- when showing images the duration tracking starts only after the image has been completely loaded: http://flowplayer.org/forum/2/15301
+- fix for verifying license keys for domains that have more than 4 labels in them
+- if plugin loading failis because of a IO error, the plugin will be discarded and the player initialization continues:
+
+3.0.4
+-----
+- The "play" pseudo-plugin now supports fadeIn(), fadeOut(), showPlugin(), hidePlugin() and
+ additionally you can configure it like this:
+ // make only the play button invisible (buffering animation is still used)
+ play: { display: 'none' }
+ // disable the play button and the buffering animation
+ play: null
+ // disable the buffering animation
+ buffering: null
+- Added possibility to seek when in the buffering state: http://flowplayer.org/forum/3/13896
+- Added copyright notices and other GPL required entries to the user interface
+
+Fixes:
+- clip urls were not resolved correctly if the HTML page URL had a query string starting with a question mark (http://flowplayer.org/forum/8/14016#post-14016)
+- Fixed context menu for with IE (commercial version)
+- a cuepoint at time zero was fired several times
+- screen is now arranged correctly even when only bottom or top is defined for it in the configuration
+- Fixed context menu for with IE (commercial version)
+- a cuepoint at time zero was fired several times
+- screen is now arranged correctly even when only bottom or top is defined for it in the configuration
+- Now possible to call play() in an onError handler: http://flowplayer.org/forum/8/12939
+- Does not throw an error if the player cannot persist the volume on the client computer: http://flowplayer.org/forum/8/13286#post-13495
+- Triggering fullscreen does not pause the player in IE
+- The play button overlay no longer has a gap between it's pieces when a label is used: http://flowplayer.org/forum/8/14250
+- clip.update() JS call now resets the duration
+- a label configured for the play button overlay did not work in the commercial version
+
+3.0.3
+-----
+- fixed cuepoint firing: Does not skip cuepoints any more
+- Plugins can now be loaded from a different domain to the flowplayer.swf
+- Specifying a clip to play by just using the 'clip' node in the configuration was not working, a playlist definition was required. This is now fixed.
+- Fixed: A playlist with different providers caused the onMetadata event to fire events with metadata from the previous clip in the playlist. Occurred when moving in the playlist with next() and prev()
+- the opacity setting now works with the logo
+- fadeOut() call to the "screen" plugin was sending the listenerId and pluginName arguments in wrong order
+- stop(), pause(), resume(), close() no longer return the flowplayer object to JS
+- changing the size of the screen in a onFullscreen listener now always works, there was a bug that caused this to fail occasionally
+- fixed using arbitrary SWFs as plugins
+- the API method setPlaylist() no longer starts playing if autoPlay: true, neither it starts buffering if autoBuffering: true
+- the API method play() now accepts an array of clip objects as an argument, the playlist is replaced with the specified clips and playback starts from the 1st clip
+
+3.0.2
+-----
+- setting play: null now works again
+- pressing the play again button overlay does not open a linkUrl associated with a clip
+- now displays a live feed even when the RTMP server does not send any metadata and the onStart method is not therefore dispatched
+- added onMetaData clip event
+- fixed 'orig' scaling: the player went to 'fit' scaling after coming back from fullscreen. This is now fixed and the original dimensions are preserved in non-fullscreen mode.
+- cuepoint times are now given in milliseconds, the firing precision is 100 ms. All cuepoint times are rounded to the nearest 100 ms value (for example 1120 rounds to 1100)
+- backgroundGradient was drawn over the background image in the canvas and in the content and controlbar plugins. Now it's drawn below the image.
+- added cuepointMultiplier property to clips. This can be used to multiply the time values read from cuepoint metadata embedded into video files.
+- the player's framerate was increased to 24 FPS, makes all animations smoother
+
+3.0.1
+-----
+- Fixed negative cuepoints from common clip. Now these are properly propagated to the clips in playlist.
+- buffering animation is now the same size as the play button overlay
+- commercial version now supports license keys that allows the use of subdomains
+- error messages are now automatically hidden after a 4 second delay. They are also hidden when a new clips
+ starts playing (when onBeforeBegin is fired)
+- added possibility to disable the buffering animation like so: buffering: false
+- pressing the play button overlay does not open a linkUrl associated with a clip
+- license key verification failed if a port number was used in the URL (like in this url: http://mydomain.com:8080/video.html)
+- added audio support, clip has a new "image" property
+- workaround for missing "NetStream.Play.Start" notfication that was happending with Red5. Because of this issue the video was not shown.
+- commercial version has the possibility to change the zIndex of the logo
+
+3.0.0
+-----
+- Removed security errors that happened when loading images from foreign domains (domains other than the domain of the core SWF).
+ Using a backgroundImage on canvas, in the content plugin, and for the controls is also possible to be loaded
+ from a foreign domain - BUT backgroundRepeat cannot be used for foreign images.
+- Now allows the embedding HTML to script the player even if the player is loaded from another domain.
+- Added a 'live' property to Clips, used for live streams.
+- A player embedded to a foreign domain now loads images, css files and other resources from the domain where the palyer SWF was loaded from. This is to generate shorter embed-codes.
+- Added linkUrl and linkWindow properties to the logo, in commercial version you can set these to point to a linked page. The linked page gets opened
+ when the logo is clicked. Possible values for linkWindow:
+ * "_self" specifies the current frame in the current window.
+ * "_blank" specifies a new window.
+ * "_parent" specifies the parent of the current frame.
+ * "_top" specifies the top-level frame in the current window.
+- Added linkUrl and linkWindow properties to clips. The linked page is opened when the video are is clicked and the corresponding clip has a linkUrl specified.
+- Made the play button overlay and the "Play again" button slightly bigger.
+
+RC4
+---
+- Now shows a "Play again" button at the end of the video/playlist
+- Commercial version shows a Flowplayer logo if invalidKey was supplied, but the otherwise the player works
+- setting play: null in configuration will disable the play button overlay
+- setting opacity for "play" also sets it for the buffering animation
+- Fixed firing of cuepoints too early. Cuepoint firing is now based on stream time and does not rely on timers
+- added onXMPData event listener
+- Should not stop playback too early before the clip is really completed
+- The START event is now delayed so that the metadata is available when the event is fired, METADATA event was removed,
+ new event BEGIN that is dispatched when the playback has been successfully started. Metadata is not normally
+ available when BEGIN is fired.
+
+RC3
+---
+- stopBuffering() now dispatches the onStop event first if the player is playing/paused/buffering at the time of calling it
+- fixed detection of images based on file extensions
+- fixed some issues with having images in the playlist
+- made it possible to autoBuffer next video while showing an image (image without a duration)
+
+RC2
+---
+- fixed: setting the screen height in configuration did not have any effect
+
+RC1