MDL-52958 medialib: Allow vimeo fullscreen using links
[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')) {
83c9d449
AB
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.
daefd6eb 41 define('CORE_MEDIA_VIDEO_WIDTH', 400);
42}
43if (!defined('CORE_MEDIA_VIDEO_HEIGHT')) {
83c9d449 44 // Default video height. May be defined in config.php if required.
daefd6eb 45 define('CORE_MEDIA_VIDEO_HEIGHT', 300);
46}
47if (!defined('CORE_MEDIA_AUDIO_WIDTH')) {
83c9d449
AB
48 // Default audio width if no width is specified.
49 // May be defined in config.php if required.
daefd6eb 50 define('CORE_MEDIA_AUDIO_WIDTH', 300);
51}
52
53
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 */
60abstract 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';
70
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';
82
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';
92
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';
100
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();
119
120 foreach ($urls as $url) {
121 $matches = null;
122
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 }
130
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];
136
137 // Trim from URL.
138 $url = str_replace($matches[0], '', $url);
139 }
140
141 // Clean up url.
142 $url = clean_param($url, PARAM_URL);
143 if (empty($url)) {
144 continue;
145 }
146
147 // Turn it into moodle_url object.
148 $returnurls[] = new moodle_url($url);
149 }
150
151 return $returnurls;
152 }
153
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) {
2f1e464a 159 // Note: Does not use core_text (. is UTF8-safe).
daefd6eb 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 }
168
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) {
a3c17aed 175 global $CFG;
176
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 }
183
daefd6eb 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 }
daefd6eb 189 return $path;
190 }
191
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 }
200}
201
202
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 */
223abstract 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-->';
229
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);
253
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 }
267
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 }
290
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();
314
315 /**
316 * @return bool True if player is enabled
317 */
318 public function is_enabled() {
319 global $CFG;
320
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 }
326
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 }
344
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 }
357
358 // Get filename of first URL.
359 $url = reset($urls);
360 $name = core_media::get_filename($url);
361
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 }
367
368 return $name;
369 }
370
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 }
380
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 }
394}
395
396
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 */
405abstract 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;
413
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';
424
425 public function embed($urls, $name, $width, $height, $options) {
426 return $this->embed_external(reset($urls), $name, $width, $height, $options);
427 }
428
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);
439
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);
446
447 // Check against regex.
448 if (preg_match($this->get_regex(), $url->out(false), $this->matches)) {
449 return array($url);
450 }
451
452 return array();
453 }
454
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 }
462
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 }
475}
476
477
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 */
484class 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);
488
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);
493
494 $output = <<<OET
495<span class="mediaplugin mediaplugin_vimeo">
feddb588 496<iframe title="$info" src="https://player.vimeo.com/video/$videoid"
44841ca9
DM
497 width="$width" height="$height" frameborder="0"
498 webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
daefd6eb 499</span>
500OET;
501
502 return $output;
503 }
504
505 protected function get_regex() {
506 // Initial part of link.
feddb588 507 $start = '~^https?://vimeo\.com/';
daefd6eb 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) {
1ba43a9c 530 $videoid = end($this->matches);
daefd6eb 531
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);
537
538 self::pick_video_size($width, $height);
539
d518ff18
PN
540 $params = '';
541 $start = self::get_start_time($url);
542 if ($start > 0) {
543 $params .= "start=$start&";
544 }
545
8fcb99bc
PN
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 }
552
13725b37 553 return <<<OET
feddb588 554<span class="mediaplugin mediaplugin_youtube">
daefd6eb 555<iframe title="$info" width="$width" height="$height"
d518ff18 556 src="https://www.youtube.com/embed/$videoid?{$params}rel=0&wmode=transparent" frameborder="0" allowfullscreen="1"></iframe>
feddb588 557</span>
daefd6eb 558OET;
feddb588 559
daefd6eb 560 }
561
d518ff18
PN
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;
571
572 $rawtime = $url->param('t');
573 if (empty($rawtime)) {
574 $rawtime = $url->param('start');
575 }
576
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 }
599
600 return intval($seconds);
601 }
602
daefd6eb 603 protected function get_regex() {
1ba43a9c
JPG
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/)';
608
daefd6eb 609 // Initial part of link.
1ba43a9c
JPG
610 $start = '~^https?://(www\.)?(' . $link . '|' . $shortlink . ')';
611 // Middle bit: Video key value
612 $middle = '([a-z0-9\-_]+)';
daefd6eb 613 return $start . $middle . core_media_player_external::END_LINK_REGEX_PART;
614 }
615
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 }
621
622 public function get_embeddable_markers() {
1ba43a9c 623 return array('youtube.com', 'youtube-nocookie.com', 'youtu.be', 'y2u.be');
daefd6eb 624 }
625}
626
627
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 */
634class 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 }
640
641 protected function embed_external(moodle_url $url, $name, $width, $height, $options) {
642 $site = $this->matches[1];
643 $playlist = $this->matches[3];
644
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);
650
651 self::pick_video_size($width, $height);
652
feddb588 653 return <<<OET
daefd6eb 654<span class="mediaplugin mediaplugin_youtube">
feddb588 655<iframe width="$width" height="$height" src="https://$site/embed/videoseries?list=$playlist" frameborder="0" allowfullscreen="1"></iframe>
daefd6eb 656</span>
657OET;
daefd6eb 658 }
659
660 protected function get_regex() {
661 // Initial part of link.
feddb588 662 $start = '~^https?://(www\.youtube(-nocookie)?\.com)/';
daefd6eb 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 }
667
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 }
673
674 public function get_embeddable_markers() {
675 return array('youtube');
676 }
677}
678
679
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 */
686class 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);
691
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());
695
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]))));
708
709 return $output;
710 }
711
712 public function get_supported_extensions() {
713 return array('mp3');
714 }
715
716 public function get_rank() {
717 return 80;
718 }
719}
720
721
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 */
728class 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);
733
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());
737
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 }
745
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 }
755
756 public function get_supported_extensions() {
757 return array('flv', 'f4v');
758 }
759
760 public function get_rank() {
761 return 70;
762 }
763}
764
765
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 */
772class 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);
777
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 }
790
791 // MIME type for object tag.
792 $mimetype = core_media::get_mimetype($firsturl);
793
794 $fallback = core_media_player::PLACEHOLDER;
795
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" />
4ebdd154 815 <!--[if !IE]><!-->
daefd6eb 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
4ebdd154 824 <!--[if !IE]><!-->
daefd6eb 825 </object>
826 <!--<![endif]-->
827 </object>
828</span>
829OET;
830 }
831
832 public function get_supported_extensions() {
833 return array('wmv', 'avi');
834 }
835
836 public function get_rank() {
837 return 60;
838 }
839}
840
841
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 */
848class 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);
853
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 }
861
862 // MIME type for object tag.
863 $mimetype = core_media::get_mimetype($firsturl);
864
865 $fallback = core_media_player::PLACEHOLDER;
866
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" />
c476e678 875 <param name="loop" value="false" />
daefd6eb 876 <param name="autoplay" value="false" />
877 <param name="autostart" value="false" />
878 <param name="scale" value="aspect" />
4ebdd154 879 <!--[if !IE]><!-->
daefd6eb 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" />
c476e678 884 <param name="loop" value="false" />
daefd6eb 885 <param name="autoplay" value="false" />
886 <param name="autostart" value="false" />
887 <param name="scale" value="aspect" />
888 <!--<![endif]-->
889 $fallback
4ebdd154 890 <!--[if !IE]><!-->
daefd6eb 891 </object>
892 <!--<![endif]-->
893 </object>
894</span>
895OET;
896 }
897
898 public function get_supported_extensions() {
899 return array('mpg', 'mpeg', 'mov', 'mp4', 'm4v', 'm4a');
900 }
901
902 public function get_rank() {
959dd367 903 return 10;
daefd6eb 904 }
905}
906
907
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 */
916class 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);
921
922 // Get name to use as title.
923 $info = s($this->get_name($name, $urls));
924
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;
930
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" />
4ebdd154 938 <!--[if !IE]><!-->
daefd6eb 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
4ebdd154 945 <!--[if !IE]><!-->
daefd6eb 946 </object>
947 <!--<![endif]-->
948 </object>
949</span>
950OET;
951 }
952
953 public function get_supported_extensions() {
954 return array('ra', 'ram', 'rm', 'rv');
955 }
956
957 public function get_rank() {
958 return 40;
959 }
960}
961
962
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 */
975class core_media_player_swf extends core_media_player {
976 public function embed($urls, $name, $width, $height, $options) {
977 self::pick_video_size($width, $height);
978
979 $firsturl = reset($urls);
980 $url = $firsturl->out(true);
981
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" />
c476e678 988 <param name="loop" value="false" />
daefd6eb 989 <param name="controller" value="true" />
990 <param name="scale" value="aspect" />
991 <param name="base" value="." />
992 <param name="allowscriptaccess" value="never" />
27635149 993 <param name="allowfullscreen" value="true" />
4ebdd154 994<!--[if !IE]><!-->
daefd6eb 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" />
c476e678 998 <param name="loop" value="false" />
daefd6eb 999 <param name="scale" value="aspect" />
1000 <param name="base" value="." />
1001 <param name="allowscriptaccess" value="never" />
27635149 1002 <param name="allowfullscreen" value="true" />
daefd6eb 1003<!--<![endif]-->
1004$fallback
4ebdd154 1005<!--[if !IE]><!-->
daefd6eb 1006 </object>
1007<!--<![endif]-->
1008 </object>
1009</span>
1010OET;
1011
1012 return $output;
1013 }
1014
1015 public function get_supported_extensions() {
1016 return array('swf');
1017 }
1018
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 }
1026
1027 public function get_rank() {
1028 return 30;
1029 }
1030}
1031
1032
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 */
1039class 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.
378b3eac 1043 $oldandroid = core_useragent::is_webkit_android() &&
c3d2fbf9 1044 !core_useragent::check_webkit_android_version('533.1');
daefd6eb 1045
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 }
1056
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 }
1064
1065 $sources = implode("\n", $sources);
1066 $title = s($this->get_name($name, $urls));
1067
1068 if (!$width) {
1069 // No width specified, use system default.
1070 $width = CORE_MEDIA_VIDEO_WIDTH;
1071 }
1072
1073 if (!$height) {
1074 // Let browser choose height automatically.
1075 $size = "width=\"$width\"";
1076 } else {
1077 $size = "width=\"$width\" height=\"$height\"";
1078 }
1079
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">
1088document.getElementById('$id').addEventListener('click', function() {
1089 this.play();
1090}, false);
1091</script>
1092OET;
1093 }
1094
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>
1104OET;
1105 }
1106
1107 public function get_supported_extensions() {
1108 return array('m4v', 'webm', 'ogv', 'mp4');
1109 }
1110
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.
378b3eac 1127 if (core_useragent::is_ie() || core_useragent::is_safari()) {
daefd6eb 1128 continue;
1129 }
1130 } else {
540af6f3 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))) {
daefd6eb 1136 continue;
1137 }
1138 }
1139
1140 $result[] = $url;
1141 }
1142 }
1143 return $result;
1144 }
1145
1146 public function get_rank() {
959dd367 1147 return 50;
daefd6eb 1148 }
1149}
1150
1151
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 */
1158class core_media_player_html5audio extends core_media_player {
1159 public function embed($urls, $name, $width, $height, $options) {
1160
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 }
1167
1168 $sources = implode("\n", $sources);
1169 $title = s($this->get_name($name, $urls));
1170
1171 // Default to not specify size (so it can be changed in css).
1172 $size = '';
1173 if ($width) {
1174 $size = 'width="' . $width . '"';
1175 }
1176
1177 $fallback = core_media_player::PLACEHOLDER;
1178
1179 return <<<OET
1180<audio controls="true" $size class="mediaplugin mediaplugin_html5audio" preload="no" title="$title">
1181$sources
1182$fallback
1183</audio>
1184OET;
1185 }
1186
1187 public function get_supported_extensions() {
1188 return array('ogg', 'oga', 'aac', 'm4a', 'mp3');
1189 }
1190
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.
378b3eac 1199 if (core_useragent::is_ie() || core_useragent::is_safari()) {
daefd6eb 1200 continue;
1201 }
1202 } else {
540af6f3 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))) {
daefd6eb 1212 continue;
1213 }
1214 }
1215 // Old Android versions (pre 2.3.3) 'support' audio tag but no codecs.
378b3eac
DP
1216 if (core_useragent::is_webkit_android() &&
1217 !core_useragent::is_webkit_android('533.1')) {
daefd6eb 1218 continue;
1219 }
1220
1221 $result[] = $url;
1222 }
1223 }
1224 return $result;
1225 }
1226
1227 public function get_rank() {
959dd367 1228 return 20;
daefd6eb 1229 }
1230}
1231
1232
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 */
1241class 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 }
1247
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 }
1262
1263 public function list_supported_urls(array $urls, array $options = array()) {
1264 // Supports all URLs.
1265 return $urls;
1266 }
1267
1268 public function is_enabled() {
1269 // Cannot be disabled.
1270 return true;
1271 }
1272
1273 public function get_rank() {
1274 return 0;
1275 }
1276}