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