Merge branch 'MDL-52965_master' of git://github.com/dmonllao/moodle
[moodle.git] / lib / medialib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Classes for handling embedded media (mainly audio and video).
19  *
20  * These are used only from within the core media renderer.
21  *
22  * To embed media from Moodle code, do something like the following:
23  *
24  * $mediarenderer = $PAGE->get_renderer('core', 'media');
25  * echo $mediarenderer->embed_url(new moodle_url('http://example.org/a.mp3'));
26  *
27  * You do not need to require this library file manually. Getting the renderer
28  * (the first line above) requires this library file automatically.
29  *
30  * @package core_media
31  * @copyright 2012 The Open University
32  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33  */
35 defined('MOODLE_INTERNAL') || die();
37 if (!defined('CORE_MEDIA_VIDEO_WIDTH')) {
38     // Default video width if no width is specified; some players may do something
39     // more intelligent such as use real video width.
40     // May be defined in config.php if required.
41     define('CORE_MEDIA_VIDEO_WIDTH', 400);
42 }
43 if (!defined('CORE_MEDIA_VIDEO_HEIGHT')) {
44     // Default video height. May be defined in config.php if required.
45     define('CORE_MEDIA_VIDEO_HEIGHT', 300);
46 }
47 if (!defined('CORE_MEDIA_AUDIO_WIDTH')) {
48     // Default audio width if no width is specified.
49     // May be defined in config.php if required.
50     define('CORE_MEDIA_AUDIO_WIDTH', 300);
51 }
54 /**
55  * Constants and static utility functions for use with core_media_renderer.
56  *
57  * @copyright 2011 The Open University
58  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
59  */
60 abstract class core_media {
61     /**
62      * Option: Disable text link fallback.
63      *
64      * Use this option if you are going to print a visible link anyway so it is
65      * pointless to have one as fallback.
66      *
67      * To enable, set value to true.
68      */
69     const OPTION_NO_LINK = 'nolink';
71     /**
72      * Option: When embedding, if there is no matching embed, do not use the
73      * default link fallback player; instead return blank.
74      *
75      * This is different from OPTION_NO_LINK because this option still uses the
76      * fallback link if there is some kind of embedding. Use this option if you
77      * are going to check if the return value is blank and handle it specially.
78      *
79      * To enable, set value to true.
80      */
81     const OPTION_FALLBACK_TO_BLANK = 'embedorblank';
83     /**
84      * Option: Enable players which are only suitable for use when we trust the
85      * user who embedded the content.
86      *
87      * At present, this option enables the SWF player.
88      *
89      * To enable, set value to true.
90      */
91     const OPTION_TRUSTED = 'trusted';
93     /**
94      * Option: Put a div around the output (if not blank) so that it displays
95      * as a block using the 'resourcecontent' CSS class.
96      *
97      * To enable, set value to true.
98      */
99     const OPTION_BLOCK = 'block';
101     /**
102      * Given a string containing multiple URLs separated by #, this will split
103      * it into an array of moodle_url objects suitable for using when calling
104      * embed_alternatives.
105      *
106      * Note that the input string should NOT be html-escaped (i.e. if it comes
107      * from html, call html_entity_decode first).
108      *
109      * @param string $combinedurl String of 1 or more alternatives separated by #
110      * @param int $width Output variable: width (will be set to 0 if not specified)
111      * @param int $height Output variable: height (0 if not specified)
112      * @return array Array of 1 or more moodle_url objects
113      */
114     public static function split_alternatives($combinedurl, &$width, &$height) {
115         $urls = explode('#', $combinedurl);
116         $width = 0;
117         $height = 0;
118         $returnurls = array();
120         foreach ($urls as $url) {
121             $matches = null;
123             // You can specify the size as a separate part of the array like
124             // #d=640x480 without actually including a url in it.
125             if (preg_match('/^d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) {
126                 $width  = $matches[1];
127                 $height = $matches[2];
128                 continue;
129             }
131             // Can also include the ?d= as part of one of the URLs (if you use
132             // more than one they will be ignored except the last).
133             if (preg_match('/\?d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) {
134                 $width  = $matches[1];
135                 $height = $matches[2];
137                 // Trim from URL.
138                 $url = str_replace($matches[0], '', $url);
139             }
141             // Clean up url.
142             $url = clean_param($url, PARAM_URL);
143             if (empty($url)) {
144                 continue;
145             }
147             // Turn it into moodle_url object.
148             $returnurls[] = new moodle_url($url);
149         }
151         return $returnurls;
152     }
154     /**
155      * Returns the file extension for a URL.
156      * @param moodle_url $url URL
157      */
158     public static function get_extension(moodle_url $url) {
159         // Note: Does not use core_text (. is UTF8-safe).
160         $filename = self::get_filename($url);
161         $dot = strrpos($filename, '.');
162         if ($dot === false) {
163             return '';
164         } else {
165             return strtolower(substr($filename, $dot + 1));
166         }
167     }
169     /**
170      * Obtains the filename from the moodle_url.
171      * @param moodle_url $url URL
172      * @return string Filename only (not escaped)
173      */
174     public static function get_filename(moodle_url $url) {
175         global $CFG;
177         // Use the 'file' parameter if provided (for links created when
178         // slasharguments was off). If not present, just use URL path.
179         $path = $url->get_param('file');
180         if (!$path) {
181             $path = $url->get_path();
182         }
184         // Remove everything before last / if present. Does not use textlib as / is UTF8-safe.
185         $slash = strrpos($path, '/');
186         if ($slash !== false) {
187             $path = substr($path, $slash + 1);
188         }
189         return $path;
190     }
192     /**
193      * Guesses MIME type for a moodle_url based on file extension.
194      * @param moodle_url $url URL
195      * @return string MIME type
196      */
197     public static function get_mimetype(moodle_url $url) {
198         return mimeinfo('type', self::get_filename($url));
199     }
203 /**
204  * Base class for media players.
205  *
206  * Media players return embed HTML for a particular way of playing back audio
207  * or video (or another file type).
208  *
209  * In order to make the code more lightweight, this is not a plugin type
210  * (players cannot have their own settings, database tables, capabilities, etc).
211  * These classes are used only by core_media_renderer in outputrenderers.php.
212  * If you add a new class here (in core code) you must modify the
213  * get_players_raw function in that file to include it.
214  *
215  * If a Moodle installation wishes to add extra player objects they can do so
216  * by overriding that renderer in theme, and overriding the get_players_raw
217  * function. The new player class should then of course be defined within the
218  * custom theme or other suitable location, not in this file.
219  *
220  * @copyright 2011 The Open University
221  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
222  */
223 abstract class core_media_player {
224     /**
225      * Placeholder text used to indicate where the fallback content is placed
226      * within a result.
227      */
228     const PLACEHOLDER = '<!--FALLBACK-->';
230     /**
231      * Generates code required to embed the player.
232      *
233      * The returned code contains a placeholder comment '<!--FALLBACK-->'
234      * (constant core_media_player::PLACEHOLDER) which indicates the location
235      * where fallback content should be placed in the event that this type of
236      * player is not supported by user browser.
237      *
238      * The $urls parameter includes one or more alternative media formats that
239      * are supported by this player. It does not include formats that aren't
240      * supported (see list_supported_urls).
241      *
242      * The $options array contains key-value pairs. See OPTION_xx constants
243      * for documentation of standard option(s).
244      *
245      * @param array $urls URLs of media files
246      * @param string $name Display name; '' to use default
247      * @param int $width Optional width; 0 to use default
248      * @param int $height Optional height; 0 to use default
249      * @param array $options Options array
250      * @return string HTML code for embed
251      */
252     public abstract function embed($urls, $name, $width, $height, $options);
254     /**
255      * Gets the list of file extensions supported by this media player.
256      *
257      * Note: This is only required for the default implementation of
258      * list_supported_urls. If you override that function to determine
259      * supported URLs in some way other than by extension, then this function
260      * is not necessary.
261      *
262      * @return array Array of strings (extension not including dot e.g. 'mp3')
263      */
264     public function get_supported_extensions() {
265         return array();
266     }
268     /**
269      * Lists keywords that must be included in a url that can be embedded with
270      * this player. Any such keywords should be added to the array.
271      *
272      * For example if this player supports FLV and F4V files then it should add
273      * '.flv' and '.f4v' to the array. (The check is not case-sensitive.)
274      *
275      * Default handling calls the get_supported_extensions function and adds
276      * a dot to each of those values, so players only need to override this
277      * if they don't implement get_supported_extensions.
278      *
279      * This is used to improve performance when matching links in the media filter.
280      *
281      * @return array Array of keywords to add to the embeddable markers list
282      */
283     public function get_embeddable_markers() {
284         $markers = array();
285         foreach ($this->get_supported_extensions() as $extension) {
286             $markers[] = '.' . $extension;
287         }
288         return $markers;
289     }
291     /**
292      * Gets the ranking of this player. This is an integer used to decide which
293      * player to use (after applying other considerations such as which ones
294      * the user has disabled).
295      *
296      * Rank must be unique (no two players should have the same rank).
297      *
298      * Rank zero has a special meaning, indicating that this 'player' does not
299      * really embed the video.
300      *
301      * Rank is not a user-configurable value because it needs to be defined
302      * carefully in order to ensure that the embedding fallbacks actually work.
303      * It might be possible to have some user options which affect rank, but
304      * these would be best defined as e.g. checkboxes in settings that have
305      * a particular effect on the rank of a couple of plugins, rather than
306      * letting users generally alter rank.
307      *
308      * Note: Within medialib.php, players are listed in rank order (highest
309      * rank first).
310      *
311      * @return int Rank (higher is better)
312      */
313     public abstract function get_rank();
315     /**
316      * @return bool True if player is enabled
317      */
318     public function is_enabled() {
319         global $CFG;
321         // With the class core_media_player_html5video it is enabled
322         // based on $CFG->core_media_enable_html5video.
323         $setting = str_replace('_player_', '_enable_', get_class($this));
324         return !empty($CFG->{$setting});
325     }
327     /**
328      * Given a list of URLs, returns a reduced array containing only those URLs
329      * which are supported by this player. (Empty if none.)
330      * @param array $urls Array of moodle_url
331      * @param array $options Options (same as will be passed to embed)
332      * @return array Array of supported moodle_url
333      */
334     public function list_supported_urls(array $urls, array $options = array()) {
335         $extensions = $this->get_supported_extensions();
336         $result = array();
337         foreach ($urls as $url) {
338             if (in_array(core_media::get_extension($url), $extensions)) {
339                 $result[] = $url;
340             }
341         }
342         return $result;
343     }
345     /**
346      * Obtains suitable name for media. Uses specified name if there is one,
347      * otherwise makes one up.
348      * @param string $name User-specified name ('' if none)
349      * @param array $urls Array of moodle_url used to make up name
350      * @return string Name
351      */
352     protected function get_name($name, $urls) {
353         // If there is a specified name, use that.
354         if ($name) {
355             return $name;
356         }
358         // Get filename of first URL.
359         $url = reset($urls);
360         $name = core_media::get_filename($url);
362         // If there is more than one url, strip the extension as we could be
363         // referring to a different one or several at once.
364         if (count($urls) > 1) {
365             $name = preg_replace('~\.[^.]*$~', '', $name);
366         }
368         return $name;
369     }
371     /**
372      * Compares by rank order, highest first. Used for sort functions.
373      * @param core_media_player $a Player A
374      * @param core_media_player $b Player B
375      * @return int Negative if A should go before B, positive for vice versa
376      */
377     public static function compare_by_rank(core_media_player $a, core_media_player $b) {
378         return $b->get_rank() - $a->get_rank();
379     }
381     /**
382      * Utility function that sets width and height to defaults if not specified
383      * as a parameter to the function (will be specified either if, (a) the calling
384      * code passed it, or (b) the URL included it).
385      * @param int $width Width passed to function (updated with final value)
386      * @param int $height Height passed to function (updated with final value)
387      */
388     protected static function pick_video_size(&$width, &$height) {
389         if (!$width) {
390             $width = CORE_MEDIA_VIDEO_WIDTH;
391             $height = CORE_MEDIA_VIDEO_HEIGHT;
392         }
393     }
397 /**
398  * Base class for players which handle external links (YouTube etc).
399  *
400  * As opposed to media files.
401  *
402  * @copyright 2011 The Open University
403  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
404  */
405 abstract class core_media_player_external extends core_media_player {
406     /**
407      * Array of matches from regular expression - subclass can assume these
408      * will be valid when the embed function is called, to save it rerunning
409      * the regex.
410      * @var array
411      */
412     protected $matches;
414     /**
415      * Part of a regular expression, including ending ~ symbol (note: these
416      * regexes use ~ instead of / because URLs and HTML code typically include
417      * / symbol and makes harder to read if you have to escape it).
418      * Matches the end part of a link after you have read the 'important' data
419      * including optional #d=400x300 at end of url, plus content of <a> tag,
420      * up to </a>.
421      * @var string
422      */
423     const END_LINK_REGEX_PART = '[^#]*(#d=([\d]{1,4})x([\d]{1,4}))?~si';
425     public function embed($urls, $name, $width, $height, $options) {
426         return $this->embed_external(reset($urls), $name, $width, $height, $options);
427     }
429     /**
430      * Obtains HTML code to embed the link.
431      * @param moodle_url $url Single URL to embed
432      * @param string $name Display name; '' to use default
433      * @param int $width Optional width; 0 to use default
434      * @param int $height Optional height; 0 to use default
435      * @param array $options Options array
436      * @return string HTML code for embed
437      */
438     protected abstract function embed_external(moodle_url $url, $name, $width, $height, $options);
440     public function list_supported_urls(array $urls, array $options = array()) {
441         // These only work with a SINGLE url (there is no fallback).
442         if (count($urls) != 1) {
443             return array();
444         }
445         $url = reset($urls);
447         // Check against regex.
448         if (preg_match($this->get_regex(), $url->out(false), $this->matches)) {
449             return array($url);
450         }
452         return array();
453     }
455     /**
456      * Returns regular expression used to match URLs that this player handles
457      * @return string PHP regular expression e.g. '~^https?://example.org/~'
458      */
459     protected function get_regex() {
460         return '~^unsupported~';
461     }
463     /**
464      * Annoyingly, preg_match $matches result does not always have the same
465      * number of parameters - it leaves out optional ones at the end. WHAT.
466      * Anyway, this function can be used to fix it.
467      * @param array $matches Array that should be adjusted
468      * @param int $count Number of capturing groups (=6 to make $matches[6] work)
469      */
470     protected static function fix_match_count(&$matches, $count) {
471         for ($i = count($matches); $i <= $count; $i++) {
472             $matches[$i] = false;
473         }
474     }
478 /**
479  * Player that embeds Vimeo links.
480  *
481  * @copyright 2011 The Open University
482  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
483  */
484 class core_media_player_vimeo extends core_media_player_external {
485     protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
486         $videoid = $this->matches[1];
487         $info = s($name);
489         // Note: resizing via url is not supported, user can click the fullscreen
490         // button instead. iframe embedding is not xhtml strict but it is the only
491         // option that seems to work on most devices.
492         self::pick_video_size($width, $height);
494         $output = <<<OET
495 <span class="mediaplugin mediaplugin_vimeo">
496 <iframe title="$info" src="https://player.vimeo.com/video/$videoid"
497   width="$width" height="$height" frameborder="0"
498   webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
499 </span>
500 OET;
502         return $output;
503     }
505     protected function get_regex() {
506         // Initial part of link.
507         $start = '~^https?://vimeo\.com/';
508         // Middle bit: either watch?v= or v/.
509         $middle = '([0-9]+)';
510         return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
511     }
513     public function get_rank() {
514         return 1010;
515     }
517     public function get_embeddable_markers() {
518         return array('vimeo.com/');
519     }
522 /**
523  * Player that creates YouTube embedding.
524  *
525  * @copyright 2011 The Open University
526  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
527  */
528 class core_media_player_youtube extends core_media_player_external {
529     protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
530         $videoid = end($this->matches);
532         $info = trim($name);
533         if (empty($info) or strpos($info, 'http') === 0) {
534             $info = get_string('siteyoutube', 'core_media');
535         }
536         $info = s($info);
538         self::pick_video_size($width, $height);
540         $params = '';
541         $start = self::get_start_time($url);
542         if ($start > 0) {
543             $params .= "start=$start&";
544         }
546         $listid = $url->param('list');
547         // Check for non-empty but valid playlist ID.
548         if (!empty($listid) && !preg_match('/[^a-zA-Z0-9\-_]/', $listid)) {
549             // This video is part of a playlist, and we want to embed it as such.
550             $params .= "list=$listid&";
551         }
553         return <<<OET
554 <span class="mediaplugin mediaplugin_youtube">
555 <iframe title="$info" width="$width" height="$height"
556   src="https://www.youtube.com/embed/$videoid?{$params}rel=0&wmode=transparent" frameborder="0" allowfullscreen="1"></iframe>
557 </span>
558 OET;
560     }
562     /**
563      * Check for start time parameter.  Note that it's in hours/mins/secs in the URL,
564      * but the embedded player takes only a number of seconds as the "start" parameter.
565      * @param moodle_url $url URL of video to be embedded.
566      * @return int Number of seconds video should start at.
567      */
568     protected static function get_start_time($url) {
569         $matches = array();
570         $seconds = 0;
572         $rawtime = $url->param('t');
573         if (empty($rawtime)) {
574             $rawtime = $url->param('start');
575         }
577         if (is_numeric($rawtime)) {
578             // Start time already specified as a number of seconds; ensure it's an integer.
579             $seconds = $rawtime;
580         } else if (preg_match('/(\d+?h)?(\d+?m)?(\d+?s)?/i', $rawtime, $matches)) {
581             // Convert into a raw number of seconds, as that's all embedded players accept.
582             for ($i = 1; $i < count($matches); $i++) {
583                 if (empty($matches[$i])) {
584                     continue;
585                 }
586                 $part = str_split($matches[$i], strlen($matches[$i]) - 1);
587                 switch ($part[1]) {
588                     case 'h':
589                         $seconds += 3600 * $part[0];
590                         break;
591                     case 'm':
592                         $seconds += 60 * $part[0];
593                         break;
594                     default:
595                         $seconds += $part[0];
596                 }
597             }
598         }
600         return intval($seconds);
601     }
603     protected function get_regex() {
604         // Regex for standard youtube link
605          $link = '(youtube(-nocookie)?\.com/(?:watch\?v=|v/))';
606         // Regex for shortened youtube link
607         $shortlink = '((youtu|y2u)\.be/)';
609         // Initial part of link.
610          $start = '~^https?://(www\.)?(' . $link . '|' . $shortlink . ')';
611         // Middle bit: Video key value
612         $middle = '([a-z0-9\-_]+)';
613         return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
614     }
616     public function get_rank() {
617         // I decided to make the link-embedding ones (that don't handle file
618         // formats) have ranking in the 1000 range.
619         return 1001;
620     }
622     public function get_embeddable_markers() {
623         return array('youtube.com', 'youtube-nocookie.com', 'youtu.be', 'y2u.be');
624     }
628 /**
629  * Player that creates YouTube playlist embedding.
630  *
631  * @copyright 2011 The Open University
632  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
633  */
634 class core_media_player_youtube_playlist extends core_media_player_external {
635     public function is_enabled() {
636         global $CFG;
637         // Use the youtube on/off flag.
638         return $CFG->core_media_enable_youtube;
639     }
641     protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
642         $site = $this->matches[1];
643         $playlist = $this->matches[3];
645         $info = trim($name);
646         if (empty($info) or strpos($info, 'http') === 0) {
647             $info = get_string('siteyoutube', 'core_media');
648         }
649         $info = s($info);
651         self::pick_video_size($width, $height);
653         return <<<OET
654 <span class="mediaplugin mediaplugin_youtube">
655 <iframe width="$width" height="$height" src="https://$site/embed/videoseries?list=$playlist" frameborder="0" allowfullscreen="1"></iframe>
656 </span>
657 OET;
658     }
660     protected function get_regex() {
661         // Initial part of link.
662         $start = '~^https?://(www\.youtube(-nocookie)?\.com)/';
663         // Middle bit: either view_play_list?p= or p/ (doesn't work on youtube) or playlist?list=.
664         $middle = '(?:view_play_list\?p=|p/|playlist\?list=)([a-z0-9\-_]+)';
665         return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
666     }
668     public function get_rank() {
669         // I decided to make the link-embedding ones (that don't handle file
670         // formats) have ranking in the 1000 range.
671         return 1000;
672     }
674     public function get_embeddable_markers() {
675         return array('youtube');
676     }
680 /**
681  * MP3 player inserted using JavaScript.
682  *
683  * @copyright 2011 The Open University
684  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
685  */
686 class core_media_player_mp3 extends core_media_player {
687     public function embed($urls, $name, $width, $height, $options) {
688         // Use first url (there can actually be only one unless some idiot
689         // enters two mp3 files as alternatives).
690         $url = reset($urls);
692         // Unique id even across different http requests made at the same time
693         // (for AJAX, iframes).
694         $id = 'core_media_mp3_' . md5(time() . '_' . rand());
696         // When Flash or JavaScript are not available only the fallback is displayed,
697         // using span not div because players are inline elements.
698         $spanparams = array('id' => $id, 'class' => 'mediaplugin mediaplugin_mp3');
699         if ($width) {
700             $spanparams['style'] = 'width: ' . $width . 'px';
701         }
702         $output = html_writer::tag('span', core_media_player::PLACEHOLDER, $spanparams);
703         // We can not use standard JS init because this may be cached
704         // note: use 'small' size unless embedding in block mode.
705         $output .= html_writer::script(js_writer::function_call(
706                 'M.util.add_audio_player', array($id, $url->out(false),
707                 empty($options[core_media::OPTION_BLOCK]))));
709         return $output;
710     }
712     public function get_supported_extensions() {
713         return array('mp3');
714     }
716     public function get_rank() {
717         return 80;
718     }
722 /**
723  * Flash video player inserted using JavaScript.
724  *
725  * @copyright 2011 The Open University
726  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
727  */
728 class core_media_player_flv extends core_media_player {
729     public function embed($urls, $name, $width, $height, $options) {
730         // Use first url (there can actually be only one unless some idiot
731         // enters two mp3 files as alternatives).
732         $url = reset($urls);
734         // Unique id even across different http requests made at the same time
735         // (for AJAX, iframes).
736         $id = 'core_media_flv_' . md5(time() . '_' . rand());
738         // Compute width and height.
739         $autosize = false;
740         if (!$width && !$height) {
741             $width = CORE_MEDIA_VIDEO_WIDTH;
742             $height = CORE_MEDIA_VIDEO_HEIGHT;
743             $autosize = true;
744         }
746         // Fallback span (will normally contain link).
747         $output = html_writer::tag('span', core_media_player::PLACEHOLDER,
748                 array('id'=>$id, 'class'=>'mediaplugin mediaplugin_flv'));
749         // We can not use standard JS init because this may be cached.
750         $output .= html_writer::script(js_writer::function_call(
751                 'M.util.add_video_player', array($id, addslashes_js($url->out(false)),
752                 $width, $height, $autosize)));
753         return $output;
754     }
756     public function get_supported_extensions() {
757         return array('flv', 'f4v');
758     }
760     public function get_rank() {
761         return 70;
762     }
766 /**
767  * Embeds Windows Media Player using object tag.
768  *
769  * @copyright 2011 The Open University
770  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
771  */
772 class core_media_player_wmp extends core_media_player {
773     public function embed($urls, $name, $width, $height, $options) {
774         // Get URL (we just use first, probably there is only one).
775         $firsturl = reset($urls);
776         $url = $firsturl->out(false);
778         // Work out width.
779         if (!$width || !$height) {
780             // Object tag has default size.
781             $mpsize = '';
782             $size = 'width="' . CORE_MEDIA_VIDEO_WIDTH .
783                     '" height="' . (CORE_MEDIA_VIDEO_HEIGHT+64) . '"';
784             $autosize = 'true';
785         } else {
786             $size = 'width="' . $width . '" height="' . ($height + 15) . '"';
787             $mpsize = 'width="' . $width . '" height="' . ($height + 64) . '"';
788             $autosize = 'false';
789         }
791         // MIME type for object tag.
792         $mimetype = core_media::get_mimetype($firsturl);
794         $fallback = core_media_player::PLACEHOLDER;
796         // Embed code.
797         return <<<OET
798 <span class="mediaplugin mediaplugin_wmp">
799     <object classid="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6" $mpsize
800             standby="Loading Microsoft(R) Windows(R) Media Player components..."
801             type="application/x-oleobject">
802         <param name="Filename" value="$url" />
803         <param name="src" value="$url" />
804         <param name="url" value="$url" />
805         <param name="ShowControls" value="true" />
806         <param name="AutoRewind" value="true" />
807         <param name="AutoStart" value="false" />
808         <param name="Autosize" value="$autosize" />
809         <param name="EnableContextMenu" value="true" />
810         <param name="TransparentAtStart" value="false" />
811         <param name="AnimationAtStart" value="false" />
812         <param name="ShowGotoBar" value="false" />
813         <param name="EnableFullScreenControls" value="true" />
814         <param name="uimode" value="full" />
815         <!--[if !IE]><!-->
816         <object data="$url" type="$mimetype" $size>
817             <param name="src" value="$url" />
818             <param name="controller" value="true" />
819             <param name="autoplay" value="false" />
820             <param name="autostart" value="false" />
821             <param name="resize" value="scale" />
822         <!--<![endif]-->
823             $fallback
824         <!--[if !IE]><!-->
825         </object>
826         <!--<![endif]-->
827     </object>
828 </span>
829 OET;
830     }
832     public function get_supported_extensions() {
833         return array('wmv', 'avi');
834     }
836     public function get_rank() {
837         return 60;
838     }
842 /**
843  * Media player using object tag and QuickTime player.
844  *
845  * @copyright 2011 The Open University
846  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
847  */
848 class core_media_player_qt extends core_media_player {
849     public function embed($urls, $name, $width, $height, $options) {
850         // Show first URL.
851         $firsturl = reset($urls);
852         $url = $firsturl->out(true);
854         // Work out size.
855         if (!$width || !$height) {
856             $size = 'width="' . CORE_MEDIA_VIDEO_WIDTH .
857                     '" height="' . (CORE_MEDIA_VIDEO_HEIGHT + 15) . '"';
858         } else {
859             $size = 'width="' . $width . '" height="' . ($height + 15) . '"';
860         }
862         // MIME type for object tag.
863         $mimetype = core_media::get_mimetype($firsturl);
865         $fallback = core_media_player::PLACEHOLDER;
867         // Embed code.
868         return <<<OET
869 <span class="mediaplugin mediaplugin_qt">
870     <object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
871             codebase="http://www.apple.com/qtactivex/qtplugin.cab" $size>
872         <param name="pluginspage" value="http://www.apple.com/quicktime/download/" />
873         <param name="src" value="$url" />
874         <param name="controller" value="true" />
875         <param name="loop" value="false" />
876         <param name="autoplay" value="false" />
877         <param name="autostart" value="false" />
878         <param name="scale" value="aspect" />
879         <!--[if !IE]><!-->
880         <object data="$url" type="$mimetype" $size>
881             <param name="src" value="$url" />
882             <param name="pluginurl" value="http://www.apple.com/quicktime/download/" />
883             <param name="controller" value="true" />
884             <param name="loop" value="false" />
885             <param name="autoplay" value="false" />
886             <param name="autostart" value="false" />
887             <param name="scale" value="aspect" />
888         <!--<![endif]-->
889             $fallback
890         <!--[if !IE]><!-->
891         </object>
892         <!--<![endif]-->
893     </object>
894 </span>
895 OET;
896     }
898     public function get_supported_extensions() {
899         return array('mpg', 'mpeg', 'mov', 'mp4', 'm4v', 'm4a');
900     }
902     public function get_rank() {
903         return 10;
904     }
908 /**
909  * Media player using object tag and RealPlayer.
910  *
911  * Hopefully nobody is using this obsolete format any more!
912  *
913  * @copyright 2011 The Open University
914  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
915  */
916 class core_media_player_rm extends core_media_player {
917     public function embed($urls, $name, $width, $height, $options) {
918         // Show first URL.
919         $firsturl = reset($urls);
920         $url = $firsturl->out(true);
922         // Get name to use as title.
923         $info = s($this->get_name($name, $urls));
925         // The previous version of this code has the following comment, which
926         // I don't understand, but trust it is correct:
927         // Note: the size is hardcoded intentionally because this does not work anyway!
928         $width = CORE_MEDIA_VIDEO_WIDTH;
929         $height = CORE_MEDIA_VIDEO_HEIGHT;
931         $fallback = core_media_player::PLACEHOLDER;
932         return <<<OET
933 <span class="mediaplugin mediaplugin_real">
934     <object title="$info" classid="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA"
935             data="$url" width="$width" height="$height"">
936         <param name="src" value="$url" />
937         <param name="controls" value="All" />
938         <!--[if !IE]><!-->
939         <object title="$info" type="audio/x-pn-realaudio-plugin"
940                 data="$url" width="$width" height="$height">
941             <param name="src" value="$url" />
942             <param name="controls" value="All" />
943         <!--<![endif]-->
944             $fallback
945         <!--[if !IE]><!-->
946         </object>
947         <!--<![endif]-->
948   </object>
949 </span>
950 OET;
951     }
953     public function get_supported_extensions() {
954         return array('ra', 'ram', 'rm', 'rv');
955     }
957     public function get_rank() {
958         return 40;
959     }
963 /**
964  * Media player for Flash SWF files.
965  *
966  * This player contains additional security restriction: it will only be used
967  * if you add option core_media_player_swf::ALLOW = true.
968  *
969  * Code should only set this option if it has verified that the data was
970  * embedded by a trusted user (e.g. in trust text).
971  *
972  * @copyright 2011 The Open University
973  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
974  */
975 class core_media_player_swf extends core_media_player {
976     public function embed($urls, $name, $width, $height, $options) {
977         self::pick_video_size($width, $height);
979         $firsturl = reset($urls);
980         $url = $firsturl->out(true);
982         $fallback = core_media_player::PLACEHOLDER;
983         $output = <<<OET
984 <span class="mediaplugin mediaplugin_swf">
985   <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="$width" height="$height">
986     <param name="movie" value="$url" />
987     <param name="autoplay" value="true" />
988     <param name="loop" value="false" />
989     <param name="controller" value="true" />
990     <param name="scale" value="aspect" />
991     <param name="base" value="." />
992     <param name="allowscriptaccess" value="never" />
993     <param name="allowfullscreen" value="true" />
994 <!--[if !IE]><!-->
995     <object type="application/x-shockwave-flash" data="$url" width="$width" height="$height">
996       <param name="controller" value="true" />
997       <param name="autoplay" value="true" />
998       <param name="loop" value="false" />
999       <param name="scale" value="aspect" />
1000       <param name="base" value="." />
1001       <param name="allowscriptaccess" value="never" />
1002       <param name="allowfullscreen" value="true" />
1003 <!--<![endif]-->
1004 $fallback
1005 <!--[if !IE]><!-->
1006     </object>
1007 <!--<![endif]-->
1008   </object>
1009 </span>
1010 OET;
1012         return $output;
1013     }
1015     public function get_supported_extensions() {
1016         return array('swf');
1017     }
1019     public function list_supported_urls(array $urls, array $options = array()) {
1020         // Not supported unless the creator is trusted.
1021         if (empty($options[core_media::OPTION_TRUSTED])) {
1022             return array();
1023         }
1024         return parent::list_supported_urls($urls, $options);
1025     }
1027     public function get_rank() {
1028         return 30;
1029     }
1033 /**
1034  * Player that creates HTML5 <video> tag.
1035  *
1036  * @copyright 2011 The Open University
1037  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1038  */
1039 class core_media_player_html5video extends core_media_player {
1040     public function embed($urls, $name, $width, $height, $options) {
1041         // Special handling to make videos play on Android devices pre 2.3.
1042         // Note: I tested and 2.3.3 (in emulator) works without, is 533.1 webkit.
1043         $oldandroid = core_useragent::is_webkit_android() &&
1044                 !core_useragent::check_webkit_android_version('533.1');
1046         // Build array of source tags.
1047         $sources = array();
1048         foreach ($urls as $url) {
1049             $mimetype = core_media::get_mimetype($url);
1050             $source = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype));
1051             if ($mimetype === 'video/mp4') {
1052                 if ($oldandroid) {
1053                     // Old Android fails if you specify the type param.
1054                     $source = html_writer::tag('source', '', array('src' => $url));
1055                 }
1057                 // Better add m4v as first source, it might be a bit more
1058                 // compatible with problematic browsers.
1059                 array_unshift($sources, $source);
1060             } else {
1061                 $sources[] = $source;
1062             }
1063         }
1065         $sources = implode("\n", $sources);
1066         $title = s($this->get_name($name, $urls));
1068         if (!$width) {
1069             // No width specified, use system default.
1070             $width = CORE_MEDIA_VIDEO_WIDTH;
1071         }
1073         if (!$height) {
1074             // Let browser choose height automatically.
1075             $size = "width=\"$width\"";
1076         } else {
1077             $size = "width=\"$width\" height=\"$height\"";
1078         }
1080         $sillyscript = '';
1081         $idtag = '';
1082         if ($oldandroid) {
1083             // Old Android does not support 'controls' option.
1084             $id = 'core_media_html5v_' . md5(time() . '_' . rand());
1085             $idtag = 'id="' . $id . '"';
1086             $sillyscript = <<<OET
1087 <script type="text/javascript">
1088 document.getElementById('$id').addEventListener('click', function() {
1089     this.play();
1090 }, false);
1091 </script>
1092 OET;
1093         }
1095         $fallback = core_media_player::PLACEHOLDER;
1096         return <<<OET
1097 <span class="mediaplugin mediaplugin_html5video">
1098 <video $idtag controls="true" $size preload="metadata" title="$title">
1099     $sources
1100     $fallback
1101 </video>
1102 $sillyscript
1103 </span>
1104 OET;
1105     }
1107     public function get_supported_extensions() {
1108         return array('m4v', 'webm', 'ogv', 'mp4');
1109     }
1111     public function list_supported_urls(array $urls, array $options = array()) {
1112         $extensions = $this->get_supported_extensions();
1113         $result = array();
1114         foreach ($urls as $url) {
1115             $ext = core_media::get_extension($url);
1116             if (in_array($ext, $extensions)) {
1117                 // Unfortunately html5 video does not handle fallback properly.
1118                 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=10975
1119                 // That means we need to do browser detect and not use html5 on
1120                 // browsers which do not support the given type, otherwise users
1121                 // will not even see the fallback link.
1122                 // Based on http://en.wikipedia.org/wiki/HTML5_video#Table - this
1123                 // is a simplified version, does not take into account old browser
1124                 // versions or manual plugins.
1125                 if ($ext === 'ogv' || $ext === 'webm') {
1126                     // Formats .ogv and .webm are not supported in IE or Safari.
1127                     if (core_useragent::is_ie() || core_useragent::is_safari()) {
1128                         continue;
1129                     }
1130                 } else {
1131                     // Formats .m4v and .mp4 are not supported in Opera, or in Firefox before 27.
1132                     // https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
1133                     // has the details.
1134                     if (core_useragent::is_opera() || (core_useragent::is_firefox() &&
1135                             !core_useragent::check_firefox_version(27))) {
1136                         continue;
1137                     }
1138                 }
1140                 $result[] = $url;
1141             }
1142         }
1143         return $result;
1144     }
1146     public function get_rank() {
1147         return 50;
1148     }
1152 /**
1153  * Player that creates HTML5 <audio> tag.
1154  *
1155  * @copyright 2011 The Open University
1156  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1157  */
1158 class core_media_player_html5audio extends core_media_player {
1159     public function embed($urls, $name, $width, $height, $options) {
1161         // Build array of source tags.
1162         $sources = array();
1163         foreach ($urls as $url) {
1164             $mimetype = core_media::get_mimetype($url);
1165             $sources[] = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype));
1166         }
1168         $sources = implode("\n", $sources);
1169         $title = s($this->get_name($name, $urls));
1171         // Default to not specify size (so it can be changed in css).
1172         $size = '';
1173         if ($width) {
1174             $size = 'width="' . $width . '"';
1175         }
1177         $fallback = core_media_player::PLACEHOLDER;
1179         return <<<OET
1180 <audio controls="true" $size class="mediaplugin mediaplugin_html5audio" preload="none" title="$title">
1181 $sources
1182 $fallback
1183 </audio>
1184 OET;
1185     }
1187     public function get_supported_extensions() {
1188         return array('ogg', 'oga', 'aac', 'm4a', 'mp3');
1189     }
1191     public function list_supported_urls(array $urls, array $options = array()) {
1192         $extensions = $this->get_supported_extensions();
1193         $result = array();
1194         foreach ($urls as $url) {
1195             $ext = core_media::get_extension($url);
1196             if (in_array($ext, $extensions)) {
1197                 if ($ext === 'ogg' || $ext === 'oga') {
1198                     // Formats .ogg and .oga are not supported in IE or Safari.
1199                     if (core_useragent::is_ie() || core_useragent::is_safari()) {
1200                         continue;
1201                     }
1202                 } else {
1203                     // Formats .aac, .mp3, and .m4a are not supported in Opera.
1204                     if (core_useragent::is_opera()) {
1205                         continue;
1206                     }
1207                     // Formats .mp3 and .m4a were not reliably supported in Firefox before 27.
1208                     // https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
1209                     // has the details. .aac is still not supported.
1210                     if (core_useragent::is_firefox() && ($ext === 'aac' ||
1211                             !core_useragent::check_firefox_version(27))) {
1212                         continue;
1213                     }
1214                 }
1215                 // Old Android versions (pre 2.3.3) 'support' audio tag but no codecs.
1216                 if (core_useragent::is_webkit_android() &&
1217                         !core_useragent::is_webkit_android('533.1')) {
1218                     continue;
1219                 }
1221                 $result[] = $url;
1222             }
1223         }
1224         return $result;
1225     }
1227     public function get_rank() {
1228         return 20;
1229     }
1233 /**
1234  * Special media player class that just puts a link.
1235  *
1236  * Always enabled, used as the last fallback.
1237  *
1238  * @copyright 2011 The Open University
1239  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1240  */
1241 class core_media_player_link extends core_media_player {
1242     public function embed($urls, $name, $width, $height, $options) {
1243         // If link is turned off, return empty.
1244         if (!empty($options[core_media::OPTION_NO_LINK])) {
1245             return '';
1246         }
1248         // Build up link content.
1249         $output = '';
1250         foreach ($urls as $url) {
1251             $title = core_media::get_filename($url);
1252             $printlink = html_writer::link($url, $title, array('class' => 'mediafallbacklink'));
1253             if ($output) {
1254                 // Where there are multiple available formats, there are fallback links
1255                 // for all formats, separated by /.
1256                 $output .= ' / ';
1257             }
1258             $output .= $printlink;
1259         }
1260         return $output;
1261     }
1263     public function list_supported_urls(array $urls, array $options = array()) {
1264         // Supports all URLs.
1265         return $urls;
1266     }
1268     public function is_enabled() {
1269         // Cannot be disabled.
1270         return true;
1271     }
1273     public function get_rank() {
1274         return 0;
1275     }