88775c6a053ac7b99e13b71070659e15ab4f08dd
[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 textlib (. 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         $path = $url->get_path();
176         // Remove everything before last / if present. Does not use textlib as / is UTF8-safe.
177         $slash = strrpos($path, '/');
178         if ($slash !== false) {
179             $path = substr($path, $slash + 1);
180         }
182         return $path;
183     }
185     /**
186      * Guesses MIME type for a moodle_url based on file extension.
187      * @param moodle_url $url URL
188      * @return string MIME type
189      */
190     public static function get_mimetype(moodle_url $url) {
191         return mimeinfo('type', self::get_filename($url));
192     }
196 /**
197  * Base class for media players.
198  *
199  * Media players return embed HTML for a particular way of playing back audio
200  * or video (or another file type).
201  *
202  * In order to make the code more lightweight, this is not a plugin type
203  * (players cannot have their own settings, database tables, capabilities, etc).
204  * These classes are used only by core_media_renderer in outputrenderers.php.
205  * If you add a new class here (in core code) you must modify the
206  * get_players_raw function in that file to include it.
207  *
208  * If a Moodle installation wishes to add extra player objects they can do so
209  * by overriding that renderer in theme, and overriding the get_players_raw
210  * function. The new player class should then of course be defined within the
211  * custom theme or other suitable location, not in this file.
212  *
213  * @copyright 2011 The Open University
214  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
215  */
216 abstract class core_media_player {
217     /**
218      * Placeholder text used to indicate where the fallback content is placed
219      * within a result.
220      */
221     const PLACEHOLDER = '<!--FALLBACK-->';
223     /**
224      * Generates code required to embed the player.
225      *
226      * The returned code contains a placeholder comment '<!--FALLBACK-->'
227      * (constant core_media_player::PLACEHOLDER) which indicates the location
228      * where fallback content should be placed in the event that this type of
229      * player is not supported by user browser.
230      *
231      * The $urls parameter includes one or more alternative media formats that
232      * are supported by this player. It does not include formats that aren't
233      * supported (see list_supported_urls).
234      *
235      * The $options array contains key-value pairs. See OPTION_xx constants
236      * for documentation of standard option(s).
237      *
238      * @param array $urls URLs of media files
239      * @param string $name Display name; '' to use default
240      * @param int $width Optional width; 0 to use default
241      * @param int $height Optional height; 0 to use default
242      * @param array $options Options array
243      * @return string HTML code for embed
244      */
245     public abstract function embed($urls, $name, $width, $height, $options);
247     /**
248      * Gets the list of file extensions supported by this media player.
249      *
250      * Note: This is only required for the default implementation of
251      * list_supported_urls. If you override that function to determine
252      * supported URLs in some way other than by extension, then this function
253      * is not necessary.
254      *
255      * @return array Array of strings (extension not including dot e.g. 'mp3')
256      */
257     public function get_supported_extensions() {
258         return array();
259     }
261     /**
262      * Lists keywords that must be included in a url that can be embedded with
263      * this player. Any such keywords should be added to the array.
264      *
265      * For example if this player supports FLV and F4V files then it should add
266      * '.flv' and '.f4v' to the array. (The check is not case-sensitive.)
267      *
268      * Default handling calls the get_supported_extensions function and adds
269      * a dot to each of those values, so players only need to override this
270      * if they don't implement get_supported_extensions.
271      *
272      * This is used to improve performance when matching links in the media filter.
273      *
274      * @return array Array of keywords to add to the embeddable markers list
275      */
276     public function get_embeddable_markers() {
277         $markers = array();
278         foreach ($this->get_supported_extensions() as $extension) {
279             $markers[] = '.' . $extension;
280         }
281         return $markers;
282     }
284     /**
285      * Gets the ranking of this player. This is an integer used to decide which
286      * player to use (after applying other considerations such as which ones
287      * the user has disabled).
288      *
289      * Rank must be unique (no two players should have the same rank).
290      *
291      * Rank zero has a special meaning, indicating that this 'player' does not
292      * really embed the video.
293      *
294      * Rank is not a user-configurable value because it needs to be defined
295      * carefully in order to ensure that the embedding fallbacks actually work.
296      * It might be possible to have some user options which affect rank, but
297      * these would be best defined as e.g. checkboxes in settings that have
298      * a particular effect on the rank of a couple of plugins, rather than
299      * letting users generally alter rank.
300      *
301      * Note: Within medialib.php, players are listed in rank order (highest
302      * rank first).
303      *
304      * @return int Rank (higher is better)
305      */
306     public abstract function get_rank();
308     /**
309      * @return bool True if player is enabled
310      */
311     public function is_enabled() {
312         global $CFG;
314         // With the class core_media_player_html5video it is enabled
315         // based on $CFG->core_media_enable_html5video.
316         $setting = str_replace('_player_', '_enable_', get_class($this));
317         return !empty($CFG->{$setting});
318     }
320     /**
321      * Given a list of URLs, returns a reduced array containing only those URLs
322      * which are supported by this player. (Empty if none.)
323      * @param array $urls Array of moodle_url
324      * @param array $options Options (same as will be passed to embed)
325      * @return array Array of supported moodle_url
326      */
327     public function list_supported_urls(array $urls, array $options = array()) {
328         $extensions = $this->get_supported_extensions();
329         $result = array();
330         foreach ($urls as $url) {
331             if (in_array(core_media::get_extension($url), $extensions)) {
332                 $result[] = $url;
333             }
334         }
335         return $result;
336     }
338     /**
339      * Obtains suitable name for media. Uses specified name if there is one,
340      * otherwise makes one up.
341      * @param string $name User-specified name ('' if none)
342      * @param array $urls Array of moodle_url used to make up name
343      * @return string Name
344      */
345     protected function get_name($name, $urls) {
346         // If there is a specified name, use that.
347         if ($name) {
348             return $name;
349         }
351         // Get filename of first URL.
352         $url = reset($urls);
353         $name = core_media::get_filename($url);
355         // If there is more than one url, strip the extension as we could be
356         // referring to a different one or several at once.
357         if (count($urls) > 1) {
358             $name = preg_replace('~\.[^.]*$~', '', $name);
359         }
361         return $name;
362     }
364     /**
365      * Compares by rank order, highest first. Used for sort functions.
366      * @param core_media_player $a Player A
367      * @param core_media_player $b Player B
368      * @return int Negative if A should go before B, positive for vice versa
369      */
370     public static function compare_by_rank(core_media_player $a, core_media_player $b) {
371         return $b->get_rank() - $a->get_rank();
372     }
374     /**
375      * Utility function that sets width and height to defaults if not specified
376      * as a parameter to the function (will be specified either if, (a) the calling
377      * code passed it, or (b) the URL included it).
378      * @param int $width Width passed to function (updated with final value)
379      * @param int $height Height passed to function (updated with final value)
380      */
381     protected static function pick_video_size(&$width, &$height) {
382         if (!$width) {
383             $width = CORE_MEDIA_VIDEO_WIDTH;
384             $height = CORE_MEDIA_VIDEO_HEIGHT;
385         }
386     }
390 /**
391  * Base class for players which handle external links (YouTube etc).
392  *
393  * As opposed to media files.
394  *
395  * @copyright 2011 The Open University
396  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
397  */
398 abstract class core_media_player_external extends core_media_player {
399     /**
400      * Array of matches from regular expression - subclass can assume these
401      * will be valid when the embed function is called, to save it rerunning
402      * the regex.
403      * @var array
404      */
405     protected $matches;
407     /**
408      * Part of a regular expression, including ending ~ symbol (note: these
409      * regexes use ~ instead of / because URLs and HTML code typically include
410      * / symbol and makes harder to read if you have to escape it).
411      * Matches the end part of a link after you have read the 'important' data
412      * including optional #d=400x300 at end of url, plus content of <a> tag,
413      * up to </a>.
414      * @var string
415      */
416     const END_LINK_REGEX_PART = '[^#]*(#d=([\d]{1,4})x([\d]{1,4}))?~si';
418     public function embed($urls, $name, $width, $height, $options) {
419         return $this->embed_external(reset($urls), $name, $width, $height, $options);
420     }
422     /**
423      * Obtains HTML code to embed the link.
424      * @param moodle_url $url Single URL to embed
425      * @param string $name Display name; '' to use default
426      * @param int $width Optional width; 0 to use default
427      * @param int $height Optional height; 0 to use default
428      * @param array $options Options array
429      * @return string HTML code for embed
430      */
431     protected abstract function embed_external(moodle_url $url, $name, $width, $height, $options);
433     public function list_supported_urls(array $urls, array $options = array()) {
434         // These only work with a SINGLE url (there is no fallback).
435         if (count($urls) != 1) {
436             return array();
437         }
438         $url = reset($urls);
440         // Check against regex.
441         if (preg_match($this->get_regex(), $url->out(false), $this->matches)) {
442             return array($url);
443         }
445         return array();
446     }
448     /**
449      * Returns regular expression used to match URLs that this player handles
450      * @return string PHP regular expression e.g. '~^https?://example.org/~'
451      */
452     protected function get_regex() {
453         return '~^unsupported~';
454     }
456     /**
457      * Annoyingly, preg_match $matches result does not always have the same
458      * number of parameters - it leaves out optional ones at the end. WHAT.
459      * Anyway, this function can be used to fix it.
460      * @param array $matches Array that should be adjusted
461      * @param int $count Number of capturing groups (=6 to make $matches[6] work)
462      */
463     protected static function fix_match_count(&$matches, $count) {
464         for ($i = count($matches); $i <= $count; $i++) {
465             $matches[$i] = false;
466         }
467     }
471 /**
472  * Player that embeds Vimeo links.
473  *
474  * @copyright 2011 The Open University
475  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
476  */
477 class core_media_player_vimeo extends core_media_player_external {
478     protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
479         $videoid = $this->matches[1];
480         $info = s($name);
482         // Note: resizing via url is not supported, user can click the fullscreen
483         // button instead. iframe embedding is not xhtml strict but it is the only
484         // option that seems to work on most devices.
485         self::pick_video_size($width, $height);
487         $output = <<<OET
488 <span class="mediaplugin mediaplugin_vimeo">
489 <iframe title="$info" src="http://player.vimeo.com/video/$videoid"
490   width="$width" height="$height" frameborder="0"></iframe>
491 </span>
492 OET;
494         return $output;
495     }
497     protected function get_regex() {
498         // Initial part of link.
499         $start = '~^http://vimeo\.com/';
500         // Middle bit: either watch?v= or v/.
501         $middle = '([0-9]+)';
502         return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
503     }
505     public function get_rank() {
506         return 1010;
507     }
509     public function get_embeddable_markers() {
510         return array('vimeo.com/');
511     }
514 /**
515  * Player that creates YouTube embedding.
516  *
517  * @copyright 2011 The Open University
518  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
519  */
520 class core_media_player_youtube extends core_media_player_external {
521     protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
522         global $CFG;
524         $site = $this->matches[1];
525         $videoid = $this->matches[3];
527         $info = trim($name);
528         if (empty($info) or strpos($info, 'http') === 0) {
529             $info = get_string('siteyoutube', 'core_media');
530         }
531         $info = s($info);
533         self::pick_video_size($width, $height);
535         if (empty($CFG->xmlstrictheaders)) {
536             return <<<OET
537 <iframe title="$info" width="$width" height="$height"
538   src="$site/embed/$videoid?rel=0&wmode=transparent" frameborder="0" allowfullscreen></iframe>
539 OET;
540         }
542         // NOTE: we can not use any link fallback because it breaks built-in
543         // player on iOS devices.
544         $output = <<<OET
545 <span class="mediaplugin mediaplugin_youtube">
546 <object title="$info" type="application/x-shockwave-flash"
547   data="$site/v/$videoid&amp;fs=1&amp;rel=0" width="$width" height="$height">
548  <param name="movie" value="$site/v/$videoid&amp;fs=1&amp;rel=0" />
549  <param name="FlashVars" value="playerMode=embedded" />
550  <param name="allowFullScreen" value="true" />
551 </object>
552 </span>
553 OET;
555         return $output;
556     }
558     protected function get_regex() {
559         // Initial part of link.
560         $start = '~^(https?://www\.youtube(-nocookie)?\.com)/';
561         // Middle bit: either watch?v= or v/.
562         $middle = '(?:watch\?v=|v/)([a-z0-9\-_]+)';
563         return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
564     }
566     public function get_rank() {
567         // I decided to make the link-embedding ones (that don't handle file
568         // formats) have ranking in the 1000 range.
569         return 1001;
570     }
572     public function get_embeddable_markers() {
573         return array('youtube');
574     }
578 /**
579  * Player that creates YouTube playlist embedding.
580  *
581  * @copyright 2011 The Open University
582  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
583  */
584 class core_media_player_youtube_playlist extends core_media_player_external {
585     public function is_enabled() {
586         global $CFG;
587         // Use the youtube on/off flag.
588         return $CFG->core_media_enable_youtube;
589     }
591     protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
592         $site = $this->matches[1];
593         $playlist = $this->matches[3];
595         $info = trim($name);
596         if (empty($info) or strpos($info, 'http') === 0) {
597             $info = get_string('siteyoutube', 'core_media');
598         }
599         $info = s($info);
601         self::pick_video_size($width, $height);
603         // TODO: iframe HTML 5 video not implemented and object does not work
604         // on iOS devices.
605         $fallback = core_media_player::PLACEHOLDER;
606         $output = <<<OET
607 <span class="mediaplugin mediaplugin_youtube">
608 <object title="$info" type="application/x-shockwave-flash"
609   data="$site/p/$playlist&amp;fs=1&amp;rel=0" width="$width" height="$height">
610  <param name="movie" value="$site/v/$playlist&amp;fs=1&amp;rel=0" />
611  <param name="FlashVars" value="playerMode=embedded" />
612  <param name="allowFullScreen" value="true" />
613 $fallback</object>
614 </span>
615 OET;
617         return $output;
618     }
620     protected function get_regex() {
621         // Initial part of link.
622         $start = '~^(https?://www\.youtube(-nocookie)?\.com)/';
623         // Middle bit: either view_play_list?p= or p/ (doesn't work on youtube) or playlist?list=.
624         $middle = '(?:view_play_list\?p=|p/|playlist\?list=)([a-z0-9\-_]+)';
625         return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
626     }
628     public function get_rank() {
629         // I decided to make the link-embedding ones (that don't handle file
630         // formats) have ranking in the 1000 range.
631         return 1000;
632     }
634     public function get_embeddable_markers() {
635         return array('youtube');
636     }
640 /**
641  * MP3 player inserted using JavaScript.
642  *
643  * @copyright 2011 The Open University
644  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
645  */
646 class core_media_player_mp3 extends core_media_player {
647     public function embed($urls, $name, $width, $height, $options) {
648         // Use first url (there can actually be only one unless some idiot
649         // enters two mp3 files as alternatives).
650         $url = reset($urls);
652         // Unique id even across different http requests made at the same time
653         // (for AJAX, iframes).
654         $id = 'core_media_mp3_' . md5(time() . '_' . rand());
656         // When Flash or JavaScript are not available only the fallback is displayed,
657         // using span not div because players are inline elements.
658         $spanparams = array('id' => $id, 'class' => 'mediaplugin mediaplugin_mp3');
659         if ($width) {
660             $spanparams['style'] = 'width: ' . $width . 'px';
661         }
662         $output = html_writer::tag('span', core_media_player::PLACEHOLDER, $spanparams);
663         // We can not use standard JS init because this may be cached
664         // note: use 'small' size unless embedding in block mode.
665         $output .= html_writer::script(js_writer::function_call(
666                 'M.util.add_audio_player', array($id, $url->out(false),
667                 empty($options[core_media::OPTION_BLOCK]))));
669         return $output;
670     }
672     public function get_supported_extensions() {
673         return array('mp3');
674     }
676     public function get_rank() {
677         return 80;
678     }
682 /**
683  * Flash video player inserted using JavaScript.
684  *
685  * @copyright 2011 The Open University
686  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
687  */
688 class core_media_player_flv extends core_media_player {
689     public function embed($urls, $name, $width, $height, $options) {
690         // Use first url (there can actually be only one unless some idiot
691         // enters two mp3 files as alternatives).
692         $url = reset($urls);
694         // Unique id even across different http requests made at the same time
695         // (for AJAX, iframes).
696         $id = 'core_media_flv_' . md5(time() . '_' . rand());
698         // Compute width and height.
699         $autosize = false;
700         if (!$width && !$height) {
701             $width = CORE_MEDIA_VIDEO_WIDTH;
702             $height = CORE_MEDIA_VIDEO_HEIGHT;
703             $autosize = true;
704         }
706         // Fallback span (will normally contain link).
707         $output = html_writer::tag('span', core_media_player::PLACEHOLDER,
708                 array('id'=>$id, 'class'=>'mediaplugin mediaplugin_flv'));
709         // We can not use standard JS init because this may be cached.
710         $output .= html_writer::script(js_writer::function_call(
711                 'M.util.add_video_player', array($id, addslashes_js($url->out(false)),
712                 $width, $height, $autosize)));
713         return $output;
714     }
716     public function get_supported_extensions() {
717         return array('flv', 'f4v');
718     }
720     public function get_rank() {
721         return 70;
722     }
726 /**
727  * Embeds Windows Media Player using object tag.
728  *
729  * @copyright 2011 The Open University
730  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
731  */
732 class core_media_player_wmp extends core_media_player {
733     public function embed($urls, $name, $width, $height, $options) {
734         // Get URL (we just use first, probably there is only one).
735         $firsturl = reset($urls);
736         $url = $firsturl->out(false);
738         // Work out width.
739         if (!$width || !$height) {
740             // Object tag has default size.
741             $mpsize = '';
742             $size = 'width="' . CORE_MEDIA_VIDEO_WIDTH .
743                     '" height="' . (CORE_MEDIA_VIDEO_HEIGHT+64) . '"';
744             $autosize = 'true';
745         } else {
746             $size = 'width="' . $width . '" height="' . ($height + 15) . '"';
747             $mpsize = 'width="' . $width . '" height="' . ($height + 64) . '"';
748             $autosize = 'false';
749         }
751         // MIME type for object tag.
752         $mimetype = core_media::get_mimetype($firsturl);
754         $fallback = core_media_player::PLACEHOLDER;
756         // Embed code.
757         return <<<OET
758 <span class="mediaplugin mediaplugin_wmp">
759     <object classid="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6" $mpsize
760             standby="Loading Microsoft(R) Windows(R) Media Player components..."
761             type="application/x-oleobject">
762         <param name="Filename" value="$url" />
763         <param name="src" value="$url" />
764         <param name="url" value="$url" />
765         <param name="ShowControls" value="true" />
766         <param name="AutoRewind" value="true" />
767         <param name="AutoStart" value="false" />
768         <param name="Autosize" value="$autosize" />
769         <param name="EnableContextMenu" value="true" />
770         <param name="TransparentAtStart" value="false" />
771         <param name="AnimationAtStart" value="false" />
772         <param name="ShowGotoBar" value="false" />
773         <param name="EnableFullScreenControls" value="true" />
774         <param name="uimode" value="full" />
775         <!--[if !IE]>-->
776         <object data="$url" type="$mimetype" $size>
777             <param name="src" value="$url" />
778             <param name="controller" value="true" />
779             <param name="autoplay" value="false" />
780             <param name="autostart" value="false" />
781             <param name="resize" value="scale" />
782         <!--<![endif]-->
783             $fallback
784         <!--[if !IE]>-->
785         </object>
786         <!--<![endif]-->
787     </object>
788 </span>
789 OET;
790     }
792     public function get_supported_extensions() {
793         return array('wmv', 'avi');
794     }
796     public function get_rank() {
797         return 60;
798     }
802 /**
803  * Media player using object tag and QuickTime player.
804  *
805  * @copyright 2011 The Open University
806  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
807  */
808 class core_media_player_qt extends core_media_player {
809     public function embed($urls, $name, $width, $height, $options) {
810         // Show first URL.
811         $firsturl = reset($urls);
812         $url = $firsturl->out(true);
814         // Work out size.
815         if (!$width || !$height) {
816             $size = 'width="' . CORE_MEDIA_VIDEO_WIDTH .
817                     '" height="' . (CORE_MEDIA_VIDEO_HEIGHT + 15) . '"';
818         } else {
819             $size = 'width="' . $width . '" height="' . ($height + 15) . '"';
820         }
822         // MIME type for object tag.
823         $mimetype = core_media::get_mimetype($firsturl);
825         $fallback = core_media_player::PLACEHOLDER;
827         // Embed code.
828         return <<<OET
829 <span class="mediaplugin mediaplugin_qt">
830     <object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
831             codebase="http://www.apple.com/qtactivex/qtplugin.cab" $size>
832         <param name="pluginspage" value="http://www.apple.com/quicktime/download/" />
833         <param name="src" value="$url" />
834         <param name="controller" value="true" />
835         <param name="loop" value="true" />
836         <param name="autoplay" value="false" />
837         <param name="autostart" value="false" />
838         <param name="scale" value="aspect" />
839         <!--[if !IE]>-->
840         <object data="$url" type="$mimetype" $size>
841             <param name="src" value="$url" />
842             <param name="pluginurl" value="http://www.apple.com/quicktime/download/" />
843             <param name="controller" value="true" />
844             <param name="loop" value="true" />
845             <param name="autoplay" value="false" />
846             <param name="autostart" value="false" />
847             <param name="scale" value="aspect" />
848         <!--<![endif]-->
849             $fallback
850         <!--[if !IE]>-->
851         </object>
852         <!--<![endif]-->
853     </object>
854 </span>
855 OET;
856     }
858     public function get_supported_extensions() {
859         return array('mpg', 'mpeg', 'mov', 'mp4', 'm4v', 'm4a');
860     }
862     public function get_rank() {
863         return 50;
864     }
868 /**
869  * Media player using object tag and RealPlayer.
870  *
871  * Hopefully nobody is using this obsolete format any more!
872  *
873  * @copyright 2011 The Open University
874  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
875  */
876 class core_media_player_rm extends core_media_player {
877     public function embed($urls, $name, $width, $height, $options) {
878         // Show first URL.
879         $firsturl = reset($urls);
880         $url = $firsturl->out(true);
882         // Get name to use as title.
883         $info = s($this->get_name($name, $urls));
885         // The previous version of this code has the following comment, which
886         // I don't understand, but trust it is correct:
887         // Note: the size is hardcoded intentionally because this does not work anyway!
888         $width = CORE_MEDIA_VIDEO_WIDTH;
889         $height = CORE_MEDIA_VIDEO_HEIGHT;
891         $fallback = core_media_player::PLACEHOLDER;
892         return <<<OET
893 <span class="mediaplugin mediaplugin_real">
894     <object title="$info" classid="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA"
895             data="$url" width="$width" height="$height"">
896         <param name="src" value="$url" />
897         <param name="controls" value="All" />
898         <!--[if !IE]>-->
899         <object title="$info" type="audio/x-pn-realaudio-plugin"
900                 data="$url" width="$width" height="$height">
901             <param name="src" value="$url" />
902             <param name="controls" value="All" />
903         <!--<![endif]-->
904             $fallback
905         <!--[if !IE]>-->
906         </object>
907         <!--<![endif]-->
908   </object>
909 </span>
910 OET;
911     }
913     public function get_supported_extensions() {
914         return array('ra', 'ram', 'rm', 'rv');
915     }
917     public function get_rank() {
918         return 40;
919     }
923 /**
924  * Media player for Flash SWF files.
925  *
926  * This player contains additional security restriction: it will only be used
927  * if you add option core_media_player_swf::ALLOW = true.
928  *
929  * Code should only set this option if it has verified that the data was
930  * embedded by a trusted user (e.g. in trust text).
931  *
932  * @copyright 2011 The Open University
933  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
934  */
935 class core_media_player_swf extends core_media_player {
936     public function embed($urls, $name, $width, $height, $options) {
937         self::pick_video_size($width, $height);
939         $firsturl = reset($urls);
940         $url = $firsturl->out(true);
942         $fallback = core_media_player::PLACEHOLDER;
943         $output = <<<OET
944 <span class="mediaplugin mediaplugin_swf">
945   <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="$width" height="$height">
946     <param name="movie" value="$url" />
947     <param name="autoplay" value="true" />
948     <param name="loop" value="true" />
949     <param name="controller" value="true" />
950     <param name="scale" value="aspect" />
951     <param name="base" value="." />
952     <param name="allowscriptaccess" value="never" />
953 <!--[if !IE]>-->
954     <object type="application/x-shockwave-flash" data="$url" width="$width" height="$height">
955       <param name="controller" value="true" />
956       <param name="autoplay" value="true" />
957       <param name="loop" value="true" />
958       <param name="scale" value="aspect" />
959       <param name="base" value="." />
960       <param name="allowscriptaccess" value="never" />
961 <!--<![endif]-->
962 $fallback
963 <!--[if !IE]>-->
964     </object>
965 <!--<![endif]-->
966   </object>
967 </span>
968 OET;
970         return $output;
971     }
973     public function get_supported_extensions() {
974         return array('swf');
975     }
977     public function list_supported_urls(array $urls, array $options = array()) {
978         // Not supported unless the creator is trusted.
979         if (empty($options[core_media::OPTION_TRUSTED])) {
980             return array();
981         }
982         return parent::list_supported_urls($urls, $options);
983     }
985     public function get_rank() {
986         return 30;
987     }
991 /**
992  * Player that creates HTML5 <video> tag.
993  *
994  * @copyright 2011 The Open University
995  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
996  */
997 class core_media_player_html5video extends core_media_player {
998     public function embed($urls, $name, $width, $height, $options) {
999         // Special handling to make videos play on Android devices pre 2.3.
1000         // Note: I tested and 2.3.3 (in emulator) works without, is 533.1 webkit.
1001         $oldandroid = check_browser_version('WebKit Android') &&
1002                 !check_browser_version('WebKit Android', '533.1');
1004         // Build array of source tags.
1005         $sources = array();
1006         foreach ($urls as $url) {
1007             $mimetype = core_media::get_mimetype($url);
1008             $source = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype));
1009             if ($mimetype === 'video/mp4') {
1010                 if ($oldandroid) {
1011                     // Old Android fails if you specify the type param.
1012                     $source = html_writer::tag('source', '', array('src' => $url));
1013                 }
1015                 // Better add m4v as first source, it might be a bit more
1016                 // compatible with problematic browsers.
1017                 array_unshift($sources, $source);
1018             } else {
1019                 $sources[] = $source;
1020             }
1021         }
1023         $sources = implode("\n", $sources);
1024         $title = s($this->get_name($name, $urls));
1026         if (!$width) {
1027             // No width specified, use system default.
1028             $width = CORE_MEDIA_VIDEO_WIDTH;
1029         }
1031         if (!$height) {
1032             // Let browser choose height automatically.
1033             $size = "width=\"$width\"";
1034         } else {
1035             $size = "width=\"$width\" height=\"$height\"";
1036         }
1038         $sillyscript = '';
1039         $idtag = '';
1040         if ($oldandroid) {
1041             // Old Android does not support 'controls' option.
1042             $id = 'core_media_html5v_' . md5(time() . '_' . rand());
1043             $idtag = 'id="' . $id . '"';
1044             $sillyscript = <<<OET
1045 <script type="text/javascript">
1046 document.getElementById('$id').addEventListener('click', function() {
1047     this.play();
1048 }, false);
1049 </script>
1050 OET;
1051         }
1053         $fallback = core_media_player::PLACEHOLDER;
1054         return <<<OET
1055 <span class="mediaplugin mediaplugin_html5video">
1056 <video $idtag controls="true" $size preload="metadata" title="$title">
1057     $sources
1058     $fallback
1059 </video>
1060 $sillyscript
1061 </span>
1062 OET;
1063     }
1065     public function get_supported_extensions() {
1066         return array('m4v', 'webm', 'ogv', 'mp4');
1067     }
1069     public function list_supported_urls(array $urls, array $options = array()) {
1070         $extensions = $this->get_supported_extensions();
1071         $result = array();
1072         foreach ($urls as $url) {
1073             $ext = core_media::get_extension($url);
1074             if (in_array($ext, $extensions)) {
1075                 // Unfortunately html5 video does not handle fallback properly.
1076                 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=10975
1077                 // That means we need to do browser detect and not use html5 on
1078                 // browsers which do not support the given type, otherwise users
1079                 // will not even see the fallback link.
1080                 // Based on http://en.wikipedia.org/wiki/HTML5_video#Table - this
1081                 // is a simplified version, does not take into account old browser
1082                 // versions or manual plugins.
1083                 if ($ext === 'ogv' || $ext === 'webm') {
1084                     // Formats .ogv and .webm are not supported in IE or Safari.
1085                     if (check_browser_version('MSIE') || check_browser_version('Safari')) {
1086                         continue;
1087                     }
1088                 } else {
1089                     // Formats .m4v and .mp4 are not supported in Firefox or Opera.
1090                     if (check_browser_version('Firefox') || check_browser_version('Opera')) {
1091                         continue;
1092                     }
1093                 }
1095                 $result[] = $url;
1096             }
1097         }
1098         return $result;
1099     }
1101     public function get_rank() {
1102         return 20;
1103     }
1107 /**
1108  * Player that creates HTML5 <audio> tag.
1109  *
1110  * @copyright 2011 The Open University
1111  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1112  */
1113 class core_media_player_html5audio extends core_media_player {
1114     public function embed($urls, $name, $width, $height, $options) {
1116         // Build array of source tags.
1117         $sources = array();
1118         foreach ($urls as $url) {
1119             $mimetype = core_media::get_mimetype($url);
1120             $sources[] = html_writer::tag('source', '', array('src' => $url, 'type' => $mimetype));
1121         }
1123         $sources = implode("\n", $sources);
1124         $title = s($this->get_name($name, $urls));
1126         // Default to not specify size (so it can be changed in css).
1127         $size = '';
1128         if ($width) {
1129             $size = 'width="' . $width . '"';
1130         }
1132         $fallback = core_media_player::PLACEHOLDER;
1134         return <<<OET
1135 <audio controls="true" $size class="mediaplugin mediaplugin_html5audio" preload="no" title="$title">
1136 $sources
1137 $fallback
1138 </audio>
1139 OET;
1140     }
1142     public function get_supported_extensions() {
1143         return array('ogg', 'oga', 'aac', 'm4a', 'mp3');
1144     }
1146     public function list_supported_urls(array $urls, array $options = array()) {
1147         $extensions = $this->get_supported_extensions();
1148         $result = array();
1149         foreach ($urls as $url) {
1150             $ext = core_media::get_extension($url);
1151             if (in_array($ext, $extensions)) {
1152                 if ($ext === 'ogg' || $ext === 'oga') {
1153                     // Formats .ogg and .oga are not supported in IE or Safari.
1154                     if (check_browser_version('MSIE') || check_browser_version('Safari')) {
1155                         continue;
1156                     }
1157                 } else {
1158                     // Formats .aac, .mp3, and .m4a are not supported in Firefox or Opera.
1159                     if (check_browser_version('Firefox') || check_browser_version('Opera')) {
1160                         continue;
1161                     }
1162                 }
1163                 // Old Android versions (pre 2.3.3) 'support' audio tag but no codecs.
1164                 if (check_browser_version('WebKit Android') &&
1165                         !check_browser_version('WebKit Android', '533.1')) {
1166                     continue;
1167                 }
1169                 $result[] = $url;
1170             }
1171         }
1172         return $result;
1173     }
1175     public function get_rank() {
1176         return 10;
1177     }
1181 /**
1182  * Special media player class that just puts a link.
1183  *
1184  * Always enabled, used as the last fallback.
1185  *
1186  * @copyright 2011 The Open University
1187  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1188  */
1189 class core_media_player_link extends core_media_player {
1190     public function embed($urls, $name, $width, $height, $options) {
1191         // If link is turned off, return empty.
1192         if (!empty($options[core_media::OPTION_NO_LINK])) {
1193             return '';
1194         }
1196         // Build up link content.
1197         $output = '';
1198         foreach ($urls as $url) {
1199             $title = core_media::get_filename($url);
1200             $printlink = html_writer::link($url, $title, array('class' => 'mediafallbacklink'));
1201             if ($output) {
1202                 // Where there are multiple available formats, there are fallback links
1203                 // for all formats, separated by /.
1204                 $output .= ' / ';
1205             }
1206             $output .= $printlink;
1207         }
1208         return $output;
1209     }
1211     public function list_supported_urls(array $urls, array $options = array()) {
1212         // Supports all URLs.
1213         return $urls;
1214     }
1216     public function is_enabled() {
1217         // Cannot be disabled.
1218         return true;
1219     }
1221     public function get_rank() {
1222         return 0;
1223     }