MDL-29624 Media embedding system, part 1: new API and filter changes
authorsam marshall <s.marshall@open.ac.uk>
Tue, 13 Dec 2011 17:08:34 +0000 (17:08 +0000)
committersam marshall <s.marshall@open.ac.uk>
Wed, 9 May 2012 17:29:29 +0000 (18:29 +0100)
Includes new API in medialib.php and core_media_renderer in outputrenderers.php.
Enable/disable settings moved from filter to systemwide appearance page.
Filter changed to use new API.

AMOS BEGIN
 MOV [flashanimation,filter_mediaplugin],[flashanimation,core_media]
 MOV [flashanimation_help,filter_mediaplugin],[flashanimation_desc,core_media]
 MOV [flashvideo,filter_mediaplugin],[flashvideo,core_media]
 MOV [flashvideo_help,filter_mediaplugin],[flashvideo_desc,core_media]
 MOV [html5audio,filter_mediaplugin],[html5audio,core_media]
 MOV [html5audio_help,filter_mediaplugin],[html5audio_desc,core_media]
 MOV [html5video,filter_mediaplugin],[html5video,core_media]
 MOV [html5video_help,filter_mediaplugin],[html5video_desc,core_media]
 MOV [mp3audio,filter_mediaplugin],[mp3audio,core_media]
 MOV [mp3audio_help,filter_mediaplugin],[mp3audio_desc,core_media]
 MOV [legacyquicktime,filter_mediaplugin],[legacyquicktime,core_media]
 MOV [legacyquicktime_help,filter_mediaplugin],[legacyquicktime_desc,core_media]
 MOV [legacyreal,filter_mediaplugin],[legacyreal,core_media]
 MOV [legacyreal_help,filter_mediaplugin],[legacyreal_desc,core_media]
 MOV [legacywmp,filter_mediaplugin],[legacywmp,core_media]
 MOV [legacywmp_help,filter_mediaplugin],[legacywmp_desc,core_media]
 MOV [legacyheading,filter_mediaplugin],[legacyheading,core_media]
 MOV [legacyheading_help,filter_mediaplugin],[legacyheading_desc,core_media]
 MOV [sitevimeo,filter_mediaplugin],[sitevimeo,core_media]
 MOV [sitevimeo_help,filter_mediaplugin],[sitevimeo_desc,core_media]
 MOV [siteyoutube,filter_mediaplugin],[siteyoutube,core_media]
 MOV [siteyoutube_help,filter_mediaplugin],[siteyoutube_desc,core_media]
AMOS END

17 files changed:
admin/settings/appearance.php
filter/mediaplugin/db/install.php
filter/mediaplugin/db/upgrade.php
filter/mediaplugin/dev/perftest.php [new file with mode: 0644]
filter/mediaplugin/filter.php
filter/mediaplugin/filtersettings.php [deleted file]
filter/mediaplugin/lang/en/filter_mediaplugin.php
filter/mediaplugin/tests/filter_test.php
filter/mediaplugin/version.php
lang/en/media.php [new file with mode: 0644]
lib/medialib.php [new file with mode: 0644]
lib/moodlelib.php
lib/outputrenderers.php
lib/tests/medialib_test.php [new file with mode: 0644]
lib/upgrade.txt
mod/upgrade.txt
theme/base/style/core.css

index 67d824a..7bca9fd 100644 (file)
@@ -115,6 +115,49 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $ADMIN->add('appearance', new admin_externalpage('resetemoticons', new lang_string('emoticonsreset', 'admin'),
         new moodle_url('/admin/resetemoticons.php'), 'moodle/site:config', true));
 
+
+    // The "media" subpage.
+    $temp = new admin_settingpage('mediasettings', get_string('mediasettings', 'core_media'));
+
+    $temp->add(new admin_setting_heading('mediaformats', get_string('mediaformats', 'core_media'),
+            format_text(get_string('mediaformats_desc', 'core_media'), FORMAT_MARKDOWN)));
+
+    // External services.
+    $temp->add(new admin_setting_configcheckbox('core_media_enable_youtube',
+            get_string('siteyoutube', 'core_media'), get_string('siteyoutube_desc', 'core_media'), 1));
+    $temp->add(new admin_setting_configcheckbox('core_media_enable_vimeo',
+            get_string('sitevimeo', 'core_media'), get_string('sitevimeo_desc', 'core_media'), 0));
+
+    // Options which require Flash.
+    $temp->add(new admin_setting_configcheckbox('core_media_enable_mp3',
+            get_string('mp3audio', 'core_media'), get_string('mp3audio_desc', 'core_media'), 1));
+    $temp->add(new admin_setting_configcheckbox('core_media_enable_flv',
+            get_string('flashvideo', 'core_media'), get_string('flashvideo_desc', 'core_media'), 1));
+    $temp->add(new admin_setting_configcheckbox('core_media_enable_swf',
+            get_string('flashanimation', 'core_media'), get_string('flashanimation_desc', 'core_media'), 1));
+
+    // HTML 5 media.
+    // Audio now enabled by default so that it can provide a fallback for mp3 on devices without flash.
+    $temp->add(new admin_setting_configcheckbox('core_media_enable_html5audio',
+            get_string('html5audio', 'core_media'), get_string('html5audio_desc', 'core_media'), 1));
+    // Video now enabled by default so it can provide mp4 support.
+    $temp->add(new admin_setting_configcheckbox('core_media_enable_html5video',
+            get_string('html5video', 'core_media'), get_string('html5video_desc', 'core_media'), 1));
+
+    // Legacy players.
+    $temp->add(new admin_setting_heading('legacymediaformats',
+            get_string('legacyheading', 'core_media'), get_string('legacyheading_desc', 'core_media')));
+
+    $temp->add(new admin_setting_configcheckbox('core_media_enable_qt',
+            get_string('legacyquicktime', 'core_media'), get_string('legacyquicktime_desc', 'core_media'), 1));
+    $temp->add(new admin_setting_configcheckbox('core_media_enable_wmp',
+            get_string('legacywmp', 'core_media'), get_string('legacywmp_desc', 'core_media'), 1));
+    $temp->add(new admin_setting_configcheckbox('core_media_enable_rm',
+            get_string('legacyreal', 'core_media'), get_string('legacyreal_desc', 'core_media'), 1));
+
+    $ADMIN->add('appearance', $temp);
+
+
     // "documentation" settingpage
     $temp = new admin_settingpage('documentation', new lang_string('moodledocs'));
     $temp->add(new admin_setting_configtext('docroot', new lang_string('docroot', 'admin'), new lang_string('configdocroot', 'admin'), 'http://docs.moodle.org', PARAM_URL));
index 2489fac..1200eb6 100644 (file)
@@ -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
index 96394bd..c77415f 100644 (file)
@@ -33,5 +33,26 @@ function xmldb_filter_mediaplugin_upgrade($oldversion) {
     $dbman = $DB->get_manager();
 
 
+    if ($oldversion < 2011121200) {
+        // Move all the media enable setttings that are now handled by core media renderer.
+        foreach (array('html5video', 'html5audio', 'mp3', 'flv', 'wmp', 'qt', 'rm',
+                'youtube', 'vimeo', 'swf') as $type) {
+            $existingkey = 'filter_mediaplugin_enable_' . $type;
+            if (array_key_exists($existingkey, $CFG)) {
+                set_config('core_media_enable_' . $type, $CFG->{$existingkey});
+                unset_config($existingkey);
+            }
+        }
+
+        // Override setting for html5 to turn it on (previous default was off; because
+        // of changes in the way fallbacks are handled, this is now unlikely to cause
+        // a problem, and is required for mobile a/v support on non-Flash devices, so
+        // this change is basically needed in order to maintain existing behaviour).
+        set_config('core_media_enable_html5video', 1);
+        set_config('core_media_enable_html5audio', 1);
+
+        upgrade_plugin_savepoint(true, 2011121200, 'filter', 'mediaplugin');
+    }
+
     return true;
 }
diff --git a/filter/mediaplugin/dev/perftest.php b/filter/mediaplugin/dev/perftest.php
new file mode 100644 (file)
index 0000000..e590b39
--- /dev/null
@@ -0,0 +1,173 @@
+<?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/>.
+
+/**
+ * Media filter performance test script.
+ *
+ * For developer test usage only. This can be used to compare performance if
+ * there are changes to the system in future.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package filter_mediaplugin
+ */
+
+require(dirname(__FILE__) . '/../../../config.php');
+require_once($CFG->dirroot . '/filter/mediaplugin/filter.php');
+
+// Only available to site admins.
+require_login();
+if (!is_siteadmin()) {
+    print_error('nopermissions', 'error', '', 'perftest');
+}
+
+// Set up page.
+$PAGE->set_context(context_system::instance());
+$PAGE->set_url(new moodle_url('/filter/mediaplugin/perftest.php'));
+$PAGE->set_heading($SITE->fullname);
+print $OUTPUT->header();
+
+// Hack setup to enable all players.
+$CFG->core_media_enable_youtube    = 1;
+$CFG->core_media_enable_vimeo      = 1;
+$CFG->core_media_enable_mp3        = 1;
+$CFG->core_media_enable_flv        = 1;
+$CFG->core_media_enable_swf        = 1;
+$CFG->core_media_enable_html5audio = 1;
+$CFG->core_media_enable_html5video = 1;
+$CFG->core_media_enable_qt         = 1;
+$CFG->core_media_enable_wmp        = 1;
+$CFG->core_media_enable_rm         = 1;
+
+$CFG->filter_mediaplugin_enable_youtube    = 1;
+$CFG->filter_mediaplugin_enable_vimeo      = 1;
+$CFG->filter_mediaplugin_enable_mp3        = 1;
+$CFG->filter_mediaplugin_enable_flv        = 1;
+$CFG->filter_mediaplugin_enable_swf        = 1;
+$CFG->filter_mediaplugin_enable_html5audio = 1;
+$CFG->filter_mediaplugin_enable_html5video = 1;
+$CFG->filter_mediaplugin_enable_qt         = 1;
+$CFG->filter_mediaplugin_enable_wmp        = 1;
+$CFG->filter_mediaplugin_enable_rm         = 1;
+
+// Create plugin.
+$filterplugin = new filter_mediaplugin(null, array());
+
+// Note: As this is a developer test page, language strings are not used: all
+// text is English-only.
+
+/**
+ * Starts time counter.
+ */
+function filter_mediaplugin_perf_start() {
+    global $filter_mediaplugin_starttime;
+    $filter_mediaplugin_starttime = microtime(true);
+}
+
+/**
+ * Ends and displays time counter.
+ * @param string $name Counter name to display
+ */
+function filter_mediaplugin_perf_stop($name) {
+    global $filter_mediaplugin_starttime;
+    $time = microtime(true) - $filter_mediaplugin_starttime;
+
+    echo html_writer::tag('li', $name . ': ' . html_writer::tag('strong', round($time, 2)) . 'ms');
+}
+
+// 1) Some sample text strings.
+//    Note: These are from a random sample of real forum data. Just in case there
+//    are any privacy concerns I have altered names as may be clear.
+$samples = array(
+    "<p>Hi,</p>&#13;\n<p>I've got myself 2 Heaney's \"The Burial at Thebes\"</p>",
+    "best mark iv heard so far v v good",
+    "<p>I have a script draft anyone want to look at it?",
+    "<p>Thanks for your input Legolas and Ghimli!</p>",
+    "<p>Just to say that I'm thinking of those of you who are working on TMA02.</p>",
+    "<p><strong>1.</strong> <strong>If someone asks you 'where do you come from?'</strong></p>",
+    "<p>With regards to Aragorn's question 'what would we do different'?</p>&#13;\n",
+    "<p>Just thought I'd drop a line to see how everyone is managing generally?</p>&#13;\n",
+    "<p>Feb '12 - Oct '12  AA100</p>&#13;\n<p>Nov '12 - April '13 - A150</p>&#13;\n",
+    "<p>So where does that leave the bible???</p>",
+);
+
+// 2) Combine sample text strings into one really big (20KB) string.
+$length = 0;
+$bigstring = '';
+$index = 0;
+while ($length < 20 * 1024) {
+    $bigstring .= $samples[$index];
+    $length += strlen($samples[$index]);
+    $index++;
+    if ($index >= count($samples)) {
+        $index = 0;
+    }
+}
+
+// 3) Make random samples from this. I did the following stats on recent forum
+//    posts:
+//    0-199 characters approx 30%
+//    200-1999 approx 60%
+//    2000-19999 approx 10%.
+
+$samplebank = array();
+foreach (array(100 => 300, 1000 => 600, 10000 => 100) as $chars => $num) {
+    for ($i = 0; $i < $num; $i++) {
+        $start = rand(0, $length - $chars - 1);
+        $samplebank[] = substr($bigstring, $start, $chars);
+    }
+}
+
+echo html_writer::start_tag('ul');
+
+// First test: filter text that doesn't have any links.
+filter_mediaplugin_perf_start();
+foreach ($samplebank as $sample) {
+    $filterplugin->filter($sample);
+}
+filter_mediaplugin_perf_stop('No links');
+
+// Second test: filter text with one link added (that doesn't match).
+$link = '<a href="http://www.example.org/another/link/">Link</a>';
+$linksamples = array();
+foreach ($samplebank as $sample) {
+    // Make it the same length but with $link replacing the end part.
+    $linksamples[] = substr($sample, 0, -strlen($link)) . $link;
+}
+
+filter_mediaplugin_perf_start();
+foreach ($linksamples as $sample) {
+    $filterplugin->filter($sample);
+}
+filter_mediaplugin_perf_stop('One link (no match)');
+
+// Third test: filter text with one link added that does match (mp3).
+$link = '<a href="http://www.example.org/another/file.mp3">MP3 audio</a>';
+$linksamples = array();
+foreach ($samplebank as $sample) {
+    // Make it the same length but with $link replacing the end part.
+    $linksamples[] = substr($sample, 0, -strlen($link)) . $link;
+}
+
+filter_mediaplugin_perf_start();
+foreach ($linksamples as $sample) {
+    $filterplugin->filter($sample);
+}
+filter_mediaplugin_perf_stop('One link (mp3)');
+
+// End page.
+echo html_writer::end_tag('ul');
+print $OUTPUT->footer();
\ No newline at end of file
index 2d9b6d8..57e58d5 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-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.
  *
@@ -65,850 +40,93 @@ if (!defined('FILTER_MEDIAPLUGIN_VIDEO_HEIGHT')) {
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class filter_mediaplugin extends moodle_text_filter {
+    /** @var bool True if currently filtering trusted text */
+    private $trusted;
+    /** @var core_media_renderer Media renderer */
+    private $mediarenderer;
+    /** @var string Partial regex pattern indicating possible embeddable content */
+    private $embedmarkers;
 
-    function filter($text, array $options = array()) {
-        global $CFG;
+    public function filter($text, array $options = array()) {
+        global $CFG, $PAGE;
 
         if (!is_string($text) or empty($text)) {
             // non string data can not be filtered anyway
             return $text;
         }
+
         if (stripos($text, '</a>') === false) {
-            // performance shortcut - all regexes below end with the </a> tag,
-            // if not present nothing can match
+            // Performance shortcut - if not </a> tag, nothing can match.
             return $text;
         }
 
-        $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\-_]+)[^"#]*(#d=([\d]{1,4})x([\d]{1,4}))?"[^>]*>([^>]*)<\/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\-_]+)[^"#]*(#d=([\d]{1,4})x([\d]{1,4}))?[^>]*>([^>]*)<\/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\-_]+)[^"#]*(#d=([\d]{1,4})x([\d]{1,4}))?[^>]*>([^>]*)<\/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\-_]+)[^"#]*(#d=([\d]{1,4})x([\d]{1,4}))?[^>]*>([^>]*)<\/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]+)[^"#]*(#d=([\d]{1,4})x([\d]{1,4}))?[^>]*>([^>]*)<\/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_html5video)) {
-            $search = '/<a\s[^>]*href="([^"#\?]+\.(m4v|webm|ogv|mp4)([#\?][^"]*)?)"[^>]*>([^>]*)<\/a>/is';
-            $newtext = preg_replace_callback($search, 'filter_mediaplugin_html5video_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($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 (!$this->mediarenderer) {
+            $this->mediarenderer = $PAGE->get_renderer('core', 'media');
+            $this->embedmarkers = $this->mediarenderer->get_embeddable_markers();
         }
 
-        if (!empty($CFG->filter_mediaplugin_enable_flv)) {
-            $search = '/<a\s[^>]*href="([^"#\?]+\.(flv|f4v)([#\?][^"]*)?)"[^>]*>([^>]*)<\/a>/is';
-            $newtext = preg_replace_callback($search, 'filter_mediaplugin_flv_callback', $newtext);
-        }
-
-
-        // The rest of legacy formats - these should not be used if possible
-
-        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_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_rm)) {
-            // hopefully nobody is using this any more!!
-            // rpm is redhat packaging format these days, it is better to prevent these in default installs
-
-            $search = '/<a\s[^>]*href="([^"#\?]+\.(ra|ram|rm|rv))"[^>]*>([^>]*)<\/a>/is';
-            $newtext = preg_replace_callback($search, 'filter_mediaplugin_real_callback', $newtext);
-        }
+        // Check SWF permissions.
+        $this->trusted = !empty($options['noclean']) or !empty($CFG->allowobjectembed);
 
+        // Handle all links that contain any 'embeddable' marker text (it could
+        // do all links, but the embeddable markers thing should make it faster
+        // by meaning for most links it doesn't drop into PHP code).
+        $newtext = preg_replace_callback($re = '~<a\s[^>]*href="([^"]*(?:' .
+                $this->embedmarkers . ')[^"]*)"[^>]*>([^>]*)</a>~is',
+                array($this, 'callback'), $text);
 
         if (empty($newtext) or $newtext === $text) {
             // error or not filtered
-            unset($newtext);
             return $text;
         }
 
-
         return $newtext;
     }
-}
-
-
-///===========================
-/// utility functions
-
-/**
- * Get mimetype of given url, useful for # alternative urls.
- *
- * @private
- * @param string $url
- * @return string $mimetype
- */
-function filter_mediaplugin_get_mimetype($url) {
-    $matches = null;
-    if (preg_match("|^(.*)/[a-z]*file.php(\?file=)?(/[^&\?#]*)|", $url, $matches)) {
-        // remove the special moodle file serving hacks so that the *file.php is ignored
-        $url = $matches[1].$matches[3];
-    } else {
-        $url = preg_replace('/[#\?].*$/', '', $url);
-    }
-
-    $mimetype = mimeinfo('type', $url);
-
-    return $mimetype;
-}
-
-/**
- * Parse list of alternative URLs
- * @param string $url urls separated with '#', size specified as ?d=640x480 or #d=640x480
- * @param int $defaultwidth
- * @param int $defaultheight
- * @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 (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);
-        }
-
-        $url = str_replace('&amp;', '&', $url);
-        $url = clean_param($url, PARAM_URL);
-        if (empty($url)) {
-            continue;
-        }
-
-        $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
-
-
-/**
- * Replace audio links with audio tag.
- *
- * @param array $link
- * @return string
- */
-function filter_mediaplugin_html5audio_callback(array $link) {
-    global $CFG;
-
-    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]);
-
-    $fallbackurl  = null;
-    $fallbackmime = null;
-    $sources      = array();
-    $fallbacklink = null;
-
-    foreach ($urls as $url) {
-        $mimetype = filter_mediaplugin_get_mimetype($url);
-        if (strpos($mimetype, 'audio/') !== 0) {
-            continue;
+    /**
+     * Replace link with embedded content, if supported.
+     *
+     * @param array $matches
+     * @return string
+     */
+    private function callback(array $matches) {
+        global $CFG, $PAGE;
+        // Check if we ignore it.
+        if (preg_match('/class="[^"]*nomediaplugin/i', $matches[0])) {
+            return $matches[0];
         }
-        $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 below
+        // Get name.
+        $name = trim($matches[2]);
+        if (empty($name) or strpos($name, 'http') === 0) {
+            $name = ''; // Use default name.
         }
-        if ($fallbackurl === null) {
-            if ($mimetype === 'audio/mp3' or $mimetype === 'audio/aac') {
-                $fallbackurl  = str_replace('&', '&amp;', $url);
-                $fallbackmime = $mimetype;
-            }
-        }
-    }
-    if (!$sources) {
-        return $link[0];
-    }
-
-    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;
-    }
-
-    $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;
-}
+        // Split provided URL into alternatives.
+        $urls = core_media::split_alternatives($matches[1], $width, $height);
 
-/**
- * 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) {
+        $options = array();
 
-    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 = filter_mediaplugin_get_mimetype($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;
+        // Allow SWF (or not).
+        if ($this->trusted) {
+            $options[core_media::OPTION_TRUSTED] = true;
         }
 
-        if ($fallbacklink === null) {
-            $fallbacklink = html_writer::link($url.'#', $info); // the extra '#' prevents linking in mp3 filter below
-        }
-        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\"";
-    }
-
-    $output = <<<OET
-<span class="mediaplugin mediaplugin_html5video">
-<video controls="true" $size preload="metadata" title="$title">
-$sources
-$fallback
-</video>
-</span>
-OET;
-
-    return $output;
-}
-
-/**
- * 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_mp3_'.time().'_'.$count; //we need something unique because it might be stored in text cache
-
-    $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
+        // We could test whether embed is possible using can_embed, but to save
+        // time, let's just embed it with the 'fallback to blank' option which
+        // does most of the same stuff anyhow.
+        $options[core_media::OPTION_FALLBACK_TO_BLANK] = true;
 
-    $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
+        // NOTE: Options are not passed through from filter because the 'embed'
+        // code does not recognise filter options (it's a different kind of
+        // option-space) as it can be used in non-filter situations.
+        $result = $this->mediarenderer->embed_alternatives($urls, $name, $width, $height, $options);
 
-    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) {
-
-    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];
-
-    $url = $link[1];
-    $rawurl = str_replace('&amp;', '&', $url);
-
-    $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 = <<<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) {
-    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
-
-    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 = filter_mediaplugin_get_mimetype($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);
+        // If something was embedded, return it, otherwise return original.
+        if ($result !== '') {
+            return $result;
         } else {
-            $sources[] = $source;
-        }
-
-        if ($flashurl === null) {
-            $flashurl  = $url;
+            return $matches[0];
         }
     }
-    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, addslashes_js($flashurl), $width, $height, $autosize))); // we can not use standard JS init because this may be cached
-
-    return $output;
 }
-
-/**
- * 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
- *
- * Note: resizing via url is not supported, user can click the fullscreen button instead
- *
- * @param  $link
- * @return string
- */
-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[7]);
-    if (empty($info) or strpos($info, 'http') === 0) {
-        $info = get_string('siteyoutube', 'filter_mediaplugin');
-    }
-    $info = s($info);
-
-    $width  = empty($link[5]) ? FILTER_MEDIAPLUGIN_VIDEO_WIDTH  : $link[5];
-    $height = empty($link[6]) ? FILTER_MEDIAPLUGIN_VIDEO_HEIGHT : $link[6];
-
-    if (empty($CFG->xmlstrictheaders)) {
-        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
- *
- * 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) {
-    global $CFG;
-
-    if (filter_mediaplugin_ignore($link[0])) {
-        return $link[0];
-    }
-
-    $site     = $link[1];
-    $playlist = $link[3];
-
-    $info = trim($link[7]);
-    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  = empty($link[5]) ? FILTER_MEDIAPLUGIN_VIDEO_WIDTH  : $link[5];
-    $height = empty($link[6]) ? FILTER_MEDIAPLUGIN_VIDEO_HEIGHT : $link[6];
-
-    // 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 Vimeo into embedded Vimeo videos
- *
- * @param  $link
- * @return string
- */
-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[5]));
-
-    //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  = empty($link[3]) ? FILTER_MEDIAPLUGIN_VIDEO_WIDTH  : $link[3];
-    $height = empty($link[4]) ? FILTER_MEDIAPLUGIN_VIDEO_HEIGHT : $link[4];
-
-    $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) {
-
-    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="'.FILTER_MEDIAPLUGIN_VIDEO_WIDTH.'" height="'.(FILTER_MEDIAPLUGIN_VIDEO_HEIGHT+64).'"';
-        $autosize = 'true';
-    } else {
-        $size = 'width="'.$link[4].'" height="'.($link[5] + 15).'"';
-        $mpsize = 'width="'.$link[4].'" height="'.($link[5] + 64).'"';
-        $autosize = 'false';
-    }
-    $mimetype = filter_mediaplugin_get_mimetype($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="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" />
-   <param name="controller" value="true" />
-   <param name="autoplay" value="false" />
-   <param name="autostart" value="false" />
-   <param name="resize" value="scale" />
-  </object>
-<!--<![endif]-->
-</object></span>
-OET;
-}
-
-/**
- * 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="'.FILTER_MEDIAPLUGIN_VIDEO_WIDTH.'" height="'.(FILTER_MEDIAPLUGIN_VIDEO_HEIGHT+15).'"';
-    } else {
-        $size = 'width="'.$link[4].'" height="'.($link[5]+15).'"';
-    }
-    $mimetype = filter_mediaplugin_get_mimetype($url);
-
-    // 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="controller" value="true" />
- <param name="loop" value="true" />
- <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" />
-   <param name="pluginurl" value="http://www.apple.com/quicktime/download/" />
-   <param name="controller" value="true" />
-   <param name="loop" value="true" />
-   <param name="autoplay" value="false" />
-   <param name="autostart" value="false" />
-   <param name="scale" value="aspect" />
-  </object>
-<!--<![endif]-->
-</object></span>
-OET;
-}
-
diff --git a/filter/mediaplugin/filtersettings.php b/filter/mediaplugin/filtersettings.php
deleted file mode 100644 (file)
index 9e3ff29..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-<?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) {
-
-    // 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));
-
-    // 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));
-
-    // 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
-
-    // 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_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));
-
-}
index 94062fa..1a6c427 100644 (file)
 $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 specified.';
-$string['html5audio'] = 'HTML 5 audio';
-$string['html5audio_help'] = 'Audio files with extension *.ogg, *.aac 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.aac#http://example.org/audio.aac#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.aac#http://example.org/video.ogv#d=640x480), QuickTime player is used as a fallback for old browsers.';
-$string['mp3audio'] = 'MP3 audio';
-$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.';
 
index 7749a20..8687610 100644 (file)
@@ -37,16 +37,16 @@ class filter_mediaplugin_testcase extends advanced_testcase {
         $this->resetAfterTest(true);
 
         // we need to enable the plugins somehow
-        $CFG->filter_mediaplugin_enable_youtube    = 1;
-        $CFG->filter_mediaplugin_enable_vimeo      = 1;
-        $CFG->filter_mediaplugin_enable_mp3        = 1;
-        $CFG->filter_mediaplugin_enable_flv        = 1;
-        $CFG->filter_mediaplugin_enable_swf        = 1;
-        $CFG->filter_mediaplugin_enable_html5audio = 1;
-        $CFG->filter_mediaplugin_enable_html5video = 1;
-        $CFG->filter_mediaplugin_enable_qt         = 1;
-        $CFG->filter_mediaplugin_enable_wmp        = 1;
-        $CFG->filter_mediaplugin_enable_rm         = 1;
+        $CFG->core_media_enable_youtube    = 1;
+        $CFG->core_media_enable_vimeo      = 1;
+        $CFG->core_media_enable_mp3        = 1;
+        $CFG->core_media_enable_flv        = 1;
+        $CFG->core_media_enable_swf        = 1;
+        $CFG->core_media_enable_html5audio = 1;
+        $CFG->core_media_enable_html5video = 1;
+        $CFG->core_media_enable_qt         = 1;
+        $CFG->core_media_enable_wmp        = 1;
+        $CFG->core_media_enable_rm         = 1;
 
 
         $filterplugin = new filter_mediaplugin(null, array());
index f5b2288..c10b57f 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2011112900;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2011112900;        // Requires this Moodle version
+$plugin->version   = 2011121200;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2011120500;        // Requires this Moodle version
 $plugin->component = 'filter_mediaplugin'; // Full name of the plugin (used for diagnostics)
diff --git a/lang/en/media.php b/lang/en/media.php
new file mode 100644 (file)
index 0000000..15fd175
--- /dev/null
@@ -0,0 +1,51 @@
+<?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/>.
+
+/**
+ * Language strings for media embedding.
+ * @package core
+ * @subpackage media
+ * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['flashanimation'] = 'Flash animation';
+$string['flashanimation_desc'] = 'Files with extension *.swf. For security reasons this format is only embedded within trusted text.';
+$string['flashvideo'] = 'Flash video';
+$string['flashvideo_desc'] = 'Files with extension *.flv and *.f4v. Plays video clips using Flowplayer, requires Flash plugin and javascript.';
+$string['html5audio'] = 'HTML 5 audio';
+$string['html5audio_desc'] = 'Audio files with extension *.ogg, *.aac and *.mp3. Used primarily for mobile devices. (Format support depends on browser.)';
+$string['html5video'] = 'HTML 5 video';
+$string['html5video_desc'] = 'Video files with extension *.webm, *.m4v, *.ogv, *.mp4 and others. Used primarily for mobile devices. (Format support depends on browser.)';
+$string['mediaformats'] = 'Available players';
+$string['mediaformats_desc'] = 'When players are enabled in these settings, files can be embedded using the media filter (if enabled) or using a File or URL resources with the Embed option. When not enabled, these formats are not embedded and users can manually download or follow links to these resources.
+
+Where two players support the same format, enabling both increases compatibility across different devices such as mobile phones. It is possible to increase compatibility further by providing multiple files in different formats for a single audio or video clip.';
+$string['mediasettings'] = 'Media embedding';
+$string['mp3audio'] = 'MP3 audio';
+$string['mp3audio_desc'] = 'Files with extension *.mp3. Plays audio using Flowplayer, requires Flash plugin.';
+$string['legacyquicktime'] = 'QuickTime player';
+$string['legacyquicktime_desc'] = 'Files with extension *.mov, *.mp4, *.m4a, *.mp4 and *.mpg. Requires QuickTime player or codecs.';
+$string['legacyreal'] = 'Real media player';
+$string['legacyreal_desc'] = 'Files with extension *.rm, *.ra, *.ram, *.rp and *.rv. Requires RealPlayer.';
+$string['legacywmp'] = 'Windows media player';
+$string['legacywmp_desc'] = 'Files with extension *.avi and *.wmv. Fully compatible with Internet Explorer in Windows; may not work in other browsers or operating systems.';
+$string['legacyheading'] = 'Legacy media players';
+$string['legacyheading_desc'] = 'These players are not frequently used on the Web and require browser plugins that are less widely installed.';
+$string['sitevimeo'] = 'Vimeo';
+$string['sitevimeo_desc'] = 'Vimeo video sharing site.';
+$string['siteyoutube'] = 'YouTube';
+$string['siteyoutube_desc'] = 'YouTube video sharing site, video and playlist links supported.';
diff --git a/lib/medialib.php b/lib/medialib.php
new file mode 100644 (file)
index 0000000..afde3b0
--- /dev/null
@@ -0,0 +1,1232 @@
+<?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/>.
+
+/**
+ * Classes for handling embedded media (mainly audio and video).
+ *
+ * These are used only from within the core media renderer.
+ *
+ * To embed media from Moodle code, do something like the following:
+ *
+ * $mediarenderer = $PAGE->get_renderer('core', 'media');
+ * echo $mediarenderer->embed_url(new moodle_url('http://example.org/a.mp3'));
+ *
+ * You do not need to require this library file manually. Getting the renderer
+ * (the first line above) requires this library file automatically.
+ *
+ * @package core_media
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if (!defined('CORE_MEDIA_VIDEO_WIDTH')) {
+    /**
+     * Default video width if no width is specified; some players may do something
+     * more intelligent such as use real video width.
+     *
+     * May be defined in config.php if required.
+     */
+    define('CORE_MEDIA_VIDEO_WIDTH', 400);
+}
+if (!defined('CORE_MEDIA_VIDEO_HEIGHT')) {
+    /**
+     * Default video height. May be defined in config.php if required.
+     */
+    define('CORE_MEDIA_VIDEO_HEIGHT', 300);
+}
+if (!defined('CORE_MEDIA_AUDIO_WIDTH')) {
+    /**
+     * Default audio width if no width is specified.
+     *
+     * May be defined in config.php if required.
+     */
+    define('CORE_MEDIA_AUDIO_WIDTH', 300);
+}
+
+
+/**
+ * Constants and static utility functions for use with core_media_renderer.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class core_media {
+    /**
+     * Option: Disable text link fallback.
+     *
+     * Use this option if you are going to print a visible link anyway so it is
+     * pointless to have one as fallback.
+     *
+     * To enable, set value to true.
+     */
+    const OPTION_NO_LINK = 'nolink';
+
+    /**
+     * Option: When embedding, if there is no matching embed, do not use the
+     * default link fallback player; instead return blank.
+     *
+     * This is different from OPTION_NO_LINK because this option still uses the
+     * fallback link if there is some kind of embedding. Use this option if you
+     * are going to check if the return value is blank and handle it specially.
+     *
+     * To enable, set value to true.
+     */
+    const OPTION_FALLBACK_TO_BLANK = 'embedorblank';
+
+    /**
+     * Option: Enable players which are only suitable for use when we trust the
+     * user who embedded the content.
+     *
+     * At present, this option enables the SWF player.
+     *
+     * To enable, set value to true.
+     */
+    const OPTION_TRUSTED = 'trusted';
+
+    /**
+     * Option: Put a div around the output (if not blank) so that it displays
+     * as a block using the 'resourcecontent' CSS class.
+     *
+     * To enable, set value to true.
+     */
+    const OPTION_BLOCK = 'block';
+
+    /**
+     * Given a string containing multiple URLs separated by #, this will split
+     * it into an array of moodle_url objects suitable for using when calling
+     * embed_alternatives.
+     *
+     * Note that the input string should NOT be html-escaped (i.e. if it comes
+     * from html, call html_entity_decode first).
+     *
+     * @param string $combinedurl String of 1 or more alternatives separated by #
+     * @param int $width Output variable: width (will be set to 0 if not specified)
+     * @param int $height Output variable: height (0 if not specified)
+     * @return array Array of 1 or more moodle_url objects
+     */
+    public static function split_alternatives($combinedurl, &$width, &$height) {
+        $urls = explode('#', $combinedurl);
+        $width = 0;
+        $height = 0;
+        $returnurls = array();
+
+        foreach ($urls as $url) {
+            $matches = null;
+
+            // You can specify the size as a separate part of the array like
+            // #d=640x480 without actually including a url in it.
+            if (preg_match('/^d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) {
+                $width  = $matches[1];
+                $height = $matches[2];
+                continue;
+            }
+
+            // Can also include the ?d= as part of one of the URLs (if you use
+            // more than one they will be ignored except the last).
+            if (preg_match('/\?d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) {
+                $width  = $matches[1];
+                $height = $matches[2];
+
+                // Trim from URL.
+                $url = str_replace($matches[0], '', $url);
+            }
+
+            // Clean up url.
+            $url = clean_param($url, PARAM_URL);
+            if (empty($url)) {
+                continue;
+            }
+
+            // Turn it into moodle_url object.
+            $returnurls[] = new moodle_url($url);
+        }
+
+        return $returnurls;
+    }
+
+    /**
+     * Returns the file extension for a URL.
+     * @param moodle_url $url URL
+     */
+    public static function get_extension(moodle_url $url) {
+        // Note: Does not use textlib (. is UTF8-safe).
+        $filename = self::get_filename($url);
+        $dot = strrpos($filename, '.');
+        if ($dot === false) {
+            return '';
+        } else {
+            return strtolower(substr($filename, $dot + 1));
+        }
+    }
+
+    /**
+     * Obtains the filename from the moodle_url.
+     * @param moodle_url $url URL
+     * @return string Filename only (not escaped)
+     */
+    public static function get_filename(moodle_url $url) {
+        $path = $url->get_path();
+        // Remove everything before last / if present. Does not use textlib as / is UTF8-safe.
+        $slash = strrpos($path, '/');
+        if ($slash !== false) {
+            $path = substr($path, $slash + 1);
+        }
+
+        return $path;
+    }
+
+    /**
+     * Guesses MIME type for a moodle_url based on file extension.
+     * @param moodle_url $url URL
+     * @return string MIME type
+     */
+    public static function get_mimetype(moodle_url $url) {
+        return mimeinfo('type', self::get_filename($url));
+    }
+}
+
+
+/**
+ * Base class for media players.
+ *
+ * Media players return embed HTML for a particular way of playing back audio
+ * or video (or another file type).
+ *
+ * In order to make the code more lightweight, this is not a plugin type
+ * (players cannot have their own settings, database tables, capabilities, etc).
+ * These classes are used only by core_media_renderer in outputrenderers.php.
+ * If you add a new class here (in core code) you must modify the
+ * get_players_raw function in that file to include it.
+ *
+ * If a Moodle installation wishes to add extra player objects they can do so
+ * by overriding that renderer in theme, and overriding the get_players_raw
+ * function. The new player class should then of course be defined within the
+ * custom theme or other suitable location, not in this file.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class core_media_player {
+    /**
+     * Placeholder text used to indicate where the fallback content is placed
+     * within a result.
+     */
+    const PLACEHOLDER = '<!--FALLBACK-->';
+
+    /**
+     * Generates code required to embed the player.
+     *
+     * The returned code contains a placeholder comment '<!--FALLBACK-->'
+     * (constant core_media_player::PLACEHOLDER) which indicates the location
+     * where fallback content should be placed in the event that this type of
+     * player is not supported by user browser.
+     *
+     * The $urls parameter includes one or more alternative media formats that
+     * are supported by this player. It does not include formats that aren't
+     * supported (see list_supported_urls).
+     *
+     * The $options array contains key-value pairs. See OPTION_xx constants
+     * for documentation of standard option(s).
+     *
+     * @param array $urls URLs of media files
+     * @param string $name Display name; '' to use default
+     * @param int $width Optional width; 0 to use default
+     * @param int $height Optional height; 0 to use default
+     * @param array $options Options array
+     * @return string HTML code for embed
+     */
+    public abstract function embed($urls, $name, $width, $height, $options);
+
+    /**
+     * Gets the list of file extensions supported by this media player.
+     *
+     * Note: This is only required for the default implementation of
+     * list_supported_urls. If you override that function to determine
+     * supported URLs in some way other than by extension, then this function
+     * is not necessary.
+     *
+     * @return array Array of strings (extension not including dot e.g. 'mp3')
+     */
+    public function get_supported_extensions() {
+        return array();
+    }
+
+    /**
+     * Lists keywords that must be included in a url that can be embedded with
+     * this player. Any such keywords should be added to the array.
+     *
+     * For example if this player supports FLV and F4V files then it should add
+     * '.flv' and '.f4v' to the array. (The check is not case-sensitive.)
+     *
+     * Default handling calls the get_supported_extensions function and adds
+     * a dot to each of those values, so players only need to override this
+     * if they don't implement get_supported_extensions.
+     *
+     * This is used to improve performance when matching links in the media filter.
+     *
+     * @return array Array of keywords to add to the embeddable markers list
+     */
+    public function get_embeddable_markers() {
+        $markers = array();
+        foreach ($this->get_supported_extensions() as $extension) {
+            $markers[] = '.' . $extension;
+        }
+        return $markers;
+    }
+
+    /**
+     * Gets the ranking of this player. This is an integer used to decide which
+     * player to use (after applying other considerations such as which ones
+     * the user has disabled).
+     *
+     * Rank must be unique (no two players should have the same rank).
+     *
+     * Rank zero has a special meaning, indicating that this 'player' does not
+     * really embed the video.
+     *
+     * Rank is not a user-configurable value because it needs to be defined
+     * carefully in order to ensure that the embedding fallbacks actually work.
+     * It might be possible to have some user options which affect rank, but
+     * these would be best defined as e.g. checkboxes in settings that have
+     * a particular effect on the rank of a couple of plugins, rather than
+     * letting users generally alter rank.
+     *
+     * Note: Within medialib.php, players are listed in rank order (highest
+     * rank first).
+     *
+     * @return int Rank (higher is better)
+     */
+    public abstract function get_rank();
+
+    /**
+     * @return bool True if player is enabled
+     */
+    public function is_enabled() {
+        global $CFG;
+
+        // With the class core_media_player_html5video it is enabled
+        // based on $CFG->core_media_enable_html5video.
+        $setting = str_replace('_player_', '_enable_', get_class($this));
+        return !empty($CFG->{$setting});
+    }
+
+    /**
+     * Given a list of URLs, returns a reduced array containing only those URLs
+     * which are supported by this player. (Empty if none.)
+     * @param array $urls Array of moodle_url
+     * @param array $options Options (same as will be passed to embed)
+     * @return array Array of supported moodle_url
+     */
+    public function list_supported_urls(array $urls, array $options = array()) {
+        $extensions = $this->get_supported_extensions();
+        $result = array();
+        foreach ($urls as $url) {
+            if (in_array(core_media::get_extension($url), $extensions)) {
+                $result[] = $url;
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Obtains suitable name for media. Uses specified name if there is one,
+     * otherwise makes one up.
+     * @param string $name User-specified name ('' if none)
+     * @param array $urls Array of moodle_url used to make up name
+     * @return string Name
+     */
+    protected function get_name($name, $urls) {
+        // If there is a specified name, use that.
+        if ($name) {
+            return $name;
+        }
+
+        // Get filename of first URL.
+        $url = reset($urls);
+        $name = core_media::get_filename($url);
+
+        // If there is more than one url, strip the extension as we could be
+        // referring to a different one or several at once.
+        if (count($urls) > 1) {
+            $name = preg_replace('~\.[^.]*$~', '', $name);
+        }
+
+        return $name;
+    }
+
+    /**
+     * Compares by rank order, highest first. Used for sort functions.
+     * @param core_media_player $a Player A
+     * @param core_media_player $b Player B
+     * @return int Negative if A should go before B, positive for vice versa
+     */
+    public static function compare_by_rank(core_media_player $a, core_media_player $b) {
+        return $b->get_rank() - $a->get_rank();
+    }
+
+    /**
+     * Utility function that sets width and height to defaults if not specified
+     * as a parameter to the function (will be specified either if, (a) the calling
+     * code passed it, or (b) the URL included it).
+     * @param int $width Width passed to function (updated with final value)
+     * @param int $height Height passed to function (updated with final value)
+     */
+    protected static function pick_video_size(&$width, &$height) {
+        if (!$width) {
+            $width = CORE_MEDIA_VIDEO_WIDTH;
+            $height = CORE_MEDIA_VIDEO_HEIGHT;
+        }
+    }
+}
+
+
+/**
+ * Base class for players which handle external links (YouTube etc).
+ *
+ * As opposed to media files.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class core_media_player_external extends core_media_player {
+    /**
+     * Array of matches from regular expression - subclass can assume these
+     * will be valid when the embed function is called, to save it rerunning
+     * the regex.
+     * @var array
+     */
+    protected $matches;
+
+    /**
+     * Part of a regular expression, including ending ~ symbol (note: these
+     * regexes use ~ instead of / because URLs and HTML code typically include
+     * / symbol and makes harder to read if you have to escape it).
+     * Matches the end part of a link after you have read the 'important' data
+     * including optional #d=400x300 at end of url, plus content of <a> tag,
+     * up to </a>.
+     * @var string
+     */
+    const END_LINK_REGEX_PART = '[^#]*(#d=([\d]{1,4})x([\d]{1,4}))?~si';
+
+    public function embed($urls, $name, $width, $height, $options) {
+        return $this->embed_external(reset($urls), $name, $width, $height, $options);
+    }
+
+    /**
+     * Obtains HTML code to embed the link.
+     * @param moodle_url $url Single URL to embed
+     * @param string $name Display name; '' to use default
+     * @param int $width Optional width; 0 to use default
+     * @param int $height Optional height; 0 to use default
+     * @param array $options Options array
+     * @return string HTML code for embed
+     */
+    protected abstract function embed_external(moodle_url $url, $name, $width, $height, $options);
+
+    public function list_supported_urls(array $urls, array $options = array()) {
+        // These only work with a SINGLE url (there is no fallback).
+        if (count($urls) != 1) {
+            return array();
+        }
+        $url = reset($urls);
+
+        // Check against regex.
+        if (preg_match($this->get_regex(), $url->out(false), $this->matches)) {
+            return array($url);
+        }
+
+        return array();
+    }
+
+    /**
+     * Returns regular expression used to match URLs that this player handles
+     * @return string PHP regular expression e.g. '~^https?://example.org/~'
+     */
+    protected function get_regex() {
+        return '~^unsupported~';
+    }
+
+    /**
+     * Annoyingly, preg_match $matches result does not always have the same
+     * number of parameters - it leaves out optional ones at the end. WHAT.
+     * Anyway, this function can be used to fix it.
+     * @param array $matches Array that should be adjusted
+     * @param int $count Number of capturing groups (=6 to make $matches[6] work)
+     */
+    protected static function fix_match_count(&$matches, $count) {
+        for ($i = count($matches); $i <= $count; $i++) {
+            $matches[$i] = false;
+        }
+    }
+}
+
+
+/**
+ * Player that embeds Vimeo links.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_vimeo extends core_media_player_external {
+    protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
+        $videoid = $this->matches[1];
+        $info = s($name);
+
+        // 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.
+        self::pick_video_size($width, $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;
+    }
+
+    protected function get_regex() {
+        // Initial part of link.
+        $start = '~^http://vimeo\.com/';
+        // Middle bit: either watch?v= or v/.
+        $middle = '([0-9]+)';
+        return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
+    }
+
+    public function get_rank() {
+        return 1010;
+    }
+
+    public function get_embeddable_markers() {
+        return array('vimeo.com/');
+    }
+}
+
+/**
+ * Player that creates YouTube embedding.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_youtube extends core_media_player_external {
+    protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
+        global $CFG;
+
+        $site = $this->matches[1];
+        $videoid = $this->matches[3];
+
+        $info = trim($name);
+        if (empty($info) or strpos($info, 'http') === 0) {
+            $info = get_string('siteyoutube', 'core_media');
+        }
+        $info = s($info);
+
+        self::pick_video_size($width, $height);
+
+        if (empty($CFG->xmlstrictheaders)) {
+            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;
+    }
+
+    protected function get_regex() {
+        // Initial part of link.
+        $start = '~^(https?://www\.youtube(-nocookie)?\.com)/';
+        // Middle bit: either watch?v= or v/.
+        $middle = '(?:watch\?v=|v/)([a-z0-9\-_]+)';
+        return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
+    }
+
+    public function get_rank() {
+        // I decided to make the link-embedding ones (that don't handle file
+        // formats) have ranking in the 1000 range.
+        return 1001;
+    }
+
+    public function get_embeddable_markers() {
+        return array('youtube');
+    }
+}
+
+
+/**
+ * Player that creates YouTube playlist embedding.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_youtube_playlist extends core_media_player_external {
+    public function is_enabled() {
+        global $CFG;
+        // Use the youtube on/off flag.
+        return $CFG->core_media_enable_youtube;
+    }
+
+    protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
+        $site = $this->matches[1];
+        $playlist = $this->matches[3];
+
+        $info = trim($name);
+        if (empty($info) or strpos($info, 'http') === 0) {
+            $info = get_string('siteyoutube', 'core_media');
+        }
+        $info = s($info);
+
+        self::pick_video_size($width, $height);
+
+        // TODO: iframe HTML 5 video not implemented and object does not work
+        // on iOS devices.
+        $fallback = core_media_player::PLACEHOLDER;
+        $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" />
+$fallback</object>
+</span>
+OET;
+
+        return $output;
+    }
+
+    protected function get_regex() {
+        // Initial part of link.
+        $start = '~^(https?://www\.youtube(-nocookie)?\.com)/';
+        // Middle bit: either view_play_list?p= or p/ (doesn't work on youtube) or playlist?list=.
+        $middle = '(?:view_play_list\?p=|p/|playlist\?list=)([a-z0-9\-_]+)';
+        return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
+    }
+
+    public function get_rank() {
+        // I decided to make the link-embedding ones (that don't handle file
+        // formats) have ranking in the 1000 range.
+        return 1000;
+    }
+
+    public function get_embeddable_markers() {
+        return array('youtube');
+    }
+}
+
+
+/**
+ * MP3 player inserted using JavaScript.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_mp3 extends core_media_player {
+    public function embed($urls, $name, $width, $height, $options) {
+        // Use first url (there can actually be only one unless some idiot
+        // enters two mp3 files as alternatives).
+        $url = reset($urls);
+
+        // Unique id even across different http requests made at the same time
+        // (for AJAX, iframes).
+        $id = 'core_media_mp3_' . md5(time() . '_' . rand());
+
+        // When Flash or JavaScript are not available only the fallback is displayed,
+        // using span not div because players are inline elements.
+        $spanparams = array('id' => $id, 'class' => 'mediaplugin mediaplugin_mp3');
+        if ($width) {
+            $spanparams['style'] = 'width: ' . $width . 'px';
+        }
+        $output = html_writer::tag('span', core_media_player::PLACEHOLDER, $spanparams);
+        // We can not use standard JS init because this may be cached
+        // note: use 'small' size unless embedding in block mode.
+        $output .= html_writer::script(js_writer::function_call(
+                'M.util.add_audio_player', array($id, $url->out(false),
+                empty($options[core_media::OPTION_BLOCK]))));
+
+        return $output;
+    }
+
+    public function get_supported_extensions() {
+        return array('mp3');
+    }
+
+    public function get_rank() {
+        return 80;
+    }
+}
+
+
+/**
+ * Flash video player inserted using JavaScript.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_flv extends core_media_player {
+    public function embed($urls, $name, $width, $height, $options) {
+        // Use first url (there can actually be only one unless some idiot
+        // enters two mp3 files as alternatives).
+        $url = reset($urls);
+
+        // Unique id even across different http requests made at the same time
+        // (for AJAX, iframes).
+        $id = 'core_media_flv_' . md5(time() . '_' . rand());
+
+        // Compute width and height.
+        $autosize = false;
+        if (!$width && !$height) {
+            $width = CORE_MEDIA_VIDEO_WIDTH;
+            $height = CORE_MEDIA_VIDEO_HEIGHT;
+            $autosize = true;
+        }
+
+        // Fallback span (will normally contain link).
+        $output = html_writer::tag('span', core_media_player::PLACEHOLDER,
+                array('id'=>$id, 'class'=>'mediaplugin mediaplugin_flv'));
+        // We can not use standard JS init because this may be cached.
+        $output .= html_writer::script(js_writer::function_call(
+                'M.util.add_video_player', array($id, addslashes_js($url->out(false)),
+                $width, $height, $autosize)));
+        return $output;
+    }
+
+    public function get_supported_extensions() {
+        return array('flv', 'f4v');
+    }
+
+    public function get_rank() {
+        return 70;
+    }
+}
+
+
+/**
+ * Embeds Windows Media Player using object tag.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_wmp extends core_media_player {
+    public function embed($urls, $name, $width, $height, $options) {
+        // Get URL (we just use first, probably there is only one).
+        $firsturl = reset($urls);
+        $url = $firsturl->out(false);
+
+        // Work out width.
+        if (!$width || !$height) {
+            // Object tag has default size.
+            $mpsize = '';
+            $size = 'width="' . CORE_MEDIA_VIDEO_WIDTH .
+                    '" height="' . (CORE_MEDIA_VIDEO_HEIGHT+64) . '"';
+            $autosize = 'true';
+        } else {
+            $size = 'width="' . $width . '" height="' . ($height + 15) . '"';
+            $mpsize = 'width="' . $width . '" height="' . ($height + 64) . '"';
+            $autosize = 'false';
+        }
+
+        // MIME type for object tag.
+        $mimetype = core_media::get_mimetype($firsturl);
+
+        $fallback = core_media_player::PLACEHOLDER;
+
+        // Embed code.
+        return <<<OET
+<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" />
+        <param name="ShowControls" value="true" />
+        <param name="AutoRewind" value="true" />
+        <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" />
+            <param name="controller" value="true" />
+            <param name="autoplay" value="false" />
+            <param name="autostart" value="false" />
+            <param name="resize" value="scale" />
+        <!--<![endif]-->
+            $fallback
+        <!--[if !IE]>-->
+        </object>
+        <!--<![endif]-->
+    </object>
+</span>
+OET;
+    }
+
+    public function get_supported_extensions() {
+        return array('wmv', 'avi');
+    }
+
+    public function get_rank() {
+        return 60;
+    }
+}
+
+
+/**
+ * Media player using object tag and QuickTime player.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_qt extends core_media_player {
+    public function embed($urls, $name, $width, $height, $options) {
+        // Show first URL.
+        $firsturl = reset($urls);
+        $url = $firsturl->out(true);
+
+        // Work out size.
+        if (!$width || !$height) {
+            $size = 'width="' . CORE_MEDIA_VIDEO_WIDTH .
+                    '" height="' . (CORE_MEDIA_VIDEO_HEIGHT + 15) . '"';
+        } else {
+            $size = 'width="' . $width . '" height="' . ($height + 15) . '"';
+        }
+
+        // MIME type for object tag.
+        $mimetype = core_media::get_mimetype($firsturl);
+
+        $fallback = core_media_player::PLACEHOLDER;
+
+        // Embed code.
+        return <<<OET
+<span class="mediaplugin mediaplugin_qt">
+    <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="controller" value="true" />
+        <param name="loop" value="true" />
+        <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" />
+            <param name="pluginurl" value="http://www.apple.com/quicktime/download/" />
+            <param name="controller" value="true" />
+            <param name="loop" value="true" />
+            <param name="autoplay" value="false" />
+            <param name="autostart" value="false" />
+            <param name="scale" value="aspect" />
+        <!--<![endif]-->
+            $fallback
+        <!--[if !IE]>-->
+        </object>
+        <!--<![endif]-->
+    </object>
+</span>
+OET;
+    }
+
+    public function get_supported_extensions() {
+        return array('mpg', 'mpeg', 'mov', 'mp4', 'm4v', 'm4a');
+    }
+
+    public function get_rank() {
+        return 50;
+    }
+}
+
+
+/**
+ * Media player using object tag and RealPlayer.
+ *
+ * Hopefully nobody is using this obsolete format any more!
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_rm extends core_media_player {
+    public function embed($urls, $name, $width, $height, $options) {
+        // Show first URL.
+        $firsturl = reset($urls);
+        $url = $firsturl->out(true);
+
+        // Get name to use as title.
+        $info = s($this->get_name($name, $urls));
+
+        // The previous version of this code has the following comment, which
+        // I don't understand, but trust it is correct:
+        // Note: the size is hardcoded intentionally because this does not work anyway!
+        $width = CORE_MEDIA_VIDEO_WIDTH;
+        $height = CORE_MEDIA_VIDEO_HEIGHT;
+
+        $fallback = core_media_player::PLACEHOLDER;
+        return <<<OET
+<span class="mediaplugin mediaplugin_real">
+    <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]-->
+            $fallback
+        <!--[if !IE]>-->
+        </object>
+        <!--<![endif]-->
+  </object>
+</span>
+OET;
+    }
+
+    public function get_supported_extensions() {
+        return array('ra', 'ram', 'rm', 'rv');
+    }
+
+    public function get_rank() {
+        return 40;
+    }
+}
+
+
+/**
+ * Media player for Flash SWF files.
+ *
+ * This player contains additional security restriction: it will only be used
+ * if you add option core_media_player_swf::ALLOW = true.
+ *
+ * Code should only set this option if it has verified that the data was
+ * embedded by a trusted user (e.g. in trust text).
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_swf extends core_media_player {
+    public function embed($urls, $name, $width, $height, $options) {
+        self::pick_video_size($width, $height);
+
+        $firsturl = reset($urls);
+        $url = $firsturl->out(true);
+
+        $fallback = core_media_player::PLACEHOLDER;
+        $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]-->
+$fallback
+<!--[if !IE]>-->
+    </object>
+<!--<![endif]-->
+  </object>
+</span>
+OET;
+
+        return $output;
+    }
+
+    public function get_supported_extensions() {
+        return array('swf');
+    }
+
+    public function list_supported_urls(array $urls, array $options = array()) {
+        // Not supported unless the creator is trusted.
+        if (empty($options[core_media::OPTION_TRUSTED])) {
+            return array();
+        }
+        return parent::list_supported_urls($urls, $options);
+    }
+
+    public function get_rank() {
+        return 30;
+    }
+}
+
+
+/**
+ * Player that creates HTML5 <video> tag.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_html5video extends core_media_player {
+    public function embed($urls, $name, $width, $height, $options) {
+        // Special handling to make videos play on Android devices pre 2.3.
+        // Note: I tested and 2.3.3 (in emulator) works without, is 533.1 webkit.
+        $oldandroid = check_browser_version('WebKit Android') &&
+                !check_browser_version('WebKit Android', '533.1');
+
+        // Build array of source tags.
+        $sources = array();
+        foreach ($urls as $url) {
+            $mimetype = core_media::get_mimetype($url);
+            $source = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype));
+            if ($mimetype === 'video/mp4') {
+                if ($oldandroid) {
+                    // Old Android fails if you specify the type param.
+                    $source = html_writer::tag('source', '', array('src' => $url));
+                }
+
+                // Better add m4v as first source, it might be a bit more
+                // compatible with problematic browsers.
+                array_unshift($sources, $source);
+            } else {
+                $sources[] = $source;
+            }
+        }
+
+        $sources = implode("\n", $sources);
+        $title = s($this->get_name($name, $urls));
+
+        if (!$width) {
+            // No width specified, use system default.
+            $width = CORE_MEDIA_VIDEO_WIDTH;
+        }
+
+        if (!$height) {
+            // Let browser choose height automatically.
+            $size = "width=\"$width\"";
+        } else {
+            $size = "width=\"$width\" height=\"$height\"";
+        }
+
+        $sillyscript = '';
+        $idtag = '';
+        if ($oldandroid) {
+            // Old Android does not support 'controls' option.
+            $id = 'core_media_html5v_' . md5(time() . '_' . rand());
+            $idtag = 'id="' . $id . '"';
+            $sillyscript = <<<OET
+<script type="text/javascript">
+document.getElementById('$id').addEventListener('click', function() {
+    this.play();
+}, false);
+</script>
+OET;
+        }
+
+        $fallback = core_media_player::PLACEHOLDER;
+        return <<<OET
+<span class="mediaplugin mediaplugin_html5video">
+<video $idtag controls="true" $size preload="metadata" title="$title">
+    $sources
+    $fallback
+</video>
+$sillyscript
+</span>
+OET;
+    }
+
+    public function get_supported_extensions() {
+        return array('m4v', 'webm', 'ogv', 'mp4');
+    }
+
+    public function list_supported_urls(array $urls, array $options = array()) {
+        $extensions = $this->get_supported_extensions();
+        $result = array();
+        foreach ($urls as $url) {
+            $ext = core_media::get_extension($url);
+            if (in_array($ext, $extensions)) {
+                // Unfortunately html5 video does not handle fallback properly.
+                // https://www.w3.org/Bugs/Public/show_bug.cgi?id=10975
+                // That means we need to do browser detect and not use html5 on
+                // browsers which do not support the given type, otherwise users
+                // will not even see the fallback link.
+                // Based on http://en.wikipedia.org/wiki/HTML5_video#Table - this
+                // is a simplified version, does not take into account old browser
+                // versions or manual plugins.
+                if ($ext === 'ogv' || $ext === 'webm') {
+                    // Formats .ogv and .webm are not supported in IE or Safari.
+                    if (check_browser_version('MSIE') || check_browser_version('Safari')) {
+                        continue;
+                    }
+                } else {
+                    // Formats .m4v and .mp4 are not supported in Firefox or Opera.
+                    if (check_browser_version('Firefox') || check_browser_version('Opera')) {
+                        continue;
+                    }
+                }
+
+                $result[] = $url;
+            }
+        }
+        return $result;
+    }
+
+    public function get_rank() {
+        return 20;
+    }
+}
+
+
+/**
+ * Player that creates HTML5 <audio> tag.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_html5audio extends core_media_player {
+    public function embed($urls, $name, $width, $height, $options) {
+
+        // Build array of source tags.
+        $sources = array();
+        foreach ($urls as $url) {
+            $mimetype = core_media::get_mimetype($url);
+            $sources[] = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype));
+        }
+
+        $sources = implode("\n", $sources);
+        $title = s($this->get_name($name, $urls));
+
+        // Default to not specify size (so it can be changed in css).
+        $size = '';
+        if ($width) {
+            $size = 'width="' . $width . '"';
+        }
+
+        $fallback = core_media_player::PLACEHOLDER;
+
+        return <<<OET
+<audio controls="true" $size class="mediaplugin mediaplugin_html5audio" preload="no" title="$title">
+$sources
+$fallback
+</audio>
+OET;
+    }
+
+    public function get_supported_extensions() {
+        return array('ogg', 'oga', 'aac', 'm4a', 'mp3');
+    }
+
+    public function list_supported_urls(array $urls, array $options = array()) {
+        $extensions = $this->get_supported_extensions();
+        $result = array();
+        foreach ($urls as $url) {
+            $ext = core_media::get_extension($url);
+            if (in_array($ext, $extensions)) {
+                if ($ext === 'ogg' || $ext === 'oga') {
+                    // Formats .ogg and .oga are not supported in IE or Safari.
+                    if (check_browser_version('MSIE') || check_browser_version('Safari')) {
+                        continue;
+                    }
+                } else {
+                    // Formats .aac, .mp3, and .m4a are not supported in Firefox or Opera.
+                    if (check_browser_version('Firefox') || check_browser_version('Opera')) {
+                        continue;
+                    }
+                }
+                // Old Android versions (pre 2.3.3) 'support' audio tag but no codecs.
+                if (check_browser_version('WebKit Android') &&
+                        !check_browser_version('WebKit Android', '533.1')) {
+                    continue;
+                }
+
+                $result[] = $url;
+            }
+        }
+        return $result;
+    }
+
+    public function get_rank() {
+        return 10;
+    }
+}
+
+
+/**
+ * Special media player class that just puts a link.
+ *
+ * Always enabled, used as the last fallback.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_player_link extends core_media_player {
+    public function embed($urls, $name, $width, $height, $options) {
+        // If link is turned off, return empty.
+        if (!empty($options[core_media::OPTION_NO_LINK])) {
+            return '';
+        }
+
+        // Build up link content.
+        $output = '';
+        foreach ($urls as $url) {
+            $title = core_media::get_filename($url);
+            $printlink = html_writer::link($url, $title, array('class' => 'mediafallbacklink'));
+            if ($output) {
+                // Where there are multiple available formats, there are fallback links
+                // for all formats, separated by /.
+                $output .= ' / ';
+            }
+            $output .= $printlink;
+        }
+        return $output;
+    }
+
+    public function list_supported_urls(array $urls, array $options = array()) {
+        // Supports all URLs.
+        return $urls;
+    }
+
+    public function is_enabled() {
+        // Cannot be disabled.
+        return true;
+    }
+
+    public function get_rank() {
+        return 0;
+    }
+}
index 1e69ce7..d781292 100644 (file)
@@ -7707,6 +7707,7 @@ function get_core_subsystems() {
             'langconfig'  => NULL,
             'license'     => NULL,
             'mathslib'    => NULL,
+            'media'       => 'media',
             'message'     => 'message',
             'mimetypes'   => NULL,
             'mnet'        => 'mnet',
index f0d73e9..9e24b0f 100644 (file)
@@ -2901,3 +2901,257 @@ class core_renderer_ajax extends core_renderer {
      */
     public function heading($text, $level = 2, $classes = 'main', $id = null) {}
 }
+
+
+/**
+ * Renderer for media files.
+ *
+ * Used in file resources, media filter, and any other places that need to
+ * output embedded media.
+ *
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_media_renderer extends plugin_renderer_base {
+    /** @var array Array of available 'player' objects */
+    private $players;
+    /** @var string Regex pattern for links which may contain embeddable content */
+    private $embeddablemarkers;
+
+    /**
+     * Constructor requires medialib.php.
+     *
+     * This is needed in the constructor (not later) so that you can use the
+     * constants and static functions that are defined in core_media class
+     * before you call renderer functions.
+     */
+    public function __construct() {
+        global $CFG;
+        require_once($CFG->libdir . '/medialib.php');
+    }
+
+    /**
+     * Obtains the list of core_media_player objects currently in use to render
+     * items.
+     *
+     * The list is in rank order (highest first) and does not include players
+     * which are disabled.
+     *
+     * @return array Array of core_media_player objects in rank order
+     */
+    protected function get_players() {
+        global $CFG;
+
+        // Save time by only building the list once
+        if (!$this->players) {
+            // Get raw list of players.
+            $players = $this->get_players_raw();
+
+            // Chuck all the ones that are disabled.
+            foreach ($players as $key => $player) {
+                if (!$player->is_enabled()) {
+                    unset($players[$key]);
+                }
+            }
+
+            // Sort in rank order (highest first).
+            usort($players, array('core_media_player', 'compare_by_rank'));
+            $this->players = $players;
+        }
+        return $this->players;
+    }
+
+    /**
+     * Obtains a raw list of player objects that includes objects regardless
+     * of whether they are disabled or not, and without sorting.
+     *
+     * You can override this in a subclass if you need to add additional
+     * players.
+     *
+     * The return array is be indexed by player name to make it easier to
+     * remove players in a subclass.
+     *
+     * @return array $players Array of core_media_player objects in any order
+     */
+    protected function get_players_raw() {
+        return array(
+            'vimeo' => new core_media_player_vimeo(),
+            'youtube' => new core_media_player_youtube(),
+            'youtube_playlist' => new core_media_player_youtube_playlist(),
+            'html5video' => new core_media_player_html5video(),
+            'html5audio' => new core_media_player_html5audio(),
+            'mp3' => new core_media_player_mp3(),
+            'flv' => new core_media_player_flv(),
+            'wmp' => new core_media_player_wmp(),
+            'qt' => new core_media_player_qt(),
+            'rm' => new core_media_player_rm(),
+            'swf' => new core_media_player_swf(),
+            'link' => new core_media_player_link(),
+        );
+    }
+
+    /**
+     * Renders a media file (audio or video) using suitable embedded player.
+     *
+     * See embed_alternatives function for full description of parameters.
+     * This function calls through to that one.
+     *
+     * When using this function you can also specify width and height in the
+     * URL by including ?d=100x100 at the end. If specified in the URL, this
+     * will override the $width and $height parameters.
+     *
+     * @param moodle_url $url Full URL of media file
+     * @param string $name Optional user-readable name to display in download link
+     * @param int $width Width in pixels (optional)
+     * @param int $height Height in pixels (optional)
+     * @param array $options Array of key/value pairs
+     * @return string HTML content of embed
+     */
+    public function embed_url(moodle_url $url, $name = '', $width = 0, $height = 0,
+            $options = array()) {
+
+        // Get width and height from URL if specified (overrides parameters in
+        // function call).
+        $rawurl = $url->out(false);
+        if (preg_match('/[?#]d=([\d]{1,4}%?)x([\d]{1,4}%?)/', $rawurl, $matches)) {
+            $width = $matches[1];
+            $height = $matches[2];
+            $url = new moodle_url(str_replace($matches[0], '', $rawurl));
+        }
+
+        // Defer to array version of function.
+        return $this->embed_alternatives(array($url), $name, $width, $height, $options);
+    }
+
+    /**
+     * Renders media files (audio or video) using suitable embedded player.
+     * The list of URLs should be alternative versions of the same content in
+     * multiple formats. If there is only one format it should have a single
+     * entry.
+     *
+     * If the media files are not in a supported format, this will give students
+     * a download link to each format. The download link uses the filename
+     * unless you supply the optional name parameter.
+     *
+     * Width and height are optional. If specified, these are suggested sizes
+     * and should be the exact values supplied by the user, if they come from
+     * user input. These will be treated as relating to the size of the video
+     * content, not including any player control bar.
+     *
+     * For audio files, height will be ignored. For video files, a few formats
+     * work if you specify only width, but in general if you specify width
+     * you must specify height as well.
+     *
+     * The $options array is passed through to the core_media_player classes
+     * that render the object tag. The keys can contain values from
+     * core_media::OPTION_xx.
+     *
+     * @param array $alternatives Array of moodle_url to media files
+     * @param string $name Optional user-readable name to display in download link
+     * @param int $width Width in pixels (optional)
+     * @param int $height Height in pixels (optional)
+     * @param array $options Array of key/value pairs
+     * @return string HTML content of embed
+     */
+    public function embed_alternatives($alternatives, $name = '', $width = 0, $height = 0,
+            $options = array()) {
+
+        // Get list of player plugins (will also require the library).
+        $players = $this->get_players();
+
+        // Set up initial text which will be replaced by first player that
+        // supports any of the formats.
+        $out = core_media_player::PLACEHOLDER;
+
+        // Loop through all players that support any of these URLs.
+        foreach ($players as $player) {
+            // Option: When no other player matched, don't do the default link player.
+            if (!empty($options[core_media::OPTION_FALLBACK_TO_BLANK]) &&
+                    $player->get_rank() === 0 && $out === core_media_player::PLACEHOLDER) {
+                continue;
+            }
+
+            $supported = $player->list_supported_urls($alternatives, $options);
+            if ($supported) {
+                // Embed.
+                $text = $player->embed($supported, $name, $width, $height, $options);
+
+                // Put this in place of the 'fallback' slot in the previous text.
+                $out = str_replace(core_media_player::PLACEHOLDER, $text, $out);
+            }
+        }
+
+        // Remove 'fallback' slot from final version and return it.
+        $out = str_replace(core_media_player::PLACEHOLDER, '', $out);
+        if (!empty($options[core_media::OPTION_BLOCK]) && $out !== '') {
+            $out = html_writer::tag('div', $out, array('class' => 'resourcecontent'));
+        }
+        return $out;
+    }
+
+    /**
+     * Checks whether a file can be embedded. If this returns true you will get
+     * an embedded player; if this returns false, you will just get a download
+     * link.
+     *
+     * This is a wrapper for can_embed_urls.
+     *
+     * @param moodle_url $url URL of media file
+     * @param array $options Options (same as when embedding)
+     * @return bool True if file can be embedded
+     */
+    public function can_embed_url(moodle_url $url, $options = array()) {
+        return $this->can_embed_urls(array($url), $options);
+    }
+
+    /**
+     * Checks whether a file can be embedded. If this returns true you will get
+     * an embedded player; if this returns false, you will just get a download
+     * link.
+     *
+     * @param array $urls URL of media file and any alternatives (moodle_url)
+     * @param array $options Options (same as when embedding)
+     * @return bool True if file can be embedded
+     */
+    public function can_embed_urls(array $urls, $options = array()) {
+        // Check all players to see if any of them support it.
+        foreach ($this->get_players() as $player) {
+            // Link player (always last on list) doesn't count!
+            if ($player->get_rank() <= 0) {
+                break;
+            }
+            // First player that supports it, return true.
+            if ($player->list_supported_urls($urls, $options)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Obtains a list of markers that can be used in a regular expression when
+     * searching for URLs that can be embedded by any player type.
+     *
+     * This string is used to improve peformance of regex matching by ensuring
+     * that the (presumably C) regex code can do a quick keyword check on the
+     * URL part of a link to see if it matches one of these, rather than having
+     * to go into PHP code for every single link to see if it can be embedded.
+     *
+     * @return string String suitable for use in regex such as '(\.mp4|\.flv)'
+     */
+    public function get_embeddable_markers() {
+        if (empty($this->embeddablemarkers)) {
+            $markers = '';
+            foreach ($this->get_players() as $player) {
+                foreach ($player->get_embeddable_markers() as $marker) {
+                    if ($markers !== '') {
+                        $markers .= '|';
+                    }
+                    $markers .= preg_quote($marker);
+                }
+            }
+            $this->embeddablemarkers = $markers;
+        }
+        return $this->embeddablemarkers;
+    }
+}
diff --git a/lib/tests/medialib_test.php b/lib/tests/medialib_test.php
new file mode 100644 (file)
index 0000000..b73f053
--- /dev/null
@@ -0,0 +1,608 @@
+<?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/>.
+
+/**
+ * Test classes for handling embedded media (audio/video).
+ *
+ * @package core_media
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/medialib.php');
+
+/**
+ * Test script for media embedding.
+ */
+class medialib_testcase extends advanced_testcase {
+
+    /** @var array Files covered by test */
+    public static $includecoverage = array('lib/medialib.php', 'lib/outputrenderers.php');
+
+    /**
+     * Pre-test setup. Preserves $CFG.
+     */
+    public function setUp() {
+        global $CFG;
+        parent::setUp();
+
+        // Reset CFG
+        $this->resetAfterTest(true);
+
+        // Consistent initial setup: all players disabled.
+        $CFG->core_media_enable_html5video = false;
+        $CFG->core_media_enable_html5audio = false;
+        $CFG->core_media_enable_mp3 = false;
+        $CFG->core_media_enable_flv = false;
+        $CFG->core_media_enable_wmp = false;
+        $CFG->core_media_enable_qt = false;
+        $CFG->core_media_enable_rm = false;
+        $CFG->core_media_enable_youtube = false;
+        $CFG->core_media_enable_vimeo = false;
+        $CFG->core_media_enable_swf = false;
+
+        // Strict headers turned off.
+        $CFG->xmlstrictheaders = false;
+
+        $_SERVER = array('HTTP_USER_AGENT' => '');
+        $this->pretend_to_be_safari();
+    }
+
+    /**
+     * Sets user agent to Safari.
+     */
+    private function pretend_to_be_safari() {
+        // Pretend to be using Safari browser (must support mp4 for tests to work).
+        $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) ' .
+                'AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1';
+    }
+
+    /**
+     * Sets user agent to Firefox.
+     */
+    private function pretend_to_be_firefox() {
+        // Pretend to be using Firefox browser (must support ogg for tests to work).
+        $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 6.1; rv:8.0) Gecko/20100101 Firefox/8.0';
+    }
+
+    /**
+     * Post-test cleanup. Replaces old $CFG.
+     */
+    public function tearDown() {
+        // Replace original user agent.
+        if (isset($this->realserver)) {
+            $_SERVER = $this->realserver;
+        }
+
+        parent::tearDown();
+    }
+
+    /**
+     * Test for the core_media_player is_enabled.
+     */
+    public function test_is_enabled() {
+        global $CFG;
+
+        // Test enabled: unset, 0, 1.
+        $test = new core_media_player_test;
+        $this->assertFalse($test->is_enabled());
+        $CFG->core_media_enable_test = 0;
+        $this->assertFalse($test->is_enabled());
+        $CFG->core_media_enable_test = 1;
+        $this->assertTrue($test->is_enabled());
+    }
+
+    /**
+     * Test for the core_media_player list_supported_urls.
+     */
+    public function test_list_supported_urls() {
+        global $CFG;
+        $test = new core_media_player_test;
+
+        // Some example URLs.
+        $supported1 = new moodle_url('http://example.org/1.test');
+        $supported2 = new moodle_url('http://example.org/2.TST');
+        $unsupported = new moodle_url('http://example.org/2.jpg');
+
+        // No URLs => none.
+        $result = $test->list_supported_urls(array());
+        $this->assertEquals(array(), $result);
+
+        // One supported URL => same.
+        $result = $test->list_supported_urls(array($supported1));
+        $this->assertEquals(array($supported1), $result);
+
+        // Two supported URLS => same.
+        $result = $test->list_supported_urls(array($supported1, $supported2));
+        $this->assertEquals(array($supported1, $supported2), $result);
+
+        // One unsupported => none.
+        $result = $test->list_supported_urls(array($unsupported));
+        $this->assertEquals(array(), $result);
+
+        // Two supported and one unsupported => same.
+        $result = $test->list_supported_urls(array($supported2, $unsupported, $supported1));
+        $this->assertEquals(array($supported2, $supported1), $result);
+    }
+
+    /**
+     * Test for core_media_renderer get_players
+     */
+    public function test_get_players() {
+        global $CFG, $PAGE;
+
+        // All players are initially disabled (except link, which you can't).
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $this->assertEquals('link', $renderer->get_players_test());
+
+        // A couple enabled, check the order.
+        $CFG->core_media_enable_html5audio = true;
+        $CFG->core_media_enable_mp3 = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $this->assertEquals('mp3, html5audio, link', $renderer->get_players_test());
+    }
+
+    /**
+     * Test for core_media_renderer can_embed_url
+     */
+    public function test_can_embed_url() {
+        global $CFG, $PAGE;
+
+        // All players are initially disabled, so mp4 cannot be rendered.
+        $url = new moodle_url('http://example.org/test.mp4');
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $this->assertFalse($renderer->can_embed_url($url));
+
+        // Enable QT player.
+        $CFG->core_media_enable_qt = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $this->assertTrue($renderer->can_embed_url($url));
+
+        // QT + html5.
+        $CFG->core_media_enable_html5video = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $this->assertTrue($renderer->can_embed_url($url));
+
+        // Only html5.
+        $CFG->core_media_enable_qt = false;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $this->assertTrue($renderer->can_embed_url($url));
+
+        // Only WMP.
+        $CFG->core_media_enable_html5video = false;
+        $CFG->core_media_enable_wmp = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $this->assertFalse($renderer->can_embed_url($url));
+    }
+
+    /**
+     * Test for core_media_renderer embed_url.
+     * Checks multiple format/fallback support.
+     */
+    public function test_embed_url_fallbacks() {
+        global $CFG, $PAGE;
+
+        $url = new moodle_url('http://example.org/test.mp4');
+
+        // All plugins disabled, NOLINK option.
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $t = $renderer->embed_url($url, 0, 0, '',
+                array(core_media::OPTION_NO_LINK => true));
+        // Completely empty.
+        $this->assertEquals('', $t);
+
+        // All plugins disabled but not NOLINK.
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $t = $renderer->embed_url($url);
+        $this->assert_contents(false, false, true, $t);
+
+        // HTML5 plugin enabled.
+        $CFG->core_media_enable_html5video = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $t = $renderer->embed_url($url);
+        $this->assert_contents(false, true, true, $t);
+
+        // QT plugin enabled as well.
+        $CFG->core_media_enable_qt = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $t = $renderer->embed_url($url);
+        $this->assert_contents(true, true, true, $t);
+
+        // Nolink turned off, plugins still enabled.
+        $t = $renderer->embed_url($url, 0, 0, '',
+                array(core_media::OPTION_NO_LINK => true));
+        $this->assert_contents(true, true, false, $t);
+    }
+
+    /**
+     * Checks the contents of the resulting HTML code to ensure it used the
+     * correct embed method(s).
+     *
+     * @param bool $hasqt True if it should have QT embed code
+     * @param bool $hashtml5 True if it should have HTML5 embed code
+     * @param bool $haslink True if it should have a fallback link
+     * @param string $text HTML content
+     */
+    private function assert_contents($hasqt, $hashtml5, $haslink, $text) {
+        // I tried to avoid making it specific to the exact details of the html
+        // code, picking out only single key strings that would let it check
+        // whether final code contains the right things.
+        $qt = 'qtplugin.cab';
+        $html5 = '</video>';
+        $link = 'mediafallbacklink';
+
+        $this->assertEquals($hasqt, self::str_contains($text, $qt));
+        $this->assertEquals($hashtml5, self::str_contains($text, $html5));
+        $this->assertEquals($haslink, self::str_contains($text, $link));
+    }
+
+    /**
+     * Test for core_media_renderer embed_url.
+     * Check SWF works including the special option required to enable it
+     */
+    public function test_embed_url_swf() {
+        global $CFG, $PAGE;
+        $CFG->core_media_enable_swf = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+
+        // Without any options...
+        $url = new moodle_url('http://example.org/test.swf');
+        $t = $renderer->embed_url($url);
+        $this->assertFalse(self::str_contains($t, '</object>'));
+
+        // ...and with the 'no it's safe, I checked it' option.
+        $url = new moodle_url('http://example.org/test.swf');
+        $t = $renderer->embed_url($url, '', 0, 0, array(core_media::OPTION_TRUSTED => true));
+        $this->assertTrue(self::str_contains($t, '</object>'));
+    }
+
+    /**
+     * Test for core_media_renderer embed_url.
+     * Exercises all the basic formats not covered elsewhere.
+     */
+    public function test_embed_url_other_formats() {
+        global $CFG, $PAGE;
+
+        // Enable all players and get renderer.
+        $CFG->core_media_enable_html5audio = true;
+        $CFG->core_media_enable_mp3 = true;
+        $CFG->core_media_enable_flv = true;
+        $CFG->core_media_enable_wmp = true;
+        $CFG->core_media_enable_rm = true;
+        $CFG->core_media_enable_youtube = true;
+        $CFG->core_media_enable_vimeo = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+
+        // Check each format one at a time. This is a basic check to be sure
+        // the HTML is included for files of the right type, not a test that
+        // the HTML itself is correct.
+
+        // Format: mp3.
+        $url = new moodle_url('http://example.org/test.mp3');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, 'core_media_mp3_'));
+
+        // Format: flv.
+        $url = new moodle_url('http://example.org/test.flv');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, 'core_media_flv_'));
+
+        // Format: wmp.
+        $url = new moodle_url('http://example.org/test.avi');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, '6BF52A52-394A-11d3-B153-00C04F79FAA6'));
+
+        // Format: rm.
+        $url = new moodle_url('http://example.org/test.rm');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, 'CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA'));
+
+        // Format: youtube.
+        $url = new moodle_url('http://www.youtube.com/watch?v=vyrwMmsufJc');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, '</iframe>'));
+        $url = new moodle_url('http://www.youtube.com/v/vyrwMmsufJc');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, '</iframe>'));
+
+        // Format: youtube playlist.
+        $url = new moodle_url('http://www.youtube.com/view_play_list?p=PL6E18E2927047B662');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, '</object>'));
+        $url = new moodle_url('http://www.youtube.com/playlist?list=PL6E18E2927047B662');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, '</object>'));
+        $url = new moodle_url('http://www.youtube.com/p/PL6E18E2927047B662');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, '</object>'));
+
+        // Format: vimeo.
+        $url = new moodle_url('http://vimeo.com/1176321');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, '</iframe>'));
+
+        // Format: html5audio.
+        $this->pretend_to_be_firefox();
+        $url = new moodle_url('http://example.org/test.ogg');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, '</audio>'));
+    }
+
+    /**
+     * Test for core_media_renderer embed_url.
+     * Checks the EMBED_OR_BLANK option.
+     */
+    public function test_embed_or_blank() {
+        global $CFG, $PAGE;
+        $CFG->core_media_enable_html5audio = true;
+        $this->pretend_to_be_firefox();
+
+        $renderer = new core_media_renderer_test($PAGE, '');
+
+        $options = array(core_media::OPTION_FALLBACK_TO_BLANK => true);
+
+        // Embed that does match something should still include the link too.
+        $url = new moodle_url('http://example.org/test.ogg');
+        $t = $renderer->embed_url($url, '', 0, 0, $options);
+        $this->assertTrue(self::str_contains($t, '</audio>'));
+        $this->assertTrue(self::str_contains($t, 'mediafallbacklink'));
+
+        // Embed that doesn't match something should be totally blank.
+        $url = new moodle_url('http://example.org/test.mp4');
+        $t = $renderer->embed_url($url, '', 0, 0, $options);
+        $this->assertEquals('', $t);
+    }
+
+    /**
+     * Test for core_media_renderer embed_url.
+     * Checks that size is passed through correctly to player objects and tests
+     * size support in html5video output.
+     */
+    public function test_embed_url_size() {
+        global $CFG, $PAGE;
+
+        // Technically this could break in every format and they handle size
+        // in several different ways, but I'm too lazy to test it in every
+        // format, so let's just pick one to check the values get passed
+        // through.
+        $CFG->core_media_enable_html5video = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $url = new moodle_url('http://example.org/test.mp4');
+
+        // HTML5 default size - specifies core width and does not specify height.
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, 'width="' . CORE_MEDIA_VIDEO_WIDTH . '"'));
+        $this->assertFalse(self::str_contains($t, 'height'));
+
+        // HTML5 specified size - specifies both.
+        $t = $renderer->embed_url($url, '', '666', '101');
+        $this->assertTrue(self::str_contains($t, 'width="666"'));
+        $this->assertTrue(self::str_contains($t, 'height="101"'));
+
+        // HTML5 size specified in url, overrides call.
+        $url = new moodle_url('http://example.org/test.mp4?d=123x456');
+        $t = $renderer->embed_url($url, '', '666', '101');
+        $this->assertTrue(self::str_contains($t, 'width="123"'));
+        $this->assertTrue(self::str_contains($t, 'height="456"'));
+    }
+
+    /**
+     * Test for core_media_renderer embed_url.
+     * Checks that name is passed through correctly to player objects and tests
+     * name support in html5video output.
+     */
+    public function test_embed_url_name() {
+        global $CFG, $PAGE;
+
+        // As for size this could break in every format but I'm only testing
+        // html5video.
+        $CFG->core_media_enable_html5video = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+        $url = new moodle_url('http://example.org/test.mp4');
+
+        // HTML5 default name - use filename.
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, 'title="test.mp4"'));
+
+        // HTML5 specified name - check escaping.
+        $t = $renderer->embed_url($url, 'frog & toad');
+        $this->assertTrue(self::str_contains($t, 'title="frog &amp; toad"'));
+    }
+
+    /**
+     * Test for core_media_renderer split_alternatives.
+     */
+    public function test_split_alternatives() {
+        // Single URL - identical moodle_url.
+        $mp4 = 'http://example.org/test.mp4';
+        $result = core_media::split_alternatives($mp4, $w, $h);
+        $this->assertEquals($mp4, $result[0]->out(false));
+
+        // Width and height weren't specified.
+        $this->assertEquals(0, $w);
+        $this->assertEquals(0, $h);
+
+        // Two URLs - identical moodle_urls.
+        $webm = 'http://example.org/test.webm';
+        $result = core_media::split_alternatives("$mp4#$webm", $w, $h);
+        $this->assertEquals($mp4, $result[0]->out(false));
+        $this->assertEquals($webm, $result[1]->out(false));
+
+        // Two URLs plus dimensions.
+        $size = 'd=400x280';
+        $result = core_media::split_alternatives("$mp4#$webm#$size", $w, $h);
+        $this->assertEquals($mp4, $result[0]->out(false));
+        $this->assertEquals($webm, $result[1]->out(false));
+        $this->assertEquals(400, $w);
+        $this->assertEquals(280, $h);
+
+        // Two URLs plus legacy dimensions (use last one).
+        $result = core_media::split_alternatives("$mp4?d=1x1#$webm?$size", $w, $h);
+        $this->assertEquals($mp4, $result[0]->out(false));
+        $this->assertEquals($webm, $result[1]->out(false));
+        $this->assertEquals(400, $w);
+        $this->assertEquals(280, $h);
+    }
+
+    /**
+     * Test for core_media_renderer embed_alternatives (with multiple urls)
+     */
+    public function test_embed_alternatives() {
+        global $PAGE, $CFG;
+
+        // Most aspects of this are same as single player so let's just try
+        // a single typical / complicated scenario.
+
+        // MP3, WebM and FLV.
+        $urls = array(
+            new moodle_url('http://example.org/test.mp4'),
+            new moodle_url('http://example.org/test.webm'),
+            new moodle_url('http://example.org/test.flv'),
+        );
+
+        // Enable html5 and flv.
+        $CFG->core_media_enable_html5video = true;
+        $CFG->core_media_enable_flv = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+
+        // Result should contain HTML5 with two sources + FLV.
+        $t = $renderer->embed_alternatives($urls);
+
+        // HTML5 sources - mp4, not flv or webm (not supported in Safari).
+        $this->assertTrue(self::str_contains($t, '<source src="http://example.org/test.mp4"'));
+        $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.webm"'));
+        $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.flv"'));
+
+        // FLV is before the video tag (indicating html5 is used as fallback to flv
+        // and not vice versa).
+        $this->assertTrue((bool)preg_match('~core_media_flv_.*<video~s', $t));
+
+        // Do same test with firefox and check we get the webm and not mp4.
+        $this->pretend_to_be_firefox();
+        $t = $renderer->embed_alternatives($urls);
+
+        // HTML5 sources - webm, not not flv or mp4 (not supported in Firefox).
+        $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.mp4"'));
+        $this->assertTrue(self::str_contains($t, '<source src="http://example.org/test.webm"'));
+        $this->assertFalse(self::str_contains($t, '<source src="http://example.org/test.flv"'));
+    }
+
+    /**
+     * Checks if text contains a given string. Should be PHP built-in function,
+     * but oddly isn't.
+     *
+     * @param string $text Text
+     * @param string $value Value that should be contained within the text
+     * @return bool True if value is contained
+     */
+    public static function str_contains($text, $value) {
+        return strpos($text, $value) !== false;
+    }
+
+    /**
+     * Converts moodle_url array into a single comma-separated string for
+     * easier testing.
+     *
+     * @param array $urls Array of moodle_urls
+     * @return string String containing those URLs, comma-separated
+     */
+    public static function string_urls($urls) {
+        $out = array();
+        foreach ($urls as $url) {
+            $out[] = $url->out(false);
+        }
+        return implode(',', $out);
+    }
+
+    /**
+     * Converts associative array into a semicolon-separated string for easier
+     * testing.
+     *
+     * @param array $options Associative array
+     * @return string String of form 'a=b;c=d'
+     */
+    public static function string_options($options) {
+        $out = '';
+        foreach ($options as $key => $value) {
+            if ($out) {
+                $out .= ';';
+            }
+            $out .= "$key=$value";
+        }
+        return $out;
+    }
+}
+
+/**
+ * Media player stub for testing purposes.
+ */
+class core_media_player_test extends core_media_player {
+    /** @var array Array of supported extensions */
+    public $ext;
+    /** @var int Player rank */
+    public $rank;
+    /** @var int Arbitrary number */
+    public $num;
+
+    /**
+     * @param int $num Number (used in output)
+     * @param int $rank Player rank
+     * @param array $ext Array of supported extensions
+     */
+    public function __construct($num = 1, $rank = 13, $ext = array('tst', 'test')) {
+        $this->ext = $ext;
+        $this->rank = $rank;
+        $this->num = $num;
+    }
+
+    public function embed($urls, $name, $width, $height, $options) {
+        return $this->num . ':' . medialib_test::string_urls($urls) .
+                ",$name,$width,$height,<!--FALLBACK-->," . medialib_test::string_options($options);
+    }
+
+    public function get_supported_extensions() {
+        return $this->ext;
+    }
+
+    public function get_rank() {
+        return $this->rank;
+    }
+}
+
+/**
+ * Media renderer override for testing purposes.
+ */
+class core_media_renderer_test extends core_media_renderer {
+    /**
+     * Access list of players as string, shortening it by getting rid of
+     * repeated text.
+     * @return string Comma-separated list of players
+     */
+    public function get_players_test() {
+        $players = $this->get_players();
+        $out = '';
+        foreach ($players as $player) {
+            if ($out) {
+                $out .= ', ';
+            }
+            $out .= str_replace('core_media_player_', '', get_class($player));
+        }
+        return $out;
+    }
+}
index ffe792a..8bd6316 100644 (file)
@@ -12,7 +12,9 @@ Note:
 debug messages and will produce fatal errors
 
 API changes:
+
 * send_stored_file() has changed its interface
+* deleted several resourcelib_embed_* functions from resourcelib.php
 
 === 2.2 ===
 
index 3419e82..20957dc 100644 (file)
@@ -5,12 +5,16 @@ information provided here is intended especially for developers.
 === 2.3 ===
 
 required changes in code:
+
 * define the capability mod/xxx:addinstance (and the corresponding lang string)
   (unless your mod is a MOD_ARCHETYPE_SYSTEM).
 * xxx_pluginfile() is now given the 7th parameter (hopefully the last one) that
   contains additional options for the file serving. The array should be re-passed
   to send_stored_file().
 
+* most resourcelib_embed_* functions are replaced with core_media_renderer;
+  for an example, see mod/resource/locallib.php, resource_display_embed()
+
 
 === 2.2 ===
 
index 2857512..6eb8fa3 100644 (file)
@@ -765,8 +765,13 @@ body.tag .managelink {padding: 5px;}
 .dir-rtl .felement.feditor select {margin-right:18.75%;margin-left:auto;}
 .dir-rtl .mform .fitem .felement {margin-right: 16%;margin-left:auto;}
 
-/* Resourcelib mp3 player size: only width could be changed here, height hardcoded in JS */
-.resourcecontent .resourcemediaplugin_mp3 object {height:25px; width: 600px}
+/* Audio player size in 'block' mode (can only change width, height is hardcoded in JS) */
+.resourcecontent .mediaplugin_mp3 object {height:25px; width: 600px}
+.resourcecontent audio.mediaplugin_html5audio {width: 600px}
+
+/* Audio player size in 'inline' mode (can only change width, as above) */
+.mediaplugin_mp3 object {height:15px;width:300px}
+audio.mediaplugin_html5audio {width: 300px}
 
 /*
 Fix for SubScript & SuperScript